Skip to content

OBR published economic effects of policy: AS2023–AB2025 packages, supply-side scorings, March 2026 decisions (external side) - #75

Open
vahid-ahmadi wants to merge 3 commits into
mainfrom
uk/obr-policy-effects-harvest
Open

OBR published economic effects of policy: AS2023–AB2025 packages, supply-side scorings, March 2026 decisions (external side)#75
vahid-ahmadi wants to merge 3 commits into
mainfrom
uk/obr-policy-effects-harvest

Conversation

@vahid-ahmadi

Copy link
Copy Markdown
Contributor

Harvest-first step of #55 (per the 8/16 steer and the claim comment's execution order): the external side of the population the Macro members will answer — no PE counterparts, no engine registration, no emulator work in this PR.

What was retrieved (all Wayback original bytes; obr.uk is Turnstile-guarded, so the uk_obr/NOTES.md no-bypass recipe applies; URLs + snapshots + SHA-256 in sources/obr-policy-effects/raw/README.md, and the adapter sha256-gates every raw file before parsing)

artifact content harvested
EFO Nov 2023 Ch.2 Chart 2.A — AS2023 package real-GDP impacts by channel (full expensing, NICs cut, welfare reforms, demand)
EFO Mar 2024 Ch.2 Chart 2.A — SB2024 package real-GDP impacts by channel
EFO Oct 2024 Ch.2 Charts A + B — AB2024 package real-GDP impacts by expenditure component AND by measure
EFO Nov 2025 Ch.3 Chart 3.3 (GDP by component) + Chart 3.4 (CPI by measure) — AB2025
EFO Mar 2026 annex tables Table B.1 — total/direct/indirect effect of Government decisions on borrowing, nested, GBP bn
Briefing Paper No.10 (Nov 2025) Table 2.1 — every measure OBR has scored for supply-side effects, per-measure potential-output impact (% GDP) by channel, SB2023 → SS2025

Rows: 266, four metrics

gdp_level_effect 151 · cpi_inflation_effect 36 · supply_side_impact 19 · decisions_effect_on_borrowing 60. Every row carries fiscal_event, basis (post_behavioural / supply_side), scope, verbatim source_column, and — for the nested TB.1 — the aggregate_level/parent double-count guard in the obr-welfare adapter's convention, with the published borrowing sign convention carried verbatim rather than normalised.

A free cross-validation surfaced by the spot pins: Briefing Paper 10's AS2023 employee-NICs supply-side scoring (0.16741543…) is bit-identical to the Nov 2023 Chart 2.A terminal-year NICs value — the inventory restates the original scorings at full precision.

Tests

tests/test_obr_policy_effects_adapter.py: block counts, closed row shape, unit-per-metric, five spot values traced to published cells, TB.1 hierarchy identities (direct+indirect=total, subtotals=Σcomponents, £1 tolerance), byte-stable rebuild from the committed raw bytes. Full suite 248 passed / 6 skipped; ruff clean.

Deferred, honestly

Annex A workbooks for five vintages were fetched, inspected, and NOT vendored (forecast levels only, no policy content — documented in raw/README.md); likewise the Mar 2025 / Nov 2025 / Mar 2026 Chapter 2 and Nov 2025 Ch.4/5 workbooks. Spring 2025/2026 package-effect chart data was not located in this pass; Mar 2026 decisions are covered in aggregate by TB.1. DB ingest waits for the post-#57 ingest shape; counterparts (PE-UK LSR, emulator) are #55 steps 2–3.

Builds #55 (step 1: harvest).

🤖 Generated with Claude Code

…l side) — AS2023-AB2025 packages, supply-side scorings, March 2026 decisions

Harvest-first step of #55: 266 claims across four metrics from six
Wayback-original OBR artifacts (sha256-gated in the adapter):

- gdp_level_effect: package impacts on real GDP for AS2023/SB2024 (by
  channel), AB2024 (by component AND by measure), AB2025 (by component)
- cpi_inflation_effect: AB2025 budget policies by measure
- supply_side_impact: Briefing Paper No.10 Table 2.1 — all 19 measures
  OBR has scored for potential output (% GDP, labour/capital/TFP
  channel), SB2023 -> SS2025
- decisions_effect_on_borrowing: March 2026 Table B.1 nested
  total/direct/indirect split, GBP, published sign convention carried
  verbatim, aggregate_level/parent double-count guard (obr-welfare
  convention)

Tests pin block counts, closed row shape, spot values, the TB.1
hierarchy identities, and byte-stable rebuild from the committed raw
bytes. No PE counterparts, no engine registration — external side only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor

