Skip to content

Charge the winter fuel payment back through income tax, person by person - #2066

Draft
MaxGhenis wants to merge 27 commits into
mainfrom
winter-fuel-payment-charge
Draft

MaxGhenis wants to merge 27 commits into
mainfrom
winter-fuel-payment-charge

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1760

Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/winter_fuel_payment_charge.yaml::2025 total income of exactly £35,000 is not charged
Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/winter_fuel_payment_charge.yaml::2025 Northern Ireland pays everyone of pensionable age

Summary

Models the winter fuel payment charge (ITEPA 2003 s.681I, inserted by FA 2026 Sch 10, from 2025-26) person by person, for the Winter Fuel Payment in England, Wales and Northern Ireland and for Scotland's Pension Age Winter Heating Payment. The payments are now paid in full to everyone entitled, and the charge recovers them through income tax.

This was stacked on #2038, which has merged. The branch has since merged main at 7ad002f54, and this PR targets main.

The law

  • P is liable if entitled to a winter fuel payment for the qualifying week and P's total income for the tax year exceeds £35,000 (s.681I(1)).
  • The charge equals the payment (s.681I(3)). There is no taper.
  • P is not liable if entitled to Income Support, income-based JSA, Pension Credit, income-related ESA or Universal Credit on any day in the qualifying week (s.681I(4), (5)).
  • "Winter fuel payment" covers payments under SI 2025/969, SSI 2024/351 and NISR 2025/142 (s.681I(6)(a)).
  • Total income is the sum of the amounts of income on which P is charged to income tax (ITA 2007 s.23 Step 1), before the Step 2 reliefs. Each amount is worked out under the rules for that kind of income, so it is after employment expenses (ITEPA 2003 s.11), capital allowances (CAA 2001 s.247), the trading and property allowances (ITTOIA 2005 Pts 7A and 7B), and without exempt income such as ISA interest (ITTOIA 2005 s.694). Employee pension contributions under a net pay arrangement come off employment income (FA 2004 s.193(2)), so total income is after them; contributions relieved at source (s.192) are not deducted. Trade loss relief, including losses brought forward, comes at Step 2.
  • The charge is additional tax at Step 7 of the income tax calculation: ITA 2007 s.30(1) lists "Chapter 9 of Part 10 of ITEPA 2003 (winter fuel payment charge)", and s.23 Step 7 adds it after the Step 6 tax reductions.

What was wrong

On main a household test stands in for the charge: a person not on a relevant benefit is paid only if some member of pensionable age in the household has total income under £35,000, in England and Wales only.

  1. The charge looks at P's own total income. The test lets a whole household through on one low-income member, and charges nobody whose household has one.
  2. The boundary is "exceeds £35,000". The test excludes income of exactly £35,000.
  3. Northern Ireland pays everyone of pensionable age who meets the other conditions from 2025 (NISR 2025/142 reg 2) and the charge applies there. The model pays NI pensioners not on a relevant benefit nothing.
  4. PAWHP is not charged at all.

Design: an income tax charge, not a net payment

The charge could be netted off each person's payment, or added to income tax. This PR adds it to income tax.

  • The statute makes it income tax. s.681I(1) creates "a charge to income tax", and ITA 2007 s.30 makes it Step 7 additional tax. The regulations still pay everyone of pensionable age who meets their other conditions in full (SI 2025/969 reg 2; NISR 2025/142 reg 2; SSI 2024/351 as amended).
  • The model already treats the neighbouring charge this way. The high income child benefit charge (ITEPA Pt 10 Ch 8, also in s.30) is CB_HITC inside income_tax, with child_benefit kept gross.
  • Each government's line stays its own. gov_spending counts what DWP, the Department for Communities and Social Security Scotland pay, and gov_tax counts the UK income tax charge. Netting would show an income tax receipt as lower Scottish spending on PAWHP.
  • Entitlement comes before the charge. The shared amounts depend on "living with a person entitled to a winter fuel payment". A person charged in full is still entitled, so the gross payment has to exist in the model either way.

What the choice changes, against netting:

  • household_net_income and hbai_household_net_income are the same under both. Each counts the payment and deducts the tax.
  • The split moves. household_benefits, hbai_benefits and gov_spending carry the gross payment. household_tax, income_tax and gov_tax carry the charge. In 2025 that is £270.9m of charge (the 2026-10-17 run, being re-run: see the impact section).

