Skip to content

Make Pension Credit and pension-age HB projections safe under reforms - #2092

Merged
MaxGhenis merged 33 commits into
mainfrom
feature/pension-credit-2025-rates
Oct 28, 2026
Merged

MaxGhenis merged 33 commits into
mainfrom
feature/pension-credit-2025-rates

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2083
Fixes #2072
Fixes #2154
Fixes #2155
Fixes #2195

Changes

Merge main eb0b8087d821 (including #2576 and #2207) into Anthony Volk’s 52c96b111efe (merge head 9b0d406723d1), preserving his commits without rebasing or force-pushing. Four round-1 commits (head 000f16be9f7f) address the hub review of the merge head. Two further commits (head a82e405f8b29) fix the round-2 advanced-base/pre-base-maximum finding and extend its regression coverage. The expanded property also found pre-base threshold/allowance anchors that Core could not uprate after the enacted base advanced; the builders now carry those amounts to the common base before forecasting.

  • Keep Fix Pension Credit and pension-age Housing Benefit earnings uprating #2576’s dedicated earnings series, published Savings Credit maximum and forecast algebra: higher pension-age HB = guarantee + maximum Savings Credit.
  • Retain this PR’s common projection base derived from the seven enacted guarantee/threshold/protected-allowance paths. An incomplete rates update raises with every path and year; reform dates do not move the enacted base.
  • Generate separate threshold and single/lone-parent/couple allowance indices through standard uprating, retaining explicit reform anchors. Forecast thresholds and protected HB allowances follow the guarantee and maximum as general uprating projects their reformed paths, as on main. Thresholds use the live phase-in rate; at zero they retain the preceding threshold and pay no Savings Credit. Below-guarantee and zero allowance/threshold reforms build; threshold residuals are non-negative.
  • Maximum Savings Credit reforms reach both forecasts, as they did on main. Each index reads the maximum from the reform’s tree as general uprating will project it: Core’s uprating runs on private copies of the maximum and of the index chain it is uprated by (gov.benefit_uprating_cpi, then September CPI). A reform from the base year or any later year, or to that index, therefore moves the projected threshold and higher allowances as it moves the maximum. A threshold or allowance whose own base-year value is reformed keeps projecting its own reformed gap. The base-year maximum fill preserves the tested zero-maximum reforms anchored in 2025 or 2026 when a complete seven-field rates update advances the base to 2027; the explicit 2027 threshold and HB amounts remain their own anchors.
  • Keep main’s existing non-negative earnings/CPI floors and post-2021 cohort link to earnings; remove the duplicate builders and overlapping changelog fragments.
  • Keep Correct historical Housing Benefit pensioner allowances #2207’s sourced 2015–19 history and all subsequent published rates, including 2025 HB £244.40/£366. The old PR’s 2015 placeholders were 2019 rates: those amounts remain at their correct 2019 dates. Add this PR’s dated GB/NI citations and regression cases.
  • Add the Pension Credit page’s Uprating section, link the State Pension page to it, and publish the notebook: myst.yml’s Programmes block now lists docs/book/programs/gov/dwp/pension-credit.ipynb, since notebooks are not pattern-matched. Retain Guarantee Credit boundary regressions.

The two existing builder modules retain main’s formulas. The threshold and protected-HB builders generate separate indices before general uprating. tax_benefit_system.py registers them in GENERATION_STEPS, preserves the YAML-as-loaded enacted snapshot separately from reform and modifier inputs, and passes that context through ordinary and prior-law rebuilds. Main's Carer's Allowance earnings-limit projection remains after general uprating for both the main tree and its baseline copy.

Invariants and evidence

  • Unreformed, each projected higher HB allowance equals the projected guarantee plus the projected Savings Credit maximum in every forecast year: existing Fix Pension Credit and pension-age Housing Benefit earnings uprating #2576 properties plus test_unreformed_higher_allowances_equal_guarantee_plus_maximum_every_year.
  • No projected allowance, threshold or maximum is negative under valid finite non-negative monetary/rate reforms; sampled combinations of guarantee, threshold, phase-in, allowance and maximum Savings Credit reforms build: test_combined_reforms_build_and_never_project_negative_amounts (Hypothesis, amounts from zero to 1e100, rates up to 100, future anchors, one to three maximum anchors per relationship from 2025, and an optional complete rates update advancing the base to 2027). The combined property also compares the reformed maxima with frozen main, including the advanced-base/pre-base-maximum combination. An unchanged maximum timeline still permits the ordinary inferred rates-update fill. The full combined generator also retains pre-base own-threshold/allowance cases, including an explicit all-zero combination after the base advances.
  • Enacted amounts reproduce exactly in their years: test_forecasts_reproduce_each_enacted_value_exactly and existing published-rate properties. Independently rounded enacted HB amounts are preserved; the exact forecast identity applies after them.
  • The reconciled and frozen main builders agree wherever main’s behavior is retained: test_reconciled_builders_agree_with_main_where_its_behavior_is_retained (Hypothesis, positive and negative macro paths, unreformed), and test_maximum_reforms_reproduce_main_with_a_floored_threshold (Hypothesis, any path of maximum Savings Credit reforms before, at or after the base year, enacted phase-in paths and an optional reform to the maximum’s uprating index; equal to main apart from the threshold floor).
  • A maximum Savings Credit reform reaches both forecasts: test_maximum_reforms_reach_both_dependent_forecasts (the review’s zero-maximum oracle, base-year and 2028 anchors), test_maximum_reform_reaches_both_forecasts_through_every_route (reform dict, Scenario, structural Reform.from_dict class and a reprocessing simulation modifier; each forecast year’s threshold is the guarantee less the reformed maximum / phase-in and each allowance the guarantee plus it, and no Savings Credit is paid once the maximum is zero), and the own-base precedence cases test_a_reformed_own_base_takes_precedence_over_a_maximum_reform and test_a_reformed_own_base_keeps_its_index_beside_a_maximum_reform.
  • Lone-parent independence, below-guarantee/zero reforms, base-year updates and 2035 Guarantee Credit boundary continuity retain focused regressions.

Validation

Current head f94f9a2cb includes the seven round-3 fix commits through 2feb4750c, which restore main’s guarantee-reform projections, retain the loaded-YAML projection base through modifiers and prior-law rebuilding, strengthen allowance and payment regressions, and preserve explicit zero anchors. Merge 897a7a3aa incorporates main 2330118d1 while retaining the threshold and HB builders in GENERATION_STEPS and running project_carers_allowance_earnings_limit after general uprating on both the tree and its baseline. Commit f7d5a2184 moves extend_after_last_stated unchanged from main into its sole consumer, the Carer’s Allowance projection module. The current lock pins policyengine-core 3.33.12. All round-1 and round-2 validation below is historical at the stated commits.

Round 4, at f94f9a2cb (fixes for the independent review of f7d5a2184, which found six CI failures from the merge):

  • The two forecast-algebra loops in test_pension_credit_guarantee_reform_projections.py asserted forecast identities through 2040, but main and this branch both generate the earnings index only through 2039; annualisation holds 2039 into 2040. They now stop at the earnings index's last generated year, read from the pre-annualisation timeline. The explicit 2040 inputs, buildability and payment checks are unchanged.
  • test_parameter_cache_apis.py's small_system() bypasses the constructor, so it now sets the enacted context the constructor sets. The cycle-error mock in test_dated_reform_routes.py accepts the new enacted argument.
  • New Hypothesis property test_explicit_own_anchors_survive_projection: one to three explicit threshold or protected-HB amounts (including zero then positive) survive projection exactly, years whose latest explicit amount is zero stay zero, and the index stays positive and finite. A scratch mutant that let projection erase a later explicit anchor made it fail ((2030, [(2026, 300.0), (2030, 0.0)])); the mutant was not committed.
  • The Carer's Allowance docstring no longer says the Savings Credit threshold projection also runs after uprating.
  • Locally, Python 3.13.9 with the locked environment, one process under a 4 GB cap: test_pension_credit_guarantee_reform_projections.py 68 passed (including the three earnings-path and non-negative-guarantee nodes that failed in CI); test_parameter_cache_apis.py, the cycle-error test, the new property and the zero-anchor regression 15 passed. ruff format and ruff check clean on the changed files.

Historical round-2 environment at a82e405f8b29: Python 3.13 and policyengine-core 3.33.2 (uv sync --frozen --extra dev). ruff format --check and ruff check passed.

Round-2 validation at a82e405f8b29: both new pre-base zero-maximum regressions and the explicit advanced-base Hypothesis example first failed against unchanged 000f16be9f7f production code (maximum £18 instead of zero). With the fix, all 15 invariant tests pass, including the unchanged unreformed differential against main, the advanced-base all-zero own-threshold/allowance combination and an unchanged-maximum no-op example. The 2028 single threshold and higher single/lone-parent HB allowances are each £259.75, with maximum zero; the explicit 2027 rates are retained. The regenerated 504-value comparison below confirms the precision bounds. The remaining-suite results and limitations follow.

Round-2 completed totals: 126/198 Python cases and 484/797 YAML cases in 43/70 files.

Round-2 scope Passed / planned
Projection invariants 15 / 15
Pension Credit uprating 34 / 34
Protected HB allowance uprating 13 / 13
Guarantee reform projections 50 / 51
Savings Credit threshold projection 14 / 14
Process-time reforms 0 / 71
Pension Credit YAML 320 / 320 cases; 27 / 27 files
Housing Benefit YAML directory 155 / 456 cases; 14 / 40 files
Additional gov/dwp HB YAML 9 / 21 cases; 2 / 3 files

Counts are unique completed cases across the two additive fix commits: 55 Python cases passed at 6dc0a139ecf2 before the own-base bridge; the remaining completed Python cases are at a82e405f8b29. Partial timed-out runs and expected red regressions are excluded.

Not completed locally:

  • policyengine_uk/tests/test_pension_credit_guarantee_reform_projections.py::test_nonnegative_guarantee_reforms_never_break_pension_age_projections
  • policyengine_uk/tests/test_process_time_reforms.py
  • 27 YAML files: 26 in the Housing Benefit directory and 1 additional gov/dwp/housing_benefit_*.yaml file(s). Exact paths are retained in .job/YAML-VALIDATION.md.

The guarantee Hypothesis property hit the unchanged 1,800-second timeout on both single-process attempts, with no assertion output. test_process_time_reforms.py also timed out (1,800.68 seconds overall; 902.64 MB peak). Its immediate single-process retry was refused by the full gate at load1 161.903; it never launched. The load gate repeatedly refused launches above 100. New runtime launches stop at 17:40 UTC to leave room for the full cap and hand-off within the six-hour job limit. CI remains the evidence for unfinished coverage.

Historical round-1 local runtime tests at 000f16be9f7f (foreground; at most two xdist workers; Hypothesis-heavy files under a 2 GB process-group memory cap, which made four runs single-process): test_pension_age_projection_invariants.py 13 passed, test_pension_credit_uprating.py 34, test_housing_benefit_protected_allowance_uprating.py 13, test_pension_credit_guarantee_reform_projections.py 51, test_savings_credit_threshold_projection.py 14 and test_process_time_reforms.py 71. Policy YAML: Pension Credit 320 cases, Housing Benefit 456, and 21 more in the three gov/dwp/housing_benefit_*.yaml files. The full Python suite was not run locally; CI is the evidence for it.

Historical round-1 mutation evidence at 000f16be9f7f: against the 9b0d406723d1 builders, all nine then-new synthetic test nodes failed (the then-combined property checked only buildability/non-negativity and passed there), and 11 of the 12 new public-route tests failed; the base-year modifier case already passed there, because that route reads the reformed maximum as its own base. Without the fill guard, the differential fails on its pre-base-year anchor. Without the own-base fallback, all six synthetic precedence cases fail. With the review’s proposed September-CPI-only helper, 0 of 68 drawn differential cases without an index reform fail and 11 of 12 with one do.

The current-law comparison regenerated at a82e405f8b29 remains equivalent to floating-point precision. Comparing each threshold, maximum, higher HB allowance and forecast index from 2020 to 2040 on the main tree and its baseline copy against 9b0d406723d1 (504 values), 496 are identical and the rest differ by at most two units in the last place and 2.3e-16 relatively (measured maximum 2.229947121e-16). For the 2033 single threshold, the comparison is between 254.9092681404982 and 254.90926814049826 (two ulps).

Historical round-1 docs evidence: make documentation (the docs CI command) built 51 pages locally, including programs/gov/dwp/pension-credit.ipynb; the State Pension page’s link resolves as an internal cross-reference to its uprating heading. CI at 9b0d406723d1 built 50 pages, without it.

CI at 000f16be9f7f (Code changes run 40481780315): Lint, documentation, smoke-import (3.11–3.14), release-build, Changelog, Fix evidence and Docs changes pass, as do Python shards 2–5. Test / YAML passed all 5,111 policy YAML cases, then hit its 30-minute job limit while listing the Python tests; Python shard 1 reached 99% with no failures before its 75-minute limit. Test / Result therefore fails. Other PRs on current main hit the same limits (run 40424696088 passed on its third attempt after its YAML job was cancelled at 30 minutes); a re-run is the hub's call. At 9b0d406723d1, CI passed 4,911 policy YAML cases and audited all 10,201 collected Python tests: 10,138 passed, 61 allowlisted skips and 2 allowlisted xfails, including all six 13 October regressions; required Test / Result and Lint passed. No local microsimulation was run.

Aggregate impact at 000f16be9f7f passed: on certified enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1), base main 159a5ec99d against this head merged with it, for 2025-26 to 2031-32, no household’s net income changes in any year, and receipts, government balance, poverty and inequality do not change. No check is broken; one VAT sign check fails on both sides. At 9b0d406723d1 the run timed out.

