Repository navigation
Conversation
ITA 2007 s. 23 deducts tax reductions at Step 6 from the Step 5 tax and adds the s. 30 charges at Step 7. s. 29(2) allows a reduction only so far as there is Step 5 tax to deduct it from. income_tax netted the reductions against the Step 5 tax and the Step 7 charges together, so other_tax_credits above the Step 5 tax offset the High Income Child Benefit Charge and the annual allowance charge, and capped_mcad was capped at the Step 5 tax plus the HICBC. Split income_tax_additions and income_tax_subtractions into explicit Step 5, Step 6 and Step 7 lists, add income_tax_reductions (Step 6, limited to the Step 5 tax) and income_tax_charges (Step 7), and cap capped_mcad at the Step 5 tax left after other_tax_credits (s. 27(5)). Fixes #2544 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
YAML cases for an HICBC payer whose other_tax_credits exceed their Step 5 tax, a married couple's allowance claimant who pays HICBC after VCT relief, the annual allowance charge, and reductions exactly at the Step 5 tax. Hypothesis properties: the Step 6 deduction is within [0, Step 5]; income tax is Step 5 - Step 6 + Step 7, at least the Step 7 charges and at most Step 5 + Step 7; each reduction is limited to the tax left before it; more other_tax_credits never raise income tax; and a differential against the pre-fix formula, which the fix raises by exactly the charges it offset. 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: 0 (thresholds in Notes
Moves flagged for reviewNone. Receipts by tax head
Spending by benefit and service
Caseloads
Government balance, each component counted once
Model totals
Household income totals
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; poverty rates; people in poverty. 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 case expected a £700 cap on an £800 reduction against £500 of Step 5 tax and a £200 HICBC. Under ITA 2007 s. 29(2) the reduction is limited to the £500 of Step 5 tax, and the HICBC is added at Step 7, so income tax is £200. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Reset the reusable simulation before setting inputs: reset_calculations() cleared employment income and Child Benefit, which the simulation was not built with, so the HICBC cases never ran. The tests now assert that every drawn input reaches the calculation, and draw pension-age people too. - Compare against a simulation of the pre-fix rules (a structural reform of income_tax and capped_mcad), not a reconstruction from the fixed run's values. The property allowance election weighs income tax on each route, so it can differ under the old rules. Income tax is never lower on either route, so never lower; where both take the same route, Step 5, the charges and the finance-cost reduction are unchanged and the restored charge is exact. - Drop the assumption that more other_tax_credits leave Step 5 unchanged, which an election change breaks. - Add the YAML case of a landlord whose election let the old netting offset £168 of HICBC, and qualify the s. 27(5) ordering in the docstring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22
left a comment
There was a problem hiding this comment.
Reviewed at f903b28a75b9ef223c45547be6b3e4b40cd5c3c3. No actionable findings from independent code and statutory-source reviews.
The change correctly caps Step 6 reductions against Step 5 tax before adding Step 7 charges. Final-tax cases confirm that excess credits cannot remove the £1,406 HICBC or £4,000 annual-allowance charge. All 45 focused tests passed, including generated invariants, MCA, property elections and downstream consumers.
This approval covers the present head. The declared #2066 Winter Fuel Payment charge prerequisite still requires integration and retesting before landing; the documented aggregate foreign-relief and Gift Aid limitations remain.
Fixes #2544
Reproducer:
policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/step_6_step_7.yaml,policyengine_uk/tests/test_income_tax_step_6_step_7_properties.py::test_the_fix_restores_exactly_the_charges_the_old_netting_offsetaxiom: uk:statutes/ukpga/2007/3/23#income_tax_liability encoded-correct (companion case
no_personal_allowance_and_reductions_cannot_make_tax_left_negativeinuk/statutes/ukpga/2007/3/23.test.yaml: reductions of £800 against £500 of tax leave the £20 of s. 30 tax)Lands after #2066 (winter fuel payment charge): this PR is rebased onto it once it merges, as set out below.
The bug
income_taxreturnedmax_(0, additions - subtractions).income_tax_additionsheld the Step 5 tax together with two Step 7 charges (CB_HITC,personal_pension_contributions_tax), so any Step 6 reduction above the Step 5 tax offset those charges. Separately,capped_mcadwas capped atincome_tax_pre_charges + CB_HITC.ITA 2007 s. 23 deducts the tax reductions at Step 6 "from the amount of tax calculated at Step 5", and adds the s. 30 amounts at Step 7 "to the amount of tax left after Step 6".
On main (76cfc9e), for 2026-27, from the new YAML cases:
other_tax_credits, one child (HICBC £1,406)capped_mcad£436other_tax_creditsother_tax_credits, HICBC £1,406Changes
income_tax_additionsandincome_tax_subtractionsare replaced by three explicit lists, with references to ss. 23, 26, 29 and 30:gov.hmrc.income_tax.step_5_tax_components(the four rate taxes, now whatincome_tax_pre_chargesadds);step_6_tax_reductions(same contents as the old subtractions);step_7_additional_tax(CB_HITC,personal_pension_contributions_tax).income_tax_reductions: the Step 6 deduction, the listed reductions (each with its own caps) limited in total to the Step 5 tax.income_tax_charges: the Step 7 list.income_tax= max(0, Step 5 − Step 6) + Step 7.capped_mcadis capped at the Step 5 tax left afterother_tax_credits(s. 27(5)). Foreign tax relief, also inother_tax_credits, should come after the MCA (s. 27(6)); the order changes the split, not the total.property_finance_cost_reliefandutils/uc_income_tax.pyread the renamed Step 6 list. For a given property allowance route their values are unchanged, which the differential test checks for the finance-cost relief. The election itself compares income tax on the two routes, so it can now choose differently. In the last table row, main takes the allowance route (Step 5 £27,032, no finance-cost reduction) because the old netting made it £168 cheaper. Under the fix both routes cost £1,406, and the model takes the other one (Step 5 £27,432, finance-cost reduction £232).CB_HITC, which stays on the Step 7 list.rgacrosspolicyengine_uk/).other_tax_creditslumps VCT, EIS, SEIS, CITR, SITR, maintenance and foreign tax relief, so the s. 29(4A)–(4C) cap (the Step 5 tax less the Gift Aid tax treated as deducted under s. 414) can't be applied. It's documented onother_tax_creditsandincome_tax_reductions.fiscal_partition.yamllists the two new variables as components of theincome_taxhead.is_allowance_eligible.yaml's "Capped Married Couples' Allowance Deduction" case pinned the old cap: an £800 reduction against £500 of Step 5 tax and a £200 HICBC was capped at £700. It now expects £500 (s. 29(2)), with income tax of £200.2544.fixed.md, and2544.removed.mdfor the removed parameter names.Invariants (stated and tested)
For every input,
test_income_tax_step_6_step_7_properties.pychecks, over Hypothesis-drawn single-person cases (pay, savings, dividends, property profits and finance costs,other_tax_credits, MCA, employer pension contributions, Child Benefit; every third taxpayer Scottish):capped_mcad= min(MCA deduction, max(0, Step 5 −other_tax_credits)), and the finance-cost reduction ≤ the tax left after both.other_tax_creditsnever raises income tax. (Step 5 and the charges can change with them, because the property allowance election weighs the tax on each route.)income_taxandcapped_mcad, so its election is made under the old rules too):The draws are single-person cases, a third Scottish and a third aged 70: pay, savings, dividends, property profits and finance costs,
other_tax_credits, MCA, employer pension contributions and Child Benefit. Every drawn input is checked to reach the calculation. Across 30 examples (1,440 cases): HICBC applies in 226, the AA charge in 614, the Step 5 cap binds in 699 (341 of them with a Step 7 charge), the fix raises income tax in 342, and the election differs in 60.Mutation check: restoring the old netting fails invariants 2 and 5; restoring the old
capped_mcadcap fails invariant 3 and the MCA YAML case.Impact (certified uk-data 1.58.1)
Run locally on
enhanced_frs_2024_25.h5at HF revision2526edbbb687, sha256056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784(verified before the runs). One microsimulation at a time: main 76cfc9e, then this branch. Aggregates only.income_tax_pre_charges)No aggregate moves beyond float32 rounding (£0.001m). On the certified dataset every Step 6 reduction totals £0 in both years (
other_tax_credits, the MCA deduction and the finance-cost reduction), so the cap never binds. The fix changes household calculations that enter those inputs, and SPI-based datasets, which carryother_tax_credits. CI's Aggregate impact report agrees for 2025-26 to 2031-32: it passes with 0 checks broken and 0 moves flagged. Income tax moves by between −£10 and +£14 in a year (pounds, not millions), which is float32 summation order: the Step 5 and Step 7 sums are now added separately.Tests run
policyengine-core test policyengine_uk/tests/policy/baseline/gov/hmrc): 867 passed and 1 failed. The failure was the case that pinned the old cap, since corrected; that file now passes 9/9.pytest -n 4): 1,289 passed, 3 skipped, 0 failed.Rebase onto #2066
When #2066 merges:
income_tax(income_tax_before_winter_fuel_payment_charge+winter_fuel_payment_charge).income_tax_before_winter_fuel_payment_charge= max(0,income_tax_pre_charges−income_tax_reductions) +income_tax_charges, replacing its read of the removed lists.step_7_additional_tax. The means tests deduct the pre-WFP aggregate, and the charge depends on Pension Credit and Universal Credit receipt, so putting it on the list would make the model circular.step_7_additional_tax,income_tax_chargesandincome_taxthat the WFP charge, also a s. 30(1) Step 7 amount, is added inincome_taxafter the list.INCOME_TAX = "income_tax_before_winter_fuel_payment_charge". The pre-fix reform then overrides that variable instead ofincome_tax, and the accounting, monotonicity and differential checks run on the pre-WFP aggregate.income_tax=INCOME_TAX+winter_fuel_payment_chargeand thatincome_tax≥income_tax_charges+winter_fuel_payment_charge. The age-70 draws exercise it.other_tax_creditsabove the Step 5 tax and a WFP charge, where income tax equals the WFP charge plus any other Step 7 charge.fiscal_partition.yamllines.#1963 (Marriage Allowance) has been told about the rename (comment on #1963); whichever lands second carries
marriage_allowance_tax_reductionintostep_6_tax_reductions.🤖 Generated with Claude Code