Three consequences of putting it in income tax:

  • The means tests deduct income tax before the charge. Pension Credit income, Income Support applicable income, Housing Benefit applicable income and net earnings, and Council Tax Reduction applicable income deducted income_tax. They now deduct income_tax_before_winter_fuel_payment_charge. The charge depends on Pension Credit and Universal Credit receipt, so deducting it from Pension Credit income makes the model circular: with pension_credit_income deducting income_tax, the core raises CycleError on income_tax@2025. It also is not tax on the income those tests assess: it is the payment, recovered. Universal Credit earned income (Deduct only each person's own tax and NI on earnings from UC earned income #1949) and the benefit cap's earnings test (uc_individual_earned_income_before_mif) already deduct each person's own uc_income_tax_on_earnings, worked from the rates, which leaves out charges. The Enhanced FRS run confirms no means-tested award moves (see below).

  • The property allowance election counts the charge. uses_property_allowance compares each route's income_tax, as on main, which now includes the charge. A route can change total income, because the allowance is deducted in working out property profits, and it can change the tax deducted from Pension Credit income, and so Pension Credit and the s.681I(4) exemption. Two YAML cases cover this. In "a landlord's property allowance election can keep total income under £35,000", partial relief brings total income from £35,300 to £34,500, so the pensioner pays £4,386 rather than £4,246 plus the £200 charge. In "a landlord's property allowance election counts the charge", total income is over £35,000 either way, and £4,490 of tax with the allowance gets the pensioner Pension Credit and no charge, against £4,350 and the charge with expenses. The cost is that each route's branch also works out the person's relevant-benefit receipt.

    Two parts of the election on main assumed that lower taxable property income means lower tax, which the charge breaks. While the charge has effect, both now compare routes:

    • Landlords without finance costs. On main they take the allowance whenever it lowers taxable property income, with no comparison. The allowance's lower tax can raise the income Pension Credit counts, end the award and with it the exemption, and the charge can be more than the saving. In "a landlord without finance costs keeps expenses when the allowance would end Pension Credit", the allowance would save £160 of tax but end £80 of Pension Credit and bring in the £200 charge (£5,227.40 against £5,187.40). While the charge has effect these landlords compare both routes too. Ties keep the allowance, so anyone the charge does not reach gets the same route as before.
    • One landlord per household, not per benefit unit. A couple's Pension Credit decides whether the partner is entitled to a payment, which changes another benefit unit's shared payment and so its charge. On main landlords in separate benefit units of one household switch together, so one landlord's switch was counted in the other's comparison. In "landlords in two benefit units of one household each elect with the other's route fixed", the lodger elects partial relief and pays £6,186 rather than £6,211.
  • PAWHP has to count as household income. Count the Pension Age Winter Heating Payment in household and HBAI income #2047 (merged) put pawhp in household_benefits, hbai_household_net_income and hbai_benefits. Without that, the charge on PAWHP would lower a Scottish household's income with no payment to set it against.

Who is "entitled to a relevant benefit"

The charge reads the model's is_on_income_support, is_on_income_based_jsa, is_on_pension_credit, is_on_income_related_esa and is_on_universal_credit. Since #2027 and #2038 these name the person entitled:

  • a couple's Income Support, income-based JSA, income-related ESA or Pension Credit is the claimant's, the member it is paid to (for Pension Credit, a member who has reached the qualifying age), not the partner's;
  • both joint claimants are entitled to Universal Credit (WRA 2012 s.1(2)(b)), but not a partner who cannot be a joint claimant;
  • anyone else in the benefit unit is entitled only to an award of their own.

So the partner of an Income Support claimant, paid a Winter Fuel Payment of their own because the claimant is under pensionable age, is charged if their own total income exceeds £35,000. A YAML case covers this. The Hypothesis reference exempts only the person a relevant benefit is paid to, not the whole couple.

Changes

  • winter_fuel_payment_charge (person): the person's winter_fuel_payment plus pension_age_winter_heating_payment, when their own total income exceeds the threshold and they are on none of the s.681I(5) benefits.
  • total_income_charged_to_tax (person, new): total income as s.23 Step 1 defines it. It is the model's taxable income components (net_income_before_trade_loss_relief), less the employee pension contributions relieved under a net pay arrangement, with losses brought forward (loss_relief, which the model deducts from trading profits) added back. adjusted_total_income, the base of the s.24A cap, already worked this out for its own Step 1; it now reads the new variable instead, and at Steps 3 and 4 deducts only the relieved contributions not already out of total income (s.24A(8)), so its values do not change, up to float32 rounding (review r9 measured 6p on an adjusted total income of about £905,000).
  • net_pay_pension_relief (person, new): the employee contributions, up to taxable employment income, and none from age 75 (s.188(3)(a)). The model holds employment_income gross of pension contributions, takes employee contributions as paid under a net pay arrangement and gives their relief through allowances, after total income. FA 2004 s.193(2) deducts them from employment income, so without this a working pensioner's total income would include them. The s.190 limit for relief is relevant UK earnings, which include all employment income chargeable to tax, statutory sick and maternity pay among it (s.189(2)(a); HMRC PTM044100), so it never holds the deduction below taxable employment income. Within the limit, the model relieves employee contributions before personal ones, so contributions relieved at source never change the deduction. total_income, which sums gross income and which other parts of the model read, is unchanged.
  • income_tax = income_tax_before_winter_fuel_payment_charge (the previous formula) + winter_fuel_payment_charge.
  • New parameters under gov.hmrc.income_tax.charges.winter_fuel_payment: in_effect (true from 2025), income_threshold (£35,000), relevant_benefits.
  • gov.dwp.winter_fuel_payment.eligibility.require_benefits is false from 2025, so the Winter Fuel Payment reaches everyone of pensionable age in England, Wales and Northern Ireland who meets its other conditions.
  • gov.dwp.winter_fuel_payment.eligibility.taxable_income_test is deprecated, not removed. A reform that sets a missing parameter fails to load, so both parameters stay. The test is off from 2025 and has effect only in a reform that also sets require_benefits to true. Its limit now admits total income equal to it. A saved reform that only moved maximum_taxable_income no longer does anything: the lever for the threshold is now gov.hmrc.income_tax.charges.winter_fuel_payment.income_threshold.
  • Five means-test formulas read income_tax_before_winter_fuel_payment_charge (listed above). uses_property_allowance keeps comparing income_tax; its docstring now says that includes the charge.

Invariants

  1. The charge is 0 or the whole payment, never more than the payment.
  2. income_tax − income_tax_before_winter_fuel_payment_charge = winter_fuel_payment_charge, for every person.
  3. With benefit receipt held fixed, nobody's payment depends on anybody's income: incomes change only the charge. (Income can change receipt of Pension Credit or Universal Credit, and receipt does change the payments, by design.)
  4. No charge on anyone entitled to a relevant benefit or on anyone not entitled to a payment, and, under the baseline parameters, none before 2025 (a reform can switch the charge on earlier; one reform test does).
  5. With benefit receipt held fixed, the charge is nondecreasing in total income: nothing up to and including £35,000, the whole payment above it. So net income has a cliff at £35,000 by design: £1 more income can cost the whole payment.
  6. No means test reads the charge: they deduct income tax before it. A property allowance election compares tax including the charge, so it can change a landlord's tax before the charge, and through it Pension Credit (the YAML cases above).
  7. A trading loss moves the charge only where it changes who is on a relevant benefit or the person's total income, by displacing the trading allowance or by changing which property route the election picks: the relief itself comes at Step 2, after total income. (Review r8's case, with no relevant benefit: a 70-year-old with £31,900 of pension, £3,500 of property receipts (£3,300 of profit) and £2,500 of finance costs. Without a loss the election keeps expenses: total income £35,200, £4,226 of tax including the £200 charge, against £4,366 with the allowance. With a £21,000 trading loss, expenses would leave no tax before the charge but total income still £35,200, so £200 in all; the allowance gives total income of £34,400 and £166 of tax with no charge, so the election switches to it and the charge goes.)
  8. If the property allowance election settles, no landlord can lower their own income tax, charge included, by switching route alone with every other route in the household held fixed. It stops after three rounds whether or not it has settled, so this holds only for households that settle within three. No household has been found that does not: review r8 tried all 16 route combinations in 512 four-landlord households, and all settled by the second round.
  9. Total income is after the employee pension contributions under a net pay arrangement, up to taxable employment income (statutory sick and maternity pay included) and none from age 75. Contributions relieved at source never change that deduction, and with the property allowance election held fixed they never change total income. They can change the route the election picks, and through it total income and the charge. In review r10's case, a 70-year-old with £31,900 of pay, £3,500 of property receipts and £2,500 of finance costs keeps expenses (total income £35,200, charged £200); £21,000 of contributions relieved at source make the allowance the better route (total income £34,400, no charge). For the same inputs, this PR leaves adjusted total income as it was, up to float32 rounding.

