Skip to content

Refresh the OBR receipts growth series from the EFO receipts tables - #2342

Merged
MaxGhenis merged 10 commits into
mainfrom
obr-receipts-importer
Oct 24, 2026
Merged

MaxGhenis merged 10 commits into
mainfrom
obr-receipts-importer

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2341

This was stacked on #2322, which merged on 24 Oct, so the PR now targets main. d35650c49 merges main in with no conflicts; the PR's net diff against main has the same patch-id as the reviewed 9ca3cfd2d. It changes no model output: the yoy_growth.yaml edits are comments only, and no model code imports the importer.

Receipt-unit review fix (23 October, 9ca3cfd2d)

A future EFO can change its cash units. Combining £m levels with a £bn outturn base could write growth of 999.0 in place of 0.0. Every selected table in both receipts workbooks now has to declare pound billions in its own header before the fiscal-year row. The explicit aliases are £ billion, £bn and £ bn, ignoring case and whitespace. Other magnitudes, currencies and measures, and missing or ambiguous declarations, stop the import before any output. Requiring the same supported magnitude throughout also prevents mismatched releases from being combined. Receipt-growth arithmetic and economy extraction are unchanged.

Tests were committed first. Real-workbook regressions at the reviewed importer produced 19 failures and 6 passes, including the next-release attack; the unchanged control passed. Committed fixtures retain the original selected worksheet XML and shared strings, with source hashes. Mutations cover all three selected tables in each release, million-pound levels scaled by 1,000, misleading labels, missing/ambiguous declarations, and a 50-example Hypothesis property over labels and common scale factors.

Final validation: 105 importer tests, 534 other unique Python tests, and 46 policy YAML cases passed: 685 unique tests. Five tiny synthetic microsimulation fixtures were additionally rerun serially with per-test resource gates and passed. No requested cases remain excluded.

All pytest runs use foreground -n 2, with load1 <= 110 and at least 60 GB decimal free disk checked before each run. The host gate waited when load1 exceeded 110. Four corporate-receipts cases were initially deferred until compressor occupation fell below the hub's 30 GB gate; all four then passed serially. Five earlier tiny synthetic microsimulation fixtures were also rerun serially with per-test load/disk/memory checks. All 11 recorded gated-run readings (two preflights plus nine cases) meet those limits. No full-survey dataset build or certified-data measurement was run. The worktree venv follows the frozen dev lock (Python 3.13.9, core 3.33.2, NumPy 2.5.4). Full formatting/lint and uvx ruff format --check . pass.

The complete original March 2026/2025 receipts workbooks reproduce the entire committed YAML byte for byte through the receipts renderer (28 growth values). A full CLI run using all three original workbooks also leaves the four receipts series byte-identical. Its existing four-decimal economy extraction changes cumulative 2030 indices by RPI −0.0880%, CPI −0.0584%, average earnings −0.0585% and house prices +0.0194%; these independently verified shifts are now documented in the README's refresh steps, without changing the behavior.

The 2019 England business-rates source link now uses the correct 2019-to-2020-provisional-data release URL; that file's sole change is the link. The base branch and #2322 are unchanged. The UK hub owns the independent final-head review and any merge/retargeting.

Summary

#2322 added four OBR receipts growth series under gov.economic_assumptions.yoy_growth.obr. They project the corporate SDLT and business rates statistics:

  • stamp_duty_land_tax.residential and .non_residential, from receipts Table 3.7;
  • business_rates.great_britain, from current receipts on a cash basis (Table 3.8 in March 2026, 3.9 in March 2025);
  • business_rates.northern_ireland, from Table 3.2.

They were derived by hand. import_obr_forecasts.py did not touch them: read_calendar_values skipped nested series and replace_series_section handles only top-level ones. At the next EFO they would have stayed on March 2026, the failure that left the old statistics at their 2018-2021 values.

The importer now rewrites them from a new EFO's receipts tables in the same run as the economy series and forecast gaps. Run on the March 2026 tables, it reproduces every committed value exactly.

What the importer does

For each series, compute_receipts_growth:

  • finds the line by table title and row label, not table number, because numbers move between EFOs. Labels are compared lowercased without footnote markers, so March 2025's "Northern Ireland Business Rates" matches.
  • splits each sheet into tables at its title rows and reads a line only from its own table. The accrued current-receipts table's own "Business rates" row is never read, even on the same sheet, and notes above a title don't hide it. A line that is missing, or appears twice, stops the run.
  • reads the fiscal-year columns and the "Outturn"/"Forecast" markers above them, which may sit on one row or several. A fiscal year heading two columns, or a marker in two columns, stops the run.
  • computes each year's growth as the ratio of consecutive published levels minus 1, to six decimals. A level at or below zero in any year stops the run. The outturn year's base is the previous year's outturn in an earlier EFO (March 2025 for March 2026). The run stops unless that year is outturn there, and unless the first year is outturn in this EFO.

render_receipts_growth_yaml then rewrites each series' values block and metadata.reference list:

  • Earlier years are kept. Years before the EFO's outturn year stay as they were, comments included. At the next EFO, 2024 stays, so each index keeps its 2024 base. (Indices start at 1.0 in a series' first year; see create_economic_assumption_indices.py.)
  • This EFO's years. The outturn year's growth comes first, with a comment naming both EFOs and tables, then the forecast years.
  • After the forecast. Each series follows average_earnings as the same run writes it. The importer writes the value in effect in the first year after the forecast and each later change, so the series equals average_earnings in every year.
  • References. Both EFOs, their tables and the base outturn year.
  • Self-check. It parses the result and stops unless the whole document differs from the input only in those values and references.

Command line

  • --previous-receipts-file / --previous-receipts-url: the earlier EFO's receipts tables. Receipts growth is updated whenever yoy_growth.yaml is, so these and --receipts-file are required. The opt-out is --skip-receipts-growth; a --dry-run only warns.
  • Release checks. The importer reads each workbook's release from its contents sheet, or failing that from the file name or URL (URL-decoded).
    • The economy tables' contents sheet decides their release. --release-month and --release-year must agree with it when it names one.
    • It refuses receipts tables from a different EFO than the economy tables, or whose release it cannot tell. This also guards the existing September CPI gap.
    • It refuses previous tables unless they come from an earlier EFO.
  • Statutory gaps are built only when they are written, so a growth-only run (--output or --skip-statutory-gaps) does not need September CPI in the receipts tables.
  • A comment indented no deeper than a YAML key no longer ends its block.
  • --gaps-only refuses the previous receipts tables, because it leaves yoy_growth.yaml alone.
  • An invalid --release-month now fails with a clear message.
  • All files are still rendered before any is written (write_all_or_none), so growth, receipts growth and gaps never mix EFOs.

