Skip to content

Apply the Universal Credit Act 2025 standard allowance uplifts to 2029-30 - #2242

Open
MaxGhenis wants to merge 10 commits into
mainfrom
fix-uc-standard-allowance-uplift-2239
Open

MaxGhenis wants to merge 10 commits into
mainfrom
fix-uc-standard-allowance-uplift-2239

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2239.

What was wrong

rebalancing/standard_allowance_uplift.yaml held the Universal Credit Act 2025 uplifts (2.3%, 3.1%, 4.0%, 4.8%), but no formula read it. #1691 wrote the 2026-27 amounts, which include the 2.3% uplift, into standard_allowance/amount.yaml and removed the formula step that applied the uplift. From 2027-28 the allowance grew by CPI alone. The reproducer: 2027 was 2.3% above 2026, CPI only.

The law

Universal Credit Act 2025 s. 1(2) sets the minimum standard allowance for 2026-27 to 2029-30 in three steps:

  1. Take the 2025-26 amounts for 2026-27, and the previous year's Step 2 amounts for each later year.
  2. Increase them by the relevant CPI percentage, never below 0% (s. 1(3)).
  3. Increase them by the year's uplift: 2.3%, 3.1%, 4.0% or 4.8% (s. 1(4)).

Each uplift is measured against the CPI-only path from 2025-26, not compounded, because Step 1 takes the previous year's amount before its Step 3. So from one year to the next the allowance grows by CPI × (1 + this year's uplift) / (1 + last year's uplift).

Check: 2026-27 single 25+ is £400.14 × 1.038 × 1.023, rounded at each step, which gives the legislated £424.90.

Changes

  • rebalancing/create_standard_allowance_uprating.py (new) runs in process_parameters before uprating. It builds gov.dwp.universal_credit.standard_allowance.uprating from two parts:
    • CPI: gov.benefit_uprating_cpi read on 30 April of each year (the fiscal-year sampling date), as uprating extends it and including any reform to it, floored at 0% growth in 2026-27 to 2029-30 (s. 1(3)).
    • Uplift: times (1 + the uplift). For a year with legislated amounts in amount.yaml (2026-27 so far, read from the file), the uplift is the one those amounts include, from the s. 1(4) table. For later years it is the uplift in force: nil with rebalancing.active false, and the reformed value under an uplift reform.
  • amount.yaml is uprated by that index instead of gov.benefit_uprating_cpi. Years after the legislated ones therefore get CPI times the change in uplift, and CPI alone after 2029-30.
    • The CPI part is the same index as gov.benefit_uprating_cpi, so the difference from a CPI-only path is exactly the uplift.
    • s. 1(3) names September CPI. The model's benefit index is its measure of CPI for all benefit uprating (OBR CPI, lagged). Moving benefits to September CPI is separate work (the branch below).
  • Legislated amounts are used as they are. The 2026-27 rates stay at their legislated values, which include the 2.3% uplift.
    • Switching rebalancing off, or changing a later year's uplift, moves every year after 2026-27. With rebalancing off, 2027-28 onwards is the 2026-27 Step 2 amount (the legislated rate less its uplift) moved with CPI alone.
    • To change 2026-27 itself, set the amount.
    • A reform to amount is used as given, as the replay's CPI-only counterfactual is.
  • standard_allowance_uplift.yaml is dated from 6 April of each tax year and cites s. 1(4).
  • Docs. uc-rebalancing.md explains the mechanism. Its uplift-reform example keys fiscal years ("2027"): single-day keys ("2026-01-01") never reached the 30 April sampling date.
  • uc_reform.py: corrected a stale comment.

Design note (review rounds 1-4). An earlier version rescaled the legislated 2026-27 amounts to the uplift in force, so that switching rebalancing off reached 2026-27 too. Telling legislated entries from reform entries, and from the entries Parameter.update writes to restore a value after a bounded reform, kept producing edge cases. That version also needed a hook in simulation.py, a file the dated-Budget-reform work owns (session "Make dated Budget reforms correct on every route"). This version keeps the legislated amounts fixed and puts the uplift only in the index, which removes that class of case.

One limitation remains, and it predates this PR: Parameter.update restores a bounded override's prior value at the end of its period as an explicit entry, as it does for every uprated parameter. Uprating then starts from the latest explicit entry. So any year after the last legislated one, before or after a bounded override of amount, can hold a stale value instead of the projected one. For example, {"2026": 500} gives £424.90 in 2027 instead of £438.07, and {"2028": 500} gives £424.90 in 2027 too. The dated-Budget-reform work (W1) is fixing this on every route.

Tests

test_uc_standard_allowance_uplift.py:

  • Example. The issue's single claimant aged 30: 2025 £4,801.68 and 2026 £5,098.80 unchanged. Each later year equals the previous × CPI × the Act's uplift ratio. 2027 is now £5,256.85, where it was £5,216.06.
  • Differential. For every claimant type, an independent step-by-step implementation of s. 1(2), started from the 2026-27 Step 2 amounts, matches the model to 1e-9 for 2027-28 to 2029-30; after that, CPI alone.
  • Properties (Hypothesis):
    • For random uplift schedules (0-15%), 2026-27 stays legislated and each later year grows by CPI × (1 + uplift) / (1 + previous uplift), using 2026-27's legislated 2.3% as the first previous uplift.
    • Switching rebalancing off in any single year from 2027 to 2030 removes exactly that year's uplift, and in 2026 changes nothing.
    • With it off throughout, 2027-28 onwards is the 2026-27 Step 2 amount moved with CPI.
  • Reforms:
    • A reform to amount is used as given, including when combined with active false, an uplift reform, or a value equal to the legislated one.
    • Scalar active: True (from 2000) matches the baseline.
    • A flat or fiscal-year gov.benefit_uprating_cpi reform reaches the allowance in that year.
    • A one-year amount reform before the legislated year leaves the later path as it would be without it.

Mutation check. On main the example, the differential and the rebalancing-off tests fail. The scalar-activation and benefit-index regressions fail on this PR's first head.

Existing tests. These pass:

  • test_uc_rebalancing.py (existing-claimant protection, as now on main);
  • test_no_economic_assumptions.py;
  • 414 UC YAML tests and 829 UC and DWP YAML tests;
  • the 15 test files that use parameter_changes.

Under no_economic_assumptions, the allowance grows only by the uplift ratio to 2029-30 and is then flat.

National impact

Data: enhanced_frs_2024_25.h5@1.58.0, this branch against main at 6d89338 (this branch merged with it), full microsimulation (£bn, branch minus main).

2025 2026 2027 2028 2029 2030
universal_credit 0 0 +0.187 +0.403 +0.608 +0.814
uc_standard_allowance (all benefit units) 0 0 +1.871 +4.071 +6.133 +6.284
uc_LCWRA_element 0 0 −0.185 −0.409 −0.575 0
gov_balance 0 0 −0.187 −0.403 −0.607 −0.813

UC rises by much less than uc_standard_allowance, for three reasons:

  • uc_standard_allowance is computed for every benefit unit, not only those paid UC.
  • For existing LCWRA claimants, UC Act 2025 s. 4 requires the protected LCWRA amount plus each standard allowance amount to rise at least by CPI each year to 2029-30. universal_credit_july_2025_reform (as on main since this branch was opened) sets the protected amount for 2027-28 to 2029-30 at the higher of the previous year's amount and that floor. A higher allowance lowers the floor, so the protected amount rises less: the −£0.19bn to −£0.58bn. The scenario sets it only to 2029-30, so in 2030 the LCWRA element is the same in both runs.
  • Families at the benefit cap gain nothing.

Who gains and who loses:

  • 5.94m benefit units gain in 2027, 5.87m in 2029 and 5.92m in 2030.
  • 150k-262k benefit units lose £0.55 to £31.85 a year (£0.8m-£3.4m in total). These are families whose award does not rise, such as benefit-capped families. uc_deductions is uc_deduction_rate × uc_standard_allowance, so their debt deductions grow with the allowance.

Fiscal-event replay: Spring Statement 2025 UC standard allowance increase

Real microsimulation runs of the replay construction on the proposed certified data (enhanced_frs_2024_25.h5@1.58.0), this branch against main 6d89338. The construction is copied from policyengine-scorecard pipeline/build_uk_event_registry.py on main, snapshotted 2026-10-11 (event_replay.py). Gain to the Exchequer, £bn.

Reversal: the 2025-26 monthly amounts × 1.038 (September 2025 CPI) × benefit CPI ratio from April 2026, for 2026-2029.

universal_credit saving 2024 2025 2026 2027 2028 2029 2030
main 0 0 +0.721 +0.732 +0.746 +0.753 0
this PR 0 0 +0.721 +0.983 +1.291 +1.562 0

On main the reform's effect stayed flat because the certified world lacked the 2027-29 uplifts. With them it grows to £1.56bn in 2029-30, against the OBR's £1.55bn cited in #2239. Input support: the standard allowance needs no new input, so the national exposure is not inert.

Overlaps

  • The unowned pushed branch benefit-uprating-september-cpi (session 7d5c5e19, not live; notes in ~/reviews/benefit-uprating-sep-cpi-2026-09-29/STATUS.md) also applies the uplift, through its own standard_allowance/create_uprating_index.py on a September CPI index. Whoever revives it should rebase on this PR and keep one index builder, or the uplift is applied twice.
  • The fiscal-event replay (Add certified-bundle replay pipeline for recent OBR fiscal events policyengine-scorecard#157) reverses Spring Statement 2025's standard allowance increase by setting amount to a CPI-only path. With the uplift now in the certified world's amounts, that reversal widens through 2029-30, as the issue expects.

axiom: rulespec-uk#367 queued (UC Act 2025 s. 1, labelled pe-parity). The 2027-28 to 2029-30 companion cases from this PR are added there.

🤖 Generated with Claude Code

Reproducer: policyengine_uk/tests/test_dwp_published_rates.py::test_projection_continues_from_last_published_rate, policyengine_uk/tests/test_uc_standard_allowance_uplift.py::test_single_claimant_aged_30_matches_the_act, policyengine_uk/tests/test_uc_standard_allowance_uplift.py::test_amounts_match_the_act_step_by_step, policyengine_uk/tests/test_uc_standard_allowance_uplift.py::test_growth_is_cpi_times_the_change_in_uplift, policyengine_uk/tests/test_uc_standard_allowance_uplift.py::test_switching_rebalancing_off_removes_the_uplift_from_that_year, policyengine_uk/tests/test_uc_standard_allowance_uplift.py::test_rebalancing_off_throughout_follows_cpi_after_the_legislated_year

…9-30

standard_allowance_uplift held the s. 1 uplifts (2.3%, 3.1%, 4.0%, 4.8%) but
nothing read it after the 2026-27 amounts, which include the first uplift,
were written into amount.yaml, so 2027-28 to 2029-30 grew by CPI alone. The
standard allowance is now uprated by its own index, the benefit uprating CPI
index (floored at 0% in 2026-27 to 2029-30, s. 1(3)) times one plus the
year's uplift, so it grows by CPI times the change in uplift. A legislated
amount for those years is rescaled if the uplift in force differs from the
statutory one, so a reform to the uplift or to rebalancing.active reaches
every year.

Fixes #2239

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

Review round 1 on #2242:
- Treat the uplift as nil before its parameter starts, so switching
  rebalancing on from 2000 no longer crashes.
- Rescale only amounts that still hold their amount.yaml value; an amount a
  reform sets is used as given.
- Build the index from gov.benefit_uprating_cpi as uprating extends it, so a
  reform to that index is followed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2 on #2242:
- Sample gov.benefit_uprating_cpi on 30 April of each year, so a fiscal-year
  reform to it reaches that year rather than the next.
- Rescale only amount entries loaded from amount.yaml (they carry its file
  path), each at most once, so a reform that sets the legislated amount itself
  is used as given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 5 commits October 11, 2026 09:21
Review round 3 on #2242: after a bounded amount reform, Parameter.update
writes an entry restoring the legislated amount, with no file path, and the
builder took it for a reform amount, so it kept the statutory uplift with
rebalancing off. apply_parameter_changes now records the periods each change
set; the builder leaves entries in those periods as given and rescales every
other entry by the uplift of the amount.yaml entry it carries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ance-uplift-2239

# Conflicts:
#	docs/book/policy/uc-rebalancing.md
#	policyengine_uk/tax_benefit_system.py
…he index

Review round 4 on #2242 found another bounded-override case in the rescale
of legislated amounts. Rather than track which entries a reform wrote, drop
the rescale and the simulation.py hook it needed: the legislated 2026-27
amounts stay as legislated, and the index uses the uplift they include for
legislated years (read from amount.yaml) and the uplift in force after.
Switching rebalancing off or changing a later uplift moves every year after
the legislated ones; changing a legislated year needs its amount set.

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

Copy link
Copy Markdown
Collaborator Author

PE-UK main fix landed (UK hub, 2026-10-11 21:42Z): #2349 merged as ed7b42f9. It fixes test_dataframe_dataset.py::test_an_exported_frame_loads_back_and_exports_itself, which failed on every PR after #2251. Merge main into this branch and push once; don't re-run an old run, because re-runs use the original commit. If this PR's only Test failure was that test, the next run should be green.

MaxGhenis and others added 2 commits October 11, 2026 17:44
The published-rates test skipped the four UC standard allowances because the
model did not follow Universal Credit Act 2025 s. 1. It does now, through the
allowance's own uprating index, so they are checked like any other series;
only the LCWRA element (s. 3) stays an exception.

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

This branch has not been deployed

No deployments
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.

UC standard allowance omits the legislated 2027-28 to 2029-30 uplifts; standard_allowance_uplift is never read

1 participant