Repository navigation
Apply the Pension Credit earnings disregards (SPC Regs 2002 Sch VI) - #2019
Conversation
Codex review — changes requiredReviewed commit code-1 — [P1] Cap the disregard against earnings after statutory deductionsThe new cap at Cap using the nonnegative net earnings available at this stage and add a low-earnings/pension-contribution regression that asserts the final award. The property tests currently construct gross-only earnings and so do not challenge this deduction ordering. Confidence: high; current-head code, source rule and final award reproduction agree. Policy/source review independently confirms the ordering in regulation 17A(4A), regulation 17(9) and Schedule VI. The code and policy reports describe the same defect, counted once. The standard/higher disregard amounts and category conditions were corroborated. The required correction is the cap's placement after statutory deductions. Validation: 43 focused tests passed. A separate executed household diagnostic reproduced the £60 final Pension Credit overpayment described above. Three official source originals were inspected and their cached bytes verified. Existing passing tests do not cover the failing net-earnings ordering. No material review gaps remain within this scope. Focused tests used an existing cached Python 3.13 environment (Core 3.32.9, NumPy 2.1.3, pandas 2.3.1, microdf-python 1.2.1, Hypothesis 6.168.2, pytest 8.4.2) with this PR’s isolated source snapshot. No dependencies were installed; this was not a freshly synced lock environment or a full-suite/population run. This is a review comment, not a formal GitHub review vote. Live check before posting (2026-10-02T10:35:18.903311+00:00): same commit, CLEAN; all reported CI checks pass. |
|
Review pass (Claude Code, on Vahid's behalf) at Short version: the Schedule VI amounts, categories and dating are right, but the cap needs to move. I agree with the Codex review's blocking finding and reproduced it independently. Everything else is small. 1. Blocking: cap the disregard at net earnings, not gross.
2. Nit: Sch VI para 6 isn't mentioned. Earnings from an employment that ended before the claimant first became entitled to Pension Credit are disregarded in full (other than reg 17(9)(b) amounts). The model can't see when an employment ended, so listing para 6 under "Not modelled" next to paras 2-2B would be enough. 3. Nit: coordination with #1896. The claimant-or-partner test reads Checked and correct:
|
20 pounds a week for a lone parent (para 1), a carer satisfying Sch I para 4 (para 3), or a claimant or partner on a listed disability benefit or certified blind (para 4(1)); otherwise 5 pounds single and 10 pounds couple (para 5); at most 20 pounds (para 4A), never more than the earnings. Refs #1954. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2019) Reg 17(9) disregards the Sch VI sums in calculating the claimant's earnings, which are net of income tax and NI (reg 17(10)) and of half of pension contributions (reg 17A(4A)). Capping at gross earnings let a taxpayer's first pounds of earnings raise Pension Credit. Income tax on earnings is the lesser of the person's income tax and the basic rate on their earnings. Also: the carer condition reads the claimant's or partner's is_carer_for_benefits (Sch I para 4), not the carer addition, which counts dependants; is_claimant_or_partner after #1896; property tests compute net earnings independently, include dependants' DLA and caring, and add that earning more never raises Pension Credit; parameter description and test comment fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2c87cee to
3d4f12b
Compare
…ment (review of #2019) - Income tax on earnings is now the tax on them as the top slice of taxable non-savings income on the person's own Scottish or rest-of-UK schedule, capped at their income tax. The 20% cap let earnings crossing Scottish bands raise Pension Credit (reviewer's couple: +2.89). - The para 3 carer condition reads is_entitled_to_carer_benefit, the carer addition's own Sch I para 4 test; caring hours alone no longer qualify (reviewer's case overpaid 780 a year). - Tests: Scottish couple pair (income flat with and without earnings), hours-only vs entitled carer cases; the property test adds Scottish households, carer entitlement, care hours and pension contributions, and measures tax on earnings independently by rerunning without them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Composition note from #2259 (Pension Credit: a loss on one source counts as nil), found by its GPT-6.1 Sol review. #2259 makes Here, Suggested fix, whichever lands second: sum the individually floored person-level sources, for example 🤖 Generated with Claude Code |
|
Independent review (GPT-6.1 Sol, UK hub sweep, 2026-10-12): REQUEST_CHANGES at the head below. The hub is routing it to this PR's owner session. Methodology readings the source leaves open go to Max as a decision before they're picked. VERDICT: REQUEST_CHANGES
Verification: 35 targeted YAML tests passed serially; changed Python files passed lint and formatting checks. Property-test execution was blocked by missing Hypothesis/pytest-xdist and sandbox temporary-file restrictions. Invariants are stated; no existing tests were removed, skipped, xfailed or weakened. The required Both requested |
- Employed earnings (SPC Regs 2002 regs 17(10), 17A): less the income tax they add for the claimant and partner, measured on a branch with nobody's employment income (marginal attribution; methodology ruling d1424), and primary Class 1. - Self-employed earnings (regs 17(11), 17B; CER 1996 regs 13(4), 14): less notional basic-rate tax on profit over personal reliefs, main-rate Class 4 and (before April 2024) Class 2. Pension Credit income replaces the tax and contributions actually payable on the profit with these. - Each source floored at nil (CER reg 13(12)). - Sch VI para 4(1)(a)(vi): working tax credit with a disability element. - HB savings-credit-only route (HB(SPC) Regs reg 27(4)): deduct only the higher HB disregards, since Pension Credit income is now after Sch VI. - Regression cases for savings interactions, pension relief, losses, notional deductions; property tests for statute, flatness under the disregard and monotonicity in either partner's earnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The last description update dropped this PR's 🤖 Generated with Claude Code |
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Fine once the d1424 tax-attribution ruling is recorded. The code matches Schedule VI and the earlier blocking findings are fixed.
@MaxGhenis this is a delta review since my comment at 2c87cee (2 Oct). It also covers the 12 Oct review at 816a4e1 and the #2259 composition note.
Earlier findings, re-checked on the current head merged locally with main 714b143:
- Cap at net earnings (2 Oct, blocking): fixed. £400 of wages, £400 of pension contributions and a £9,000 State Pension (2024): disregard £200, Pension Credit income £9,000, Pension Credit £2,343.80, as hand-computed (£218.15 × 52 − £9,000).
- Earnings raising Pension Credit through savings tax (12 Oct, finding 1): fixed. £12,000 State Pension, £12,000 interest and Attendance Allowance: Pension Credit is £4,667.80 with or without £1,040 of wages. The £832 disregard exactly offsets the wages net of the tax they add.
- Carer by caring hours alone (finding 2): fixed now that #2312 is on main. With £12,000 of wages and 40 caring hours, the disregard is the £5 standard amount, not £20.
- Self-employed notional tax (finding 3): fixed. CER reg 14 is applied as written: basic or Scottish basic rate, Chapter 2, 3 and 3A reliefs only, and main-rate Class 4 between the limits. Class 2 is included only while
ni_class_2is non-zero; it is £0 from 2024-25, which matches the omission of reg 14(2)(a). - Loss netting (#2259 note, finding 5): fixed. £400 of wages and an £8,000 self-employment loss give the £260 disregard.
- WTC (finding 7): fixed. Para 4(1)(a)(vi) is read from the elements and WTC in payment.
is_uc_claimant(my nit 3): fixed. The formula now usesis_claimant_or_partner.
-
should,
pension_credit_earnings_income_tax.py:3-8: the top-slice attribution is still marked provisional, pending the d1424 ruling, and the PR body says it waits on that. Nothing in the code needs to change if option (a) stands. Once the ruling is made, please replace the "provisional" wording inDECISIONwith the decision and its date, so the docs don't describe a choice that is still open. -
nit (carried over from my 2 Oct comment),
pension_credit_earnings_disregard.pydocumentation: Sch VI para 6 (earnings from an employment that ended before first entitlement are disregarded in full) is still not in the "Not modelled" list. One clause would do. -
nit,
pension_credit_earnings_income_tax.py: the file holds four variables, and none is named after the file. The repo keeps one variable per file, named for it. Splitting it, with the shared helper inutils, would makepension_credit_employment_income_taxeasier to find. -
nit, PR body: the "Dependency" paragraph says carers above the earnings limit keep the £20 "until #2312 lands". #2312 merged on 12 Oct, so after the next merge from main that sentence is out of date.
-
nit, overlaps: draft #2066 (winter fuel charge) conflicts with this PR in
pension_credit_income.py. #2092, #1913 and #1969 merge cleanly with it. None of Vahid's own open PRs touch these files.
What I checked
- Read the full diff, the PR body and all the earlier review threads. Read SPC Regs 2002 Sch VI (paras 1 to 7, including 4A(2)'s couple limit) and CER 1996 reg 14 on legislation.gov.uk.
- The amounts (£20, £5, £10, unchanged since 6 October 2003), the para 4(1)(a) list and its dated additions, and the para 3 link to Sch I para 4 all match.
- The disregard sits at benefit-unit level with one weekly amount, which is right for para 4A(2).
- On the head (d91b322):
- the Pension Credit YAML directory and
housing_benefit_savings_credit_only.yaml: 251 passed; test_pension_credit_earnings_disregard_properties.pyandtest_pension_credit_income_losses.py: 9 passed.
- the Pension Credit YAML directory and
- On a local merge with main (not pushed):
- the Pension Credit and Housing Benefit YAML directories: 485 passed;
- the Carer's Allowance earnings-limit, overlapping-benefits and pension-age HB means-test files: 47 passed;
- the property and loss tests: 9 passed;
- the probes above.
ruff checkandruff format --checkon the changed Python files: pass.- CI was green at d91b322.
- Not run: the national impact table. It needs the Enhanced FRS build, which I did not download. I did not time the branch simulations either.
🤖 Generated with Claude Code
#2019 now measures net employed earnings from employment_income alone, and takes the income tax payable on them as the top-slice tax without anyone's employment income. Statutory sick, maternity and paternity pay are earnings of an employed earner (SPC Regs 2002 reg. 17A(2)(h) and (i)), so list them once, in gov.dwp.pension_credit.guarantee_credit.statutory_payments, and read the list in all three places that need employed earnings: pension_credit_earnings, the Sch VI disregard's net earnings, and the top-slice tax (whose branch now zeroes the statutory payments with employment income). earnings_sources is back to employment and self-employment income. Property tests: - drop the temporary point-count prints; - the Housing Benefit differential draws only SSP and SMP until #2368 counts statutory paternity pay in HB, and excludes savings-credit-only awards, whose HB income is the Pension Credit assessment plus the savings credit (HB (SPC) Regs 2006 reg 27); - the move-between-wages-and-pay property checks Pension Credit quantities only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the independent review of #2509: - Cases 8 to 11 pin how statutory pay runs through #2019's top-slice tax on earnings: tax wholly on the pay (8), only the tax the pay causes (9), self-employed profit taxed beneath the pay (10), and a couple whose partner's pay costs the claimant the marriage allowance (11). With the tax module as #2019 has it, Cases 8 to 10 fail. - Case 12: statutory sick pay is Savings Credit qualifying income (reg 9 does not exclude it). - The disregard and the top-slice tax read one helper, policyengine_uk/utils/pension_credit.employed_earnings_sources, so the list of employed earnings cannot drift between them. - Labels and documentation in pension_credit_earnings_income_tax now say employed earnings, not employment income; the parameter reference cites reg 15(1)(o) to (q), matching its description. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Adopted and fixed at
Hub artifacts in this assigned worktree: #2509 is stacked on this branch and needs main merged after #2019 lands. |
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 4 (thresholds in Notes
Moves flagged for review
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)
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: receipts by tax head; components the model counts twice; population; 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
|
|
Round 2 fixes pushed at
New CI results are pending. Please dispatch the hub's independent Sol review at this head. No self review or PR merge was run. #2509, stacked on this branch, needs main merged after this lands. |
|
Merged by the UK hub (session local_8e19f9d5, Opus 5.5) on its merge gates. The hub adopted this PR under d1280. One merge-queue enqueue, pinned to head
🤖 Generated with Claude Code |
…egards) council_tax_reduction_applicable_income keeps main's applicant-and-partner benefits block and its documentation, followed by this PR's capital, tariff and home-letting text. pension_credit_income keeps #2019's formula. Its tax on earnings is the extra tax the earnings cause, including on savings and dividends (Max's d1424 ruling, option (a)), and self-employed profit carries notional tax. This PR then subtracts what reg 17(10)(a) and Sch IV para 18 leave out: the tax on uncounted income, and the Step 7 charges, that the claimant and partner would pay without earnings. The new Person variable pension_credit_uncounted_income_tax_without_earnings is income tax less legacy_means_test_income_tax, on #2019's no-earnings counterfactual. - With no capital income or charges it is nil, and #2019's results stand. - With no earnings the formula reduces to this PR's. - It does not depend on earnings, so the disregard invariance holds. Subtracting #2019's self-employment tax from legacy_means_test_income_tax directly would remove induced dividend tax twice. #2019's two branches still serve income_tax. A generic helper, person_variable_with_inputs_zeroed, computes the counted tax on a third, and the UC non-dependant check precomputes it on the parent. Main's savings-band pair now gives 12,000: the tax on the interest due without earnings (1,086) is not deducted. #2019's property reference subtracts the same quantity, from its own simulation. A new case covers wages that push dividends into the higher band. Not run locally (disk pause #11); verification follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…now deducted test_income_from_capital_changes_no_legacy_means_test: with earnings, #2019's tax on earnings (Max's d1424 ruling) includes the extra tax earnings cause on savings and dividends. Scaling income from capital can therefore move Pension Credit income. Hypothesis found such a case: a couple whose wages of 6,707 sit under the personal allowance, so at three times the dividends 279.77 of dividend tax is induced and deducted. The property now asserts: - invariance wherever pension_credit_employment_income_tax does not move; - otherwise, that Pension Credit income moves by exactly minus that change and the disregard's. The awards that follow Pension Credit are compared wherever the tax on earnings is steady. The docstring labels this an intended exception. test_means_tests_see_the_loss_only_through_tax: the legacy means tests deduct legacy_means_test_income_tax, and Pension Credit's new uncounted-tax term comes off too. Both are recomputed from the tax components, so with income_tax held a loss that moves interest between bands moved them, and Housing Benefit income with them (1,047 in the failing family). Both are now held, as the tax the tests deduct. Both pass single-process, with derandomized examples. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SPC Regs 2002 reg. 17A(2)(h) and (i) make statutory sick, maternity and paternity pay earnings of an employed earner, and reg. 17(10) deducts the tax payable on them as it is (not the notional tax reg. 17(11) gives self-employed earners). Under #2019's top-slice split (d1424), both counterfactual branches therefore zero the pay with employment income. Update #2019's branch-reuse expectation, in both parametrizations, to the employed-earnings keys this PR introduces, and give one non-dependant pensioner statutory sick pay so the shared-branch and calculation-order tests run with it present. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1954. Applies SPC Regs 2002 Schedule VI earnings disregards to Pension Credit and the corresponding Housing Benefit savings-credit-only adjustment. Max's d1424 ruling remains option (a): tax on earnings is the extra tax those earnings cause, including savings-band and partner interactions.
Round 2 fixes
tax_credits_pension_credit_passport.yaml, including masked passing fixtures. Person-level wages are gross; Schedule VI is applied once. Re-derived every existing expected output from the law and current parameters and kept all assertions/tolerances unchanged. The complete repository fixture audit found no further stale inputs;.job/FIXTURES_R2.mdrecords the searches, classifications and all eleven hand calculations.personal_allowance,blind_persons_allowance,marriage_allowanceandmarried_couples_allowance_deductionconsistently in both trading-loss comparisons. Pin CI's complete five-family counterexample as@example. Its partner's £1,260 Marriage Allowance transfer explains the £252 uncontrolled notional-tax difference. The original test body reproduces the failure; the repaired invariant passes without overriding notional deductions or PC income. No assertion, tolerance, strategy or property setting was weakened.collected.txtbecause the failed YAML step skipped those later steps. Preserve the upstream failure gate, setup/cancellation guards and exactly-once shard audit. This was an artifact diagnostic failure; dataset credentials were present.ea7c98b5320d5ce984f88e2d100cc853667c4a2ecleanly on top of the existing branch. Original author/history retained; no rebase or force push.Invariants and scope
Schedule VI remains £5/week single, £10 couple, or £20 for a qualifying lone parent/carer/disability/blindness condition, capped at net earnings. Each employment's loss floors at zero. Employed earnings deduct attributed tax, primary NI and half pension contributions; self-employment uses CER13–14 notional tax/NI in place of actual deductions. Dependants' benefits/caring do not qualify a claimant/partner. Dated ESA/PIP/AFIP limbs, Scottish basic-rate notional tax and historical Class 2 remain covered.
Round 1 fixes remain: preserve late explicit inputs using core's
drop_computed_arrays(); compute historical Class 2 from CER chargeable income independently of actual trading allowance/loss relief; share two counterfactuals across PC and UC housing. At £13,000 profit in 2023, deductions are £86 tax + £38.70 Class 4 + £179.40 Class 2 = £304.10. Sources: dated CER14, dated SSCBA11, SI2024/377 r8(7).Ordinary covered earnings leave PC income flat subject to the pension-contribution floor and existing PSA cliff. Special occupations/transitions, carer continuation, unrepresented Scottish replacement benefits/war mobility and the long-term incapacity-benefit proxy remain documented exclusions.
Validation at
54b409db47fd543792a0f2ce5c9e2c43010ddb7eAll 247 Python tests across 36 modules passed, foreground
-n 3 --dist worksteal -o xfail_strict=true; no failures, errors, skips or xfails. Exact JUnit node coverage matches fresh unsharded collection. This includes all 30 review modules, three R1 regression modules and three current-main interaction modules. Four existing employer-NI divide warnings.Restoration/isolation 14/14 and branch/late-input probes 14/14 pass.
Requested YAML scope plus passport: 773 passed on the merged tree.
Existing CI shard-check module: 46 passed, foreground
-n 1.Exact original CI trading-loss replay: fails by £252; repaired focused property: passes. Complete CI example retained.
uvx ruff format --check .: 1841 files already formatted; Ruff lint and Git whitespace checks pass.No self review or merge into main. The hub dispatches independent review.
Cost on certified data
Fresh serial processes use certified uk-data 1.58.1, 128,409,762 bytes, SHA256 checked before every load:
056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784. Both sources use the same frozen Python 3.13.9/core 3.33.2 environment. Current main isea7c98b53; initial/reverse measurements use heade3b9330deand the final pair uses final head. Model, parameter, lock and Python sources are identical across those head revisions (the last commit changes only workflow conditions). CPU covers target calculation, excluding import/load/weights; RSS is cumulative process peak at the end of the target, decimal GB. Maximum guarded compressor 9.942 GB, below 28 GB. Numerical/compression caps ≤4; native pool introspection returned no pools, so those are configured caps.Residual cost remains for hub assessment. A further complete-household subset prototype selected 72,710 of 113,589 people and replayed/verified all 1,455 recorded input-periods. It cost 0.369 CPU seconds versus 0.302 for the existing wages helper (+22.24%) and failed equality for 214 person tax amounts (maximum £6,280.05), so it was discarded. Its mismatch cause was not isolated; preserved inputs alone did not preserve reconstructed simulation context. This attempt does not justify changing any output. Production retains the validated full-vector branch API and two distinct top-slice tax evaluations. Restricting only to currently PC-eligible units would also alter public intermediate outputs and needs to preserve protected couples and the UC non-dependant calculation. Initial/reverse pairs overlapped separate small-situation Python tests; the final pair ran after that suite completed. These are individual timing observations, not a stable performance estimate. Details:
.job/PERFORMANCE_ATTEMPT_R2.mdand aggregate-only measurement records.Impact against the same current main
Annual nominal £million. GC/SC paid apply outer PC eligibility and take-up. Historical Class 2 does not affect these post-abolition years. Shared certified cache read directly; no H5 copy retained.
2026 PC paid +£559,416.56, HNI +£565,478.50; 2030 PC paid +£736,736.97. Remaining benefits in the table are unchanged apart from Winter Fuel Allowance +£6,062.74 in 2026. Aggregate/provenance/guard records and all test logs are in the adoption job's
.job/hand-off.axiom: TheAxiomFoundation/rulespec-uk#414 queued (SPC Regs 2002 Sch VI; Roman-numeral schedule corpus blocker), TheAxiomFoundation/rulespec-uk#478 queued (SPC regs 17(9)-(11), 17A(4A) and 17B; CER 1996 regs 13-14; amendment/extent-aware quotations corrected)
Reproducer:
policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_pension_credit_passport.yaml,policyengine_uk/tests/test_trading_loss_properties.py::test_means_tests_see_the_loss_only_through_tax,policyengine_uk/tests/test_pension_credit_late_inputs.py::test_explicit_allowances_keep_couple_award_flat,policyengine_uk/tests/test_pension_credit_notional_class2.py::test_notional_class2_ignores_actual_trading_allowance,policyengine_uk/tests/test_pension_credit_tax_branch_reuse.py::test_full_calculation_reuses_two_earnings_tax_counterfactualsStacked PR: #2509 needs main merged after this lands. Do not merge this PR in this job; hand back for hub-dispatched independent review.