Skip to content

Keep the protected UC LCWRA amount from 2030-31 - #2428

Merged
MaxGhenis merged 8 commits into
mainfrom
uc-lcwra-post-2029
Oct 16, 2026
Merged

MaxGhenis merged 8 commits into
mainfrom
uc-lcwra-post-2029

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2282.

Reproducer: policyengine_uk/tests/test_uc_health_element_after_freeze.py::test_protected_amount_is_uprated_by_the_element_index_from_2030_31

Builds on #2284 (the UC Act 2025 s. 3 freeze, merged as 0f20450), which adds the uprating index this PR uses. Retargeted to main.

What was wrong

universal_credit_july_2025_reform (scenarios/uc_reform.py) set the health element only for 2026-27 to 2029-30 (range(2026, 2030)). From 2030-31 the uc_LCWRA_element formula paid every LCWRA claimant elements.disabled.amount, the new-claimant amount, including pre-2026, severe conditions criteria and terminally ill claimants. On main a protected claimant went from £457.15 a month in 2029-30 to £235.86 in 2030-31.

The law

  • Universal Credit Act 2025 s. 2(1)(b) only relabels the reg. 36 row for pre-2026, severe conditions criteria and terminally ill claimants; the row has no end date.
  • s. 3(1) disapplies SSAA 1992 s. 150(1) and (2) for the LCWRA element (both rows, reg. 27) only "in the tax years ending with 5 April 2026, 5 April 2027, 5 April 2028 and 5 April 2029". The review in 2029-30 governs the April 2030 rates again (s. 150(10)).
  • s. 4(1) requires increases to the protected amount only "for any of the tax years 2026-27 to 2029-30".

So both amounts continue from 2030-31 and are uprated under s. 150. For UC amounts that uprating is discretionary (s. 150(2)(b); s. 150(3) does not list them); the model assumes it follows the benefit uprating index, as for every other UC amount.

Changes

  • scenarios/uc_reform.py
    • The scenario sets the element from 2026-27 to the end of the data's projection, PROJECTION_END_YEAR (2032), not to 2029-30.
    • A protected amount with no stated value takes the s. 4 floor to 2029-30 (unchanged) and, from 2030-31, the year before's amount times the growth of gov.economic_assumptions.indices.uc_health_element, the index the new-claimant amount follows (Freeze the UC LCWRA element for 2026-27 to 2029-30 (UC Act 2025 s. 3) #2284). The index is the benefit uprating index, held in any year the freeze is in force, and never falls.
    • The new-claimant share is read from a parameter.
    • The scenario's years are one public name, REBALANCING_YEARS (2026 to PROJECTION_END_YEAR), for other code to read (the UK hub's guard for elements.disabled.amount reforms).
  • rebalancing/new_claimant_share.yaml (new): 11%, 13%, 16% and 22% for 2026-27 to 2029-30, WPI Economics' estimates for the Trussell Trust (the values that were in the code), held at 22% from 2030-31. That hold is an assumption, not a source: the share keeps rising as the caseload turns over, so holding it understates new claimants and overstates protected amounts from 2030-31. I could not find the WPI publication online, so the parameter cites the Act rather than a link.
  • protected_health_element.yaml: the description gives the post-2029 rule.
  • Docs: uc-rebalancing.md explains the post-2029 amounts and the share.

Still not covered: from 2033-34, after the data's projection, the scenario no longer sets the element and the formula pays every claimant the new-claimant amount. Removing that limit means computing the element in the formula from the claim status and the parameters, which is #2252's proposal.

Invariants

  1. No drop. From 2026-27 to 2031-32, neither a protected nor a new claimant's element falls from one year to the next, for any CPI path; with non-negative CPI their Universal Credit does not fall either (the standard allowance follows CPI down when it falls).
  2. Protected path. From 2030-31 the protected amount is the year before's times the element index's growth.
  3. Reforms take effect. A 2030-31 change to either amount moves that claimant's element and Universal Credit by the same amount. The scenario projects a protected amount from the changed one; a one-year change to the new-claimant amount gives way to prior law's path the next year, as main's dated reforms define for an uprated parameter. The draw reads the share in every year it sets.

Tests

