Repository navigation
Restrict the ordinary ATI pension uplift to relief at source - #2488
Conversation
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Looks right, fine to approve.
@juaristi22, the fix follows FA 2004. Under s 190(1) relief is capped at relevant UK earnings. Section 190(2) raises the cap to the basic amount, and s 190(3) makes that subject to s 191(7), which says the increase "may only be given in accordance with section 192" (relief at source). The new basic_amount_increase is the same expression that pension_contributions_relief.py:53-61 already uses, so the ATI cap and the relief itself now agree.
- nit:
adjusted_total_income.py:65-75andpension_contributions_relief.py:53-61now compute the same uplift with different earnings bases. ATI usestaxable_employment_incomeplus profits before carry-forward. The relief usesemployment_incomeplus positive profits. The PR body says the ATI base is kept on purpose, and I agree that this PR is not the place to change it. A shared helper would stop the two copies drifting. This is fine as a follow-up. - nit: the mixed case (net pay £3,000, relief at source £600, earnings £2,000) gives £2,600. That is right whichever contributions take the earnings-limited £2,000 first, because only the £600 can use the £1,600 uplift. A one-line comment in the YAML saying so would help the next reader.
What I checked
- Read the full diff, the PR body and #2486. Checked FA 2004 ss 190 and 191 on legislation.gov.uk.
- Hand-computed the primary 2026 case. ATI = £252,000 − £2,000 = £250,000. Cap = max(£50,000, 25% × £250,000) = £62,500. ANI = £252,000 − £62,500 − £2,000 = £187,500, so the personal allowance is nil. Tax = (£37,700 − £500) × 10.75% + (£125,140 − £37,700) × 35.75% + (£187,500 − £125,140) × 39.35% = £59,797.46, matching the YAML.
policyengine-core testonadjusted_total_income_pension_relief.yaml: 14 passed. On the wholeincome_tax/reliefsfolder: 60 passed.- Reverted
adjusted_total_income.pyto main: 4 of the 14 cases fail, including the Reproducer. ruff checkandruff format --checkon the changed variable: clean.- Hosted CI: all 14 checks pass at 3a195c3.
- The changelog fragment is
fixed. The Reproducer and axiom lines are present. - As the body says, draft #2066 edits the same file. I did not check that merge.
🤖 Generated with Claude Code
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Approving, per my review above. The nits can follow.
The page said the earnings limit applies first, so someone with no earnings gets no relief, and that the annual allowance caps relief. On main, relief at source can use the £3,600 basic amount above earnings (FA 2004 s. 191(7)), the annual allowance does not limit relief, the taper tests adjusted and threshold income, the charge is sliced across the rate bands, and relieved contributions reduce adjusted net income. Mentions #2464 (SSP and SMP in relevant earnings) and #2488 (ATI uplift) as pending. Figures re-run on policyengine-uk 2.135.6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Integrated current main ( Local validation passed 38 focused YAML cases, 318 income-tax programme cases (14 overlap with the focused run), and 17 Python tests. Independent verification also passed the 38 focused cases. Formatting, lint, source/preservation and actual-base Fix checks passed. Hosted CI for the updated head remains pending. Axiom-needed follow-up remains open; no parity is claimed. The updated head needs a fresh review before any future queue admission. |
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
|
…benefits No conflicts: none of the ten files main changed since 76cfc9e is changed by this PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arge adjusted_total_income: main's #2488 relief limit (relevant earnings, raised towards the basic amount only by relief-at-source contributions) with this branch's Step 1 from total_income_charged_to_tax and its net pay add-back. Documentation keeps both. The net pay property test's reference takes #2488's limit for invariant 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…income adjusted_total_income gave contributions relief up to its own earnings figure (taxable employment income plus trading profits before losses brought forward), while pension_contributions_relief and the annual allowance tests used pay before expenses plus trading profit before capital allowances and the trading allowance. Finance Act 2004 s. 189(2) counts employment income chargeable to income tax, here pay and statutory pay less allowable expenses but before the pension deductions the relief itself gives, and income chargeable under ITTOIA 2005 Part 2, which is after capital allowances and the trading allowance and before losses. Move that definition into utils/pensions.py (relevant_uk_earnings and trading_profits_before_losses) and use it, with pension_contributions_relief_amount, in all four places. The relief-at-source limit on the basic amount is unchanged from #2488. Part of #2523; follows #2488. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #2486
Ordinary loss-cap adjusted total income (ATI) lets pure net-pay pension contributions use the basic-amount uplift above relevant earnings. Limit that increment to actual gross personal pension contributions, the model's relief-at-source input, while retaining the cap on all member contributions. Preserve ATI's own taxable-employment/statutory-pay and pre-carry-forward-profit base, age gate, zero floor and reformable parameters.
A source-native 2026 case has £2,000 pay, £250,000 dividends, a £200,000 trading loss and £3,600 reported employee pension contributions. Current ATI is £248,400 and final tax £59,954.86; corrected ATI is £250,000 and final tax £59,797.46. The £2,000-payment control already gives the corrected result. No calculated tax base, relief or award is forced as input. Mixed net-pay £3,000/RAS £600 retains £2,600 relief under the existing input convention; pure RAS keeps the basic uplift.
Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/adjusted_total_income_pension_relief.yaml::Pure net pay above earnings cannot use the RAS basic uplift
Validation: 68 distinct cases pass (14 new YAML + 54 existing pension/trading-loss cases, including unchanged properties). Genuine RED: 10 passes/4 failures before production changes, including final tax, mixed payments, person separation and query order. No expectations or property domains/settings changed.
make format, lint, protected-file/AST checks and the trusted-base committed-head fix-evidence gate pass.Actual offline runtime: Python 3.13.14, Core 3.32.27 and microdf 1.2.1; locked Core 3.33.2/microdf 1.5.11 were not run locally. Four cached official sources/extracts reused; no new requests, install, private-population impact, broader pension/history audit or parity run.
Draft #2066 changes winter-fuel accounting in this same file. Whichever lands second must preserve both that accounting and this RAS restriction. Draft #2441 explicitly defers ordinary ATI; its unmerged utility is not imported. This change retains the existing scheme-input convention and does not certify a new mixed-scheme allocation contract.
axiom: needed