Repository navigation
Fix the inactive Universal Credit health-element counterfactual - #2521
Conversation
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in Notes
Moves flagged for reviewNone. Spending by benefit and service
Caseloads
Household net income by decileNo 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; components the model counts twice; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles; standard reform costings (change in gov_balance); standard reforms: change in each reform's target. Checks failing on both base and head (1)
Counted-once checkEach of 84 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.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Components the model counts twice: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 first-round review of #2521 at 2ae29597d. Two shoulds and two nits. The mechanism looks right; the first should is about the 2026-27 number it produces.
What I checked
- Law, from legislation.gov.uk. UC Act 2025 s.2 inserts the £217.26 new-claimant row into UC Regs 2013 reg.36 from 6 April 2026. s.3 disapplies SSAA 1992 s.150(1) and (2) for the LCWRA and LCW elements for the four uprating years. s.4 is the protected-amount floor. Reg.36 as amended now shows £217.26 and £429.80 (pre-2026, severe conditions or terminally ill). So with the Act switched off, the 2026-27 element is the 2025-26 £423.27 uprated under ordinary s.150, with no cohort split, freeze or floor. That is what #2283 asks for and what the PR does structurally.
uc_reform.py:290-299: the wrapper only acts inREBALANCING_YEARS, defers to explicit amount overrides (dated assignments and legacy overrides, including pending single-day ones) and scales the delegated formula's result, so eligibility and any other logic inuc_LCWRA_elementstill apply. Supplied awards are kept (uc_reform.py:320-327).parameters(str(year))resolves to the fiscal-year value here (parameters("2025")gives £423.27), so the base year is right.is_uc_benefit_cap_exempt_specified_benefit.py:58:lcwra_formula_originonly unwraps formulas registered in theWeakKeyDictionary, so external wrappers keep their own provenance. Looks correct.- Tests locally:
test_uc_rebalancing.py,test_benefit_cap_specified_benefits_properties.py,test_uc_health_*.py,test_uc_standard_allowance_uplift.py, the benefit-cap property and scenario tests andtest_construction_reform_wrappers.py: 233 passed. UC YAML (gov/dwp/universal_creditandfinance/benefit/family/universal_credit): 504 passed. With the PR's two test files on base47642d1ee, 9 of the newtest_uc_rebalancing.pytests fail, including theReproducer:test, so they detect the bug. - CI at head is green (the cancelled Fix evidence run is superseded by two successful ones). Changelog fragment is present.
Findings
-
should
uc_reform.py:259-263, 2026-27 uprating rate. The method follows #2283's spec ("the model's benefit uprating index"), but that index is out of line with law for 2026-27.benefit_uprating_cpirises by 3.40% from 2025-26 to 2026-27 (178.3175 to 184.3803, built fromlag_cpi.py). The actual 2026-27 uprating was 3.8% (September 2025 CPI). DWP's benefit and pension rates 2026 to 2027 shows the statutory minimum increase at 3.80%, and the repo's own parameters agree: the UC carer element goes £201.68 to £209.34 (reg.36) and Carer's Allowance £83.30 to £86.45, both 3.8%. So the PR pays £437.66 a month in 2026-27, where ordinary s.150 uprating gives £423.27 × 1.038 = £439.35. That is £1.69 a month (about £20 a year) low per claimant, and every later inactive year starts from the low base. Two ways to resolve it: (a) correct the index's 2026 value to the outturn in its own PR, which fixes every amount that depends on it, and note that here; or (b) anchor 2026-27 explicitly in this PR and use the index only from 2027-28. Either way, the tests can't catch this, because_inactive_lcwra_expected(test_uc_rebalancing.py:415-421) rebuilds the same index ratio. Please add one hard-coded 2026 assertion (£439.35 × 12 = £5,272.20, to within rounding). -
should PR body,
Axiom: needed. The PR template (.github/PULL_REQUEST_TEMPLATE.md:16) keepsneededfor external contributors who cannot do the Axiom work, so a maintainer PR should use one of the standard options. If the inactive counterfactual needs rulespec-uk work, open the issue and use<rulespec-uk issue> queued. If you see this as a scenario counterfactual with no change to enacted law,n/a: <reason>fits. Either way, notneeded. -
nit
uc_reform.py:290. AfterPROJECTION_END_YEAR(2032),active: falsefalls back to the Act's £217.26 parameter path again, so a long-run "Act off" run halves the element from 2033-34. The docs say so (uc-rebalancing.md:27), but a warning, or a sentence in the new paragraph atuc-rebalancing.md:95, would make the limit clear to someone switching the Act off. -
nit Merge order with #1989 (draft, currently conflicting), which rewrites
uc_LCWRA_element's formula. The wrapper delegates throughget_formula(period)and the benefit-cap check matches by code location, so they should compose. Whichever lands second should reruntest_uc_rebalancing.pyandtest_benefit_cap_specified_benefits_properties.py.
🤖 Generated with Claude Code
|
Addressed the two shoulds in review5585327926. The inactive2026-27 counterfactual now anchors at £439.35/month (£5,272.20/year), with index growth from that anchor for later years. The existing public-late-amount, claimant and supplied-input contracts remain covered. The scoped RuleSpec367 handoff is delivered and queued; encoding/parity remains OPEN and no billed run is approved. The ordinary integration with captured main |
|
Merged by the UK hub (session local_8e19f9d5, Opus 5.5) on Max's explicit instruction to merge the team's ready PRs before the Budget (21 October, about 23:00Z: "go ahead and merge them. none of us has time to wait on each other"). One merge-queue enqueue, pinned to head
🤖 Generated with Claude Code |
Fixes #2283.
When Universal Credit health-component rebalancing is inactive, anchor the 2026-27 amount to the 2025-26 LCWRA rate (£423.27) increased by September 2025 CPI (3.8%) and rounded to monthly pennies (£439.35; £5,272.20 annually). From 2027-28 use benefit-index growth from that fixed 2026 anchor. This is an explicit CPI-only counterfactual convention: SSAA 1992 s.150 leaves UC uprating and rounding discretionary, so £439.35 is neither an enacted LCWRA rate nor uniquely mandated by law. Keep the inactive award as calculated output so a later public
Scenario.applyamount reform refreshes both the health element and final Universal Credit. Active claimant cohorts, explicit parameter reforms, directly supplied awards and the existing 2026 standard-allowance qualification retain their contracts. This is an inactive health-component counterfactual, not a whole-Act or parity certificate.The internal LCWRA amount wrapper now preserves the delegated formula's origin. The benefit-cap exemption keeps the model's claimant filter while supplied element entries and deliberate external formulas retain their existing treatment.
Reproducer: policyengine_uk/tests/test_uc_rebalancing.py::test_inactive_2026_uses_penny_rounded_september_cpi_anchor; policyengine_uk/tests/test_uc_rebalancing.py::test_inactive_cached_awards_follow_public_late_amount_reform; policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py::test_internal_lcwra_wrapper_keeps_the_model_claimant_filter
The public-lifecycle regression primes both award caches before applying £300/month. Its expected annual element is £3,600 and final UC is £8,698.80 (£424.90 standard allowance plus £300, each multiplied by twelve). It failed numerically before the cache repair.
Historical validation on public head
2ae29597passed 133 unique Python tests and 490 UC programme cases. Candidateffa476e6passed 32 rebalancing tests and 20 deterministic C2 controls; its anchor regression first failed at £5,251.94 instead of £5,272.20. Those results remain attributed to their captured heads. Earlier full-property and broader health/control commands reached their 180-second bounds and remain INCOMPLETE, with no dot-based test credit. The separate c281 combined 52-case run also remains INCOMPLETE; its three narrow cash controls passed separately.The prior ordinary integration with captured main
5f4b81c4retains incoming main's UC deduction, applied-cap and protected-floor allocation code unchanged. It passed 40 targeted Python cases (five cash/cache cases, 23 incoming deduction cases and 12 claimant/supplied-input controls), then the full UC programme directory once: 491 cases, comprising 490 YAML cases and one additional Python case. These are 41 unique Python cases and 490 YAML cases on the integrated state, without adding overlapping historical runs. The new natural-claimant tests use an explicit 15% deduction-rate input: £5,272.20 LCWRA plus £5,098.80 standard allowance, less £764.82 debt deductions, gives £9,606.18 UC. The same result holds with the modeled 85% protected floor. After priming both caches, a public £300/month LCWRA reform gives £7,933.98 UC while deductions remain £764.82. LCWRA entitlement naturally exempts the claimant from the benefit cap; no intermediate award or cap reduction is supplied to force the result.The identical new fixture failed numerically on untouched captured main at annual LCWRA £2,607.12 instead of the declared counterfactual £5,272.20, then passed after integrating the existing feature. This does not claim main's enacted £217.26 monthly rate is wrong. Required and applicable hosted CI, including published current-head Test / Result, Docs and latest post-body Fix evidence, remain later gates. The full benefit-cap property file has no current full-file PASS claim; the earlier local HTML build failed before completion on a DNS-dependent theme lookup, with no current HTML-build certificate.
The new origin-only tests discard an earlier model-owned award and assert the formula's result and origin before and after wrapping. Two separate default-active 2026 external nil-formula cases retain a cached model-owned award on main 47642d1 as well. The public constructor and late-apply behavior in those cases remains outside this provenance repair.
axiom: TheAxiomFoundation/rulespec-uk#367 queued
The underlying encoding and parity obligation remains OPEN and unverified. No billed run has been approved or invoked, and no parity is claimed.
The inactive scenario covers 2026-27 through 2032-33; from 2033-34 it falls back to the underlying parameter path. This does not provide a continuous long-run Act-off projection. If #1989 lands first or later, the delegated formula and benefit-cap tests need the stated composition check; no foreign branch is repaired here.
The new integration uses the actual merged #1819 deduction/floor pipeline and #2522 aggregate-report base correction, preserving both dependency changes rather than treating them as #1989 or #2493. The scoped RuleSpec handoff is already recorded; no duplicate source comment, encoding or parity claim is part of this integration.
This candidate additionally integrates captured main
78f480f2through an ordinary merge, preserving incoming main outside the six feature paths and retaining every feature production and test blob from9cb4395f. On this combined state, the five selected natural cash, public-late-amount, claimant and supplied-input tests passed 17 parameterized Python cases. The affected UC programme passed 490 YAML programme checks and one Python case. Sixteen incoming scenario-baseline controls passed and one dataset-dependent control was skipped for missing configured dataset/credential and remains NOTRUN, giving 34 unique Python cases across these current commands, with no duplicate or historical test credit. Core reports blank YAML case names, so the 490 checks are not presented as 490 unique named fixtures. Required/applicable hosted CI and independent incremental review remain later gates; this is a captured-main local result.