policyengine_uk/tests/test_uc_health_element_after_freeze.py:

  • no drop from 2029-30 to 2030-31, and none to 2031-32, for a protected and a new claimant (element and Universal Credit);
  • the protected amount's path from 2030-31 (invariant 2) and the new-claimant amount's;
  • a 2030-31 reform to each amount moves Universal Credit by the change in the element;
  • Hypothesis over OBR CPI paths for 2024-2032: the protected amount never falls for any path (−5% to +15%); for non-negative paths neither claimant's element nor Universal Credit falls, and the protected amount is never below the new-claimant amount;
  • two of Freeze the UC LCWRA element for 2026-27 to 2029-30 (UC Act 2025 s. 3) #2284's tests assumed the formula decides 2030-31 awards; they now state a new claimant, and the reform-dict test changes new_claimant_health_element, the amount the scenario pays;
  • the seeded draw reads new_claimant_share in every year to 2031-32, and a reform to it for 2031-32 changes that year's status.

Mutation check. Ending the scenario's loop at 2029 fails 5 tests; keeping the s. 4 floor after 2029-30 fails 1.

Local runs at 727085c (main merged): this file, test_uc_health_element_freeze.py, test_uc_rebalancing.py, test_dwp_published_rates.py, code_health/test_cached_arrays_not_written_in_place.py and test_process_time_reforms.py::test_generated_parameters_are_the_series_processing_builds: 1380 passed, 64 skipped; UC YAML policy tests: 482 passed. Earlier, at b6f3f3f: this file, test_uc_health_element_freeze.py, test_uc_rebalancing.py, code_health/test_cached_arrays_not_written_in_place.py and test_process_time_reforms.py::test_generated_parameters_are_the_series_processing_builds: 84 passed. The impact runs were at 577f549; later commits rename and test, and move the index without changing its values.

Impact

Three full microsimulation runs on enhanced_frs_2024_25.h5@1.58.1, 2029-30 to 2032-33, nothing scaled: main (1a31ef1), #2284 (f1a495d) and this branch (577f549). £bn a year.

2029-30 2030-31 2031-32 2032-33
universal_credit, this PR minus #2284 0 +5.015 +5.108 +5.159
uc_LCWRA_element, this PR minus #2284 0 +13.982 +14.323 +14.671
gov_balance, this PR minus #2284 0 −5.005 −5.098 −5.149
household_net_income, this PR minus #2284 0 +5.005 +5.098 +5.149
universal_credit, this PR minus main 0 +4.605 +4.700 +4.748
household_net_income, this PR minus main 0 +4.595 +4.691 +4.738

Protected and new claimants (benefit units with LCWRA, millions; 22% flat): 5.18 protected and 1.34 new in 2029-30; 5.21 and 1.35 in 2030-31; 5.23 and 1.35 in 2031-32; 5.25 and 1.36 in 2032-33.

Sensitivity: a rising new-claimant share

d1595 asks whether to hold the share at 22% or let it rise. This is a fourth real run of this branch with the share on the 2026-29 least-squares trend (11%, 13%, 16%, 22% rise 3.6 points a year): 24.5%, 28.1% and 31.7% for 2030-31 to 2032-33, set through Scenario(parameter_changes=...) on rebalancing.new_claimant_share. Either path is a one-line change to that parameter.

£bn a year 2030-31 2031-32 2032-33
universal_credit, 22% flat minus #2284 +5.015 +5.108 +5.159
universal_credit, rising minus #2284 +4.802 +4.621 +4.390
uc_LCWRA_element, 22% flat minus #2284 +13.982 +14.323 +14.671
uc_LCWRA_element, rising minus #2284 +13.405 +13.186 +12.871
universal_credit, rising minus 22% flat −0.213 −0.487 −0.769
universal_credit, rising minus main +4.391 +4.214 +3.979
Rising share 2030-31 2031-32 2032-33
New claimants among LCWRA benefit units 1.54m (23.6%) 1.73m (26.3%) 1.95m (29.5%)
Protected 5.01m 4.85m 4.66m
Against #2284: households gaining 1.80m (£4.88bn, £2,717 each) 1.71m (£4.70bn) 1.62m (£4.51bn)
Against #2284: households losing 0.033m (£86m) 0.035m (£89m) 0.049m (£133m)
Against main: households gaining 1.68m (£4.57bn) 1.60m (£4.41bn) 1.52m (£4.24bn)
Against main: households losing 0.62m (£187m) 0.71m (£208m) 0.78m (£272m)

The weighted share of LCWRA benefit units drawn as new claimants sits below the parameter (23.6% against 24.5% in 2030-31; 20.5% against 22% in 2029-30). The seeded draw is independent of LCWRA status, so the gap is sampling variation in the weighted data, the same on main.