read_calendar_values now returns nested series, keyed by dotted path (stamp_duty_land_tax.residential).

Changes to yoy_growth.yaml

Comments only; every value is unchanged.

  • Header. The comment above the receipts series no longer names March 2026, so it stays true after a refresh.
  • Outturn-year comment. It now names both EFOs, e.g. # Outturn (2024-25 over 2023-24) from OBR EFO March 2026 over March 2025, receipts Table 3.8 (March 2025: Table 3.9) "Business rates". This line is kept at later EFOs, so it has to say where its value came from.

The file is now exactly the importer's output; a test enforces this (below).

The economic assumptions README lists the four series and adds a refresh step:

  • the new flags;
  • updating OBR_RECEIPTS_BN in test_corporate_receipts_statistics.py, which pins the series to the published levels.

Check against the March 2026 workbooks

Inputs:

Results:

  • Full CLI run. --file economy, --receipts-file, --previous-receipts-file, --output to a scratch file, on the committed yoy_growth.yaml, with no release flags (each release comes from its contents sheet). The four receipts series come out byte-identical; no line of the diff falls in the receipts block. Rerun on 23 October at the final head.
    • The same run would also change four economy series. RPI, CPI, average earnings and house prices hold three-decimal values referenced to Table A.1, where the importer writes four-decimal values from Tables 1.7, 1.6 and 1.16. For example, CPI in 2027 is 0.020 against 0.0197. The committed forecast gaps are measured from the stored values, so the two files agree with each other. This PR leaves the economy series alone.
  • Library run. compute_receipts_growth gives the 28 growth values in Update corporate SDLT and business rates statistics and project them with OBR receipts #2322's growth.json exactly, and render_receipts_growth_yaml leaves the committed file byte-identical. That file came from a separate openpyxl extraction (extract_values.py), so this is a differential check of two readers. Rerun on 23 October at the final head.
  • Table location. Tables 3.7, 3.8 and 3.2 in March 2026, and 3.7, 3.9 and 3.2 in March 2025, with 2024-25 and 2023-24 as their outturn years.

Invariants (tests in test_import_obr_forecasts.py)

  1. Ratio. Hypothesis, any positive levels and 2 to 8 years. Every year from the outturn year is round(level(t) / level(t-1) - 1, 6), read back exactly from the written YAML.
  2. Tail. Same test. Every year after the forecast, to 2049, equals average_earnings in effect that year.
  3. History. Years before the outturn year are kept with their comments. A simulated March 2027 refresh keeps 2024 and its March 2026 comment.
  4. Locality. Only the four series' values and references change. The renderer checks this at runtime and a test checks it from outside, including the text before and after.
  5. Idempotence. A second run on the output changes nothing. Hypothesis checks this, and so does the golden fixture.
  6. Round-trip text. format_growth(v) is fixed-point, has at least four decimals and reads back as v. Hypothesis checks 300 floats.
  7. Reproduction. A replica of the March 2026 and March 2025 tables, built from the published cell values with the real titles, numbers and labels, leaves the committed yoy_growth.yaml byte-identical. Changing one value, one value's text (0.0380 to 0.038) or one reference makes it fail; each was tried.
  8. Refusals change no file. Each refusal is tested: tables from another EFO, receipts or previous tables whose release is unknown, economy tables whose contents contradict the release flags, a previous EFO that is not earlier, no outturn for the base year, a missing or duplicated line, a line only in another table, a fiscal year heading two columns, a marker in two columns, and a missing, non-numeric or non-positive level in any year. After a refusal, yoy_growth.yaml and both gap files are unchanged.
  9. Comments. A comment at any indentation before a series' values leaves the rendered file equal to the render without it, plus the comment in place.

Previous validation at 749b547 (23 October)

