Skip to content

Count the Pension Age Winter Heating Payment in household and HBAI income - #2047

Open
MaxGhenis wants to merge 4 commits into
mainfrom
pawhp-household-income
Open

MaxGhenis wants to merge 4 commits into
mainfrom
pawhp-household-income

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2313

This was stacked on #2038, which pays the Winter Fuel Payment in Scotland up to the 2023 qualifying week and the Pension Age Winter Heating Payment (PAWHP) per person from 2024. #2038 has merged (40121430). This branch now targets main and includes it at 6d89338ab (merge commit e554001fa, no conflicts).

Summary

#1000 (merged 5 December 2024) stopped paying winter_fuel_allowance in Scotland and added pawhp to gov_spending. It did not add it to household_benefits, hbai_household_net_income or hbai_benefits, which all count winter_fuel_allowance. Since then, Scottish pensioners' winter heating payment has been counted as government spending but never reached household net income or HBAI income. PolicyEngine/impact-iran-war-living-standards works around it (BENEFITS_OUTSIDE_HOUSEHOLD_BENEFITS = ["pawhp"]).

This PR adds pawhp next to winter_fuel_allowance in all three lists.

disable_simulated_benefits

Checking how the reform treats PAWHP showed that the reform could not run at all:

  • Simulation.__init__ never set tax_benefit_system.simulation. Core applies a reform by calling its apply() with the tax-benefit system as self, so the reform's self.simulation raised AttributeError. Core's own Simulation.__init__ sets the attribute, but PolicyEngine UK's __init__ replaces core's. It is now set before the structural reforms are applied. (adjust_budgets reads self.simulation the same way.)
  • Five list entries had no reported amount: attendance_allowance, dla_m, dla_sc, pip_dl and pip_m (ValueError: Variable attendance_allowance_reported does not exist). policyengine-uk-data converts their reported amounts into award categories (aa_category, …; add_disability_benefit_categories_from_reported_amounts), and the model pays the category's rate, so they already follow the reported award. They are dropped from the list. A code-health test now requires every listed benefit to have a _reported variable, and every simulated benefit with one to be listed or excluded with a reason. Maternity Allowance is excluded because it already adds maternity_allowance_reported.

Treatment of PAWHP. There is no pawhp_reported. The survey records one winter heating payment, FRS benefit code 62, which policyengine-uk-data loads into winter_fuel_allowance_reported for every respondent, Scotland included. Had winter_fuel_allowance stayed set from it while pawhp stayed simulated, Scottish households would have counted the payment twice now that both are household income. The reform now sets both from the one report, year by year. A Scottish household's report becomes its pawhp in the years PAWHP is paid (gov.social_security_scotland.pawhp.active, from the 2024 qualifying week) and its winter_fuel_allowance before then. Every other household's report stays winter_fuel_allowance. Each report is counted once, under the scheme that pays it, and the programme totals match the simulated definitions.

Note: the reform fires only when its parameter is set before the data load (Scenario(applied_before_data_load=True, ...)). Set through an ordinary reform= dict, it is silently ignored. That applies to every parameter-gated structural reform and is left to a follow-up.

Overlap with #2075. #2075 owns the generic repair of this reform: the tax_benefit_system.simulation line, the AA/DLA/PIP removal, and claimant-or-partner scoping of ESA, JSA and Income Support. This PR carries the same two generic fixes so it can run and test its winter heating treatment on its own stack. Whichever PR lands second keeps #2075's structure, takes winter_fuel_allowance out of its BENEFITS, and keeps the winter heating split here, so the one reported payment is counted once.

Cost-of-Living Payments

Unchanged. The pensioner payment (£300 in 2022-23 and 2023-24, £0 from 2024) is keyed on winter_fuel_allowance > 0, which this PR does not change. #2038 pays the Winter Fuel Payment in Scotland up to the 2023 week. Under the reported-benefits reform, Scottish reports stay winter_fuel_allowance in 2022 and 2023, when PAWHP is inactive. Evidence: a 2023 YAML case, a reform test (the pensioner payment moves with the report only in 2023), and the Enhanced FRS run (largest change in cost_of_living_support_payment in any household, 2024-30: £0).

