Skip to content

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

Merged
MaxGhenis merged 14 commits into
mainfrom
fix-uc-standard-allowance-uplift-2239
Oct 16, 2026
Merged

MaxGhenis merged 14 commits into
mainfrom
fix-uc-standard-allowance-uplift-2239

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2239.

Fresh impact on current main (UK hub, 10/16, after merge): policyengine-uk 2.134.6 against its parent a94c65df9, on certified uk-data 1.58.1. UC changes by £0 in 2026-27, +£0.603bn in 2029-30, +£0.611bn in 2030-31 and +£0.617bn in 2031-32. Details: #2242 (comment). The national figures further down are historical, at 6779f95ae on 1.58.0.

Current update

Head: 1582c02c3f86a44b34e5bcf087d7ddc667f9dae5. Main integration: 1582c02c3f86a44b34e5bcf087d7ddc667f9dae5, merging 36d2feb57cbde948177efe49d23d495ebd3e99c3 (includes #2428 and #2437) without rebasing.

  • Moved the generated standard-allowance index to gov.economic_assumptions.indices.uc_standard_allowance, following Freeze the UC LCWRA element for 2026-27 to 2029-30 (UC Act 2025 s. 3) #2284's LCWRA-index fix and main's generated-parameter contract. Updated the generator, allowance metadata, docs and tests.
  • Exact MOCK differential for the namespace move, before merging main: 6779f95aeb71513f72d8256a4bb09d5e37ef637c → 675970f0313c72a6dbe670edf07fe25a98450409; single-25+ allowance for 2025–2032 and protected LCWRA for 2026–2032 are byte-identical in raw float64 monthly parameters and float32 annual simulation arrays. All four standard-allowance types also match. Evidence: .job/mock-differential.json plus before/after raw binaries. Single-25+ monthly SHA256: 64977c284560a8188345eca4ef50994a9f4e9c6aaee600498b90d3edec9aa352; protected monthly LCWRA SHA256: 41cc71c432b742e06a7efe7f5b6e179258a13ee51359c2228156889a76b1fa79.
  • Fresh-main MOCK comparison: one fictional single protected LCWRA claimant, aged 30 in 2025, zero earnings, new-claimant status explicitly false. Keep the protected UC LCWRA amount from 2030-31 #2428 uprates the protected 2029 base into 2030–2032, so the standard-allowance correction also changes those later protected awards:
Measure Main 36d2feb57 Final head Change
2029–30 protected LCWRA, monthly £457.1463889792 £447.9895102742 −£9.1568787050/month
2030–31 paid LCWRA element, annual £5,595.4594726563 £5,483.3798828125 −£112.0795898438/year
  • The original-head snapshot reproduced the exact generated-series failure (1 failed); after the namespace move it passed (1 passed). The merged-tree suite below includes the full process-time file.
  • Final Python validation: 1,512 passed, 60 skipped across all 1,572 distinct collected cases, with no failures or errors. Scope: process-time reforms, standard-allowance uplift, rebalancing, LCWRA freeze/after-freeze, DWP published rates, dated-reform routes and no-economic-assumptions.

The initial four-worker run was stopped for host memory pressure after 1,473 passes and 60 skips (exit 130). Its 1,533 named JUnit outcomes were retained; four anonymous interruption placeholders were ignored. The remaining 39 exact node IDs then passed in fresh serial foreground pytest processes (-n0), without changing test settings or examples. The receipt reconciliation requires complete, disjoint coverage of all 1,572 collected IDs. The 60 existing skips comprise 49 non-uprated series and 11 series ending before 2026–27. Evidence: .job/test-summary.json, original JUnit, and 39 per-case JUnit receipts.

Initial command (numerical threads limited to one):

uv run --frozen --no-sync pytest \
  policyengine_uk/tests/test_process_time_reforms.py \
  policyengine_uk/tests/test_uc_standard_allowance_uplift.py \
  policyengine_uk/tests/test_uc_rebalancing.py \
  policyengine_uk/tests/test_uc_health_element_freeze.py \
  policyengine_uk/tests/test_uc_health_element_after_freeze.py \
  policyengine_uk/tests/test_dwp_published_rates.py \
  policyengine_uk/tests/test_dated_reform_routes.py \
  policyengine_uk/tests/test_no_economic_assumptions.py \
  -n 4 --dist worksteal -o xfail_strict=true -ra \
  --junitxml=.job/uc-python.xml

Fresh-process completion command: uv run --frozen --no-sync python .job/run-fresh-cases.py .job/unfinished-fresh.txt. The driver checks compressor use below 30 GB before every case and runs the original node ID with -n0 -o xfail_strict=true -ra plus its own JUnit receipt.

  • UC policy folder: 482 passed, one benign anyio plugin warning in 201.02s. Command: uv run --frozen --no-sync policyengine-core test policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit -c policyengine_uk.
  • make format and uvx ruff format --check .: passed. Formatting checked 1,724 files.

The full Python suite, documentation build and population/replay simulations were not rerun. Validation covers the focused combined-tree integration routes with MOCK situations only. The national impact figures below remain historical.

The national impact and earlier validation below are historical evidence at 6779f95ae, not measurements against the fresh main merge. This update runs MOCK situations only.

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 once per tree in main's shared GENERATION_STEPS, after threshold indexation and before the LCWRA generator and LSR aliases, before uprating. This covers statutory initialisation, dated reforms, and reset/reprocess. It builds gov.economic_assumptions.indices.uc_standard_allowance 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.

Main's dated-reform pipeline now restores projected amounts after bounded overrides and rebuilds the allowance index for fiscal-year reform= dictionaries as well as Scenario(parameter_changes=...). The stale limitation and advice to set amount directly have been removed from the docs. The new regression pins the statutory uplift and a 10% dated uplift through a further dated rebuild and two reset/reprocess cycles.

Projection assumption: the single-under-25 2026-27 rate is £338.58, above the Act's £336.60 minimum when rounding each step. The projection carries that margin forward; its CPI-only anchor is £338.58 / 1.023 rather than the Act's Step 2 minimum. The model still uses benefit uprating CPI as its forecast proxy, not a separate September CPI series for the allowance.

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 application of s. 1(2)'s growth to the model's 2026-27 anchors less their uplift (including the under-25 margin), 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.

Earlier mutation checks (before this merge). On the earlier 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.

Historical validation at 6779f95aeb71513f72d8256a4bb09d5e37ef637c (locked venv, Python 3.13.9 / policyengine-core 3.33.2):

  • UV_LINK_MODE=clone uv sync --frozen --extra dev: passed (workspace-local uv cache).
  • .venv/bin/python -m pytest -n 4: 1714 passed, 60 skipped, 6 warnings in 5807.86s (1:36:47), covering test_uc_standard_allowance_uplift.py, test_dwp_published_rates.py, test_dated_reform_routes.py, all other test files containing _rebuild or reset_parameters (test_threshold_indexation.py, test_capital_gains_projection.py, test_triple_lock_statutory_inputs.py, test_input_records.py, test_benefit_cap_specified_benefits_properties.py, test_data_built_imputations.py, test_ni_class_4_profits_properties.py), plus test_uc_health_element_freeze.py, test_uc_rebalancing.py, and test_no_economic_assumptions.py.
  • .venv/bin/policyengine-core test policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit -c policyengine_uk: 482 passed, 1 warning in 295.46s (0:04:55).
  • make format: passed before each commit; uvx ruff format --check .: passed before the single push.

Under no_economic_assumptions, the allowance grows only by the uplift ratio to 2029-30 and is then flat. Full Python suite and documentation build were not run locally; validation covers the requested integration routes and UC policy folder.

Historical national impact at 6779f95ae

These retained results compare the earlier PR head 6779f95ae with main 72fc91559. They have not been rerun against fresh main 36d2feb57; the current update uses MOCK situations only.

Two full enhanced-FRS current-law microsimulations, one per source tree, each calculating 2026 and 2029. Main: 72fc91559cb6dd042679dc8c66073f30bebe95f9. Branch: 6779f95aeb71513f72d8256a4bb09d5e37ef637c. Dataset: enhanced_frs_2024_25.h5@1.58.0; SHA256 5ba399ea5ab9a6c7d4e9aaf6c623080beb34d9ad5f330f6244dbad72b8d960b6. Both use the same uv.lock and locked dependencies; record IDs and weights match exactly. Weighted sums use float64; gains exceed £0.005 per year. Households are aggregated separately from benefit units.

Tax year Main UC (£bn) Branch UC (£bn) Change (£bn) Households gaining UC Households gaining net income
2026-27 83.577892 83.577892 +0.000000 0 0
2029-30 87.582309 88.189575 +0.607267 5,811,071 5,811,058

At that earlier head, 2026 had zero impact and the 2029 UC increase was £0.607bn, versus +£0.608bn in the previous body against main 6d89338a; this is a small change from the previous result. The 2029 allowance and LCWRA changes still round to the previous body's +£6.133bn and −£0.575bn; the benefit-unit gain count is now 5.886m versus the previous 5.87m. The previous body's 2025/2027/2028/2030 figures were not rerun at that earlier merge head and are omitted.

In 2029, 5,886,158 benefit units gain UC and 264,198 lose UC; 253,956 households lose UC. Some capped families can lose because uc_deductions grows with the standard allowance even where their capped award does not. The standard-allowance total (all benefit units, including those not paid UC) changes by +£6.132734bn and the LCWRA total by −£0.575195bn.

2029-30 allowance and protected LCWRA floor

For a single claimant aged 25+, the 2029-30 standard allowance rises from £452.231375 to £463.282973 per month. It now carries the Act's 4.8% uplift above the CPI-only path anchored before the 2026 uplift, rather than retaining only 2026's 2.3%. This is the model forecast using its benefit-CPI proxy; it is not a claim of exact equality to the September-CPI statutory minimum, and the single-under-25 margin is retained as described above.

MOCK situation: one fictional single existing LCWRA claimant aged 30 in 2025, with zero earnings and new-claimant status explicitly false. Monthly protected LCWRA amounts calculated by the model:

Tax year Main protected LCWRA/month Head protected LCWRA/month
2026-27 £429.80 £429.80
2027-28 £438.27 £435.56
2028-29 £447.85 £441.73
2029-30 £457.15 £447.99

The reduction from 2027 is expected: s. 4 protects the combined standard allowance and LCWRA amount for every allowance type. A higher standard allowance lowers the separate protected LCWRA component required by that floor. The legislated 2026 amounts stay unchanged.

Fiscal-event replay

The earlier replay table against main 6d89338a predates the 6779f95ae merge and has been removed. That earlier update reran the requested current-law national comparison only; it did not rerun the scorecard SS2025 replay or replace W6's certified-pair estimate. This MOCK-only update does not rerun either national comparison or replay. The parameter result above confirms that the missing 2029 uplift now reaches the allowance.

Overlaps

  • At the historical head 6779f95ae, merged main 72fc91559cb6dd042679dc8c66073f30bebe95f9 without rebasing. tax_benefit_system.py differs from main only by the generator import, one entry after threshold indexation in GENERATION_STEPS, and its docstring bullet. Main's rebuild/rollback/dated-reform structure is retained verbatim. Each active or unreformed tree runs the step once.
  • Main already merged Freeze the UC LCWRA element for 2026-27 to 2029-30 (UC Act 2025 s. 3) #2284 (head 996c38d2a50902582b70d178e4c75ea4f04aa440); both the s. 1 allowance and s. 3 LCWRA generators are retained. git merge-tree --write-tree HEAD 996c38d2a50902582b70d178e4c75ea4f04aa440 at 6779f95ae exited 0 and returned that head's tree (6bd5dbf827fb5b007d5a84c9ff600d425b99bb49). The other merge conflicts combine both documentation changes and check both dedicated indices in test_dwp_published_rates.py.
  • No duplicate standard-allowance uplift was found on main: the rebalancing parameter had no executable reader; uc_reform.py reads allowances only for the protected LCWRA floor, and Autumn Budget dated reforms add no second uplift.
  • 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 reconcile its index builder and the standard allowance's uprating metadata with this PR so the allowance selects the intended CPI and uplift path. The two builders produce separate indices; retaining both does not by itself apply the uplift 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 in this PR'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_uc_standard_allowance_uplift.py::test_single_claimant_aged_30_follows_the_act_uplift_schedule

Reproducer: policyengine_uk/tests/test_process_time_reforms.py::test_generated_parameters_are_the_series_processing_builds

…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>

@vahid-ahmadi vahid-ahmadi 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.

Fine once it is rebased on main and two statements are corrected.

@MaxGhenis the mechanism is right. The s. 1(4) uplifts, the non-compounding reading of s. 1(2) and the 0% floor in s. 1(3) all match the Act, and the projected path checks out by hand. The findings are about main having moved, and about one claimant type that sits above the Act's floor.

  1. must, policyengine_uk/tax_benefit_system.py: the branch conflicts with main. Main has restructured process_parameters (the dated-reform work), so the hook now belongs in _add_generated_parameters, after add_threshold_indexation. I tried that resolution locally and the 13 tests in test_uc_standard_allowance_uplift.py pass on it.

  2. should, the "One limitation remains" paragraph in the PR body, the module docstring (create_standard_allowance_uprating.py:36-39) and docs/book/policy/uc-rebalancing.md:98: after the merge with main, both things these describe are no longer true.

    • A bounded amount override no longer leaves a stale value. On the trial merge, {"2026": 500} gives £438.07 in 2027 (the body says £424.90), and {"2028": 500} leaves 2027 at £438.07.
    • A reform= dict now reaches the allowance. On the trial merge, Simulation(reform={uplift: {"2027": 0.07}}) gives £454.64 in 2027 (= 438.07 × 1.07 / 1.031). Turning rebalancing.active off for 2027 gives £424.90. On the PR head both stay at £438.07.
      So the "set amount directly instead" advice can go. A test of the reform= route would pin this down.
  3. should, single under 25: the 2026-27 rate is above the Act's floor, so "the legislated rate less its uplift" is not the Act's Step 2 amount for that type. Reg 36 as substituted by SI 2026/113 gives £338.58. The Act gives 316.98 × 1.038 = 329.03 (Step 2), then × 1.023 = £336.60 (Step 3). The other three types land exactly on the Act (424.90, 528.34, 666.97). So for single under 25:

    • the model's 2027-28 base is 338.58 / 1.023 = £330.97, not the Act's £329.03, and the projection runs about 0.6% above the Act's minimum to 2029-30;
    • with rebalancing off, the path starts from £330.97 rather than the Act's Step 2.
      The Act sets a minimum, so carrying the margin forward is a defensible projection. But the test docstring (test_uc_standard_allowance_uplift.py:15-17) and test_amounts_match_the_act_step_by_step (line 108) call this the Act's step-by-step amount, and they derive Step 2 the same way the model does, so the test is not independent for this type. Please either state the assumption in the docstring, the docs and the test, or start Step 2 from the 2025-26 amount × CPI, as s. 1(2) says. The SI 2026/113 explanatory memorandum says only that the rates follow the s. 1 formula. It does not explain the extra £1.98.
  4. should, overlap with #2284 (LCWRA freeze): both add a parameter generator to tax_benefit_system.py and both edit docs/book/policy/uc-rebalancing.md and tests/test_dwp_published_rates.py. They conflict in the first two files. Whichever merges second needs to keep both generators in _add_generated_parameters, and the right set of exceptions in test_dwp_published_rates.py.

  5. nit, docs/book/policy/uc-rebalancing.md:36 says the minimum is increased "by September CPI". The model uses gov.benefit_uprating_cpi, which rises 3.4% from 2025 to 2026, against September 2025 CPI of 3.8%. The PR body says this. One clause in the docs would stop a reader taking the docs as the model's method.

What I checked

  • Read the full diff and the PR body. Read UC Act 2025 s. 1 on legislation.gov.uk (Steps 1 to 3, the 0% floor, the uplift table and the "first Monday" definition of a tax year). Checked the 2026-27 amounts against UC Regs 2013 reg 36 as substituted by SI 2026/113 (in force 6 April 2026), which set the UC rates under the Act's own power rather than the Up-rating Order, and read the SI's explanatory memorandum.
  • Hand-computed: 400.14 × 1.038 = 415.35, × 1.023 = £424.90. The model's 2027 single 25+ figure is 424.90 × (188.6207 / 184.3803) × 1.031 / 1.023 = £438.07, which matches the model.
  • Model path, PR head against main (single 25+, monthly): 2027 £438.07 against £434.67; 2028 £450.73 against £443.36; 2029 £463.28 against £452.23; 2030 £472.55 (CPI alone from 2029).
  • pytest test_uc_standard_allowance_uplift.py test_dwp_published_rates.py on the PR head: 1,308 passed, 62 skipped.
  • Mutation check: the new test file run against main fails 4 tests (the example, the step-by-step test and both rebalancing-off tests).
  • Trial merge with main (local only, not pushed), as in finding 1: the uplift tests pass, and the override and reform= checks give the results in finding 2.
  • ruff check and ruff format --check on the changed Python files: pass.
  • CI was green at 2530e4a, but that run predates main's changes.
  • Not checked: the national impact and fiscal-event replay tables. They need the certified dataset, which I did not download.

🤖 Generated with Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Adoption hand-off for the hub's delta review.

Head: 6779f95aeb71513f72d8256a4bb09d5e37ef637c (merge 9f238df9b1a81701820b1a314b7307d9d3df3642, followed by the regression/docs commit). Merged current main 72fc91559cb6dd042679dc8c66073f30bebe95f9 once, without rebasing; original PR author preserved.

  • Kept main's tax_benefit_system.py verbatim except the import, one standard-allowance generator in GENERATION_STEPS immediately after threshold indexation, and the docstring bullet. It runs once per tree on statutory and dated rebuild/reset paths; main's LCWRA generator remains. The documentation and published-rate-test conflicts retain both provisions. No duplicate s.1 uplift reader exists on main or in its Autumn Budget reforms.
  • Added the dated reform plus repeated reset/reprocess regression. Locked pytest -n 4: 1714 passed, 60 skipped, 6 warnings in 5807.86s (1:36:47). UC YAML: 482 passed, 1 warning in 295.46s (0:04:55). make format and uvx ruff format --check . passed.
  • Two serial enhanced-FRS microsimulations at the SHAs above, data enhanced_frs_2024_25.h5@1.58.0, SHA256 5ba399ea5ab9a6c7d4e9aaf6c623080beb34d9ad5f330f6244dbad72b8d960b6. 2026 is unchanged. 2029 UC spending rises by £607.27m, with 5.811m households gaining UC. The previous body reported +£608m; this run gives +£607.27m. Allowance and LCWRA totals still round to +£6.133bn and −£0.575bn. The body now gives both SHAs, the data hash, current totals and household/benefit-unit counts.
  • 2029 single-25+ allowance is £463.28/month, up from £452.23, with the s.1 4.8% uplift now present. It uses the existing benefit-CPI forecast proxy; the under-25 anchor margin is documented. The scorecard fiscal-event replay was not rerun.
  • MOCK existing LCWRA claimant, monthly protected amount:
Tax year Main protected LCWRA/month Head protected LCWRA/month
2026-27 £429.80 £429.80
2027-28 £438.27 £435.56
2028-29 £447.85 £441.73
2029-30 £457.15 £447.99

The higher standard allowance lowers the separate LCWRA component required by the s.4 combined-amount floor. 2026 remains legislated.

#2284 is already merged; its head 996c38d2a50902582b70d178e4c75ea4f04aa440 is an ancestor. git merge-tree --write-tree HEAD <2284-head> exits 0 and returns the current head tree. .job/HANDOFF.md, progress, evidence and recovery bundle are saved in the assigned workspace. No self-review or PR merge performed; ready for the hub to dispatch delta review.

@juaristi22 juaristi22 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 6779f95aeb71513f72d8256a4bb09d5e37ef637c against merge base 72fc91559cb6dd042679dc8c66073f30bebe95f9, with separate code/test and policy/source roles. The prior wiring, reform/reset and documentation points are addressed. One confirmed merge blocker remains:

C1 — Include the new standard-allowance index in the generated-parameter regression’s expectations. Adding add_uc_standard_allowance_uprating to GENERATION_STEPS makes generated_parameter_defines('gov.dwp.universal_credit.standard_allowance.uprating') correctly return True, but test_process_time_reforms.py::test_generated_parameters_are_the_series_processing_builds still expects False for this path (lines396–397). Its oracle recognizes the older generated-index prefixes and omits this new series.

This is the sole failure in the current-head Python3/4 job, which reports1failed/1521passed/16skipped, and it leaves Test/Result failed. The focused local batch reproduces exactly the same failure: 38passed,1failed. Update the expected generated-series coverage and obtain passing required checks. This is a demonstrated test/completion failure, not evidence that the allowance formula itself is wrong.

Validation: the complete new uplift file plus focused dated rebuild/reset, published allowance, LCWRA and final-consumer controls ran in84.88s. A10%2029 uplift reaches finalUC and both income measures and survives another dated rebuild plus two resets; disabling rebalancing follows the documented projected path. The protectedLCWRA combination also passes. Local Core3.32.27/Hypothesis6.168.2 differs from the lockfile, but the same failure is present in pinned-environment hostedCI. No install or private population/replay rerun occurred.

The independent source check validated5official originals. UCAct2025s1 supports the noncompounding uplift schedule and0%CPI floor; SI2026/113reg3 confirms the four2026allowance anchors. The under25margin and benefit-CPI forecast proxy are now clearly described. Axiom/Reproducer status lines are present; external signed encoding was not certified.

A merge-tree check against current main 36d2feb57cbde948177efe49d23d495ebd3e99c3 is clean. Merged-blob inspection retains both generators once, in order, and #2428’s2026–2032LCWRA horizon. That is structural integration evidence; the full combined-tree suite was not executed. No branch changes or merge performed.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Handoff for the hub's independent delta review. Final head: 1582c02c3f86a44b34e5bcf087d7ddc667f9dae5.

  • Moved the generated standard-allowance index to gov.economic_assumptions.indices.uc_standard_allowance, updating its generator, readers, metadata, docs and tests. The namespace-only MOCK differential is byte-identical for single-25+ standard allowances in 2025–2032 and protected LCWRA in 2026–2032.
  • Merged main 36d2feb57cbde948177efe49d23d495ebd3e99c3, including Keep the protected UC LCWRA amount from 2030-31 #2428 and Fix Scottish top-rate taxable income threshold #2437, without rebasing. Against that main, the MOCK protected claimant's 2029–30 monthly LCWRA changes by −£9.1568787050 and 2030–31 annual LCWRA by −£112.0795898438. The corrected standard allowance lowers the protected 2029 base that Keep the protected UC LCWRA amount from 2030-31 #2428 uprates into later years.
  • Validation: 1,512 passed, 60 skipped; all 1,572 collected IDs reconciled after the memory-interrupted run; UC YAML 482 passed; make format and uvx ruff format --check . passed. All new calculations used MOCK situations.

Open items: remote CI and the hub's independent review. No self-review or PR merge performed. Progress, receipts and handoff artifacts are in .job/ in the assigned workspace.

@juaristi22 juaristi22 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.

Program review

Base repository: PolicyEngine/policyengine-uk
PR number: 2242
Reviewed head SHA: 1582c02
Merge base SHA: 36d2feb
Mode: incremental from 6779f95
Scope: C1 generated-series namespace repair, affected generation/rebuild/reset and UC/LCWRA final-income consumers through2032; identicalmergedmain portions excluded.
Source manifest: /private/tmp/policyengine-command-runs/202cefbfced3/pr-2242-r2-review-sources.json
Review status: COMPLETE

Source Documents

Critical

C1 — RESOLVED (previous severity: CRITICAL) (RESOLVED)

The original required-check failure was the generated-series classifier regression at policyengine_uk/tests/test_process_time_reforms.py:396. The generated allowance index then lived outside the economic-index namespace recognized by the regression. This head moves its INDEX_NAME and add_child target to gov.economic_assumptions.indices.uc_standard_allowance, updates the allowance parent/leaf uprating metadata, and explicitly asserts that the new index is generated. The old broad classifier oracle remains intact. The exact previously failing test actually PASSED, without skip/xfail, at this captured head in the bounded batch; JUnit proof, raw log, own validation. The confirmed local regression is closed. The old failed hosted run is historical; completion of the new required hosted Python/Test Result checks remains a coordinator merge gate.

Should Address

None.

Suggestions

None.

Evidence Gaps

No material gaps reported.

Notes

  • C1 exact formerly failing generated-parameter test PASSED without skip/xfail at1582c02. Namespace migration preserves generator computation; all allowance metadata and consumers use the generated-economic-index path.
  • Prior ancestry and unaffected-file/merged-main provenance independently verified. Retained legal noncompounding uplift,0% CPI floor, enacted2026rates, declared benefit-CPIproxy/under25margin.
  • s3/SSAA150 support ordinary review timing2030onward; future CPI is disclosed discretionary/model projection. Corrected2029protected LCWRA anchor carries through2032 and finalUC/HBAI controls.
  • Axiom issue367 open/pe-parity/dispatch-ready with later-year companion cases and recorded blockedencoder. Queued claim checked, no encoded-correct certification or billedencoder invoked. Historical population/replay totals remain author/historical evidence.
  • Fresh14:09 CI at reviewedhead: Lint/Fix evidence/docs/YAML/smokesuccess;4Pythonshards IN_PROGRESS and requiredTestResultnotyetreported. Approval covers implementation; no merge-readyclaim.
  • Timing capture-based; source role331.88s from readiness, code420.47s from readiness; acquisitionwindow137.13s. Prior inventory/eligibility triage separatelyrecorded in heartbeatmetadata.

Validation Summary

52 focused tests PASS,0failed/errors/skips in146.45s pytest/150.19s runner/151.57s boundedoffline process. Includes exactC1, full15caseupliftmodule, generated/rebuild/reset/freeze/index/publishedrates controls and3 synthetic finalUC/HBAIcontrols (dated10%+2resets, activeoff, protected2030–2032). No broad1572case rerun/privatepopulation. ExistingPython3.13/Core3.32.27/Hypothesis6.168.2 differsfromlockedCore3.33.2/Hypothesis6.169; importsassertedsnapshot/cachesexternal.9originals+9extracts hashvalid (5cached4new),0renders/rejected/discarded, bounded137.13sacquisition. FreshrequiredLintgreen/TestResultpending athead1582c02.

Timing

elapsed seconds: 539.33s; setup seconds: 0.45s; code role seconds: 420.47s; policy role seconds: 331.88s; source acquisition seconds: 137.13s; adjudication seconds: 0.00s; consolidation cleanup seconds: 64.58s

Review Severity

APPROVE. Open findings: 0 critical, 0 should address, 0 suggestions.

@MaxGhenis
MaxGhenis added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 0ab2b92 Oct 16, 2026
16 checks passed
@MaxGhenis
MaxGhenis deleted the fix-uc-standard-allowance-uplift-2239 branch October 16, 2026 19:06
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Landed by the UK hub (merge queue, pinned to head 1582c02c3f86a44b34e5bcf087d7ddc667f9dae5).

  • Gates at this head: gh pr checks exits 0 (15/15, run 38912106322), mergeable and CLEAN, not a draft, no CHANGES_REQUESTED.
  • Review: the hub's independent GPT-6.1 Sol delta review (Subfleet 20261016-114855-uk2242-review-r3b) gave APPROVE WITH NITS at this head, after APPROVE WITH NITS at 6779f95ae.
    • It confirmed the index move to gov.economic_assumptions.indices.uc_standard_allowance changes no generated value (AST and MOCK byte identity across six trees).
    • Dated reforms to CPI and the uplift rebuild the allowance on all ten dated routes.
    • It read the UC Act 2025 ss. 1–5 again. Keep the protected UC LCWRA amount from 2030-31 #2428 projects the protected LCWRA amount from this PR's corrected 2029 base: −£9.16 a month in 2029-30 for a single protected claimant, carried forward to 2032.
    • Tests: 246 Python passed (5 skipped), and 482 UC cases at the head and on the tree merged with main.
  • Impact: the PR body's national figures are labelled historical (2029 UC +£0.607bn at 6779f95ae against 72fc91559; 2026 unchanged). The review estimates the post-2029 LCWRA channel at about −£0.2bn a year, which is not measured. The hub owes a fresh paired real-data impact (2026, 2029, 2030 and 2031, main before and after this merge) before the Budget's certified release. It runs when the current disk pause lifts.
  • Follow-ups:
    • two P3 wording nits: the test docstring's "in every year" (L14) and the CPI-only comparison under deflation (create_standard_allowance_uprating.py:32);
    • the stale "ends in 2029" in docs/book/policy/uc-rebalancing.md:159;
    • the inherited limitation that Scenario.from_reform(...).apply(sim) and sim.apply_reform(...) on a built simulation don't regenerate this index. That is documented behaviour, not changed here.

MaxGhenis added a commit that referenced this pull request Oct 16, 2026
No conflicts. #2242 uplifts the UC standard allowance (parameters, the
rebalancing uprating, its test); it touches no file this PR changes, and
nothing it adds reads a variable this PR removes.

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