Local, foreground, -n 2, with host load around 50; PR CI is dispatched manually on this branch, since it targets #2322's branch.

  • test_import_obr_forecasts.py: 80 passed (60 at 0da72d8aa, 24 before this PR). The 20 new cases are listed under Review. 18 of them fail on 0da72d8aa; the other two, comments at indents 6 and 8, already worked there. The one changed existing case (test_receipts_growth_rejects_a_missing_or_non_positive_level, new message) also fails there.
  • test_corporate_receipts_statistics.py (excluding its 4 microsimulation cases, which this PR's comment-only YAML change cannot move): 137 passed.
  • test_lagged_series.py, test_council_tax_uprating.py, test_threshold_indexation.py, test_fiscal_year_parameters.py and test_domestic_rates_uprating.py: 133 passed.
  • code_health/test_cached_arrays_not_written_in_place.py: 36 passed (it failed at 0da72d8aa merged with main).
  • ruff format --check . and ruff check on the touched files pass.

Stack update (23 October 2026)

  • 8a99e330b merges Update corporate SDLT and business rates statistics and project them with OBR receipts #2322's updated head (376f10307), which carries Count corporate SDLT once and let SDLT abolition remove all of it #2235 and main at a45baf5e4. There was one conflict, in the economic assumptions README's EFO refresh checklist: main added step 6 (move PROJECTION_END_YEAR on) where this PR added step 6 (refresh the receipts series). Both are kept: receipts refresh as step 6, projection horizon as step 7, then the importer example. Main has not touched the importer or its tests since this PR branched.
  • daabdf343 fixes the review findings below.
  • 4daa09f56 merges Update corporate SDLT and business rates statistics and project them with OBR receipts #2322's f6ed43708. That commit holds test_aggregate_impact_certified.py's receipts identity to the spec's float32 tolerance, which fixes this branch's one CI failure in run 39915137960 (Test / Python 3/5; 1,743 other tests in that shard passed).
  • 749b547c5 fixes that run's other failure (Test / Python 4/5). test_cached_arrays_not_written_in_place.py's static scan treats a method call with a string first argument as a simulation read, so it took lines = content.split("\n") in render_receipts_growth_yaml for a cached array and flagged its two slice assignments. The function now builds each new list instead. Its output is unchanged: the importer tests, the scan's module (36 passed) and the real-workbook check all pass. In that run, every other shard, plus YAML, docs and smoke-import, passed. The CI run at this head is 39928984925.

Review

Round 1 (independent attack review of 0da72d8aa, 11 October): REQUEST_CHANGES, with seven should-fix findings and one nit, each reproduced. All are fixed in daabdf343, each with a test that fails on 0da72d8aa:

  1. Unidentified current receipts bypassed the release check, so growth from another EFO could be written silently. Receipts tables whose release can't be told are now refused, as previous ones already were (test_main_refuses_tables_from_the_wrong_efo[receipts_unidentified]).
  2. The economy release ignored the contents sheet. It now comes from the contents sheet, then the file name or URL, which is URL-decoded. The release flags must agree with the contents sheet (test_main_refuses_release_flags_the_economy_tables_contradict, test_main_takes_the_release_from_the_economy_contents_sheet, test_release_is_read_from_a_url_encoded_name).
  3. The first title applied to the whole sheet. Sheets are now split into tables at title rows. A later accrued table can't supply a cash-basis line, and notes above a title no longer hide it (test_a_line_is_read_only_from_its_own_table, test_a_table_title_below_publication_notes_is_found).
  4. Repeated fiscal-year headers overwrote levels. They are now refused (test_a_fiscal_year_heading_two_columns_is_refused).
  5. Marker rows overwrote each other. Markers are now collected across the table, and conflicting ones are refused (test_outturn_and_forecast_markers_may_sit_on_separate_rows, test_a_marker_in_two_columns_is_refused).
  6. Comment indentation ended YAML blocks. A shallow comment no longer ends a block (test_a_comment_at_any_indent_ends_no_yaml_block, at indents 0, 2, 4, 6 and 8).
  7. Skipped gaps still needed September CPI. Gaps are now built only when written (test_a_growth_only_run_does_not_need_september_cpi, three flag sets).
  8. Nit, pre-existing: the next EFO needs new year entries. The error now names the series and year, and README step 2 says to add the entry first (test_a_forecast_year_a_series_lacks_is_named). Inserting entries automatically would also move every later year up to the next entry, so that stays manual.

Also fixed: one probe in the review's scratch showed that a zero last level wrote growth of −1.0, zeroing the index. A level at or below zero is now refused in any year (test_a_last_level_at_or_below_zero_is_refused).

The review's own probes, rerun against daabdf343: findings 3 to 6 no longer reproduce. A title moved below its markers is now refused, because its markers fall outside the table; it used to give "found none".

Round 2 (review at 749b547c5): REQUEST_CHANGES for unchecked receipt units. Addressed by the unit checks and regressions above; independent review of the new head remains with the UK hub.

Reproducer: policyengine_uk/tests/test_import_obr_forecasts.py::test_real_receipts_units_are_refused_before_any_write, policyengine_uk/tests/test_import_obr_forecasts.py::test_next_release_real_receipts_refuse_a_thousandfold_unit_change, policyengine_uk/tests/test_import_obr_forecasts.py::test_real_receipts_workbooks_reproduce_committed_yaml_byte_for_byte

axiom: n/a: importer tooling for OBR forecast series; no policy rule changes

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 11, 2026 15:13
import_obr_forecasts.py now rewrites yoy_growth.obr.stamp_duty_land_tax.*
and business_rates.* from a new EFO's receipts tables, with the previous
EFO's tables as the base for outturn growth. Tables are found by title and
row label, years before the outturn year are kept, years after the OBR
forecast follow average_earnings, and the rewrite is checked to change only
those values and references. Run on the March 2026 tables, it reproduces the
committed series exactly.

Fixes #2341

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nly footnote markers from labels

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2322 now carries #2235's final head and main (a45baf5). One conflict,
in the economic assumptions README's EFO refresh checklist: main added
step 6 (move PROJECTION_END_YEAR on) where this PR added step 6 (refresh
the receipts series). Keep both: receipts refresh as step 6, projection
horizon as step 7, then the importer example. The importer, its tests and
the yoy_growth.yaml comments merge without conflict; main has not touched
the importer or its tests since this PR branched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An independent attack review of 0da72d8 (11 October) asked for changes,
with seven should-fix findings and one pre-existing nit, each reproduced.
All are fixed here, each with a test that fails on 0da72d8:

1. Receipts tables of unknown release are refused, as previous ones
   already were. Both the growth series and the September CPI gap read
   them, so unidentified tables could silently write the wrong EFO.
2. The economy tables' release comes from their contents sheet first,
   then the file name or URL (now URL-decoded). --release-month and
   --release-year must agree with the contents sheet.
3. Each sheet is split into tables at its title rows. A line is read only
   from its own table, so a later table on the same sheet can no longer
   stand in for it, and notes above a title no longer hide the table.
4. A fiscal year heading more than one column is refused instead of the
   later column silently replacing the earlier one.
5. "Outturn" and "Forecast" markers are collected across the table's
   rows; a marker in two different columns is refused.
6. A YAML comment indented no deeper than a key no longer ends its block.
7. Statutory gaps are built only when written, so a growth-only run
   (--output or --skip-statutory-gaps) no longer needs September CPI.
8. (Nit) A forecast year a series has no entry for now names the series
   and year; the README says to add the entry first.

Also: a receipts level at or below zero in any year is refused. A zero
last level used to write growth of -1.0, zeroing the index from then on.

The real March 2026 and March 2025 workbooks still reproduce the
committed yoy_growth.yaml byte for byte, with and without the CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

MaxGhenis commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator Author

Hand-off to the UK hub (build job 20261023-001751-stack-2322-b)

Head: 749b547c5, fast-forwards from 0da72d8aa. No history rewritten.

  • 8a99e330b merges Update corporate SDLT and business rates statistics and project them with OBR receipts #2322's new head (376f10307). There was one conflict, in the README's EFO checklist: the receipts refresh is step 6, main's projection-horizon step is step 7, and the example follows.
  • daabdf343 fixes all eight findings from the independent attack review of 0da72d8aa (11 October, REQUEST_CHANGES). It adds 20 test cases, and 18 of them fail on 0da72d8aa. It also refuses a non-positive receipts level in any year, which the review's probes found writing −100% growth.
  • 4daa09f56 merges Update corporate SDLT and business rates statistics and project them with OBR receipts #2322's f6ed43708, the float32 tolerance fix for this branch's one CI failure (run 39915137960, Test / Python 3/5).
  • 749b547c5 fixes that run's only other failure (Python 4/5). The static cached-array scan mistook content.split("\n") for a simulation read, so render_receipts_growth_yaml now rebuilds its line list instead of slice-assigning; the output is unchanged.

Tests: these ran locally with -n 2 and all passed:

  • the importer tests: 80;
  • the receipts statistics parameter tests: 137;
  • the uprating and fiscal-year tests that read yoy_growth.yaml: 133.

Ruff is clean. Against the real OBR workbooks (March 2026, plus the March 2025 receipts tables):

CI (dispatched on the branch, since this PR targets #2322's branch): run 39928984925 at 749b547c5. At 09:50Z, when this job ended, its Changes and Lint had passed and its test shards were queued. At daabdf343 (run 39915137960), only Python 3/5 and 4/5 failed, both fixed above. The PR is still a draft: the hub marks it ready once that run is green.

Impact: none on model outputs. The yoy_growth.yaml changes are comments only, and no model code imports the importer.

Open items

MaxGhenis added a commit that referenced this pull request Oct 23, 2026
test_head_totals_agree_with_the_model_weighted_sums checked that the
partition's heads plus declared double counts equal gov_tax within a
relative 1e-9. The model sums gov_tax for each household in float32,
policyengine-core's float type, while the measurement sums the heads in
float64, so the two agree only to float32 rounding. With this PR's
corporate SDLT and business rates statistics, the 2026 totals on
policyengine-uk-data 1.58.1 differ by 1.75e-9 (948,404,896,828 against
948,404,895,169), and the check failed in CI (#2342's run 39915137960).

Use report_spec.yaml's identity_relative_tolerance (1e-6). The spec
documents it as the tolerance for accounting identities between weighted
totals, set by float32 rounding; the measurement applies the same value
to its per-household identity checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 5 commits October 23, 2026 04:42
Brings the certified receipts identity's float32 tolerance from #2322,
which fixes this branch's CI failure in run 39915137960 (Test / Python
3/5, test_head_totals_agree_with_the_model_weighted_sums).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_cached_arrays_not_written_in_place.py's static scan treats a method
call whose first argument is a string literal as a read from the
simulation, so lines = content.split("\n") was tracked as a cached array
and the two slice assignments in render_receipts_growth_yaml failed it
(CI run 39915137960, Test / Python 4/5). The function edits text, not a
simulation array; build each new list instead. The output is unchanged:
the importer tests and the real-workbook check still pass byte for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Hand-off to the UK hub (Budget build 8e19f9d5)

Head: 9ca3cfd2d7a2cbc9b17602b69b3881ef405a9764, three fast-forward commits above 749b547c5. The base and PR author are unchanged.

  • Receipt tables in both releases must explicitly declare pound billions in their own headers. Unknown/missing/ambiguous declarations, including £m, stop before output; mismatched cash magnitudes cannot be combined.
  • Added real-workbook mutation/byte-control tests and a Hypothesis property over labels/scales. Tests first: 19 expected failures and 6 passes; final importer: 105 passed.
  • Fixed only the 2019 England source URL and documented the unchanged four-decimal economy extraction and verified 2030 shifts.

Validation: 639 Python + 46 policy YAML = 685 unique tests passed, all foreground -n 2; five tiny microsimulation fixtures also passed a serial repeat under per-test memory/load/disk gates. No requested cases remain excluded. Full lint/format and uvx ruff format --check . pass. Original full receipts workbooks reproduce committed YAML bytes; the full CLI preserves receipt blocks and confirms all four documented economy-index shifts. No full-survey dataset run or build.

Impact: no policy values or model output changed. The UK hub owns independent review and subsequent merge/retargeting; none was performed here. .job/HANDOFF.md, .job/handoffs.jsonl and the verified .job/HANDOFF.bundle are in the assigned workspace. No CI workflow was dispatched by this job.

Base automatically changed from update-corporate-receipts-statistics to main October 24, 2026 03:40
#2322 (this branch's old base) merged at 03:40Z, so the PR now targets main.
This merge has no conflicts, and the net diff against main has the same
patch-id as the reviewed 9ca3cfd (b106e8a7).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 24, 2026 08:48
@github-actions

Copy link
Copy Markdown
Contributor

Aggregate impact on certified data: pass

Base 4ed874f23d against head ae3b9cf3f7, both on enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1, sha256 056e80fd8e91), fiscal years 2025-26 to 2031-32. Each year is a full microsimulation run on each side.

Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in report_spec.yaml; a flag does not fail the job).

