Table 4.9 is nested, but the adapter skipped the 'of which:' markers and
emitted all 31 programs as siblings. 'DWP social security' is the SUM of
the eleven rows under its marker, and 'Other DWP in welfare cap' is a
further subtotal of ten inside it — so any consumer aggregating the
non-total rows double-counts. For 2025-26 the in-cap rows sum to
£319.85bn against a published cap total of £169.13bn, 1.89x.
Every row now carries aggregate_level (component | subtotal | total) and
parent, read from the workbook itself: an 'of which:' marker at indent L
declares the preceding level-L row the parent of the following level-L+1
rows, with levels taken from each label cell's style indent. A row
indented without a governing marker is a hard error rather than a guess.
Strictly additive — all pre-existing fields, values and key order are
byte-identical, still 259 rows.
Replace the insensitive structural check. cap + outside = total compares
three published figures, so it holds even if a component is dropped or
renamed. Each aggregate is now reconciled against the rows naming it as
parent, for all seven years:
- exact to PARENT_TOL = £1 where every component is published. Values
are full float64 £bn scaled by 1e9, so ≤11 terms of order 1e2 carry
~£1e-4 of representation error; in practice both DWP social security
sections and the in-cap section total reconcile to exactly 0.0. £1
is ~8 orders below the smallest published component (£0.093bn).
- bounded by n_suppressed × £0.1bn where components are suppressed,
since '*' only asserts |x| < £0.1bn. Two parents need this: 'Other
DWP in welfare cap' (5 of 10 suppressed) and the outside-cap section
total (1, negative in the early years).
Also fix the silent 'else: continue' at row classification: an unmapped
label carrying data now raises, and non-data prose is tallied and
printed rather than vanishing. 59 checks pass, 0 fail.
tests/test_obr_adapter.py pins the invariants against the committed
JSON: components sum to their parent per year, every component has a
resolvable parent, leaves-only sums equal the published section total
(the assertion that catches a flat emission), and the 1.89x failure mode
itself. Verified non-vacuous — collapsing the subtotals back to
components fails 6 of the 8 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #37. Fourth UK mode-1 adapter (parent #3), and the first UK entry in the
baseline_momentclaim class from #29 — current-law statistics from an external modeler, captured with the same honesty machinery as reform scores.What's here
sources/obr-welfare/raw/— the March 2026 EFO detailed forecast tables: expenditure workbook, as fetched from obr.uk (published 2026-03-03)sources/obr-welfare/adapter.py— stdlib-only xlsx reader (zipfile + sharedStrings XML) → 259 tidy rows atdata/externals/obr-welfare.jsonsource.jsonwithclaim_class: baseline_momentand an explicitbaseline_key(per Baseline as a first-class attribute of every score #13) + 7 annotationsdata/lanes.json—obr-welfare:registered → ingestedCoverage
Table 4.9 — post-measures breakdown of welfare spending: 31 benefit lines (UC, state pension, DLA/PIP, incapacity benefits, attendance allowance, pension credit, carer's allowance, HB, child benefit, WFP, …) × 2024-25 outturn + forecast years 2025-26 → 2030-31, at the workbook's full float precision. The welfare-cap/outside-cap split is carried as the row variant — UC legitimately appears in both (the cap excludes the jobseeking-conditionality portion), so a PE UC aggregate compares against the sum of the variants, which the annotations spell out. Geography follows administration: DWP lines are GB, NI mirror social security is NI, HMRC-administered lines (child benefit, tax credits, statutory payments) and totals are UK.
Tables 4.10 (changes since November 2025) and 4.11 (health- and disability-related welfare — the right counterpart for UC-health-inclusive concepts) are vendored in the same workbook for follow-up emission.
Validation
Three magnitude pins (state pension £146.2bn, UC-in-cap £66.4bn, total welfare £332.9bn in 2025-26) plus the cap + outside = total identity checked for all seven years at £1m tolerance.
*(< £0.1bn) cells → suppressed (42).Honesty boundary
The lead annotation is the consumed_as_target rule: pe-uk calibrates spending totals to official statistics that feed the same OBR round — per-benefit classification is required before any of this scores as held-out validation, with the 2024-25 outturn column highest-risk (mirror of the cbo-baseline lane's 317 pre-verified rows). Baseline discipline per #13/#21: post-measures March 2026 is a distinct
baseline_key; a newer EFO is a different baseline, never a revision. Theobr_measurescampaign family (10 staged PE results) attaches via #33.🤖 Generated with Claude Code