Repository navigation
Count the Pension Age Winter Heating Payment in household and HBAI income - #2047
Merged
Merged
Conversation
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>
This was referenced Oct 2, 2026
Collaborator
Author
|
#2038 merged into main as 4012143, so this PR's base is now
🤖 Generated with Claude Code |
MaxGhenis
marked this pull request as ready for review
October 11, 2026 15:47
This was referenced Oct 11, 2026
Main added bus_fare_spending_reported (c8d1f89), household bus and coach spending kept as a fallback for journey data. It is not a benefit, so the check that every simulated benefit with a reported amount is set by disable_simulated_benefits lists it with the other non-benefits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 12, 2026
… apart The differential property drew six derandomised populations, and nothing made one of them include a household that receives PAWHP. Two explicit examples now always run: a single Scottish pensioner in 2025 and a Scottish couple on Pension Credit in 2024. The fixed check that used to build its own population now checks that each example receives PAWHP. A new check fails if a benefit is both in disable_simulated_benefits' BENEFITS and excluded from it. winter_fuel_allowance in particular must stay out of BENEFITS: the winter heating split sets it, and a generic entry as well would be harmless only while the split runs after the generic loop (relevant when #2075 meets this branch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #2305, parameter changes set after the data load are applied before the structural reforms are created, so disable_simulated_benefits now runs from an ordinary reform= dict as well as from a Scenario applied before the load. A new test checks that the winter heating report is still counted once on that route, under Simulation and Microsimulation, and the module docstring no longer says the reform needs the parameter before the load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Landed through the merge queue at head
🤖 Generated with Claude Code |
MaxGhenis
added a commit
that referenced
this pull request
Oct 13, 2026
#2047's winter heating split (merged in 6f0148a) reads gov.social_security_scotland.pawhp.active through the parameters the reform class was built from. When the parameter switches the reform on, that is the applying simulation's own tree (create_structural_reforms_from_parameters is given self.tax_benefit_system.parameters), so nothing changes there. A reform class built from other parameters and applied later (the PR's clone, branch and reform= tests build it from a stand-in that only switches it on) raised AttributeError: 'types.SimpleNamespace' object has no attribute 'social_security_scotland' (a probe applying the class to a situation). The split now reads the switch from the system of the simulation applying the reform, which is also the tree a later parameter change would be in. The rule itself (#2047's) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 14, 2026
…form disable_simulated_benefits set carers_allowance from carers_allowance_reported in every country and left carer_support_payment simulated, so from 2025, when the model pays a carer in Scotland Carer Support Payment in place of Carer's Allowance, a Scottish carer was paid the report and a simulated Carer Support Payment (Vahid Ahmadi's review of 5366cf0, finding 2; #2047's note). Like #2047's winter heating split, the report now becomes a Scottish carer's carer_support_payment from CARER_SUPPORT_PAYMENT_FROM (2025, the year both pre-overlap formulas switch on) and their carers_allowance before, and everyone else's carers_allowance. The pre-overlap entitlements stay the model's for both schemes. New tests (test_disable_simulated_benefits_carers.py): the reported case, conservation and placement in every year on both simulation classes and the reform= route, a differential against the model's own scheme choice with the reform off, household_benefits counting the report once, and a Hypothesis property. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 15, 2026
…hanges Merges origin/main (1a31ef1). - conftest: main's --shard selection and shard header now sit in the same hooks as the data plan, instead of two definitions of each hook. The pin is found through the installed package, so CI's tests that copy conftest into a scratch directory still load it. - Pin: the certified pair is now policyengine-uk 2.130.1 with uk-data 1.58.1 (policyengine.py bundle uk-6.4.3): enhanced_frs_2024_25.h5 at Hugging Face commit 2526edbb, sha256 056e80fd. The reform-cost fixtures are recomputed on it with this model (2.132.6), each new value added as a receipt. - Partition: Scottish Child Payment, Healthy Start and the Two Child Limit Payment are now in gov_spending (#2366), Statutory Paternity Pay is a new spending head (#2365), and the Pension Age Winter Heating Payment is in household_benefits (#2047). Closure caught each one. - The SSP/SMP Universal Credit interaction is fixed on main (#2365), so the certified test no longer expects it to fail. - The workflow test moves to code_health/ and skips in an installed wheel, which has no .github, as the other CI-script tests do. - The dataset is projected to 2032 (#2376), so 2031-32 is no longer extrapolated differently; the README says so. Refs #2334. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 15, 2026
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>
MaxGhenis
added a commit
that referenced
this pull request
Oct 17, 2026
Review r11 (Opus and GPT-6.1 Sol) on 871e46d: main's benefit cap readers (#2076, #2089) called award_of_members on esa_income and jsa_income. Under disable_simulated_benefits the held total matches neither of its readings after the dataset year, so another member's held award was read as the couple's: it raised benefit_cap_welfare_benefits and could exempt a claimant with an ESA support component who receives no ESA. - utils/benefit_unit.py: award_of_couple reads claimant_or_partner_* wherever the rule's couple is the claimant and partner, and award_of_members for any other couple (a UC couple with is_uc_claimant entered). CLAIMANT_OR_PARTNER_AWARDS moves here; the reform imports it. - benefit_cap_welfare_benefits and both specified-benefit exemptions read award_of_couple. - The reform's docstring says it reads its inputs when it is applied, so a region a scenario's after-load modifier or set_input changes later is not followed; applying the reform after the change reads it (r11 Sol finding 2, Opus nit 4). New test for that route. - Changelog: the first bullet no longer claims what #2047 shipped (r11 Opus finding 2); new bullet for the benefit cap. - New test_disable_simulated_benefits_benefit_cap.py: metamorphic (another member's report never changes the cap, reform on), differential against main's replaced reader (reform off), the fallback couple, and an entered award reaching the cap. Each reader's mutant fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 targetsmainand includes it at1d3abecef(merge commit0276467dd, no conflicts).Summary
#1000 (merged 5 December 2024) stopped paying
winter_fuel_allowancein Scotland and addedpawhptogov_spending. It did not add it tohousehold_benefits,hbai_household_net_incomeorhbai_benefits, which all countwinter_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
pawhpnext towinter_fuel_allowancein all three lists.disable_simulated_benefitsChecking how the reform treats PAWHP showed that the reform could not run at all:
Simulation.__init__never settax_benefit_system.simulation. Core applies a reform by calling itsapply()with the tax-benefit system asself, so the reform'sself.simulationraisedAttributeError. Core's ownSimulation.__init__sets the attribute, but PolicyEngine UK's__init__replaces core's. It is now set before the structural reforms are applied. (adjust_budgetsreadsself.simulationthe same way.)attendance_allowance,dla_m,dla_sc,pip_dlandpip_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_reportedvariable, and every simulated benefit with one to be listed or excluded with a reason, and never both. Maternity Allowance is excluded because it already addsmaternity_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 intowinter_fuel_allowance_reportedfor every respondent, Scotland included. Hadwinter_fuel_allowancestayed set from it whilepawhpstayed 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 itspawhpin the years PAWHP is paid (gov.social_security_scotland.pawhp.active, from the 2024 qualifying week) and itswinter_fuel_allowancebefore then. Every other household's report stayswinter_fuel_allowance. Each report is counted once, under the scheme that pays it, and the programme totals match the simulated definitions.The reform runs whether its parameter is set before the data load (
Scenario(applied_before_data_load=True, ...)) or through an ordinaryreform=dict. Since #2305, parameter changes set after the data load are applied before the structural reforms are created. Tests cover both routes.Overlap with #2075. #2075 owns the generic repair of this reform: the
tax_benefit_system.simulationline, 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, takeswinter_fuel_allowanceout of itsBENEFITS, and keeps the winter heating split here after the generic loop, so the one reported payment is counted once.test_no_benefit_is_both_listed_and_excludedfails ifwinter_fuel_allowanceis left inBENEFITS, and the conservation tests fail if the split is lost.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 staywinter_fuel_allowancein 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 incost_of_living_support_paymentin any household, 2024-30: £0).Invariants
household_benefits,household_gross_incomeandhbai_benefitsby exactly itspawhp, andhousehold_net_incomeandhbai_household_net_incomeby itspawhpless 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 neitherwinter_fuel_allowancenorcost_of_living_support_payment. Tested as a Hypothesis differential property over households in every country, 2023-27, at float32 precision. Two explicit examples always run with it: a single Scottish pensioner in 2025 and a Scottish couple on Pension Credit in 2024, each checked to receive PAWHP. On the Enhanced FRS, the base-to-branch change equalspawhpfor every household in every year 2024-30. Forhousehold_benefits,hbai_benefitsand both HBAI net income measures it holds to within 1p in every household. Forhousehold_net_incomeandhousehold_gross_income, the records more than 1p off all have household income above £60k, and each is within 2 float32 spacings (at most 1.71) of the larger of its net and gross income, at most 1.6 parts in 10 million of gross income.gov_spending,gov_balance,pawhp,winter_fuel_allowanceandcost_of_living_support_paymentare unchanged in every Enhanced FRS household, 2024-30 (largest absolute change: 0).gov_spendingitem is inhousehold_benefitsexceptother_public_spending_budget_change, a contrib budget lever. Both winter heating payments appear exactly once inhousehold_benefits, the HBAI net income list,hbai_benefitsandgov_spending.winter_fuel_allowance + pawhpequals the household's reported winter heating payment (conservation). The report ispawhpexactly where the household is in Scotland and PAWHP is paid (placement).household_benefitsandhbai_household_net_incomeexceed those of the same dataset with no report by the report plus the pensioner Cost-of-Living Payment it brings (differential). Conservation and placement are tested with Hypothesis over datasets in every region starting 2022-25, underSimulation. Example tests check them underSimulationandMicrosimulation, for datasets starting 2023 and 2025, and with the reform set before and after the data load.Tests
tests/policy/baseline/household/income/winter_heating_payments_in_income.yaml: 5 cases.main(1d3abecef),household_benefits,household_net_income,hbai_benefitsandhbai_household_net_incomemiss the PAWHP, so these three cases fail there and the two controls pass (run 2026-10-13).tests/code_health/test_household_income_lists.py: invariant 3.tests/test_pawhp_household_income_properties.py: invariant 1, with its two explicit PAWHP recipients.tests/test_disable_simulated_benefits_winter_heating.py: the reform runs underSimulationandMicrosimulationand from areform=dict; its list matches the reported variables, and no benefit is both listed and excluded; invariant 4. With the reform off, the newreform=dict test's check fails, as it should.27f5fdb0f, an earlier head (no Hypothesis shrinking):pawhpdropped from each of the three lists, listed twice, the reform putting every report inwinter_fuel_allowance, double counting Scotland, splitting a year late,Simulationnot settingtax_benefit_system.simulation, andattendance_allowanceback in the reform's list.0276467dd: the YAML suites for household income, the Winter Fuel Payment, PAWHP and the Treasury (212 passed); pytest overtests/code_health, both PAWHP property files and the reform tests (2302 passed). On371ff4605: the reform tests (13 passed);ruff format --checkandruff checkclean with ruff 0.17.0.371ff4605(see the checks).Impact (Enhanced FRS)
One real microsimulation per package state on a private copy of
enhanced_frs_2024_25.h5(sha256e433e532b17b…), 2024-30, run on 2026-10-13. Base:mainat1d3abecef. Branch:0276467dd(this PR merged with thatmain; the later371ff4605changes only a test). Aggregates only. A poverty change that rests on fewer than 10 households is shown as such. The change moves no spending (gov_spendingandgov_balanceunchanged in every household), so the whole effect is on income and poverty.Absolute poverty (HBAI line, 60% of the FYE 2025 median held constant in real terms; percentage points, people in thousands):
Relative poverty (60% of the contemporary median):
Each poverty change shown rests on 10 to 36 households. 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 29 to 44 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. The AHC median rises by at most £0.39 a week, in 2029, which raises the AHC line by £0.24 a week. That year 15.1k people outside Scotland, in 14 households, fall below the raised line, so relative AHC poverty rises overall (+4.5k) even though it falls for Scottish pensioners. The absolute measures, whose line is fixed, are the cleaner read of the effect: Scottish pensioner AHC poverty falls by 0.22 to 0.44 points a year from 2025. The 2024 effect is small because the model pays PAWHP in 2024 only to recipients of a relevant benefit (
gov.social_security_scotland.pawhp.eligibility.require_benefitsis false from 2025).Scripts, logs and diagnostics:
dataset_impact.py,compare_impact.py,diagnose.py,diagnose_gross_ulp.pyandpr_tables_suppressed.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
pawhpon top ofhousehold_benefitsunconditionally. 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.hbai_benefitsomitsscottish_child_paymentandcouncil_tax_benefit, whichhousehold_benefitscounts. A follow-up will verify these and reconcile them. (gov_spendinghas counted Scottish Child Payment, Healthy Start and the Two Child Limit Payment since Land six reviewed pull requests together (rollup B) #2380.)adjust_budgetsruns for the first time with this PR'stax_benefit_system.simulationfix. It scales spending by the budget change overspending.sum(), which is weighted for aMicrosimulation(a MicroSeries) but unweighted for aSimulation(a NumPy array). A follow-up fixes theSimulationcase.disable_simulated_benefits, a Scottish carer's reported Carer's Allowance is counted twice from 2025:carers_allowanceis set from the report whilecarer_support_paymentstays simulated. The list entry predates this PR, and Fix disable_simulated_benefits and read one claimant-or-partner award #2075 owns the generic list; reported there with a synthetic-household probe (comment).Reproducer:
policyengine_uk/tests/policy/baseline/household/income/winter_heating_payments_in_income.yaml,policyengine_uk/tests/code_health/test_household_income_lists.py::test_winter_heating_payments_are_counted_in_every_income_list🤖 Generated with Claude Code