Keep this PR draft for the hub to dispatch its independent re-review; do not merge.

Reproducer: policyengine_uk/tests/test_pension_age_projection_invariants.py::test_advanced_base_preserves_a_pre_base_maximum_reform, policyengine_uk/tests/test_pension_age_projection_invariants.py::test_combined_reforms_build_and_never_project_negative_amounts, policyengine_uk/tests/test_pension_age_projection_invariants.py::test_maximum_reforms_reach_both_dependent_forecasts

axiom: n/a: this reconciliation changes forecast generation, reform safeguards and regression coverage only; main already contains the enacted Pension Credit and Housing Benefit rates and legal uprating rules.

🤖 Generated with Claude Code

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

Thanks Anthony. Reviewed at 54e19d2: the fix looks right to me, with a few small points.

All four corrected 2025-26 values match SI 2025/295 art. 29 (£227.10 / £346.60; £198.27 / £314.34; NI SR 2025/64 gives the same), and the explicit 2026 entries match SI 2026/148. Tests pass locally (PC directory 106/106; PC + integration 207/207 when merged into current main), and six of the new tests fail on the old values.

  1. Overlap with #1925. Max's #1925 sets the same four values and makes the same entitledto_scenarios.yaml edit (4,023.20 / 18,048.66), so whichever merges second will conflict. #1925 currently has conflicts with main and this PR is green, so I'd suggest merging this one first and rebasing #1925 on it. That also keeps the tests here and closes #2083, which #1925 doesn't link.
  2. The savings credit tests don't catch the fix. savings_credit.yaml Case 3 passes on the old values too (MG − threshold is 28.83 under both sets), and Case 4 only moves by 1p. A case that separates them: a single person with £11,000 income gets 413.98 on the new values vs 577.78 on the old. A positive eligibility case just above the new threshold would help too; the current eligibility cases are all negative.
  3. Nit: the other 2025 amounts in art. 29 are still off on main (carer £46.40 vs 46.37, severe disability £82.90 vs 83.25, child £67.42 vs 67.60, child disability £36.54 vs 36.68, child severe disability £114.12 vs 114.54). #1925 covers these, so fine to leave them there.
  4. Nit, follow-up rather than this PR: from 2027 the minimum guarantee is uprated by gov.benefit_uprating_cpi (minimum_guarantee.yaml), but SSAA 1992 s.150A(2) requires an increase not less than earnings growth, so projected Pension Credit is understated in later years.
  5. Nit: the docs edit and its revert (b270272 / 54e19d2) could be squashed.