Copy link
Copy Markdown
Collaborator Author

Fresh impact on current main (UK hub, policyengine-uk 2.134.6, uk-data 1.58.1)

These figures replace the PR body's historical national impact (6779f95ae against 72fc91559 on uk-data 1.58.0) for the Budget certification.

Tax year Base UC (£bn) Branch UC (£bn) UC change (£bn) Household net income change (£bn) Benefit units UC up / down Households UC up / down
2026-27 83.483 83.483 +0.000 +0.000 0 / 0 0 / 0
2029-30 87.403 88.006 +0.603 +0.602 5,895,691 / 261,575 5,820,197 / 251,835
2030-31 88.819 89.430 +0.611 +0.610 5,861,607 / 277,613 5,786,691 / 267,829
2031-32 90.158 90.775 +0.617 +0.617 5,822,374 / 290,668 5,747,335 / 280,940

Change in UC components (£bn a year; "UC-paid" sums each tree over its own units with UC above zero):

Tax year Standard allowance, all LCWRA, all Standard allowance, UC-paid LCWRA, UC-paid Deductions Benefit units paid UC
2026-27 +0.000 +0.000 +0.000 +0.000 +0.000 +0
2029-30 +6.109 -0.570 +1.024 -0.206 +0.052 +21,732
2030-31 +6.259 -0.584 +1.024 -0.206 +0.053 +15,342
2031-32 +6.412 -0.598 +0.967 -0.215 +0.055 +3,482
  • Against the historical figure: the 2029-30 change is +£0.603bn, £4.4m (0.7%) below the historical +£0.607bn. The base level also moves, from £87.582bn to £87.403bn. The drift combines the data release (1.58.0 to 1.58.1) with the four merges since 72fc91559; this job did not separate them. 2026-27 is zero in both.
  • The LCWRA channel after 2029, against the review's estimate of about −£0.2bn: the review estimated −£0.210bn and −£0.214bn. Measured, the LCWRA component of the 1,877,179 and 1,884,484 benefit units paid in both trees whose protected LCWRA falls changes by −£0.210bn in 2030-31 and −£0.215bn in 2031-32. Across all 5,207,070 and 5,229,460 benefit units whose protected LCWRA falls, including those not paid UC, it changes by −£0.584bn and −£0.598bn, also as the review estimated. The channel is already inside the net UC change above. For those paid units, the UC change equals the allowance gain less the LCWRA reduction and the extra deductions to within £1,000.
  • What drives 2030-31 and 2031-32 (from the code): the new uc_standard_allowance index puts every allowance type at 1.048 / 1.023 times base by 2029-30. Single 25+ is £463.28 against £452.23 a month. The uplift then stays at 4.8%, so the allowance grows with CPI alone, as the base does (+2.0% a year), and the 2.44% gap holds. Up to 2029-30, the s. 4 floor in uc_reform.py sets the protected LCWRA amount £9.16 a month lower on the branch (£447.99 against £457.15), because the allowance is higher. From 2030-31, Keep the protected UC LCWRA amount from 2030-31 #2428 uprates that lower 2029-30 amount by the health-element index, which is the same in both trees. The gap therefore carries forward: −£9.34 and −£9.53 a month, or −£112.08 and −£114.32 a year per protected claimant. uc_deductions is a rate times the allowance, so deductions rise by £0.052bn to £0.055bn.
  • Not attributed: benefit units paid in both trees whose LCWRA is unchanged have a residual of −£0.051bn to −£0.059bn beyond their allowance and deduction changes. universal_credit also subtracts benefit_cap_reduction, which this job did not measure.
  • 2.134.7: Keep prior law before a dated change that starts on a parameter's first stated date #2436 changes only dated-reform code. A current-law system builds the same 1,646 processed parameters at 0ab2b9239 and at ba7c3e21c; every one was compared. 2.134.7 was not rerun as a microsimulation.

Full tables, the decomposition and monthly rate paths are in IMPACT.md in UK hub job 20261016-153305-impact-uk2242. Only aggregates were saved.

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

3 participants