Workflow run and full report

Notes

  • In the years a figure from fewer than 10 records is suppressed (spending.disabled_students_allowance), every total containing it, and the decile incomes, are rebuilt from their other components, so no published total includes it. Those totals are marked †. A change is withheld where the two sides leave out different figures and can't be lined up.

Moves flagged for review

None.

Spending by benefit and service

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Caseloads

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Standard reform costings (change in gov_balance)

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise additional rate by 3pp 0 0 0 0 0 suppressed suppressed

Standard reforms: change in each reform's target

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise additional rate by 3pp: change in income_tax 0 0 0 0 0 suppressed suppressed

Household net income by decile

No household's net income changes in any year.

No change in any year: receipts by tax head; government balance, each component counted once; model totals; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles.

Checks failing on both base and head (1)
  • VAT is never negative (sign:vat): 2025-26: 76 units below zero, a weighted £-54,797,972 in total; 2026-27: 76 units below zero, a weighted £-56,271,349 in total; 2027-28: 76 units below zero, a weighted £-57,609,146 in total
Counted-once check

Each of 86 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.

  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; gov_spending; not a failure)
  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; household_benefits; not a failure)
Provenance
Side Commit Runtime Environment hash Partition hash Time
base main 4ed874f23d uk 2.152.5 (files 77596e142f87), core 3.33.2, py 3.13.17 aarch64 293cdd16b6ad 8d321bae91af 1590.4s
head #2342 merged into main ae3b9cf3f7 uk 2.152.5 (files 111ab8d5707d), core 3.33.2, py 3.13.17 aarch64 293cdd16b6ad 8d321bae91af 1602.35s