@anth-volk
anth-volk marked this pull request as ready for review October 5, 2026 14:36
@anth-volk
anth-volk requested a review from vahid-ahmadi October 5, 2026 14:36
@anth-volk anth-volk changed the title Correct 2025 Pension Credit rates Correct Pension Credit rates and Savings Credit projection Oct 5, 2026
@anth-volk
anth-volk marked this pull request as draft October 6, 2026 15:55
@anth-volk
anth-volk force-pushed the feature/pension-credit-2025-rates branch from b1097c2 to ddfd3f0 Compare October 6, 2026 16:47
@anth-volk anth-volk changed the title Correct Pension Credit rates and Savings Credit projection Correct Pension Credit and pension-age Housing Benefit projections Oct 6, 2026
@anth-volk

Copy link
Copy Markdown
Collaborator Author

Critical fixed

  • Consolidated Project Pension Credit guarantee with earnings #2146 and Project protected pension-age Housing Benefit by component #2156 into this PR so that the Pension Credit guarantee, Savings Credit threshold, and protected Housing Benefit allowances use compatible projections.
  • Replaced the provisional post-2026 Savings Credit threshold freeze with a threshold derived from the earnings-projected guarantee and CPI-projected maximum Savings Credit.
  • Added protected single, lone-parent, and couple Housing Benefit projections, removing the discontinuous downstream loss at the modeled Guarantee Credit boundary.