Invariants

  1. Income identity. For every household, switching PAWHP off lowers household_benefits, household_gross_income and hbai_benefits by exactly its pawhp, and household_net_income and hbai_household_net_income by its pawhp less the change in its income tax. That change is zero today; the term keeps the property true once the winter fuel payment charge (ITEPA 2003 s.681I) is modelled in income tax. Switching PAWHP off changes neither winter_fuel_allowance nor cost_of_living_support_payment. Tested as a Hypothesis differential property over households in every country, 2023-27, at float32 precision. On the Enhanced FRS, the base-to-branch change equals pawhp for every household in every year 2024-30. For household_benefits, hbai_benefits and both HBAI net income measures it holds to within 1p in every household. For household_net_income and household_gross_income, the records more than 1p off all have household income above £60k, and each is within 2 float32 ulps (at most 1.8) of the larger of its net and gross income, at most 1.6 parts in 10 million of gross income.
  2. No spending change. gov_spending, gov_balance, pawhp, winter_fuel_allowance and cost_of_living_support_payment are unchanged in every Enhanced FRS household, 2024-30 (largest absolute change: 0).
  3. List consistency (code health). Every gov_spending item is in household_benefits except other_public_spending_budget_change, a contrib budget lever. Both winter heating payments appear exactly once in household_benefits, the HBAI net income list, hbai_benefits and gov_spending.
  4. Reported mode. In each of the reform's years, winter_fuel_allowance + pawhp equals the household's reported winter heating payment (conservation). The report is pawhp exactly where the household is in Scotland and PAWHP is paid (placement). household_benefits and hbai_household_net_income exceed those of the same dataset with no report by the report plus the pensioner Cost-of-Living Payment it brings (differential). Tested with Hypothesis over datasets in every region starting 2022-25, under Simulation and Microsimulation.

Tests

  • tests/policy/baseline/household/income/winter_heating_payments_in_income.yaml: 5 cases.
    • A Scottish single pensioner in 2025 and a couple in 2026 (shared amounts), and a Pension Credit recipient in 2024. On main (6d89338ab), household_benefits, household_net_income, hbai_benefits and hbai_household_net_income miss the PAWHP, so these three cases fail there and the two controls pass (run 2026-10-11).
    • Two controls: an English pensioner in 2025 (Winter Fuel Payment), and a Scottish pensioner in 2023 (Winter Fuel Payment and the £300 pensioner Cost-of-Living Payment, no PAWHP).
  • tests/code_health/test_household_income_lists.py: invariant 3.
  • tests/test_pawhp_household_income_properties.py: invariant 1, plus a non-vacuity case.
  • tests/test_disable_simulated_benefits_winter_heating.py: the reform runs under Simulation and Microsimulation, its list matches the reported variables, and invariant 4.
  • Mutation check: all 9 mutants killed on 27f5fdb0f (no Hypothesis shrinking): pawhp dropped from each of the three lists, listed twice, the reform putting every report in winter_fuel_allowance, double counting Scotland, splitting a year late, Simulation not setting tax_benefit_system.simulation, and attendance_allowance back in the reform's list.
  • Full suites: the PR checks (Code changes: lint, smoke imports on Python 3.11-3.14, make test) on e554001fa: run 38152748149 (in progress when this was written; see the checks). Before Test Winter Fuel Payment and PAWHP receipt per person #2038 merged, the same workflow was dispatched on 27f5fdb0f (run 37005793386, all jobs passed).

Impact (Enhanced FRS)

One real microsimulation per package state on a private copy of enhanced_frs_2024_25.h5 (sha256 e433e532b17b…), 2024-30, re-run on 2026-10-11 after main changed PAWHP's couple rule (#2038's final head). Base: main at 6d89338ab. Branch: e554001fa (this PR merged with that main). Aggregates only. The change moves no spending (gov_spending and gov_balance unchanged in every household), so the whole effect is on income and poverty.

Year PAWHP (£m) Change in household net income (£m) Change in HBAI net income, BHC and AHC (£m) Households gaining (thousands, all in Scotland)
2024 34.7 +34.7 +34.7 135
2025 201.1 +201.1 +201.1 842
2026 208.2 +208.2 +208.2 839
2027 211.2 +211.2 +211.2 833
2028 215.5 +215.5 +215.5 833
2029 220.7 +220.7 +220.7 836
2030 226.2 +226.2 +226.2 840

Absolute poverty (HBAI line, 60% of the FYE 2025 median held constant in real terms; percentage points, people in thousands):

Year All, BHC All, AHC Pensioners, BHC Pensioners, AHC Scottish pensioners, BHC Scottish pensioners, AHC
2024 +0.000 (+0.0k) -0.001 (-0.5k) +0.000 (+0.0k) -0.004 (-0.5k) +0.000 (+0.0k) -0.052 (-0.5k)
2025 -0.004 (-3.1k) -0.017 (-12.1k) -0.025 (-3.1k) -0.100 (-12.1k) -0.296 (-3.1k) -1.171 (-12.1k)
2026 -0.001 (-0.9k) -0.007 (-5.2k) -0.007 (-0.9k) -0.037 (-4.5k) -0.086 (-0.9k) -0.437 (-4.5k)
2027 -0.002 (-1.6k) -0.004 (-2.8k) -0.011 (-1.3k) -0.024 (-2.8k) -0.129 (-1.3k) -0.277 (-2.8k)
2028 -0.003 (-2.0k) -0.004 (-3.0k) -0.014 (-1.7k) -0.026 (-3.0k) -0.164 (-1.7k) -0.293 (-3.0k)
2029 -0.002 (-1.5k) -0.003 (-2.3k) -0.012 (-1.4k) -0.019 (-2.3k) -0.139 (-1.4k) -0.220 (-2.3k)
2030 -0.003 (-2.2k) -0.004 (-2.7k) -0.018 (-2.1k) -0.021 (-2.5k) -0.204 (-2.1k) -0.242 (-2.5k)