Outputs: ~/reviews/uk-lcwra-s3-freeze/impact2282/results/ (compare.txt, compare_rising.txt and the four runs' arrays).

Review

  • Round 1 (GPT-6.1 Sol, head b6f3f3f): approve, with two non-blocking corrections: the loss attribution above (two and three elements, not only two) and the first year the formula decides (2033-34, not 2032-33). Both fixed in 555dc5f, with Freeze the UC LCWRA element for 2026-27 to 2029-30 (UC Act 2025 s. 3) #2284's round 7 note (a reform can set the index directly). File: ~/reviews/uk-lcwra-s3-freeze/review-2428-r1.md.
  • Round 2 (GPT-6.1 Sol, head 555dc5f): approve the PR as a whole. Two wording notes: the formula's exclusion of the other member of a single claim (fixed above) and the builder docstring's "other parameters" (fixed in the next commit, with the final review after main is merged).
  • Round 3 (GPT-6.1 Sol, head 727085c, the docstring note and the merge of main after Freeze the UC LCWRA element for 2026-27 to 2029-30 (UC Act 2025 s. 3) #2284 landed): approve the PR as a whole. The diff against main is byte-identical to the approved change plus the docstring fix. File: review-2428-r3.md.

Overlaps

axiom: TheAxiomFoundation/rulespec-uk#367 queued (UC Act 2025 ss. 3 and 4, labelled pe-parity; s. 3 companion cases added there for #2284). rulespec-uk uk/regulations/uksi/2013/376/36.yaml lcwra_protected_claimant_amount holds 429.80 with no later version; the post-2029 s. 150 uprating is the s. 150 module #367 queues.

Test-year fix (a66d1ad)

CI failed test_dataframe_dataset.py::test_an_export_names_the_people_in_each_year_the_rebalancing_records at 727085c: it hard-coded the rebalancing years as 2026 to 2029, but the scenario now runs to REBALANCING_YEARS (2026 to 2032). The test now derives its expected columns from that constant. Two tests with the same hard-coded years are changed the same way (test_benefit_cap_specified_benefits_properties.py, test_uc_rebalancing.py). Test files only; no source, data or parameter changes.

Test results at a66d1ad (-n 4, test fixtures only, no microdata):

  • test_dataframe_dataset.py: 78 passed. The same test fails at 727085c with the CI message.
  • test_benefit_cap_specified_benefits_properties.py: 26 passed.
  • test_uc_rebalancing.py: 17 passed. test_section_4_floor_treats_a_cpi_fall_as_zero hit a Hypothesis too_slow health check under host load in the full-file run; run alone it passes.
  • uvx ruff format --check .: 1721 files already formatted; ruff check on the three files passes.

Review of this head is pending with the UK hub. The author of this change did not review it.

🤖 Generated with Claude Code

The rebalancing scenario set the UC health element only for 2026-27 to
2029-30. From 2030-31 every LCWRA claimant fell to the new-claimant amount,
a protected claimant from £457.15 to £235.86 a month.

Universal Credit Act 2025 s. 2 relabels the reg. 36 rows for good; s. 3
disapplies the SSAA 1992 s. 150 up-rating only in the tax years ending
5 April 2026 to 5 April 2029; s. 4's floor covers only 2026-27 to 2029-30.
So both amounts continue and are uprated again from 2030-31.

- The scenario now sets the element to the end of the data's projection
  (PROJECTION_END_YEAR, 2032).
- A protected amount with no stated value takes the s. 4 floor to 2029-30
  and, from 2030-31, the year before's amount uprated by
  elements.disabled.uprating, the index the new-claimant amount follows.
- The new-claimant share is the parameter rebalancing.new_claimant_share:
  WPI Economics' 2026-27 to 2029-30 shares, held at 22% after, a stated
  assumption.

Tests: no drop from 2029-30 to 2030-31 for a protected or new claimant;
the protected amount's path from 2030-31; a 2030-31 reform to either amount
moves Universal Credit; the element never falls for any CPI path and
Universal Credit never falls for non-negative CPI; the draw reads the share
in every year it sets. Ending the loop at 2029 fails 5 tests; keeping the
s. 4 floor after 2029 fails 1.

Fixes #2282.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 15, 2026 09:46
…amount

- uc_reform.REBALANCING_YEARS: the tax years in which the rebalancing
  scenario sets the element (2026 to PROJECTION_END_YEAR), one public name
  other code can read (the UK hub's guard for elements.disabled.amount
  reforms).
- The non-negative-CPI property also checks that the protected amount is
  never below the new-claimant amount in any year (UK hub's spec on #2282).
- Two of #2284's tests assumed the formula decides 2030-31 awards. With the
  scenario setting 2030-31, a household without a stated claim status gets
  the protected amount, so they now state a new claimant, and the reform-dict
  test changes new_claimant_health_element, the amount the scenario pays.

Refs #2282.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2284 moved it to gov.economic_assumptions.indices.uc_health_element.

Refs #2282.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits October 15, 2026 14:53
- #2284 review round 7: a reform can name the generated index directly. It
  is a calendar-year series written just after the builder and before
  uprating, so it replaces the freeze and the no-fall floor for the years it
  names. The builder docstring, the index's description and the docs say
  so, and a test checks it for "2028" and "2028-01-01".
- #2428 review round 1: the scenario sets the element to 2032-33, so the
  formula decides from 2033-34; the docs and the new-claimant amount's
  description said 2032-33 and 2029-30.

Refs #2282.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2428 review round 2: other calendar-year series also read a 1 January key
as the whole year.

Refs #2282.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from uc-lcwra-s3-freeze to main October 15, 2026 19:50
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 15, 2026 20:21
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Hand-off to the UK hub (session local_51f6a5a6-ef54-422b-aa89-8a8130a675ef).

axiom: TheAxiomFoundation/rulespec-uk#367 queued (UC Act 2025 ss. 3 and 4, pe-parity).

The dataframe export test and two rebalancing tests hard-coded 2026 to 2029,
so the protected LCWRA amount running on to 2030-31 (#2428) failed CI. They
now read REBALANCING_YEARS, the scenario's own range.

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

Copy link
Copy Markdown
Collaborator Author

Hand-off: test years derived from REBALANCING_YEARS

Head: a66d1adbc0b859f40622d2eec305d6127803f499, one commit on 727085c8 (fast-forward push, no history rewritten).

Fix. test_an_export_names_the_people_in_each_year_the_rebalancing_records now expects the status columns for every year in REBALANCING_YEARS and the identifier columns through REBALANCING_YEARS[-1]. Also changed: test_benefit_cap_specified_benefits_properties.py (REBALANCED_YEARS = list(REBALANCING_YEARS)), and test_uc_rebalancing.py (YEARS and three loops use the constant). Test files only.

Results at a66d1adbc (-n 4, test fixtures only, no microdata):

  • test_dataframe_dataset.py: 78 of 78 passed, run in 8 foreground chunks, no skips. The same test fails at 727085c8 with the CI message.
  • test_benefit_cap_specified_benefits_properties.py: 26 of 26 passed.
  • test_uc_rebalancing.py: 17 passed. test_section_4_floor_treats_a_cpi_fall_as_zero hit a Hypothesis too_slow health check in the full-file run under host load. Run alone it passes (1 passed, 33s).
  • uvx ruff format --check .: 1721 files already formatted. ruff check on the three changed files: clean.

Audit of hard-coded rebalancing years.

  • Changed: the three files above.
  • Left unchanged, with reasons:
    • test_uc_health_element_freeze.py FROZEN_YEARS and the assertion near line 263: the law's freeze period (2026-27 to 2029-30) is a fixed legal range, not the scenario's years.
    • code_health/test_cached_arrays_not_written_in_place.py near lines 263 and 383: string snippets fed to the AST detector, not year-dependent.
    • test_dataframe_dataset.py __2029 columns (about lines 620-648): an unrelated fixture year in another test.
    • test_uc_health_element_after_freeze.py: already uses the constant.

Status. CI is pending at this head (Test Python 1-4, YAML, docs, fix evidence). mergeable is MERGEABLE, but the PR is BLOCKED until CI and review finish, so it is not ready. This session did not review its own change. Review and landing belong to the UK hub.

@MaxGhenis
MaxGhenis added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 81d3804 Oct 16, 2026
16 checks passed
@MaxGhenis
MaxGhenis deleted the uc-lcwra-post-2029 branch October 16, 2026 08:42
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

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

MaxGhenis added a commit that referenced this pull request Oct 16, 2026
…into uc-hb-split-shared-cap-lha-rules

No conflicts. Main's two edits to files this PR also changes
(test_benefit_cap_specified_benefits_properties.py: REBALANCED_YEARS from
REBALANCING_YEARS; test_legacy_award_readers_properties.py: comment) merged
automatically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

From 2030-31 every UC LCWRA claimant gets the new-claimant amount; the protected amount stops with the 2026-29 modifier

1 participant