Should-address fixed

  • Reused the State Pension review's May-July earnings input through a separate non-negative earnings series; Pension Credit does not inherit the CPI or 2.5% parts of the triple lock.
  • Derived component bases from enacted parameters instead of hard-coding monetary values in Python.
  • Added direct official-source references, documentation, and Towncrier fragments.
  • Added focused tests for both relationship types, every forecast year, negative-growth floors, and deterministic 2035 boundary calculations.

Skipped by design

Verification

  • make format: passed.
  • Focused Python tests: 34 passed before and after rebasing on current main.
  • Altered and directly affected YAML tests: 22 passed before and after rebasing.
  • Towncrier draft build and staged diff checks: passed.
  • Independent plan verification: 5/5 items complete; no new issues found.

@anth-volk

Copy link
Copy Markdown
Collaborator Author

Addressed all three findings from the latest review:

  • The Savings Credit projection now creates separate SINGLE and COUPLE
    forecast indices. Standard parameter uprating consumes those indices; the
    builder no longer writes future values into either threshold parameter.
  • Regression tests confirm that pre-processing scenario changes, direct
    reforms, and future explicit source values remain exact. A future explicit
    value becomes the anchor for subsequent forecast years.
  • Savings Credit metadata now cites the GB and Northern Ireland prices-review
    provisions as well as the earnings-review provisions.
  • The protected pension-age Housing Benefit allowances now cite SI 2006/214,
    schedule 3 paragraph 1, and the enacted 2026 amounts in SI 2026/148,
    article 24(6) and schedule 6.