Relative poverty (60% of the contemporary median):

Year All, BHC All, AHC Pensioners, BHC Pensioners, AHC Children, AHC Working age, AHC
2024 -0.000 (-0.1k) +0.000 (+0.0k) -0.001 (-0.1k) +0.000 (+0.0k) +0.000 (+0.0k) +0.000 (+0.0k)
2025 -0.029 (-20.3k) -0.012 (-8.1k) -0.166 (-20.1k) -0.071 (-8.6k) +0.000 (+0.0k) +0.001 (+0.5k)
2026 -0.114 (-79.8k) -0.017 (-11.6k) -0.665 (-79.7k) -0.095 (-11.4k) +0.000 (+0.0k) -0.001 (-0.3k)
2027 -0.102 (-71.6k) -0.000 (-0.3k) -0.608 (-71.5k) -0.008 (-0.9k) +0.002 (+0.3k) +0.001 (+0.3k)
2028 -0.006 (-4.4k) -0.013 (-9.2k) -0.038 (-4.4k) -0.086 (-10.1k) +0.000 (+0.0k) +0.002 (+0.9k)
2029 -0.073 (-51.6k) -0.014 (-9.9k) -0.440 (-51.6k) -0.088 (-10.3k) +0.000 (+0.0k) +0.001 (+0.3k)
2030 -0.073 (-52.2k) -0.106 (-75.7k) -0.443 (-52.2k) -0.643 (-75.7k) +0.000 (+0.0k) +0.000 (+0.0k)

Relative poverty moves in large, irregular steps because incomes are bunched near the line. A diagnostic on the run counted 25k to 182k Scottish people (by year) with equivalised income below the base BHC line by less than the largest equivalised PAWHP gain (£8.61 to £9.84 a week). Their incomes take only 27 to 42 distinct values. A gaining household receives £239 to £269 a year on average (about £5 a week), which moves whole blocks across the line: 80k Scottish pensioners in 2026, 72k in 2027, and 52k in each of 2029 and 2030 (BHC). The BHC median does not move. On the AHC basis, the PAWHP gains raise the median by at most £0.24 a week (2025), and at most 1.0k people outside Scotland cross into relative AHC poverty in any year. The absolute measures, whose line is fixed, are the cleaner read of the effect. Scottish pensioner AHC poverty falls 1.17 points (12.1k people) in 2025, the first year the model pays PAWHP to everyone of pensionable age in Scotland (gov.social_security_scotland.pawhp.eligibility.require_benefits is false from 2025).

Scripts, logs and diagnostics: dataset_impact.py, compare_impact.py, diagnose.py and diagnose_gross_ulp.py (kept privately with the run; the per-record arrays are not published).

Axiom

axiom: n/a: aggregation only (PAWHP's place in household and HBAI income lists) plus a contrib reform; PAWHP itself is TheAxiomFoundation/rulespec-uk#420 queued (from #2038)

Follow-ups

  • PolicyEngine/impact-iran-war-living-standards adds pawhp on top of household_benefits unconditionally. Once its policyengine bundle reaches a release with this PR, the PAWHP change would be counted twice there. A follow-up makes the adjustment conditional.
  • gov_spending omits scottish_child_payment, healthy_start_vouchers and two_child_limit_payment, which household_benefits counts. hbai_benefits omits scottish_child_payment and council_tax_benefit. A follow-up will verify these and reconcile them.
  • Parameter-gated structural reforms are silently ignored when their parameter is set after the data load. A follow-up also covers adjust_budgets, which this PR's tax_benefit_system.simulation fix lets run for the first time under the current Simulation.

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 2, 2026 07:42
pawhp was in gov_spending but not in household_benefits,
hbai_household_net_income or hbai_benefits, so once the Winter Fuel
Payment stopped covering Scotland (2024 qualifying week), Scottish
pensioners' winter heating payment fell out of household net income and
HBAI income. Add it to the three lists next to winter_fuel_allowance.

disable_simulated_benefits could not run: Simulation never set
tax_benefit_system.simulation (core's __init__ does, this one replaces
it), and the reform named five _reported variables that no longer exist
(AA, DLA and PIP follow the reported award through their categories).
Set the attribute, drop the five, and count the one reported winter
heating payment (FRS code 62, Scotland included) once: as pawhp in
Scotland while PAWHP is paid, as winter_fuel_allowance otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Matches the constant name used by the parallel disable-simulated-benefits
rework, and keeps the two branches from adding the same test file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Net measures fall by pawhp less the change in the household's income tax
(zero today), so the property still holds once the winter fuel payment
charge (ITEPA 2003 s.681I) is modelled in income tax. Compare at float32
precision: a 1p tolerance failed on a household with net income of about
150,000, where one float32 step is 1.6p.

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

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.

Pension Age Winter Heating Payment is missing from household and HBAI income

1 participant