Dataset: hf://policyengine/policyengine-uk-data-private/enhanced_frs_2024_25.h5@2526edbbb687a3b54dbb11272e5389e4b62d2ac2, sha256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784 (verified on both sides), policyengine-uk-data 1.58.1.

Receipts by tax head: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Income tax £299.29bn £319.01bn £336.54bn £351.69bn £367.24bn £385.36bn £403.66bn
National Insurance (employee and self-employed) £52.58bn £54.94bn £56.75bn £58.48bn £60.62bn £62.57bn £65.03bn
National Insurance (employer) £152.64bn £159.18bn £164.14bn £168.73bn £176.11bn £181.72bn £188.90bn
Employer NI change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Employer NI change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Capital gains tax £12.62bn £13.06bn £13.55bn £14.04bn £14.54bn £15.09bn £15.65bn
Student loan repayments £7.83bn £8.12bn £8.35bn £8.56bn £8.79bn £9.04bn £9.38bn
VAT £253.71bn £260.53bn £266.72bn £273.14bn £279.83bn £286.71bn £293.71bn
Change in VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
VAT on private school fees £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Fuel duties £24.53bn £24.64bn £26.64bn £26.69bn £26.42bn £25.76bn £26.35bn
Alcohol duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tobacco duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Vehicle excise duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Betting and gaming duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
TV licence fee £4.28bn £4.43bn £4.44bn £4.46bn £4.48bn £4.50bn £4.52bn
Council tax £49.21bn £51.88bn £54.97bn £58.29bn £61.55bn £64.92bn £68.49bn
High value council tax surcharge £0.00bn £0.00bn £0.00bn £0.48bn £0.49bn £0.51bn £0.52bn
Northern Ireland domestic rates £0.55bn £0.58bn £0.61bn £0.63bn £0.67bn £0.70bn £0.73bn
Business rates £34.68bn £38.04bn £38.88bn £39.76bn £43.07bn £43.13bn £44.57bn
Stamp duty land tax on the household's own purchases £6.93bn £7.42bn £7.85bn £8.26bn £8.68bn £9.09bn £9.51bn
Stamp duty land tax paid by companies £4.81bn £5.98bn £6.73bn £7.46bn £8.21bn £8.87bn £9.16bn
Land transaction tax (Wales) £0.08bn £0.08bn £0.09bn £0.09bn £0.10bn £0.10bn £0.11bn
Land and buildings transaction tax (Scotland) £0.31bn £0.32bn £0.34bn £0.36bn £0.37bn £0.39bn £0.40bn
Unspecified tax change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Unspecified tax change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax excluding the main residence £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Land value tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Carbon tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Spending by benefit and service: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
State Pension £122.15bn £126.64bn £127.76bn £129.65bn £133.46bn £137.40bn £141.47bn
Pension Credit £6.68bn £7.07bn £7.35bn £7.50bn £7.65bn £7.83bn £8.02bn
Winter Fuel Payment £1.70bn £1.68bn £1.59bn £1.57bn £1.57bn £1.55bn £1.52bn
Pension Age Winter Heating Payment (Scotland) £0.19bn £0.20bn £0.20bn £0.21bn £0.21bn £0.22bn £0.22bn
Universal Credit £75.54bn £80.75bn £83.40bn £85.21bn £86.21bn £87.61bn £88.91bn
Housing Benefit £8.59bn £8.93bn £9.22bn £9.37bn £9.64bn £9.91bn £10.20bn
Council Tax Reduction £1.98bn £2.03bn £2.07bn £2.12bn £2.17bn £2.22bn £2.26bn
Employment and Support Allowance (income-related) £0.79bn £0.81bn £0.83bn £0.85bn £0.87bn £0.89bn £0.91bn
Employment and Support Allowance (contribution-based) £5.22bn £5.36bn £5.49bn £5.62bn £5.76bn £5.90bn £6.04bn
Jobseeker's Allowance (income-based) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Jobseeker's Allowance (contribution-based) £0.29bn £0.30bn £0.30bn £0.31bn £0.32bn £0.33bn £0.33bn
Income Support £0.12bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Working Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Incapacity Benefit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Personal Independence Payment £27.05bn £28.19bn £29.28bn £30.02bn £30.75bn £31.51bn £32.28bn
Disability Living Allowance £7.52bn £7.83bn £8.14bn £8.34bn £8.55bn £8.76bn £8.97bn
Attendance Allowance £8.71bn £9.08bn £9.43bn £9.67bn £9.90bn £10.15bn £10.40bn
Carer's Allowance £4.50bn £4.69bn £4.87bn £4.99bn £5.11bn £5.23bn £5.36bn
Carer Support Payment (Scotland) £0.31bn £0.32bn £0.34bn £0.34bn £0.35bn £0.36bn £0.37bn
Carer's Allowance Supplement (Scotland) £0.00bn £0.04bn £0.04bn £0.04bn £0.04bn £0.04bn £0.04bn
Severe Disablement Allowance £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn
Industrial Injuries Disablement Benefit £0.51bn £0.52bn £0.53bn £0.54bn £0.56bn £0.57bn £0.59bn
Armed Forces Compensation Scheme £0.61bn £0.62bn £0.64bn £0.65bn £0.67bn £0.69bn £0.70bn
War Widow's or Widower's Pension £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Benefit £16.23bn £16.91bn £17.53bn £17.96bn £18.39bn £18.84bn £19.29bn
Scottish Child Payment £0.50bn £0.52bn £0.54bn £0.56bn £0.56bn £0.58bn £0.59bn
Two Child Limit Payment (Scotland, counterfactual) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Healthy Start vouchers £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn
Maternity Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Maternity Pay £3.32bn £3.41bn £3.49bn £3.57bn £3.66bn £3.75bn £3.84bn
Statutory Paternity Pay £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Sick Pay £1.13bn £1.16bn £1.19bn £1.22bn £1.25bn £1.28bn £1.31bn
Sure Start Maternity Grant £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn
Bereavement Support Payment £0.67bn £0.69bn £0.70bn £0.72bn £0.74bn £0.76bn £0.78bn
Widowed Parent's Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tax-Free Childcare £0.55bn £0.56bn £0.57bn £0.58bn £0.60bn £0.61bn £0.59bn
Extended childcare entitlement £2.77bn £3.27bn £3.36bn £3.43bn £3.53bn £3.61bn £3.70bn
Universal childcare entitlement £2.26bn £2.45bn £2.51bn £2.58bn £2.64bn £2.72bn £2.78bn
Targeted childcare entitlement £0.47bn £0.49bn £0.50bn £0.51bn £0.52bn £0.53bn £0.47bn
Care to Learn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Childcare Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Parents' Learning Allowance £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn
Adult Dependants' Grant £0.06bn £0.06bn £0.04bn £0.04bn £0.04bn £0.04bn £0.04bn
Travel Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
NHS spending £204.68bn £205.46bn £206.22bn £207.04bn £207.96bn £208.89bn £209.79bn
State education spending £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn
Transport subsidy spending £25.15bn £25.25bn £25.34bn £25.44bn £25.55bn £25.67bn £25.78bn
Cost-of-living support payment £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Bills Rebate £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Price Guarantee subsidy £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disability basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Other public spending budget change £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Caseloads: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
State Pension 11,308k 11,208k 10,906k 10,812k 10,860k 10,908k 10,955k
Pension Credit 1,491k 1,489k 1,488k 1,470k 1,473k 1,476k 1,475k
Winter Fuel Payment 7,091k 7,011k 6,600k 6,512k 6,493k 6,379k 6,248k
Pension Age Winter Heating Payment (Scotland) 806k 807k 807k 805k 809k 812k 816k
Universal Credit 6,361k 6,370k 6,404k 6,419k 6,390k 6,387k 6,368k
Housing Benefit 1,254k 1,252k 1,256k 1,242k 1,246k 1,250k 1,252k
Council Tax Reduction 3,187k 3,212k 3,225k 3,248k 3,266k 3,282k 3,295k
Employment and Support Allowance (income-related) 71k 71k 71k 72k 72k 72k 72k
Employment and Support Allowance (contribution-based) 722k 724k 727k 730k 733k 737k 740k
Jobseeker's Allowance (income-based) 0k 0k 0k 0k 0k 0k 0k
Jobseeker's Allowance (contribution-based) 67k 67k 68k 68k 68k 69k 69k
Income Support 25k 0k 0k 0k 0k 0k 0k
Working Tax Credit 0k 0k 0k 0k 0k 0k 0k
Child Tax Credit 0k 0k 0k 0k 0k 0k 0k
Incapacity Benefit 0k 0k 0k 0k 0k 0k 0k
Personal Independence Payment 3,581k 3,595k 3,608k 3,623k 3,639k 3,655k 3,671k
Disability Living Allowance 1,296k 1,301k 1,306k 1,311k 1,317k 1,323k 1,329k
Attendance Allowance 1,701k 1,708k 1,714k 1,721k 1,729k 1,736k 1,744k
Carer's Allowance 1,038k 1,042k 1,046k 1,050k 1,055k 1,059k 1,064k
Carer Support Payment (Scotland) 71k 72k 72k 72k 73k 73k 73k
Carer's Allowance Supplement (Scotland) 0k 72k 72k 72k 73k 73k 73k
Severe Disablement Allowance 4k 4k 4k 4k 4k 4k 4k
Industrial Injuries Disablement Benefit 137k 138k 139k 139k 140k 140k 141k
Armed Forces Compensation Scheme 94k 94k 94k 95k 95k 95k 96k
War Widow's or Widower's Pension 0k 0k 0k 0k 0k 0k 0k
Child Benefit 8,136k 8,165k 8,185k 8,215k 8,249k 8,282k 8,314k
Scottish Child Payment 352k 354k 355k 359k 357k 358k 357k
Two Child Limit Payment (Scotland, counterfactual) 0k 0k 0k 0k 0k 0k 0k
Healthy Start vouchers 200k 201k 202k 203k 204k 204k 205k
Maternity Allowance 0k 0k 0k 0k 0k 0k 0k
Statutory Maternity Pay 334k 335k 336k 338k 339k 341k 342k
Statutory Paternity Pay 0k 0k 0k 0k 0k 0k 0k
Statutory Sick Pay 182k 183k 183k 184k 185k 186k 186k
Sure Start Maternity Grant 26k 26k 26k 27k 27k 27k 27k
Bereavement Support Payment 98k 98k 99k 99k 100k 100k 101k
Widowed Parent's Allowance 0k 0k 0k 0k 0k 0k 0k
Tax-Free Childcare 1,018k 1,029k 1,028k 1,033k 1,034k 1,039k 1,023k
Extended childcare entitlement 718k 724k 728k 732k 737k 741k 743k
Universal childcare entitlement 986k 989k 992k 997k 1,002k 1,013k 1,015k
Targeted childcare entitlement 98k 98k 96k 96k 96k 97k 84k
Care to Learn 0k 0k 0k 0k 0k 0k 0k
Childcare Grant 0k 0k 0k 0k 0k 0k 0k
Parents' Learning Allowance 98k 99k 99k 100k 100k 100k 101k
Adult Dependants' Grant 18k 18k 12k 13k 13k 13k 13k
Travel Grant 0k 0k 0k 0k 0k 0k 0k
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund 0k 0k 0k 0k 0k 0k 0k
Cost-of-living support payment 0k 0k 0k 0k 0k 0k 0k
Energy Bills Rebate 0k 0k 0k 0k 0k 0k 0k
Energy Price Guarantee subsidy 0k 0k 0k 0k 0k 0k 0k
Basic income 0k 0k 0k 0k 0k 0k 0k
Disability basic income 0k 0k 0k 0k 0k 0k 0k
Government balance, each component counted once: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government receipts, each component once £904.02bn £948.22bn £986.59bn £1,021.15bn £1,061.16bn £1,098.47bn £1,140.69bn
Government spending £530.58bn † £545.59bn † £553.80bn † £560.99bn † £569.57bn † £578.78bn † £587.89bn †
Government balance, each receipt once £373.44bn † £402.63bn † £432.79bn † £460.16bn † £491.59bn † £519.69bn † £552.80bn †
Benefits in household_benefits but not in gov_spending £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Model totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_tax £904.02bn £948.22bn £986.59bn £1,021.15bn £1,061.16bn £1,098.47bn £1,140.69bn
household_tax £497.67bn £528.51bn £555.73bn £579.27bn £605.22bn £630.03bn £658.08bn
pre_budget_change_household_tax £489.85bn £520.38bn £547.38bn £570.23bn £595.94bn £620.49bn £648.19bn
gov_spending £530.58bn † £545.59bn † £553.80bn † £560.99bn † £569.57bn † £578.78bn † £587.89bn †
household_benefits £530.58bn † £545.59bn † £553.80bn † £560.99bn † £569.57bn † £578.78bn † £587.89bn †
gov_balance £373.44bn † £402.63bn † £432.79bn † £460.16bn † £491.59bn † £519.69bn † £552.80bn †
Household income totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household market income £1,657.06bn £1,719.25bn £1,771.68bn £1,821.99bn £1,872.88bn £1,930.99bn £2,001.98bn
Household net income £1,650.70bn † £1,695.57bn † £1,727.87bn † £1,760.76bn † £1,793.15bn † £1,834.40bn † £1,884.74bn †
Household net income (HBAI definition) £1,462.17bn £1,509.90bn £1,544.25bn £1,577.67bn £1,612.09bn £1,651.53bn £1,702.40bn
Population: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
People 69,514k 69,778k 70,036k 70,316k 70,626k 70,944k 71,249k
Benefit units 35,247k 35,381k 35,512k 35,654k 35,811k 35,972k 36,127k
Households 31,167k 31,285k 31,401k 31,526k 31,665k 31,808k 31,944k
Poverty rates: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 16.1% 15.2% 15.5% 15.6% 15.7% 15.8% 15.8%
Absolute poverty, before housing costs: children 24.2% 21.4% 21.6% 21.6% 21.7% 22.0% 21.8%
Absolute poverty, before housing costs: working-age adults 13.9% 13.5% 14.0% 14.2% 14.2% 14.3% 14.3%
Absolute poverty, before housing costs: pensioners 13.7% 13.2% 13.1% 13.4% 13.4% 13.5% 13.5%
Absolute poverty, after housing costs: everyone 21.6% 20.9% 21.3% 21.5% 21.8% 22.1% 22.2%
Absolute poverty, after housing costs: children 33.4% 30.9% 31.1% 31.1% 31.5% 32.0% 32.3%
Absolute poverty, after housing costs: working-age adults 19.9% 19.7% 20.3% 20.5% 20.8% 21.0% 21.1%
Absolute poverty, after housing costs: pensioners 12.8% 12.6% 12.4% 12.6% 12.7% 13.0% 13.0%
Relative poverty, before housing costs: everyone 17.2% 16.5% 16.5% 16.6% 16.3% 16.4% 16.4%
Relative poverty, before housing costs: children 26.4% 23.7% 23.4% 23.4% 22.9% 23.0% 22.9%
Relative poverty, before housing costs: working-age adults 14.7% 14.4% 14.7% 14.8% 14.7% 14.8% 14.8%
Relative poverty, before housing costs: pensioners 14.8% 14.8% 14.0% 14.2% 13.9% 14.0% 14.1%
Relative poverty, after housing costs: everyone 21.7% 21.1% 21.2% 21.2% 21.4% 21.4% 21.7%
Relative poverty, after housing costs: children 33.5% 31.3% 31.0% 30.8% 31.0% 31.1% 31.5%
Relative poverty, after housing costs: working-age adults 19.9% 19.8% 20.2% 20.3% 20.5% 20.5% 20.7%
Relative poverty, after housing costs: pensioners 13.0% 12.7% 12.4% 12.1% 12.0% 12.1% 12.4%
People in poverty: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 11,163k 10,583k 10,852k 11,004k 11,081k 11,222k 11,226k
Absolute poverty, before housing costs: children 3,629k 3,221k 3,260k 3,284k 3,310k 3,369k 3,350k
Absolute poverty, before housing costs: working-age adults 5,902k 5,806k 6,080k 6,197k 6,234k 6,304k 6,319k
Absolute poverty, before housing costs: pensioners 1,632k 1,556k 1,512k 1,523k 1,537k 1,550k 1,556k
Absolute poverty, after housing costs: everyone 15,000k 14,571k 14,945k 15,112k 15,392k 15,663k 15,804k
Absolute poverty, after housing costs: children 5,004k 4,656k 4,703k 4,723k 4,808k 4,897k 4,966k
Absolute poverty, after housing costs: working-age adults 8,476k 8,431k 8,812k 8,952k 9,130k 9,273k 9,341k
Absolute poverty, after housing costs: pensioners 1,520k 1,484k 1,430k 1,437k 1,455k 1,492k 1,497k
Relative poverty, before housing costs: everyone 11,974k 11,507k 11,535k 11,642k 11,544k 11,663k 11,712k
Relative poverty, before housing costs: children 3,963k 3,570k 3,543k 3,553k 3,489k 3,519k 3,522k
Relative poverty, before housing costs: working-age adults 6,246k 6,184k 6,378k 6,475k 6,460k 6,538k 6,563k
Relative poverty, before housing costs: pensioners 1,765k 1,753k 1,615k 1,613k 1,595k 1,606k 1,628k
Relative poverty, after housing costs: everyone 15,067k 14,732k 14,872k 14,931k 15,091k 15,202k 15,461k
Relative poverty, after housing costs: children 5,019k 4,715k 4,686k 4,669k 4,725k 4,754k 4,839k
Relative poverty, after housing costs: working-age adults 8,497k 8,516k 8,764k 8,884k 8,992k 9,060k 9,192k
Relative poverty, after housing costs: pensioners 1,551k 1,502k 1,422k 1,378k 1,374k 1,388k 1,429k
Inequality: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs 0.3088 0.3061 0.3067 0.3073 0.3077 0.3083 0.3093
Mean household net income in each side's own deciles: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household £17,768 † £17,884 † £17,830 † £17,847 † £18,125 † £18,315 † £18,440 †
Decile 2: mean household_net_income per household £27,995 † £28,845 † £28,594 † £29,012 † £29,189 † £29,600 † £30,278 †
Decile 3: mean household_net_income per household £34,275 † £35,509 † £36,037 † £36,498 † £36,760 † £37,372 † £37,928 †
Decile 4: mean household_net_income per household £38,650 † £40,696 † £41,778 † £42,415 † £43,055 † £43,779 † £44,259 †
Decile 5: mean household_net_income per household £43,775 † £44,546 † £45,584 † £46,298 † £47,371 † £48,231 † £49,554 †
Decile 6: mean household_net_income per household £48,323 † £49,547 † £50,591 † £51,565 † £51,587 † £53,193 † £54,178 †
Decile 7: mean household_net_income per household £56,215 † £57,308 † £58,072 † £58,467 † £59,609 † £60,184 † £61,764 †
Decile 8: mean household_net_income per household £69,098 † £70,411 † £71,825 † £72,849 † £74,256 † £75,374 † £77,582 †
Decile 9: mean household_net_income per household £79,578 † £81,719 † £82,750 † £84,111 † £85,450 † £86,991 † £89,920 †
Decile 10: mean household_net_income per household £117,506 † £120,013 † £121,860 † £123,908 † £125,844 † £128,160 † £130,960 †
Standard reform costings (change in gov_balance): head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp £6.22bn £6.50bn £6.70bn £6.90bn £7.11bn £7.34bn £7.62bn
Raise higher rate by 1pp £3.54bn £3.82bn £4.05bn £4.27bn £4.49bn £4.76bn £4.96bn
Raise personal allowance by ~800GBP/year £-3.83bn £-3.96bn £-4.03bn £-4.10bn £-4.19bn £-4.30bn £-2.02bn
Raise child benefit by 25GBP/week per additional child £-2.00bn £-1.76bn £-1.58bn £-1.48bn £-1.38bn £-1.28bn £-1.17bn
Reduce Universal Credit taper rate to 20% £-20.49bn £-21.05bn £-21.50bn £-21.94bn £-22.03bn £-22.40bn £-22.87bn
Raise Class 1 main employee NICs rate to 10% £11.07bn £11.51bn £11.84bn £12.14bn £12.49bn £12.83bn £13.23bn
Raise VAT standard rate by 2pp £25.01bn £25.68bn £26.29bn £26.92bn £27.58bn £28.26bn £28.95bn
Raise additional rate by 3pp £2.83bn £3.07bn £3.27bn £3.47bn £3.67bn suppressed suppressed
Raise the Universal Credit standard allowance by 10% £-3.03bn £-3.16bn £-3.28bn £-3.35bn £-3.39bn £-3.44bn £-3.51bn
Raise the State Pension by 10% £-7.81bn £-8.07bn £-8.06bn £-8.18bn £-8.43bn £-8.60bn £-8.96bn
Standard reforms: change in each reform's target: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp: change in income_tax £6.36bn £6.65bn £6.86bn £7.07bn £7.28bn £7.52bn £7.80bn
Raise higher rate by 1pp: change in income_tax £3.55bn £3.83bn £4.06bn £4.28bn £4.50bn £4.77bn £4.97bn
Raise personal allowance by ~800GBP/year: change in income_tax £-3.96bn £-4.10bn £-4.19bn £-4.26bn £-4.36bn £-4.47bn £-1.77bn
Raise child benefit by 25GBP/week per additional child: change in child_benefit £2.34bn £2.15bn £1.96bn £1.85bn £1.74bn £1.63bn £1.51bn
Reduce Universal Credit taper rate to 20%: change in universal_credit £20.48bn £21.04bn £21.49bn £21.93bn £22.02bn £22.40bn £22.86bn
Raise Class 1 main employee NICs rate to 10%: change in national_insurance £11.33bn £11.79bn £12.13bn £12.44bn £12.78bn £13.13bn £13.62bn
Raise VAT standard rate by 2pp: change in vat £25.01bn £25.68bn £26.29bn £26.92bn £27.58bn £28.26bn £28.95bn
Raise additional rate by 3pp: change in income_tax £2.83bn £3.07bn £3.27bn £3.47bn £3.67bn suppressed suppressed
Raise the Universal Credit standard allowance by 10%: change in universal_credit £3.03bn £3.16bn £3.28bn £3.36bn £3.41bn £3.47bn £3.52bn
Raise the State Pension by 10%: change in state_pension £10.72bn £11.23bn £11.37bn £11.62bn £12.08bn £12.54bn £13.07bn