Validation: make format passed; 41 changed/directly affected Python tests
passed; 51 focused Pension Credit and Housing Benefit policy YAML cases passed;
the Towncrier draft and diff checks passed. The full package suite was not run,
following the requested focused-test scope.

@anth-volk
anth-volk force-pushed the feature/pension-credit-2025-rates branch from 0d45a0d to 8bc60ad Compare October 7, 2026 13:09
@anth-volk
anth-volk requested review from vahid-ahmadi and removed request for vahid-ahmadi October 7, 2026 13:09
@anth-volk

anth-volk commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

@vahid-ahmadi I rebased this PR cleanly onto current main (base revision c85ffef; new PR revision 8bc60ad) and renewed your review request. Focused local validation passed: formatting/lint, 40 Python tests, and 51 YAML policy tests. I did not run the full test suite locally; GitHub checks are running against the rebased revision. Could you please re-review the latest revision?

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

Thanks Anthony. I re-reviewed at 8bc60ad. The earnings uprating of the guarantee, the derived Savings Credit threshold and the protected HB allowances all look right to me. The 2025-26 and 2026-27 enacted values are unchanged from the SIs, and the projected maximum Savings Credit follows September CPI exactly: £17.96 → £18.34 → £18.72 → £19.10 → £19.49 for a single person, 2026 to 2030.

Locally, the 40 changed Python tests pass, and so do 448 YAML cases (the Pension Credit, Housing Benefit and integration directories). From my last round:

  • the £11,000 savings credit case (413.98);
  • the positive eligibility case;
  • the s.150A earnings point.

Those are all in now, and the docs edit and its revert go away in the squash.

Should

  1. 2025-26 pension-age HB allowances are still on the old guarantee. housing_benefit/allowances/{single,lone_parent}/aged.yaml:11-12 hold 239.20 and couple/aged.yaml:11 holds 357.98, which is the old £221.85 / £338.61 guarantee plus maximum Savings Credit. DWP's 2025-26 rates are 244.40 / 366.00: £227.10 + £17.30, and £346.60 + £19.35 rounded up.

    • With the corrected guarantee, the protected allowance in 2025-26 sits only £12.10 (single) and £11.38 (couple) above it, instead of the maximum Savings Credit, so HB is understated by up to £5.20 a week for pension-age claimants just above Guarantee Credit.
    • Since this PR now owns the PC/HB relationship and edits these files, I'd fix the three 2025 values here and add one 2025 case. #1925 makes the same change, so it can drop those hunks when it rebases. It conflicts with this PR on minimum_guarantee.yaml, threshold.yaml, the three aged.yaml files and entitledto_scenarios.yaml either way.
  2. BASE_YEAR = 2026 is hard-coded in create_savings_credit_threshold_projection.py:22 and create_protected_pension_age_uprating.py:18.

    • When the 2027-28 rates go in, the 2027 values become the uprating anchor but the indices are still built from projected 2027 amounts. The maximum Savings Credit then no longer follows CPI from the enacted 2027 maximum, and the HB uplift drifts the same way.
    • Fix: take the base year as the latest year with an explicit value. Raise an error if the guarantee, threshold and aged allowances disagree on that year, so the next uprating update can't miss it.

Nits

  1. A guarantee reform doesn't move the derived threshold. With minimum_guarantee.SINGLE set to 260 in 2027, the threshold stays at 216.71, so the maximum Savings Credit jumps from £18.34 to £25.97, and the protected HB allowance doesn't move either. That's what main does too, and arguably right, since the threshold is set separately by SI. A sentence in the threshold description saying reforms to the guarantee don't re-derive the threshold or the HB allowance would save someone a surprise.
  2. Docs placement: the paragraph about Pension Credit and HB sits in docs/book/programs/gov/dwp/state-pension.md:213-220. A line or link from the Pension Credit page would make it easier to find.