Gate round 1 (reviewed as a pair with #81) — strong vendoring; four findings, three blocking

Clean and verified: all six SHA-256 pins match the vendored artifacts, parsing reads stored XLSX values at full precision (the office:value discipline), a no-write regeneration was byte-identical at 30+20+36+30+35+36+19+60 = 266, and every harvested TB.1 cell is a forecast — no Ledger-boundary violations.

  1. Blocking — macro units are semantically conflated. CPI effects are published in percentage points; BP10 supply-side impacts are percent of GDP; both emit as generic percent (adapter.py:247, :300), and the adapter test entrenches the conflation. These are three different quantities (pp deviation, % of GDP, and — in OBR policy effects → DB ingest: 266 claims, four macro-effect metrics #81's mapping — GDP-level % deviation) that a query must never treat as one; each needs a deliberate unit concept, same rule that split GBP_PER_WEEK from bare GBP.

  2. Blocking — 266 is not source-reconciled. TB.1 carries 66 numeric cells across eleven lines; TB1_ROWS selects ten and emits 60 — the six memo current-budget-deficit cells are silently skipped (adapter.py:381). Choosing four metrics is defensible; the drop has to be tallied and explained (the 272 candidates → 266 claims + 6 deliberate drops accounting), never silent.

  3. Blocking — baseline/prose provenance is too coarse. source.json blanket-labels rows as "against each EFO's pre-measures forecast," but rows carry no baseline descriptor or locator — and BP10 itself distinguishes tax/welfare counterfactuals from DEL/regulatory activity baselines, while the WCA reversal scores against the earlier WCA-adjusted world. Per-row registered baseline worlds (the pre_obbba_law / hmrc_indexed pattern) with vendored/annotated supporting pages.

  4. Identity vocabulary fail-open. _slug() mints program/subgroup/type identities without a closed source map (unknowns should raise, per uk_aliases), and basis is reused for post_behavioural|supply_side when the standard axis means forecast/outturn — these rows are all forecasts; scoring method needs its own registered axis.

🤖 Generated with Claude Code

1. Units are no longer conflated. GDP-level deviations, CPI effects and
   supply-side impacts were all emitting bare `percent`; they are three
   different quantities and now carry three unit concepts —
   `percent_of_real_gdp`, `percentage_points`, `percent_of_potential_gdp`
   — with `gbp_nominal` unchanged on Table B.1. Same rule that split
   GBP_PER_WEEK from bare GBP, and the adapter test now pins the mapping
   per metric instead of entrenching the conflation.

2. 266 is source-reconciled. Table B.1 prints 66 numeric cells across 11
   lines; the parser selected 10 and the memo current-budget line fell
   through a silent `continue`. Every value-bearing line is now
   classified — emitted (TB1_ROWS) or a declared drop (TB1_DROPS, with a
   reason) — and an unclassified value line raises. The build reports and
   asserts `272 source cells = 266 claims + 6 deliberate drops`; the memo
   line is dropped because it scores the current budget, not PSNB.

3. Baseline provenance is per row, not one blanket sentence. Each row
   carries the pre-measures world of its own round
   (`obr_pre_measures_<event>`), the counterfactual KIND — Briefing paper
   No.10 scores tax/welfare measures against a legislated-parameter
   counterfactual and DEL/regulatory measures against the pre-existing
   activity baseline — and a locator. March 2026 Table B.1 keys
   `obr_november_2025_budget_forecast`, its own stated counterfactual,
   and the March 2025 WCA reversal's locator records that it scores
   against the WCA-adjusted world AS2023 created.

4. Identity vocabulary is closed, and `basis` stops squatting. Chart
   series labels, BP10 measures, types and channels are explicit
   registries; an unregistered label raises instead of `_slug()` minting
   one. `basis` is now `forecast` (its standard meaning everywhere in
   this repo) and the scoring method moved to its own `scoring_method`
   axis.

Also: publication provenance is per artifact, so each round's claims
carry their own release date and URL rather than one generic
obr.uk/publications/ stamp — the input to #81's date fix.

266 claims unchanged; suite 261 passed, ruff format clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HuXJFVme8HRbnke2Ey3Me
vahid-ahmadi pushed a commit that referenced this pull request Aug 21, 2026
Reviewed as a pair with #75, which this branch now carries; the harvest
side supplies the per-row provenance the fixes here consume.

1. Baselines are the rounds' own pre-measures worlds, not current_law.
   Every claim now carries a ReformRef.baseline descriptor —
   {policy: obr_pre_measures_<round>, counterfactual: <kind>} — mirrored
   into conditions["baseline_policy"], with the counterfactual kind
   split as Briefing paper No.10 chapter 2 splits it (legislated
   parameters for tax/welfare, the pre-existing activity baseline for
   DEL, the regulatory baseline for planning). March 2026 Table B.1 keys
   the November 2025 Budget forecast, its own stated counterfactual. All
   eleven (round, counterfactual) worlds are registered in baselines.py,
   a row whose baseline disagrees with its own round raises, and a
   baseline without a locator raises.

2. BP10 horizons are per scoring round. The paper re-states scorings from
   five earlier events, so "the fifth year of our forecast" is each
   measure's own round's fifth year: 2027-28 (Mar 2023) through 2029-30
   (Mar 2025). All 19 were keyed to 2030-31, the November 2025 round's
   horizon. Period is claim identity, so those were 19 claims about years
   OBR never scored them for. _BP10_HORIZON resolves per event and an
   unregistered round raises rather than borrowing another's year.

3. Unit concepts are deliberate, and validated before mapping. Three new
   UnitConcept members — PERCENT_OF_REAL_GDP, PERCENTAGE_POINTS,
   PERCENT_OF_POTENTIAL_GDP — replace the collapse into bare PERCENT,
   and the staged label is now compared against the metric's expected
   unit and raises on drift instead of being canon-checked then
   discarded. Bare "percent" is no longer a registered label for this
   source, so the three cannot collapse back.

4. Publication provenance is per artifact. Each round's claims carry
   their own release date and dated URL rather than one generic
   obr.uk/publications/ stamp dated 2025-11-26, and the 60 March-2026
   claims carry the publication date (2026-03-03) instead of the Wayback
   capture (2026-03-16). An unregistered artifact raises.

5. `basis` stops squatting: it is `forecast` on every row (its repo-wide
   meaning) and the scoring method moved to its own `scoring_method`
   condition, which also keys the relationship lookup.

266 claims and the exact accounting unchanged; the DB builds
deterministically (two builds agree on content_hash), suite 299 passed,
ruff format clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HuXJFVme8HRbnke2Ey3Me
@vahid-ahmadi

Copy link
Copy Markdown
Contributor Author

All four addressed in ea37f62.

  1. Units. GDP-level deviations, CPI effects and supply-side impacts were three quantities under one percent. They now carry percent_of_real_gdp, percentage_points and percent_of_potential_gdp, with gbp_nominal unchanged on Table B.1 — the same rule that split GBP_PER_WEEK from bare GBP. The adapter test pins the mapping per metric instead of entrenching the conflation.

  2. 266 is source-reconciled. You were right that the memo line fell through a silent continue. Every value-bearing TB.1 line is now classified — emitted (TB1_ROWS) or a declared drop (TB1_DROPS, with a reason) — and an unclassified value line raises. The build reports and asserts 272 source cells = 266 claims + 6 deliberate drops; the memo row is dropped because it scores the current budget, not PSNB.

  3. Baselines per row. Each row carries its own round's pre-measures world (obr_pre_measures_<event>), the counterfactual KIND — BP10 ch.2 splits tax/welfare legislated-parameter counterfactuals from DEL/regulatory activity baselines — and a locator. March 2026 Table B.1 keys obr_november_2025_budget_forecast, its own stated counterfactual, and the March 2025 WCA reversal's locator records that it scores against the WCA-adjusted world AS2023 created.

  4. Identity vocabulary + basis. Chart series labels, BP10 measures, types and channels are explicit registries; an unregistered label raises instead of _slug() minting one. basis is now forecast (its repo-wide meaning) and the scoring method moved to its own scoring_method axis.

Also: publication provenance is per artifact, which is the input #81's date fix consumes.

266 claims unchanged; suite 261 passed, ruff format clean.

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at head ea37f62 — ran the suite (13 pass) and recomputed the SHA pins (match). This is a clean, disciplined harvest; same rigor as the other lanes.

Verified:

  • Provenance — all 6 OBR workbooks are SHA-256 pinned in adapter.py, source.json, and the README, with a recompute-and-compare gate that runs first in build(); editions are pinned by dated snapshot URLs + per-artifact date (with the Wayback-capture-≠-publication-date distinction asserted), not a latest URL.
  • Adapter parses by sheet name + title anchor + a closed series registry (not fragile absolute cell refs), and values pass through verbatim — the only transform is the TB.1 £bn→nominal *1e9 (I checked the raw→nominal value matches the pin). Four distinct unit concepts stop %-vs-£bn / pp-vs-% misfiling.
  • Fail-loud on any unmapped series/measure/type/channel, title-prefix mismatch, non-FY period, or unclassified TB.1 line — all tested.
  • Accounting reconciles (272 cells = 266 claims + 6 tallied memo drops) with a TB.1 tie-back and hierarchy identities (direct+indirect=total, £1 tol), and determinism holds (sorted iteration, no timestamps, byte-stable rebuild test).

Only cosmetic nits: a hardcoded header_row=25 and a deliberately-truncated TB.1 title prefix are mildly fragile, but the title-anchor + closed-registry + FY guard would catch any real re-shuffle. Approving — merge this before the stacked ingest (#81).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants