Repository navigation
Give Council Tax Reduction and Income Support their own pension contribution shares - #2516
Conversation
Council Tax Reduction and Income Support deducted a hard-coded 0.5 of pension contributions, while the CTR pensioner earnings disregards were capped at Housing Benefit net earnings, which read Housing Benefit's share. A reform of HB's share therefore moved the CTR disregard cap but not CTR income, and neither scheme could be reformed on its own. Each scheme's regulations state their own one-half and none refers to HB's, so each gets its own parameter at 0.5: - Income Support: SI 1987/1967 regs 36(3)(b) and 38. - CTR, by scheme: England pensioners (SI 2012/2885 Sch 1 paras 19, 29), the England default scheme (SI 2012/2886), Wales (WSI 2013/3029), Scotland (SSI 2012/319, SSI 2021/249) and the working-age schemes of Merton, Kingston upon Thames, Newham, Westminster and Oxford. council_tax_reduction_pension_contribution_deduction_rate picks the family's scheme's share. CTR applicable income reads it, and the pensioner disregards now cap at council_tax_reduction_net_earnings, which shares HB's earnings rules (factored into _net_earnings.py) at the scheme's own share. Current-law outputs are unchanged. Fixes #2515 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 55 (thresholds in Notes
Moves flagged for review
15 more flags are in the report artifact. Receipts by tax head
Spending by benefit and service
Caseloads
Government balance, each component counted once
Model totals
Household income totals
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
Household net income by decileMean change in household net income per household, and as a share of the decile's base income; households stay in the base run's deciles.
No change in any year: components the model counts twice; population. 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
|
The model holds council_tax_reduction_pension_contribution_deduction_rate as float32, so a 0.05 share reads back as 0.0500000007 and the exact comparison failed in CI (Test / Python 2/4). Compare within 1e-6 and drop the comment's wrong claim that twentieths are exact in binary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Make the HB-reform disregard-cap reproducer read only variables that exist on main, so it fails there by value (disregard 250, not 200); the new variables move to a companion case. - Add a Hypothesis example with a contributing family on all 13 routes, so every route is tested whatever Hypothesis draws. - Extend the rate selector's UC caveat to Oxford, whose Appendix 3 assesses UC claimants on DWP's net earned income (#2520), and cite UC Regs 2013 reg 55(5)(a) for the full deduction. - Say that SSI 2021/249 reg 51(9) takes self-employed net profit from SSI 2012/303 regs 37 and 38, and that Oxford's share is for applicants who do not receive UC. - Describe HB net earnings as deducting HB's own share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2 (finding A): reg 55(5)(a) covers employed earnings only; the full deduction of relievable pension contributions from self-employed earnings is reg 57(2) step 4. Documentation only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hand-off to the UK hub (session local_830eef6e-8eec-4ada-8b94-6cdd0fb631b0, 2026-10-20)
|
|
Merged by the UK hub (session local_8e19f9d5, Opus 5.5) on its merge gates, from the owner session's hand-off (local_830eef6e). One merge-queue enqueue, pinned to head
🤖 Generated with Claude Code |
…eans-test Resolution, as set out in #2516's "Merge order" section: - housing_benefit_net_earnings: #2516's version (person_net_earnings with Housing Benefit's own share); housing_benefit_person_net_earnings removed. - council_tax_reduction_net_earnings: person_net_earnings at council_tax_reduction_pension_contribution_deduction_rate, summed over is_council_tax_reduction_applicant_or_partner. - council_tax_reduction_pensioner_earnings_disregard: reads council_tax_reduction_net_earnings, keeping the head-alone rates and conditions. - council_tax_reduction_applicable_income: #2516's scheme share applied to the applicant-and-partner members (auto-merged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #2515
Status: ready, handed to the UK hub.
daa3291a8in round 3.daa3291a8. The Python shards haven't completed at any head since round 1, because each push cancelled the run before. Their passing is the remaining gate.Summary
Council Tax Reduction (CTR) and Income Support (IS) deducted a hard-coded
0.5of pension contributions. The CTR pensioner earnings disregards were capped athousing_benefit_net_earnings, which reads Housing Benefit's (HB's) share. As a result:#2497 does the same for HB applicable income. JSA is #140 and is left alone.
What the law says
Each scheme's regulations state their own one-half, and none refers to HB's. So each scheme gets its own parameter at 0.5, and none reads the HB parameter.
gov.dwp.income_support.means_test.pension_contribution_deduction_rategov.local_authorities.england.council_tax_reduction.pensioners.means_test.…….england.council_tax_reduction.default_scheme.means_test.…….wales.council_tax_reduction.means_test.…….scotland.council_tax_reduction.means_test.…….{merton, kingston_upon_thames, newham}.council_tax_reduction.means_test.…….oxford.council_tax_reduction.means_test.…….westminster.council_tax_reduction.means_test.…How the law was checked:
The council parameters start on 2026-04-01, as their siblings do. The model backdates every parameter to 2015.
What changes in the model
council_tax_reduction_pension_contribution_deduction_rate(new, BenUnit) picks the family's scheme's share. It uses the same scheme tests assimulated_council_tax_reduction_benunit:council_tax_reduction_applicable_incomereads that share instead of0.5.income_support_applicable_incomereads the IS parameter instead of0.5.council_tax_reduction_net_earnings(new) is net earnings for the CTR pensioner disregards. It uses HB's earnings and deduction rules at the scheme's own share._net_earnings.py:person_net_earnings.housing_benefit_net_earningscalls it with HB's share.council_tax_reduction_pensioner_earnings_disregardnow caps atcouncil_tax_reduction_net_earnings, so a reform of a CTR share moves CTR income and the cap together, and a reform of HB's share moves neither.Known gap, not changed here. Some schemes assess a working-age applicant with a Universal Credit award on the Secretary of State's UC figures, which deduct relievable pension contributions in full (UC Regs 2013 reg 55(5)(a) for employed earnings, reg 57(2) step 4 for self-employed earnings):
The model has no such route for these schemes yet: #1962 covers Wales and Scotland, and #2520 (opened from this PR's review) covers Oxford. So the 0.5 share reaches those applicants too. Merton, Kingston upon Thames, Newham and Westminster already use UC income through
_legacy.py. The rate variable documents all this; it is outside this wiring change.Invariants
These hold for every input.
test_pension_contribution_deduction_rate_properties.pystates them and tests them with Hypothesis.The YAML cases are hand-derived. One family on each scheme gets a 0.25 reform, beside a control on another scheme. A pensioner whose net earnings fall below the £5 disregard shows the cap and the deduction moving together. Couples show contributions summed.
Current-law outputs
Unchanged by construction. Every new parameter is exactly 0.5, the value of the literals it replaces.
person_net_earningskeeps HB's operations in the same order.council_tax_reduction_net_earningsuses HB's claimant-and-partner mask.Aggregate impact on certified data: no change.
47642d1eewith main merged withdaa3291a8. It finds no change in any figure in any year, 2025-26 to 2031-32, and no household's net income changes. The PR-triggered report at this head compared against a stale base (685963c2d, 26 main commits behind its merge commit), so its 55 flagged moves belong to main's other merges, not to this PR. That defect is fixed in Compare a pull request with its merge commit's first parent in the Aggregate impact report #2522.685963c2dwith this PR merged into it (858b34022, head0a6b36b81). Both useenhanced_frs_2024_25.h5(policyengine-uk-data 1.58.1) and cover fiscal years 2025-26 to 2031-32, a full microsimulation per year on each side.cc86a337achanges only the property test.d77037fc0anddaa3291a8change only tests, documentation strings, parameter descriptions and reference metadata. The PR's Aggregate impact run re-runs on each push.Tests
No full local suite was run: the
route-a-release-2026-10reservation (to 2026-10-21 04:00Z) holds back heavy local work, so CI runs the suite.0a6b36b81:Test / Python 2/4, which failed attest_each_scheme_moves_by_contributions_times_its_own_change_in_share;cc86a337acompares within 1e-6;Test / YAMLpassed, including every hand-derived case in both new YAML files.uv.lockpins):pytest policyengine_uk/tests/test_pension_contribution_deduction_rate_properties.pypasses (2 tests, 18 s);d77037fc0, locally, one process:origin/main's source, the caseA reform of the Housing Benefit share leaves the Council Tax Reduction disregard capfails by value:council_tax_reduction_pensioner_earnings_disregardis 250, not 200.uvx ruff@0.17.0 format --check .andruff checkon the changed files pass.Review
Round 1 was GPT-6.1 Sol at
0a6b36b81(~/reviews/pe-uk-2516/review-r1.md). Its verdict was REQUEST_CHANGES at that head, and it judgedcc86a337aapprovable once CI is green. Its findings and the responses:cc86a337a.axiom:line is kept (no value changes).Round 2 was GPT-6.1 Sol at
d77037fc0(~/reviews/pe-uk-2516/review-r2.md).cc86a337awhend77037fc0was pushed, and that is the merge gate here too.daa3291a8, which adds reg 57(2) step 4 for self-employed earnings.Merge order
#2078 also restructures
housing_benefit_net_earnings. It moves the per-person rules intohousing_benefit_person_net_earningsand adds its owncouncil_tax_reduction_net_earnings(over the CTR applicant and partner). Whichever lands second must:housing_benefit_person_net_earningscallperson_net_earningswith HB's share.council_tax_reduction_net_earnings.py: sumperson_net_earningsat the CTR share (council_tax_reduction_pension_contribution_deduction_rate) over Means-test the Council Tax Reduction applicant and partner, not the benefit unit's claimant #2078's applicant mask. Means-test the Council Tax Reduction applicant and partner, not the benefit unit's claimant #2078's documentation says the variable equalshousing_benefit_net_earningswhen the applicant is the claimant; qualify that to "while the two shares are equal".council_tax_reduction_pensioner_earnings_disregardreading the mergedcouncil_tax_reduction_net_earnings, neverhousing_benefit_net_earnings. Both PRs edit this file, so expect a textual conflict.council_tax_reduction_applicable_income.py, keep* council_tax_reduction_pension_contribution_deduction_rateon Means-test the Council Tax Reduction applicant and partner, not the benefit unit's claimant #2078's new applicant mask.This PR is handed to the UK hub, which owns the merge order. If #2078 lands first, the hub dispatches this resolution here.
Reproducer:
policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/pension_contribution_deduction_rate.yaml::A reform of the Housing Benefit share leaves the Council Tax Reduction disregard cap,policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/pension_contribution_deduction_rate.yaml::A reform of the Income Support share moves Income Support applicable income,policyengine_uk/tests/test_pension_contribution_deduction_rate_properties.py::test_each_scheme_moves_by_contributions_times_its_own_change_in_shareaxiom: n/a: infrastructure — parameter wiring for shares that are 0.5 in law and in the model (current-law outputs unchanged); the law review found no current-law error in the values
🤖 Generated with Claude Code