Happy to approve once 1 is in. 2 could be a follow-up issue if you'd rather keep this PR tight.

@anth-volk

Copy link
Copy Markdown
Collaborator Author

@vahid-ahmadi The local parameter audit selected the protected pension-age Housing Benefit couple allowance and confirmed your item 1. Commit 9442ffb corrects all three 2025 rates: £244.40 for single/lone-parent claimants and £366.00 for couples. Each dated value now cites SI 2025/295 Schedule 6 and NI SR 2025/64 Schedule 6, plus DWP's published rates. The enacted 2026 amounts and projection builders are unchanged.

The three new parameter regressions fail on the old values. After the correction, the entire altered protected-allowance Python test file passes (10 cases), and the new YAML file passes both tapered-award cases. Formatting/lint, Towncrier preview and diff checks pass. No full suite or population simulation was run.

The cited audit is #2195. #1925 still owns the remaining historical rate corrections and can drop the overlapping three 2025 hunks when rebasing; #1913 still owns the cohort split. Your separate item 2 about the hard-coded projection base year remains unaddressed in this small rate correction, as do the two documentation nits. Could you re-review the rate correction?

@vahid-ahmadi

Copy link
Copy Markdown
Collaborator

Thanks Anthony, the rate correction is right. £244.40 and £366.00 match SI 2025/295 Schedule 6 and DWP's 2025-26 rates page, and at 9442ffb the protected-allowance and savings-credit tests pass (20), as do the Pension Credit and Housing Benefit YAML cases (437).

It needs a rebase before it can go in, though. #1925 merged on 11 October, and the branch now conflicts with main in seven files: the three aged.yaml files, minimum_guarantee.yaml, threshold.yaml, tax_benefit_system.py and entitledto_scenarios.yaml. Main already has 244.4 and 366 from #1925, but as bare values, so keep your cited versions when resolving.

The hard-coded BASE_YEAR is fine as a follow-up issue, as are the two docs nits. Happy to approve once it's rebased and CI is green.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis marked this pull request as ready for review October 28, 2026 03:54
@MaxGhenis
MaxGhenis added this pull request to the merge queue Oct 28, 2026
@MaxGhenis
MaxGhenis removed this pull request from the merge queue due to a manual request Oct 28, 2026
@MaxGhenis

Copy link
Copy Markdown
Collaborator

Taken out of the merge queue (UK hub, 28 Oct). An independent second-opinion review at 86502d36 found that a guarantee reform no longer moves the forecast Savings Credit threshold or the protected pension-age Housing Benefit allowances. Main has re-derived both from the reformed guarantee since #2576.

The executed probe: a single pensioner renter in 2028, with the guarantee at £300 a week.

HB aged allowance Net income change over the £5 step where Guarantee Credit ends
This head £272.20 −£3,307
Main £318.98 +£1.38

Current law matches main. The same review found that a reprocessing simulation modifier that edits these parameters now raises.

A fix build is running so that both behave as main does. It will post a hand-off here with the new head, and an independent review follows before the PR goes back in the queue.

🤖 Generated with Claude Code