Tests

  • YAML, worked by hand from the law: tests/policy/baseline/gov/hmrc/income_tax/charges/winter_fuel_payment_charge.yaml, 39 cases. They cover the £35,000 boundary on both sides, each partner charged on their own income, a person charged in full still counting as entitled for someone else's shared amount, a lodger, a non-dependant's ESA, a Universal Credit couple, the excluded partner of a Pension Credit claimant, the partner of an Income Support claimant (paid and charged), Northern Ireland, PAWHP at the 2025 and 2026 amounts, no charge in 2024, the charge inside income_tax, PAWHP as household income, Council Tax Reduction income before the charge, what total income is (after employment expenses, capital allowances, the trading allowance and net pay pension contributions, up to taxable employment income and none from age 75, but not after contributions relieved at source; statutory sick pay as relevant UK earnings for the net pay deduction, with and without contributions relieved at source; without ISA interest; with statutory sick pay; and before trade loss relief and losses brought forward), and four landlords' property allowance elections: one that keeps total income under £35,000, one that gets Pension Credit, one without finance costs that keeps expenses to keep Pension Credit, and two landlords in separate benefit units of one household.
  • Reform tests: reforms/parametric/winter_fuel_payment_charge/charge_parameters.yaml (a higher threshold, repeal, the charge on a universal 2024 payment, tax credits not exempting), and the deprecated household test in winter_fuel_allowance/taxable_income_test.yaml (still works when a reform requires a relevant benefit; inert otherwise).
  • Hypothesis (tests/test_winter_heating_payment_properties.py): the reference implementation pays everyone from 2025 and computes each person's charge from s.681I, exempting only the person a relevant benefit is paid to. The model must match it for payments, charges and income_tax less the pre-charge liability, over households of up to three benefit units in all four countries, with incomes either side of £35,000 and exactly on it. A further property sets every income to £5,000 and then £50,000 and requires identical payments, no charge at £5,000, and the whole payment charged at £50,000 for anyone not on a relevant benefit.
  • Mutation, after the first merge of main: eight mutants that each point one reader at income_tax or drop pawhp from HBAI. Seven are killed by the YAML suites, the Hypothesis properties or a CycleError. The Council Tax Reduction one was killed once a YAML case was added. The eighth, the property allowance election comparing income_tax, is now the rule: review r5 found the case above, where comparing tax before the charge picks the route with the higher final tax.
  • Mutation, before the merge of main: ten mutants, each breaking one rule (charged at exactly £35,000; household income; a household member's benefit exempting; PAWHP not charged; charged before 2025; entitlement tested after the charge; no exemption; charge left out of income_tax; Winter Fuel Payment still requiring a benefit; tax credits exempting). All ten fail at least one case.
  • Means-test tests: four existing files entered income_tax to drive a means test (Housing Benefit applicable income and net earnings, the Income Support family definitions, Pension Credit income). They now enter income_tax_before_winter_fuel_payment_charge, with the same expected values. A Housing Benefit net earnings case checks that the charge does not come off a charged pensioner's earnings (8,628.50, where income_tax would give 8,578.50).
  • Property tests that compare a means test with income tax now compare with the tax before the charge, as the means tests deduct it: the UC earnings deduction identity, the Council Tax Reduction Pension Credit routes, the CTR carer income identities, the Housing Benefit savings-credit-only identities, the pre-UC earned income deducts the whole benefit unit's tax, including tax on pensions and other unearned income #1942 UC reference, and the trade loss properties (means tests see the loss only through tax; relief taxes like an income cut; a zero-profit trade against a loss). Two of the first group were false inside their own generators until the fix: a mixed-age couple's pensioner earning £40,000 off UC, and a pensioner with £39,500 of total income claiming CTR. Both counterexamples are now plain tests that also assert the charge is levied. The trade loss properties also check invariant 7. The zero-profit property pins a pensioner whose loss ends Pension Credit (£217.48) and so brings in the £200 charge, and one charged on both sides. "Income tax falls with the loss by at most the relief" now holds the tax before the charge: review r6 found an 80-year-old with £19,200 of pension and £30,000 of dividends whose £1,100 loss ends a £3.12 Pension Credit award and so brings in the £300 charge. That case is pinned.
  • Property allowance election (tests/test_property_income_properties.py): a new property covers pensioner households: a couple, with five children or a sixth child in place of the partner, claiming Pension Credit, plus a lodger who may claim it, in England, Wales and Scotland in 2025. No landlord lowers their own income_tax by switching route alone. It pins both review r7 households. The couples property now covers every landlord the allowance would help, not only those with finance costs at stake. Two mutants each fail it: the old shortcut for landlords without finance costs, and electing per benefit unit.
  • Pension contributions in total income (tests/test_total_income_net_pay_properties.py): over single pensioners in England with pay, statutory sick and maternity pay, employment expenses, a private pension and both kinds of contribution, the model's net pay relief, total income, adjusted total income and charge match a reference worked from FA 2004 ss.188, 192 and 193 and ITA 2007 s.24A(8), and removing contributions relieved at source changes neither total income nor the charge. Three mutants each fail it: total income not after net pay contributions (which also fails two YAML cases), adjusted total income deducting them twice, and review r9's case, the deduction capped at pension_contributions_relief (which also fails the statutory sick pay YAML case).
  • Tests that need a variable with a formula: income_tax now only adds, so test_variable_code_sharing and test_rule_comparison use income_tax_before_winter_fuel_payment_charge.
  • Tests that entered total income for the charge now enter total_income_charged_to_tax (the charge YAML, the reform tests, two relevant-benefit cases and the Hypothesis file). Four cases of the deprecated household test, which reads total_income, enter a private pension instead, which feeds both.
  • Targeted suites, local: at d73e73b99 (the head's code, before two comment edits), the charge YAML (39 passed) and the net pay property (passed). The wider targeted suites are held by the same host reservation; CI runs every suite on the head.
  • CI: running on d0920a9cd.

Impact: Enhanced FRS

Being re-run against main at 7ad002f54 (the commit merged in) and this branch at d0920a9cd, after the statutory pay fix. A host reservation is holding local impact runs, so the figures below are still the 2026-10-17 run, and they will be replaced before merge.

Real runs of the microsimulation on a private copy of the Enhanced FRS 2024-25 (sha256 e433e532b17b), 2024 to 2030: base main at 88a524802 against this branch at 5c1dbebfa. Nothing is scaled or estimated. £m a year:

Year WFP base £m WFP branch £m PAWHP base £m PAWHP branch £m Charge £m Net winter payments base £m Net winter payments branch £m HNI change £m HBAI net income change £m gov_spending change £m gov_tax change £m gov_balance change £m
2024 441.3 441.3 34.7 34.7 0.0 476.0 476.0 +0.0 +0.0 +0.0 +0.0 +0.0
2025 1,701.4 1,897.6 201.1 201.1 270.9 1,902.5 1,827.8 -74.7 -74.7 +196.1 +270.9 +74.7
2026 1,690.2 1,886.1 208.2 208.2 278.7 1,898.4 1,815.6 -82.8 -82.8 +195.9 +278.7 +82.8
2027 1,644.3 1,847.7 211.2 211.2 296.3 1,855.6 1,762.7 -92.9 -92.9 +203.4 +296.3 +92.9
2028 1,619.5 1,832.8 215.5 215.5 302.3 1,835.0 1,745.9 -89.1 -89.1 +213.2 +302.3 +89.1
2029 1,615.7 1,840.8 220.7 220.7 316.0 1,836.4 1,745.5 -90.9 -90.9 +225.1 +316.0 +90.9
2030 1,604.4 1,849.1 226.2 226.2 339.9 1,830.6 1,735.3 -95.2 -95.2 +244.7 +339.9 +95.2

2025 by country (£m; people charged; households gaining or losing):

Year Country Gross base Gross branch Charge People charged HNI change Households gaining Households losing
2025 England 1,567.8 1,715.1 198.2 1,299k -50.9 155k 650k
2025 Wales 107.3 128.0 24.7 141k -4.1 0k 38k
2025 Scotland 201.1 201.1 44.1 236k -44.1 0k 228k
2025 Northern Ireland 26.3 54.5 3.8 24k +24.4 121k 0k
  • The charge raises £270.9m in 2025 from 1.70m people, and £339.9m from 2.20m in 2030. The model holds the £35,000 threshold at that amount in every year (the Treasury may change it by order, s.681J).
  • Gross spending on the Winter Fuel Payment rises £196.1m in 2025 (£1,701.4m to £1,897.6m), because everyone of pensionable age in England, Wales and Northern Ireland who meets the other conditions is now paid. PAWHP spending does not change, because this PR changes only PAWHP's documentation, not who is paid; the charge recovers £44.1m of it in 2025.
  • Household net income falls £74.7m in 2025: £196.1m more paid, less £270.9m charged. household_benefits and hbai_benefits rise by the gross payments and household_tax by the charge, so gov_balance improves by £74.7m. Northern Ireland households gain £24.4m, because pensioners there not on a relevant benefit were paid nothing on main.
  • Means-tested benefits: no benefit unit's Pension Credit, Universal Credit, Income Support, ESA, JSA, Housing Benefit, Council Tax Reduction, tax credits or benefit cap reduction moves by 1p or more in any year (the largest change is £0.0000). The means tests deduct income tax before the charge.
  • 2024 is unchanged in every line: the charge starts in 2025-26.
  • The net pay fix alone (5c1dbebfa against 95ae26c0b, same base): the 2025 charge falls from £271.1m on 1,703k people to £270.9m on 1,701k. Against the run of 2026-10-16 (base e7610ac52, branch 26ff32421), 95ae26c0b gives the same figures: the 89 commits of main merged in since move nothing here.
  • Poverty rates move by at most 0.01 percentage points in any year (the largest: pensioner poverty after housing costs in 2025, 13.21% to 13.20%).

Limits

  • Pension relief. The model's own relief for pension contributions (pension_contributions_relief) leaves statutory pay out of relevant UK earnings (Pension contribution relief is capped at wages and profit and leaves out statutory pay #2371; Include SSP and SMP in pension-relief earnings #2464 fixes it). So where statutory pay is part of someone's earnings, income tax can relieve less than total income is after. The charge follows the law here.
  • Total income. The charge's total income is only as complete as the model's taxable income components. Statutory paternity pay is left out because the model does not yet tax it (Charge income tax on statutory paternity pay #2368). The model deducts deficiency relief from dividend income, and that deduction carries over, as it already did into adjusted_total_income.
  • Miscellaneous income is held as one net figure per person (taxable_miscellaneous_income), so a loss on one miscellaneous transaction netted into it also lowers total income, although relief for it belongs at Step 2 (ITA 2007 s.152). Review r10's case: a £6,000 profit and a £2,000 loss on separate transactions, with £30,000 of pension, are £36,000 of total income in law (charged), but £34,000 in the model (not charged).
  • Reported benefits. With gov.contrib.policyengine.disable_simulated_benefits, household income takes the reported winter payment (winter_fuel_allowance, or pawhp in Scotland while PAWHP is paid), while the charge stays on each person's simulated payment.
  • Receipt in the qualifying week. As for the payments, "entitled to a relevant benefit on any day in the qualifying week" is read as a positive annual award.

Axiom

axiom: TheAxiomFoundation/rulespec-uk#420 queued

The legal id is uk/statute/ukpga/2003/1/681I. s.681I is not in the corpus and no rulespec-uk module encodes it. The issue carries (comment) the module path, the verbatim law, the required outputs, a review_finding and companion tests from the same ground truth as the YAML here. The encoder blocker is recorded in this comment: the signed encoder runs from a corpus citation, and uk/statute/ukpga/2003/1/681I has not been ingested (axiom-corpus 2137e79be).

Notes for merging

Review

Held (2026-10-18): the property allowance comparison added in 26ff32421 makes CI's Python shards run out of memory; see this comment. A decision on keeping it or restoring main's shortcut for landlords without finance costs is pending.

Review r8 approved 26ff32421, and review r9 requested the statutory pay fix on 5c1dbebfa. Review r10 (GPT-6.1 Sol) approved ed0b799ea with body fixes, now made; d0920a9cd qualifies two comments and the fixed changelog fragment after it; review r11 (GPT-6.1 Sol) approved d0920a9cd, with one body fix, now made.

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 2, 2026 07:45
ITEPA 2003 s.681I (FA 2026 Sch 10, from 2025-26) charges a person entitled
to a winter fuel payment the whole payment when their own total income
exceeds £35,000 and they are not entitled to IS, income-based JSA, Pension
Credit, income-related ESA or UC. It covers the Winter Fuel Payment in
England and Wales (SI 2025/969) and Northern Ireland (NISR 2025/142) and
Scotland's Pension Age Winter Heating Payment (SSI 2024/351).

- winter_fuel_payment_charge (Person) under gov.hmrc.income_tax.charges,
  added to income_tax after tax reductions (ITA 2007 s.23 Step 7, s.30).
- Payments stay gross; from 2025 the WFP pays everyone of pensionable age
  (require_benefits false), so entitlement, and the shared amounts, come
  before the charge.
- Means tests deduct income_tax_before_winter_fuel_payment_charge: the
  charge recovers a payment, and it depends on PC/UC receipt.
- pawhp counts in household_benefits and HBAI income, like the WFP.
- The household income test is deprecated (off from 2025; boundary <=).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ment charge

ITEPA 2003 s.681I(5) lists five relevant benefits and no tax credits. A
mutant that exempted tax credit recipients survived the other cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

#2038 merged into main as 4012143, so this PR's base is now main. It branches from #2038 at 071d5170b. Since then #2038 gained the following, and this branch needs a merge of main before its tests and impact numbers count:

  • partner_receipt_counts ("paid to" versus couple receipt);
  • a payee-only is_on_pension_credit limited to members over the qualifying age;
  • paid_relevant_benefit / reports_paid_relevant_benefit for the couple's payee, replacing reports_relevant_benefit;
  • is_on_universal_credit excluding uc_is_ineligible_partner;
  • main's dated age test in winter_fuel_payment_eligible.

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 11, 2026 11:57
Brings in #2038 as merged (paid-to versus couple receipt, payee-only
is_on_pension_credit over the qualifying age, is_on_universal_credit
without an ineligible partner, the dated age test), #1949 (UC earned income
deducts each person's own tax on earnings) and the rest of main.

Conflicts:
- uc_earned_income: take #1949's formula; uc_income_tax_on_earnings works
  from the rates and never reads income_tax.
- is_benefit_cap_exempt_earnings: main's claimant-only sum, deducting
  income_tax_before_winter_fuel_payment_charge.
- is_benefit_cap_exempt_other: main's version (it no longer tests earnings).
- winter_fuel_payment_eligible: main's age test with this branch's <=.
- The Hypothesis reference: main's paid-to logic; the WFP means condition
  holds outside 2024; the charge exempts only a person a relevant benefit is
  paid to (the claimant for IS, JSA, ESA and PC; both for UC), as the
  model's is_on_* variables now do.

Two income_tax readers came in from main and would include the charge:
- housing_benefit_net_earnings (HB regs 36(3)): deducts the tax before the
  charge, which is not tax deducted from earnings;
- uses_property_allowance: the route branches compare the tax before the
  charge, since the charge depends on Pension Credit, which depends on
  property income.
New YAML cases cover both, and the partner of an Income Support claimant
who is paid and charged. relevant_benefits.yaml describes the payee reading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The model's total_income is before the property allowance, so the charge is
the same on either route; reading income_tax in the route branches would
only recompute the means-tested benefits there (a probe with income_tax in
the route made the same choice and raised no CycleError). Correct the YAML
case's comment to match: total income is £35,300 on either route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without universal_credit: 0 the model computes a Universal Credit award
for the couple, which makes both members joint claimants and exempts the
partner. Zero Universal Credit and Pension Credit, as the other cases do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits October 11, 2026 22:09
Three property tests that CI failed on 03154a1 compared a means test
with income_tax, which now includes the winter fuel payment charge:

- test_uc_earnings_deductions_properties: the pre-#1942 UC earned income
  formula deducted benunit_tax, which now reads the charge and so UC
  receipt (CycleError). It deducts the benefit unit's income tax before
  the charge and its NI instead.
- test_council_tax_reduction_carer_income_properties and
  test_housing_benefit_savings_credit_only_properties: CTR and HB income
  deduct income_tax_before_winter_fuel_payment_charge, so the identities
  add back that liability, not income_tax.

The HB boundary test also excludes the charge, as it excludes the other
payments that depend on Pension Credit receipt. A YAML case pins CTR
income to the tax before the charge, which no YAML case covered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits October 12, 2026 08:40
Review r3 (lost to the 10/12 reboot) found two properties false inside
their own generators on aede16d, both comparing a means test with
income_tax, which now includes the winter fuel payment charge:

- test_deductions_equal_tax_and_ni_on_earnings_alone: a pensioner in a
  mixed-age couple earning £40,000 off UC is charged £200. UC deducts tax
  on earnings (reg 55(5)), so the test compares with the tax before the
  charge.
- check_routes in the CTR Pension Credit properties: MAIN_DEDUCTIONS now
  uses the tax before the charge, as the CTR means test does.

Both counterexamples are pinned as plain tests that also assert the charge
is levied. Also:
- the inert income_tax: 0 inputs in two means-test YAML files now set the
  variable the means tests read;
- the property-allowance YAML case is labelled a known gap: the law's
  partial relief would bring total income under £35,000;
- s.681J links to ITEPA, not FA 2026;
- the changelog no longer says the household test "charged" anyone;
- the HB property's reason for excluding the WFP fits its 2026 year.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

#2047 merged into main on 2026-10-13 as e9e1f5d87. It adds the same pawhp line after winter_fuel_allowance in household_benefits.py, hbai_household_net_income.py and hbai_benefits.py that this PR adds. When this branch next merges main, keep one copy of each line. tests/code_health/test_household_income_lists.py (from #2047) and tests/code_health/test_aggregation_lists.py fail on a duplicate.

#2047's test_pawhp_household_income_properties.py already allows for the s.681I charge: net income falls by pawhp less the change in income tax.

…harge

# Conflicts:
#	policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py
MaxGhenis and others added 2 commits October 14, 2026 20:25
…harge

Conflict in is_benefit_cap_exempt_earnings.py: take main's
uc_individual_earned_income_before_mif, which deducts each person's own
uc_income_tax_on_earnings and so never reads income_tax or the charge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e property election

Review r5 and CI on adcf821:
- The charge tested total_income, which leaves out the statutory sick and
  maternity pay the model taxes as employment income. Total income is the
  income charged to tax (ITA 2007 s.23 Step 1), so they now count
  (test_uc_statutory_pay_properties invariant 2 failed on CI).
- uses_property_allowance compared tax before the charge. A route can change
  Pension Credit and so the s.681I(4) exemption; it now compares income_tax,
  as main did (r5 P2, the m6 mutant was not equivalent).
- test_trading_loss_properties: the means tests deduct tax before the charge,
  so the properties hold that; relief versus income cut compares it too,
  since relief comes after total income (Step 2).
- test_variable_code_sharing reads a variable with a formula; income_tax now
  only adds.
- YAML: statutory sick pay in total income, trade loss relief after total
  income, and the election that avoids the charge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 6 commits October 15, 2026 14:55
Main now has the PAWHP household-income lines (#2047) and UC and benefit
cap earnings tests that deduct each person's own tax on earnings, so the
fragments no longer claim them. The property election compares income_tax,
which includes the charge, since 1024887.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The election has counted the charge since 1024887, so the case is about
total income being taken before the property allowance, not about the
election ignoring the charge. Expected values are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the trade loss property

- test_rule_comparison (new on main) replaces income_tax's formula, but
  income_tax now only adds the pre-charge liability and the charge, so it
  has none (CI: StopIteration). It now replaces the pre-charge formula.
- The zero-profit trade loss property held only tax before the charge,
  since 1024887. It now also checks the charge: it moves only in a
  household where the loss changes who is on a relevant benefit, as when
  lower tax ends Pension Credit and with it the exemption (s.681I(4)). A
  pinned pensioner exercises that case: on Pension Credit (£217.48) and not
  charged without the loss; off it and charged £200 with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The winter fuel payment charge tests total income (ITEPA 2003
s.681I(1)(b)), which is ITA 2007 s.23 Step 1: the income charged to tax,
after employment expenses, capital allowances, the trading and property
allowances and exempt ISA interest, and before the Step 2 reliefs. The
charge read total_income, which sums gross income. Add
total_income_charged_to_tax, the Step 1 figure adjusted_total_income
already worked out for the s.24A cap (the taxable components with losses
brought forward added back), and have both read it. Tests that set the
charge's income now set the new variable; six YAML cases pin each
deduction, and the property allowance case that pinned the old gap now
follows the law.

r6 P1: test_income_tax_falls_with_the_loss_by_at_most_the_relief compared
income_tax, which the charge can raise when the relief ends a Pension
Credit award. Compare tax before the charge, check that the charge moves
only with benefit receipt or total income, and pin r6's counterexample.
Invariants 3 and 9 say so. Nits: the code-sharing helper's name, the
deprecated limit's reference, the changed.md wording, and a pinned
pensioner charged on both sides of the zero-profit test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 5 commits October 16, 2026 16:00
…ousehold

Review r7 found two ways the property allowance election could choose the
route with the higher income tax once the winter fuel payment charge is in it:

- Without finance costs at stake, the allowance was taken whenever it lowered
  taxable property income. Lower tax before the charge can raise the income
  Pension Credit counts, end the award and with it the s.681I(4) exemption,
  and the charge can exceed the saving. While the charge has effect, these
  landlords now compare both routes too; ties keep the allowance, so anyone
  the charge does not reach gets the answer the shortcut gave.
- Landlords decided one per benefit unit, so landlords in separate benefit
  units of one household switched together. A couple's Pension Credit decides
  whether the partner is entitled to a payment, which moves another benefit
  unit's shared payment and charge. Landlords now decide one per household.

Pins both counterexamples in YAML and adds a Hypothesis property: no landlord
in a pensioner household lowers their own income tax, charge included, by
switching route alone. The couples property now covers every landlord the
allowance would help. Also qualifies the trading loss invariants (they hold
tax before the charge) and total_income_charged_to_tax's description.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
…d limit

Review r8: the opening claim that every election lowers tax holds only with finance costs to relieve; without them the allowance is the default and ties keep it. The household loop stops after MAX_ROUNDS even if routes are still changing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FA 2004 s.193(2) deducts contributions under a net pay arrangement from employment income, so total income (ITA 2007 s.23 Step 1), which the winter fuel payment charge tests, is after them. The model holds employment income gross of pension contributions, takes employee contributions as net pay and gives their relief through allowances, so total_income_charged_to_tax overstated a working pensioner's total income by their relieved employee contributions. New net_pay_pension_relief: the relieved employee contributions, up to taxable employment income. adjusted_total_income now deducts at Steps 3 and 4 only the relieved contributions not already out of total income (s.24A(8)), so its values are unchanged. Relief at source (s.192) still leaves total income alone.

Tests: four YAML cases worked from the law, and a Hypothesis property (net pay relief, total income, adjusted total income, the charge, and relief at source leaving total income alone); two mutants each fail it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 4 commits October 18, 2026 15:12
…at source

Review r9 found the net pay deduction capped at pension_contributions_relief,
whose relevant UK earnings leave out statutory sick and maternity pay. A
pensioner with £1,000 of wages, £3,000 of SSP, a £32,500 pension and £2,000 of
employee contributions had £1,000 deducted and a £200 charge, where the law
deducts the whole £2,000 (FA 2004 ss.189(2)(a), 190(1), 193(2); HMRC
PTM044100) and charges nothing. Personal contributions also moved the
deduction, through the basic-amount rise that s.191(7) keeps for relief at
source.

The deduction is now the employee contributions up to taxable employment
income, none from age 75. Relevant UK earnings include all of that income, so
the s.190 limit never binds below it. It no longer reads relief at source.
Two YAML cases pin the SSP case with and without personal contributions; the
net pay property test now draws statutory sick and maternity pay. ATI's
comment and the changelog qualify "unchanged" for float32 rounding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…del's relief

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ATI comment's "never exceed" holds for contributions of zero or more;
the add-back cancels either way. Relieving employee contributions first is
the model's order, not one the Act states. The fixed fragment now says the
payment reaches everyone of pensionable age who meets its other conditions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 18, 2026 20:03
@MaxGhenis
MaxGhenis marked this pull request as draft October 18, 2026 20:38
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Back to draft: review r11 approved the head d0920a9cd, but this PR makes CI's Python shards run out of memory, and a decision on the fix is pending.

  • Cause. Since 26ff32421 the property allowance election compares both routes for every landlord the allowance helps while the charge applies. Each comparison computes income_tax with the charge in a branch, so it works out the means-tested benefits for the whole population. Simulation.__init__ does this for each year to 2032 too, because add_universal_credit_reform calculates uc_LCWRA_element (through ESA, the personal allowance and net income to uses_property_allowance).
  • CI. Peak memory per Python shard is 9-10 GB on main and on this PR up to 96d2519d9. Since 26ff32421 every shard reaches 15.1-15.7 GB, and one shard is killed each run (runs 38962929350, 39045062614, 39159250347, 39160946751: "The runner has received a shutdown signal" after under 500 MB was left). These are not runner flakes, so rerunning the shard won't fix it.
  • Effect on results. On the Enhanced FRS, 96d2519d9 against 26ff32421: no record's income_tax, winter_fuel_payment_charge, winter_fuel_payment or pension_credit moves by 1p in any year 2025-2030. A synthetic run of 400 households takes 6.0 s with the comparison and 0.9 s without it, with the same totals.
  • Options. Keep the comparison and accept the cost, or restore main's shortcut for landlords without finance costs and document review r7's household as a known limit. The second is ready on wip/2066-election-shortcut (75ca17cdd, one commit on this head); it is not on this PR.
  • Impact section. It still holds the 2026-10-17 run and will be re-run on the final head once local runs are allowed again.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Model Winter Fuel Payment high-income recovery separately from entitlement

1 participant