@MaxGhenis
MaxGhenis added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit ee34a00 Oct 24, 2026
16 of 17 checks passed
@MaxGhenis
MaxGhenis deleted the obr-receipts-importer branch October 24, 2026 19:54
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Merged by the UK hub (Claude session 8e19f9d5) through the merge queue with the head pinned. The head is recorded in the lander log.

  • What it does: it refreshes the OBR receipts growth series in yoy_growth.yaml from the EFO receipts tables, with an importer that refuses anything it could misread. Budget day's EFO (28 Oct) goes through this importer.
  • First review (GPT-6.1 Sol, at 749b547c5): it confirmed all eight earlier findings fixed. It had one P1: the importer didn't read each table's cash units, so a release in £m would have silently written a growth value 1,000 times too large.
  • Hub fix: three commits to 9ca3cfd2d. They add receipt-unit validation, real-workbook and Hypothesis tests, Update corporate SDLT and business rates statistics and project them with OBR receipts #2322's corrected 2019 England source link, and documentation of the existing 4-decimal economy-series rounding, whose behaviour is unchanged.
  • Second review (GPT-6.1 Sol, APPROVE at 9ca3cfd2d):
    • Each selected table must now declare pounds billion in its own header, and the check runs before any level is read, for both releases.
    • The reviewer's attacks on the real OBR March 2026 workbook (SHA-256 f65b6cb7…) all refuse before writing anything. They cover sheet 3.8 converted to £m with every value scaled, missing or unknown units, mismatched or both-£m releases, and footnote-only units. A shifted header gives identical output.
    • The committed yoy_growth.yaml reproduces byte for byte from the real March 2026 and March 2025 workbooks.
    • Its local run passed 130 tests (105 importer, 25 full-workbook probes).
  • Main merge (d35650c49): once Update corporate SDLT and business rates statistics and project them with OBR receipts #2322 merged, the hub merged main into the branch with no conflicts. The PR's net diff against main has the same patch-id as the reviewed 9ca3cfd2d, so the reviewed change is what lands.
  • CI: CI only ran once the PR targeted main after Update corporate SDLT and business rates statistics and project them with OBR receipts #2322 merged; every check passes at the landed head. The merge queue re-runs the full suite.
  • Impact: none on model outputs. The receipts YAML is unchanged apart from comments.
  • Why now: the importer runs on Budget day's EFO, and Max asked on 22 Oct to clear the UK PR backlog before the Budget.

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.

OBR importer does not refresh the receipts growth series that project corporate SDLT and business rates

1 participant