MaxGhenis and others added 7 commits October 28, 2026 03:20
Exercise modifier updates and projected allowance values while keeping incomplete rates updates invalid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…llowances (#2092 P1)

Uncommitted work from Subfleet build 20261028-004213-fix-2092-r3, which was
lost (rc 1) at 07:19Z before it finished, salvaged by the UK hub. It is not
yet verified or tested. A continuation build finishes it before anything
reaches the PR branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Disable pytest approximate comparisons relative tolerance so the current-law invariant checks equality more strictly than six decimal places.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Put dated changes and the reprocessing modifier in the same Scenario, since Simulation replaces scenario when reform is supplied. Reuse the existing cloned-system fixture while preserving independent simulation state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the index constant after the latest explicit zero anchor. This avoids Core turning zero into NaN when the index ratio overflows, while preserving earlier index history and explicit monetary values. Cover both thresholds and all three protected HB allowances.

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

Copy link
Copy Markdown
Collaborator

Salvaged the lost build 20261028-004213-fix-2092-r3, reviewed its three commits, and pushed seven commits as a fast-forward to feature/pension-credit-2025-rates. The new head is 2feb4750cf960e89f9693382c47ec6cb2b6254b8. The required pre-push head check returned 86502d3699c9755efc547d0361404ea59facd16e; no history was rewritten. Please keep the PR draft for the hub's independent re-review.

  • P1: both forecast builders read the reformed guarantee's uprated path, retaining enacted denominators and own-base precedence. HB indices stay positive and finite. The six independence assertions now expect main's behavior and retain the £15,600 / £23,868 payment checks. At phase-in zero, the threshold retains £208.07, matching main; the old PR projected £216.301049 in 2027 using the enacted 0.6 rate. Savings Credit is zero in either case.
  • P2: a separate snapshot of YAML as loaded supplies the enacted base across modifier rebuilds and prior-law projections. The incomplete-YAML guard remains covered by a regression. The rates-update fixture edits its raw YAML context explicitly.
  • P3-1: the two allowance tests now assert actual 2027–2030 values through open-ended modifiers that consume the generated index. A salvaged helper also discarded its modifier when combining reform and scenario; its dated changes and modifier now share one Scenario.
  • P3-2: eligibility Case 9 uses £11,220. Plain CPI gives 208.07 × 1.035 × 52 = £11,198.3274; the derived threshold gives (247.282 − 18.588570 / 0.6) × 52 = £11,247.6546.
  • A fresh property run exposed another edge: an extreme index ratio could overflow, making an explicit zero anchor become NaN. The final fix holds the index constant after its latest explicit zero anchor. Five regressions cover both thresholds and all three protected HB allowances. The unreformed main differential now requires rel=0, abs=1e-9.

Completed cliff probes on build head 74a7fd5da and main 53d1279b79af412965ab8acea843a054239c68c2 printed identical results across all 16 monetary/boundary lines:

Case, on both revisions Guarantee Threshold HB aged allowance Net income plus TV licence boundary step
Current law 253.2168 221.5852 272.1957 +£1.01
£300 guarantee 300.0000 268.3684 318.9789 +£1.38

Those probes preceded the final zero-anchor fix; source inspection shows it leaves these nonzero anchors unchanged, but the final-head probes were not rerun.

Fresh final-code verification: 60 unique Python cases passed, comprising all 21 projection invariants, all 34 Pension Credit uprating cases, and five protected-HB cases. This includes the combined property, the strict current-law main differential and the guarantee-increase differential. Ruff formatting/lint and whitespace checks passed.

Local verification is incomplete under the required 900-second caps. The remaining projection command completed 54 of those passes before its cap; eight protected-HB and 14 threshold-projection cases remain. The public-route/process-time command selected 138 cases but reached its cap during the first case's setup, with zero completed outcomes. Consequently the rewritten payment checks and P3-1 value tests are not locally verified. The modifier probe launch was refused at load1 193.94; its nine cases, the edge probes, the long public guarantee property, and the Pension Credit/HB YAML suites remain for CI. The earlier pre-fix property failure is excluded from the passing totals; the combined property passes after the zero-anchor fix. No timed-out command was retried unchanged, and no microsimulation was run.

The PR body now corrects only the independence descriptions. Please require CI and independent re-review before landing; this hand-off does not claim that every acceptance check has completed locally.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis marked this pull request as draft October 28, 2026 09:30
MaxGhenis and others added 2 commits October 28, 2026 05:34
Resolves the tax_benefit_system.py conflict with #2699's Carer's Allowance
earnings-limit projection. This branch moves the Savings Credit threshold and
pension-age Housing Benefit allowance projections into GENERATION_STEPS, so
they no longer run after uprating; main's
project_carers_allowance_earnings_limit stays after uprating, for the tree
and its baseline copy, as on main. The process_parameter_tree docstring now
lists only that projection after uprating.

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

#2699's Carer's Allowance earnings-limit projection imports
extend_after_last_stated from the Savings Credit threshold module. This branch
rewrote that module and dropped the helper, so the merged tree failed to
import. The helper now lives, unchanged from main, in the Carer's Allowance
module, its only remaining user; last_projected_year is still imported from
the Savings Credit module, whose definition is unchanged.

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

Copy link
Copy Markdown
Collaborator

UK hub: I merged main into this branch and pushed f7d5a218446239a2d1b328bd5bbbcf8b129d568a.

  • 897a7a3aa, the merge. Main's Project the Carer's Allowance earnings limit as 16 hours at the national living wage #2699 projects the Carer's Allowance earnings limit after uprating, and that conflicted in tax_benefit_system.py. The resolution keeps this branch's move of the Savings Credit threshold and pension-age HB allowance projections into GENERATION_STEPS. It also keeps main's project_carers_allowance_earnings_limit after uprating, for the tree and for its baseline copy.
  • f7d5a2184. Project the Carer's Allowance earnings limit as 16 hours at the national living wage #2699's module imported extend_after_last_stated from the Savings Credit threshold module, which this branch rewrote, so the merged tree failed to import. That helper now lives in the Carer's Allowance module, its only user, unchanged from main.
  • Current law matches main. A smoke check against main at 2330118d1 gives identical values for 2026, 2027, 2028, 2030 and 2031: the Carer's Allowance earnings limit, the Pension Credit guarantee and the Savings Credit threshold.

An independent review of this head is running (Subfleet 20261028-053643-review-2092-r4). It covers the merge, the seven build commits and the acceptance probes the build could not finish under load. The PR stays a draft until it agrees.

…chors

Bound the two forecast-algebra loops to the earnings index's last generated
year, read from the pre-annualisation timeline: main and this branch both
project to 2039, and annualisation only holds that year into 2040. Give the
cache-API test system the enacted parameter context the constructor now sets,
and accept the enacted argument in the cycle-error mock.

Add a Hypothesis property that one to three explicit threshold or protected-HB
amounts survive projection exactly, and that years whose latest explicit amount
is zero stay zero. Drop the Carer's Allowance docstring's comparison with the
Savings Credit threshold, which now runs before general uprating.

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

Copy link
Copy Markdown
Collaborator

UK hub, round 4: the independent review of f7d5a2184 (Sol) found no P1. It found six CI failures from the merge with main, all in tests, and three nonblocking points. f94f9a2cb answers all of them.

  • Forecast horizon (3 failures): two loops in test_pension_credit_guarantee_reform_projections.py asserted forecast identities through 2040. Main and this branch both generate the earnings index only through 2039, and annualisation holds 2039 into 2040. The loops now stop at the index's last generated year, read from the pre-annualisation timeline. The explicit 2040 inputs and payment checks stay.
  • Enacted context (3 failures): small_system() in test_parameter_cache_apis.py bypasses the constructor, so it now sets _enacted_parameters. The cycle-error mock in test_dated_reform_routes.py accepts enacted.
  • Nonblocking: a new Hypothesis property, test_explicit_own_anchors_survive_projection, checks that explicit threshold and protected-HB amounts survive projection exactly and that a latest zero stays zero. It fails against a scratch mutant that erases a later anchor. The Carer's Allowance docstring and the PR body are corrected.

Locally, all six failing nodes pass, and the guarantee-reform file passes in full (68 tests). A delta review of f94f9a2cb is running; the PR stays a draft until it and CI agree.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis marked this pull request as ready for review October 28, 2026 13:10
@MaxGhenis
MaxGhenis added this pull request to the merge queue Oct 28, 2026
@MaxGhenis

Copy link
Copy Markdown
Collaborator

UK hub: enqueued at f94f9a2cb after all checks passed at that head.

  • Independent review: GPT-6.1 Sol approved f94f9a2cb with no findings (round 5, a delta review of the round-4 fixes). Round 4, also Sol, had found no P1 and six CI failures, all in tests, from the merge with main; f94f9a2cb fixed them. Round 5 confirmed that the earnings timeline ends at 2039 on the reform, Scenario, structural and modifier routes, and that the new anchor property fails against a scratch mutant.
  • Earlier review: Vahid's CHANGES_REQUESTED was answered and dismissed with a note on 28 October.
  • Impact: CI's certified Enhanced FRS run shows no change in any reported spending total, caseload or poverty figure from 2025-26 to 2031-32. This changes how Pension Credit and pension-age HB projections respond to reforms, not current law.

🤖 Generated with Claude Code

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 28, 2026
@MaxGhenis
MaxGhenis added this pull request to the merge queue Oct 28, 2026
@MaxGhenis

Copy link
Copy Markdown
Collaborator

UK hub: re-enqueued at f94f9a2cb (unchanged), directly behind #2716.

Why it left the queue: the merge queue removed this PR at 14:45Z. In its group run (40698045501, Test / Python 4/6), 1 test failed and 1,723 passed. The failure was main's test_variable_module_release.py::test_dead_systems_modules_are_released_while_a_test_runs, which depends on when garbage is collected.

The cause: a collection during that test lets a new tax-benefit system reuse a dead one's address, so no release falls due and the test fails although nothing leaked.

Evidence:

Nothing in this PR changed, and its Sol round-5 approval and green checks at f94f9a2cb stand.

🤖 Generated with Claude Code

Merged via the queue into main with commit aabeb7a Oct 28, 2026
21 of 22 checks passed
@MaxGhenis
MaxGhenis deleted the feature/pension-credit-2025-rates branch October 28, 2026 20:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment