Repository navigation
Deduct pension relief from adjusted net income - #2440
Conversation
…ess once pension_contributions_relief capped relief at the annual allowance, and personal_pension_contributions_tax also charged the excess over it, so contributions above the allowance were taxed twice. Relief now runs to the s. 190 limit (the greater of relevant UK earnings and £3,600), and the annual allowance charge recovers it once. The charge now taxes the chargeable amount as the top slice of reduced net income at the basic, higher and additional rates (FA 2004 s. 227(4A)), or the Scottish rates with the starter band at the Scottish basic rate (s. 227(4AA)), instead of one marginal rate. Unused allowance carried forward (s. 228A) is a new input, nil by default. Fixes #2237 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 on #2240: - The annual allowance charge now stacks the chargeable amount on statutory reduced net income and raises every rate limit by relieved relief-at-source contributions and grossed-up Gift Aid (FA 2004 s. 227(4C)). Translating the model's deductions and flooring at zero lost the extension once relief-at-source contributions passed income after allowances (£8,460 charged where the statute gives £8,000). - Only relief at source can give the increase to the £3,600 basic amount (s. 191(7)); personal contributions are taken as relief at source, employee contributions as net pay, as capital_gains_tax does. - Recognise the Scottish starter band only when the raw bracket has both a rate and a threshold. - Property tests cover both relief methods and Gift Aid, use float32-aware tolerances, and exclude Scotland from the no-double-tax property; a YAML example pins the Scottish deviation from relieving relief at source by deduction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2 on #2240: the upper and lower charge bounds now allow the same relative float32 error as the other assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nual-allowance-2237
…nual-allowance-2237
…nual-allowance-2237
|
Hand-off at 1dbc881 (tests
|
|
Round 2 addresses the review at
The body now qualifies HICBC's fixed £1,200 actual Child Benefit property, excludes opt-out claim resumption, replaces the conflict section with the merge resolution, and retains Re-measured incremental ANI impact with #2240 in both worlds, certified uk-data 1.58.1 and verified SHA256
Versus round 1's −£444.554m / −£1,488.281m, the income-tax reductions are £16.806m / £55.184m larger. Four sequential foreground top-level runs, four native threads, compressor 14.84–15.24GB, matching IDs/model-year weights once, no downloads or record outputs. Aggregate evidence/script and verified recovery bundle are in The branch was pushed in one successful push cycle. PR remains a draft. Full repository suite and documentation build were not run locally. No self-review, in-job reviewer or PR merge was performed. Hub follow-up: independent review at this head, CI observation, and signed Axiom s.58 encoding (module still deferred; |
|
Fixed the remaining round-two finding in
Pushed once successfully on the first attempt. PR remains a draft. Production and its round-two impact estimates are unchanged; independent review remains with the hub. |
|
Landed by the UK hub (merge queue, pinned to head
|
Conflict: test_trading_loss_properties.py docstring. Kept main's invariant 1 wording (pre-pension net_income, #2440) and this branch's invariants 2 and 3 (tax before the charge). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #2243
Pension contributions now reduce adjusted net income (ANI), so the Personal Allowance taper and High Income Child Benefit Charge recognise pension relief. The London 2024 reproducer gives £90,000 ANI, the full £12,570 Personal Allowance and £23,432 income tax: £37,700 × 20% + £39,730 × 40%. Previously it gave £150,000 ANI, no Personal Allowance and £28,460 tax.
ITA 2007 s.58 starts from s.23 Step-2 net income, then deducts gross Gift Aid and gross relieved RAS contributions under FA 2004 s.192(1). Statutory net income already reflects net-pay contributions under s.193 and relief on claim under s.194(1). HMRC ANI guidance confirms the deductions.
The model represents employment and
net_incomebefore individual pension relief, applying that aggregate relief throughallowances. This PR therefore subtractspension_contributions_reliefin ANI while retaining the existing ordinary-tax representation. Personal pension inputs are gross contributions; they are not grossed up again. Direct ANI inputs restore both pension relief and gross Gift Aid through one shared helper in the baseline and marriage-neutral reform. Both routes preserve explicitly supplied annual allowances and the pre-allowance income used for student loan repayments. Following #2240, relief follows the earnings/basic-amount limit and the annual allowance applies through a separate charge.Invariants covered by MOCK tests:
received_allowances_*, property tax and CGT usenet_income; CGT retains the relieved-RAS basic-rate band extension without also deducting it from that base.#2240 merged; resolution as in the merge commit
Merge commit
9a9a9175f9933e30952afb6671e94add9b197066incorporates exact reviewed head2154a3158b7ab03937a00186444a8836256428aa(upstream was still open when checked). Its resolved tree is exactly the review's0c60e514675f7050dde6f804c4cd863da352c680, with no difference from the supplied resolution patch/tree. #2240's relief and charge formulas, latest rate-eligibility gates and xfail removals are retained, alongside this PR's ANI deduction/inverse and numeric/supplied-ANI updates.The marriage-neutral inverse now uses the same pension/Gift Aid add-back as baseline. The YAML regression checks supplied £90,000 ANI produces £23,432 tax; the reused review script checks derived and supplied routes in separate simulations. The explicit £20,000-AA round trip checks £150,000 net income and £11,043.45 Plan 2 repayments on both routes. After #2240, relief is £60,000 on both routes, not the pre-merge £20,000: AA limits the separate charge rather than section 190 relief. The old cap solver and its override bypass are gone.
The AA property now bounds contributions using the full year-specific Personal Allowance plus other fixed allowances, and pins the review's four-person RAS/net-pay witness with
@example. The two #2240 comment corrections change no expected values.Impact
Four sequential foreground top-level microsimulations (baseline and fixed, one at a time for each year), using the certified cached enhanced FRS 2024-25 uk-data 1.58.1 artifact, snapshot
2526edbbb687a3b54dbb11272e5389e4b62d2ac2, verified SHA256056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784. Baseline is main with exact reviewed #2240 head2154a3158b7ab03937a00186444a8836256428aa; fixed head ise4e6323812ef92846ea43d09b8505cf23c2a3765. Every changed baseline variable class is restored from that baseline Git object, with source hashes saved; #2240 relief/charge formulas are retained in both worlds. The CPS reform is inactive for this score. Offline mode prevented downloads; only aggregate JSON/text was saved. All native thread caps were four and all four pre-run compressor checks were 14.84–15.24 GB.Compared with round 1 against old main (2026 −£444.554m, 2030 −£1,488.281m), income-tax reductions are now £16.806m and £55.184m larger. These estimates measure the incremental ANI correction with #2240 present in both worlds and supersede the old-main score.
Changes are fixed minus baseline. HICBC is included in income tax; the changes must not be added. Counts use matching person IDs and model-year person weights once, with a £0.01 PA-change tolerance. These are static paired-model results under inherited pension inputs and relief/AA approximations, not a #2244 taper score. Reproduction script, source hashes and aggregate provenance are retained in
.job/impact-r2.pyand.job/impact-r2-results.jsonfor the hub. No individual records were written.Validation
uv run --frozen --no-sync pytest -n 3 policyengine_uk/tests/test_adjusted_net_income_pension_relief.py policyengine_uk/tests/test_gift_aid.py policyengine_uk/tests/test_child_benefit_charge_rounding_properties.py policyengine_uk/tests/test_capital_gains_tax_components.py -q: 78 passed, 1 existing dataset-dependent CGT skip, 362.31s. All 17 ANI regression cases pass.uv run --frozen --no-sync policyengine-core testover HMRCincome_tax,pensions,capital_gains_tax,child_benefit,tax_free_childcareand DfEextended_childcare_entitlement, with-c policyengine_uk: 643 passed, 192.55s.uv run --frozen --no-sync policyengine-core test policyengine_uk/tests/policy/reforms/cps -c policyengine_uk: 10 passed, 29.69s.uv run --frozen --no-sync pytest -n 3 policyengine_uk/tests/test_pension_annual_allowance_properties.py -q: 10 passed, 749.56s, including the four-person@example. Reusedcombined-cps-check.py: exit 0; derived and supplied routes agree on all checked outputs.1dbc88172: 1 failed each. Reused originalani-mechanism-check.pyconfirms both original P2 failures. Unchanged combined AA property fails the exact four-person witness; corrected domain accepts it. Original 16 Python and eight YAML ANI cases already have independently verified behavioral failures against old main, as recorded in round 1/review.make format,uvx ruff format --check .(1,727 files) and whitespace checks pass. Committed-head fix-evidence check passed (seven changed executable test files).UV_LINK_MODE=clone uv sync --frozen --extra dev. All tests run in the foreground with at most three workers; native threads are one. Old-head tests overlapped at at most one additional worker. No dataset downloads.Round 3: trading-loss reference
At
7f1b608f20c9ddd9d4de77721e7e90ba212d73a7, the trading-loss property's reference uses the model's pre-pensionnet_incomefor its ordinary-income comparisons and subtracts independently calculated pension relief once for adjusted total income. Its module explanation is updated. This round changes onlytest_trading_loss_properties.py; production and the round-two impact estimates are unchanged.uv run --frozen --no-sync pytest -n 3 -q --hypothesis-show-statistics policyengine_uk/tests/test_trading_loss_properties.py: 12 passed, including the unchanged 16-person@example(833.10s).uv run --frozen --no-sync pytest -n 4 --dist worksteal -q policyengine_uk/tests/test_{adjusted_net_income_pension_relief,gift_aid,child_benefit_charge_rounding_properties,capital_gains_tax_components,pension_annual_allowance_properties}.py: 88 passed, 1 existing dataset-dependent CGT skip (2118.88s; exit 0). A non-failing Hypothesis GC-timing shutdown warning is retained in the log; all 89 collected tests reported outcomes.POLICYENGINE_UK_DEFAULT_DATASET=MOCK uv run --frozen --no-sync policyengine-core test policyengine_uk/tests/policy/baseline/gov/hmrc/{income_tax,pensions} -c policyengine_uk: 359 passed, 207.45s.e4e6323812ef92846ea43d09b8505cf23c2a3765failed as expected: 1 failed, £73,200 expected versus £74,100 actual, 102.66s. Saved source matches that Git blob exactly. The reusedevidence-r2/ci_loss_replay.pyretains its three assertions: old reference/current production fails; exact Limit pension relief by earnings and charge the annual allowance #2240 ANI baseline passes all 16; corrected reference/current production passes all 16. Replay exits 0 and records £3,600 relief and £296,400 ATI and explicitly asserts £73,200 versus £74,100.uv run --frozen --no-sync make formatand requireduvx ruff format --check .pass (1,727 files); source-scope and whitespace checks pass. All suites ran in foreground sessions, at most four workers, with native threads capped at one and no dataset downloads. No microsimulation, full repository suite or documentation build was run for this test-only change. No self-review, in-job reviewer or merge was performed.Pushed once successfully on the first attempt. PR remains a draft. Logs, replay source and exact commands are retained in
.job/for the hub. Independent review, CI observation and the existing Axiom/#2441 coordination remain with the hub.axiom: needed
Rulespec-uk main
uk/statutes/ukpga/2007/3/58.yamlis deferred with no rules and its companion58.test.yamlis empty. Signed encoding and pension-deduction companion cases remain for the hub; no billed encoder was invoked.Reproducer: policyengine_uk/tests/test_adjusted_net_income_pension_relief.py::test_150000_pay_and_60000_ras_restore_full_personal_allowance