From 37775ed8bed4e7e45b90431f810508e5a38b0f37 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 02:38:13 -0400 Subject: [PATCH 1/8] Test Winter Fuel Payment and PAWHP receipt per person MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both payments pooled Pension Credit, Income Support, income-related ESA and income-based JSA across the household, so a non-dependant's award qualified a pensioner in the same household (2024) or exempted them from the income test (2025 on). The regulations entitle and pay a person: - a person is on a relevant benefit through their own award, or their couple's for the claimant and partner (is_on_* variables; Universal Credit, and tax credits of at least £26 for the 2024 week, now count); - a couple on a relevant benefit gets one payment; anyone else gets the full amount alone and a shared amount when living with another entitled person (SI 2000/729 reg 2; SI 2024/869 regs 2-4; SI 2025/969 regs 2-4; SSI 2024/351 regs 5, 9, 10); - the household variables add up their members' payments. Also pay the Winter Fuel Payment in Scotland up to the 2023 week and PAWHP only from 2024; pay PAWHP from 2025 at the legislated amounts (SSI 2025/282, 2025/100, 2026/170) instead of a flat £100; cite SSI 2024/351 rather than SSI 2024/250 (a planning instrument); register PAWHP in programs.yaml. Co-Authored-By: Claude Opus 5.5 --- ...winter-heating-per-person-receipt.added.md | 2 + ...winter-heating-per-person-receipt.fixed.md | 6 + ...nter-heating-per-person-receipt.removed.md | 1 + .../winter_fuel_payment/amount/higher.yaml | 9 +- .../amount/higher_shared_with_80_or_over.yaml | 11 + .../amount/higher_shared_with_under_80.yaml | 11 + .../dwp/winter_fuel_payment/amount/lower.yaml | 9 +- .../amount/lower_shared.yaml | 11 + .../eligibility/countries.yaml | 30 ++ .../eligibility/relevant_benefits.yaml | 37 ++ .../pawhp/active.yaml | 14 + .../pawhp/amount/base.yaml | 15 - .../pawhp/amount/higher.yaml | 25 +- .../amount/higher_shared_with_80_or_over.yaml | 18 + .../amount/higher_shared_with_under_80.yaml | 18 + .../pawhp/amount/lower.yaml | 25 +- .../pawhp/amount/lower_shared.yaml | 18 + .../eligibility/higher_age_requirement.yaml | 6 +- .../pawhp/eligibility/relevant_benefits.yaml | 30 ++ .../pawhp/eligibility/require_benefits.yaml | 19 +- .../state_pension_age_requirement.yaml | 6 +- policyengine_uk/programs.yaml | 13 + .../gov/dwp/winter_fuel_allowance.yaml | 6 +- .../relevant_benefit_receipt.yaml | 480 ++++++++++++++++++ .../gov/social_security_scotland/pawhp.yaml | 366 +++++++++++++ .../test_winter_heating_payment_properties.py | 400 +++++++++++++++ policyengine_uk/utils/winter_heating.py | 76 +++ .../variables/gov/dwp/is_on_pension_credit.py | 25 + .../variables/gov/dwp/is_on_tax_credits.py | 39 ++ .../gov/dwp/is_on_universal_credit.py | 22 + ...on_winter_fuel_payment_relevant_benefit.py | 29 ++ .../gov/dwp/winter_fuel_allowance.py | 63 +-- .../variables/gov/dwp/winter_fuel_payment.py | 33 ++ .../gov/dwp/winter_fuel_payment_eligible.py | 60 +++ .../is_on_pawhp_relevant_benefit.py | 29 ++ .../gov/social_security_scotland/pawhp.py | 47 +- .../pawhp_eligible.py | 40 ++ .../pension_age_winter_heating_payment.py | 34 ++ 38 files changed, 1935 insertions(+), 148 deletions(-) create mode 100644 changelog.d/winter-heating-per-person-receipt.added.md create mode 100644 changelog.d/winter-heating-per-person-receipt.fixed.md create mode 100644 changelog.d/winter-heating-per-person-receipt.removed.md create mode 100644 policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher_shared_with_80_or_over.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher_shared_with_under_80.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/lower_shared.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/countries.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/relevant_benefits.yaml create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/pawhp/active.yaml delete mode 100644 policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/base.yaml create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher_shared_with_80_or_over.yaml create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher_shared_with_under_80.yaml create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/lower_shared.yaml create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/relevant_benefits.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml create mode 100644 policyengine_uk/tests/test_winter_heating_payment_properties.py create mode 100644 policyengine_uk/utils/winter_heating.py create mode 100644 policyengine_uk/variables/gov/dwp/is_on_pension_credit.py create mode 100644 policyengine_uk/variables/gov/dwp/is_on_tax_credits.py create mode 100644 policyengine_uk/variables/gov/dwp/is_on_universal_credit.py create mode 100644 policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py create mode 100644 policyengine_uk/variables/gov/dwp/winter_fuel_payment.py create mode 100644 policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py create mode 100644 policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py create mode 100644 policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py create mode 100644 policyengine_uk/variables/gov/social_security_scotland/pension_age_winter_heating_payment.py diff --git a/changelog.d/winter-heating-per-person-receipt.added.md b/changelog.d/winter-heating-per-person-receipt.added.md new file mode 100644 index 0000000000..6a162f060e --- /dev/null +++ b/changelog.d/winter-heating-per-person-receipt.added.md @@ -0,0 +1,2 @@ +- Add `is_on_pension_credit`, `is_on_universal_credit` and `is_on_tax_credits`: whether the award is payable to the person, through their couple's award for the claimant and partner and through their own report for anyone else. +- Add person-level `winter_fuel_payment` and `pension_age_winter_heating_payment`, with `winter_fuel_payment_eligible`, `pawhp_eligible`, `is_on_winter_fuel_payment_relevant_benefit` and `is_on_pawhp_relevant_benefit`. The household `winter_fuel_allowance` and `pawhp` now add up their members' payments. diff --git a/changelog.d/winter-heating-per-person-receipt.fixed.md b/changelog.d/winter-heating-per-person-receipt.fixed.md new file mode 100644 index 0000000000..cad767ddc7 --- /dev/null +++ b/changelog.d/winter-heating-per-person-receipt.fixed.md @@ -0,0 +1,6 @@ +- Test receipt of a relevant benefit for the Winter Fuel Payment and the Pension Age Winter Heating Payment person by person: the person's own award, or their couple's for the claimant and partner. A non-dependant's income-related ESA, income-based JSA, Income Support or Pension Credit no longer qualifies a pensioner in the same household, or exempts them from the Winter Fuel Payment income test. +- Count Universal Credit as a relevant benefit for both payments, and a tax credit award of at least £26 for the 2024 qualifying week, as SI 2024/869, SI 2025/969 and SSI 2024/351 list them. +- Pay each person the amount the regulations set: one payment for a couple on a relevant benefit, the full amount to anyone else living without another entitled person, and the shared amounts (£100, or £200 or £150 over 80, and the PAWHP equivalents) otherwise. Two benefit units in one household can now both be paid. +- Pay the Winter Fuel Payment in Scotland up to the 2023 qualifying week (SI 2000/729 covered Great Britain) and the Pension Age Winter Heating Payment only from the 2024 week. +- From the 2025 qualifying week, pay the Pension Age Winter Heating Payment to everyone of pensionable age in Scotland at the legislated amounts (SSI 2025/282, SSI 2025/100 and SSI 2026/170) instead of a flat £100 to households not on a relevant benefit. +- Cite the Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351) in the PAWHP parameters; they cited SSI 2024/250, a planning instrument. diff --git a/changelog.d/winter-heating-per-person-receipt.removed.md b/changelog.d/winter-heating-per-person-receipt.removed.md new file mode 100644 index 0000000000..97dc17c287 --- /dev/null +++ b/changelog.d/winter-heating-per-person-receipt.removed.md @@ -0,0 +1 @@ +- Remove `gov.social_security_scotland.pawhp.amount.base`: the regulations set no flat amount for households not on a relevant benefit; the new shared amounts replace it. diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher.yaml index 2661b9a7de..7eb37273aa 100644 --- a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher.yaml +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher.yaml @@ -1,8 +1,13 @@ +description: Winter Fuel Payment for a person aged 80 or over, or under 80 and on a relevant benefit with a partner aged 80 or over, unless a shared amount applies. values: 2000-01-01: 300 metadata: unit: currency-GBP label: Winter Fuel Payment higher amount reference: - - href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2 - title: The Social Fund Winter Fuel Payment Regulations 2000 \ No newline at end of file + - title: The Social Fund Winter Fuel Payment Regulations 2000, reg. 2(2)(a) and 2(3) (as in force for the 2023 qualifying week) + href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2/2023-09-18 + - title: The Social Fund Winter Fuel Payment Regulations 2024, reg. 3(a) and (b) + href: https://www.legislation.gov.uk/uksi/2024/869/regulation/3/made + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 3(3) and (4) + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/3/made diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher_shared_with_80_or_over.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher_shared_with_80_or_over.yaml new file mode 100644 index 0000000000..af2acac311 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher_shared_with_80_or_over.yaml @@ -0,0 +1,11 @@ +description: Winter Fuel Payment for a person aged 80 or over who is not on a relevant benefit and lives with another person entitled to a payment who has also reached 80. The 2024 regulations had no shared amounts; only people on a relevant benefit (or tax credits) were entitled that winter, so this amount does not arise for it. +values: + 2000-01-01: 150 +metadata: + unit: currency-GBP + label: Winter Fuel Payment shared amount 80 and over, living with 80 and over + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2000, reg. 2(2)(b)(i) (as in force for the 2023 qualifying week) + href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2/2023-09-18 + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 3(6) + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/3/made diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher_shared_with_under_80.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher_shared_with_under_80.yaml new file mode 100644 index 0000000000..68b408af29 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/higher_shared_with_under_80.yaml @@ -0,0 +1,11 @@ +description: Winter Fuel Payment for a person aged 80 or over who is not on a relevant benefit and lives with another person entitled to a payment, none of whom has reached 80. The 2024 regulations had no shared amounts; only people on a relevant benefit (or tax credits) were entitled that winter, so this amount does not arise for it. +values: + 2000-01-01: 200 +metadata: + unit: currency-GBP + label: Winter Fuel Payment shared amount 80 and over, living with under 80 + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2000, reg. 2(2)(b) (as in force for the 2023 qualifying week) + href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2/2023-09-18 + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 3(5) + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/3/made diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/lower.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/lower.yaml index a1b9dbcc2c..c04635bea4 100644 --- a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/lower.yaml +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/lower.yaml @@ -1,8 +1,13 @@ +description: Winter Fuel Payment for a person under 80, unless a shared amount applies or they are on a relevant benefit with a partner aged 80 or over. values: 2000-01-01: 200 metadata: unit: currency-GBP label: Winter Fuel Payment lower amount reference: - - href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2 - title: The Social Fund Winter Fuel Payment Regulations 2000 \ No newline at end of file + - title: The Social Fund Winter Fuel Payment Regulations 2000, reg. 2(1)(i) (as in force for the 2023 qualifying week) + href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2/2023-09-18 + - title: The Social Fund Winter Fuel Payment Regulations 2024, reg. 3(c) + href: https://www.legislation.gov.uk/uksi/2024/869/regulation/3/made + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 3(1) + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/3/made diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/lower_shared.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/lower_shared.yaml new file mode 100644 index 0000000000..abf65610c1 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/amount/lower_shared.yaml @@ -0,0 +1,11 @@ +description: Winter Fuel Payment for a person under 80 who is not on a relevant benefit and lives with another person entitled to a payment. The 2024 regulations had no shared amounts; only people on a relevant benefit (or tax credits) were entitled that winter, so this amount does not arise for it. +values: + 2000-01-01: 100 +metadata: + unit: currency-GBP + label: Winter Fuel Payment shared amount under 80 + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2000, reg. 2(1)(ii) (as in force for the 2023 qualifying week) + href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2/2023-09-18 + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 3(2) + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/3/made diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/countries.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/countries.yaml new file mode 100644 index 0000000000..cae991ddc8 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/countries.yaml @@ -0,0 +1,30 @@ +description: Countries whose residents of pensionable age receive the Winter Fuel Payment. Great Britain until the 2023 qualifying week; England and Wales from the 2024 qualifying week, when Scotland's Pension Age Winter Heating Payment replaced it. Northern Ireland pays its own Winter Fuel Payment under regulations that mirror Great Britain's, which these parameters stand in for. +values: + 2000-01-01: + value: + - ENGLAND + - WALES + - SCOTLAND + - NORTHERN_IRELAND + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2000, reg. 2(1)(a)(i) (ordinarily resident in Great Britain) + href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2/2023-09-18 + - title: The Social Fund Winter Fuel Payment Regulations (Northern Ireland) 2000 + href: https://www.legislation.gov.uk/nisr/2000/91/contents + # Dated 1 January so that the year's value is the one for its September + # qualifying week; SI 2024/869 came into force on 16 September 2024. + 2024-01-01: + value: + - ENGLAND + - WALES + - NORTHERN_IRELAND + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2024, reg. 1(2) and 2(2)(a) (England and Wales) + href: https://www.legislation.gov.uk/uksi/2024/869/regulation/2/made + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 1(2) and 2(b) (England and Wales) + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/2/made + - title: The Social Fund Winter Fuel Payment Regulations (Northern Ireland) 2025, reg. 2(b) + href: https://www.legislation.gov.uk/nisr/2025/142/regulation/2 +metadata: + unit: list + label: Winter Fuel Payment countries diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/relevant_benefits.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/relevant_benefits.yaml new file mode 100644 index 0000000000..9c8e5972a1 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/relevant_benefits.yaml @@ -0,0 +1,37 @@ +description: Benefits that count as a relevant benefit for the Winter Fuel Payment. A person on one receives the full amount whoever they live with, and the partner of the person paid is not entitled. For the 2024 qualifying week only, a relevant benefit (or a tax credit award of at least £26) was also a condition of entitlement. +values: + 2000-01-01: + value: + - is_on_pension_credit + - is_on_income_based_jsa + - is_on_income_related_esa + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2000, reg. 2(1)(ii) and 3(1)(a)(i) (as in force for the 2023 qualifying week) + href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2/2023-09-18 + # Dated 1 January so that the year's value is the one for its September + # qualifying week; SI 2024/869 came into force on 16 September 2024. + 2024-01-01: + value: + - is_on_income_support + - is_on_income_based_jsa + - is_on_pension_credit + - is_on_income_related_esa + - is_on_universal_credit + - is_on_tax_credits + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2024, reg. 2(2)(b) and 2(4) "relevant benefit" + href: https://www.legislation.gov.uk/uksi/2024/869/regulation/2/made + # SI 2025/969 came into force on 15 September 2025. + 2025-01-01: + value: + - is_on_income_support + - is_on_income_based_jsa + - is_on_pension_credit + - is_on_income_related_esa + - is_on_universal_credit + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 1(3) "relevant benefit" + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/1/made +metadata: + unit: list + label: Winter Fuel Payment relevant benefits diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/active.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/active.yaml new file mode 100644 index 0000000000..b3c01e413c --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/active.yaml @@ -0,0 +1,14 @@ +description: Whether the Pension Age Winter Heating Payment is paid. It replaced the Winter Fuel Payment in Scotland from the 2024 qualifying week. +values: + 2000-01-01: false + # Dated 1 January so that the year's value is the one for its September + # qualifying week; SSI 2024/351 came into force on 20 November 2024 and + # applies to the qualifying week in September 2024. + 2024-01-01: + value: true + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 1 and 5 + href: https://www.legislation.gov.uk/ssi/2024/351/made +metadata: + unit: bool + label: PAWHP active diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/base.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/base.yaml deleted file mode 100644 index fb6ff7740f..0000000000 --- a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/base.yaml +++ /dev/null @@ -1,15 +0,0 @@ -description: Pension Age Winter Heating Payment universal base award paid to Scottish State-Pension-age households that do not qualify for a means-tested PAWHP. £0 in winter 2024-25 (PAWHP was means-tested only); £100 from winter 2025-26 following the Scottish Government's reinstatement of a universal element. -values: - 2024-01-01: 0 - 2025-01-01: 100 -metadata: - unit: currency-GBP - label: PAWHP universal base amount - uprating: gov.economic_assumptions.indices.obr.consumer_price_index - reference: - - title: The Pension Age Winter Heating Payment (Scotland) Regulations 2024 (SSI 2024/250) - href: https://www.legislation.gov.uk/ssi/2024/250/contents/made - - title: Scottish Government — Pension Age Winter Heating Payment to be paid to all pensioners (June 2025) - href: https://www.gov.scot/news/winter-fuel-payment-to-be-paid-to-all-pensioners/ - - title: Social Security Scotland — Pension Age Winter Heating Payment - href: https://www.mygov.scot/pension-age-winter-heating-payment diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher.yaml index 800ed6c7fc..e94ee4bfaf 100644 --- a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher.yaml +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher.yaml @@ -1,12 +1,23 @@ -description: Pension Age Winter Heating Payment higher (80-and-over) award paid to Scottish households containing someone aged 80 or over who receives a qualifying benefit. +description: Pension Age Winter Heating Payment for an individual aged 80 or over, or entitled to a relevant benefit with a partner aged 80 or over, unless (from the 2025 qualifying week) they are not entitled to a relevant benefit and live with another entitled individual. values: - 2024-01-01: 300 + 2024-01-01: + value: 300 + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 10(a) and (b) (as made) + href: https://www.legislation.gov.uk/ssi/2024/351/regulation/10/made + 2025-04-01: + value: 305.10 + reference: + - title: The Social Security (Up-rating) (Miscellaneous Amendments) (Scotland) Regulations 2025 (SSI 2025/100), reg. 15(a) and (b) + href: https://www.legislation.gov.uk/ssi/2025/100/regulation/15/made + - title: The Winter Heating Assistance (Pension Age) (Scotland) Amendment Regulations 2025 (SSI 2025/282), reg. 9 (new reg. 10(4)(a) and (6)(a)) + href: https://www.legislation.gov.uk/ssi/2025/282/regulation/9/made + 2026-04-01: + value: 316.70 + reference: + - title: The Social Security (Up-rating) (Miscellaneous Amendments) (Scotland) Regulations 2026 (SSI 2026/170), reg. 15(3)(a) and (e) + href: https://www.legislation.gov.uk/ssi/2026/170/regulation/15/made metadata: unit: currency-GBP label: PAWHP higher (80+) amount uprating: gov.economic_assumptions.indices.obr.consumer_price_index - reference: - - title: The Pension Age Winter Heating Payment (Scotland) Regulations 2024 (SSI 2024/250) - href: https://www.legislation.gov.uk/ssi/2024/250/contents/made - - title: Social Security Scotland — Pension Age Winter Heating Payment - href: https://www.mygov.scot/pension-age-winter-heating-payment diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher_shared_with_80_or_over.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher_shared_with_80_or_over.yaml new file mode 100644 index 0000000000..d1524024fb --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher_shared_with_80_or_over.yaml @@ -0,0 +1,18 @@ +description: Pension Age Winter Heating Payment for an individual aged 80 or over who is not entitled to a relevant benefit and lives with another entitled individual who has reached 80. Applies from the 2025 qualifying week. Regulation 10(6) does not say which of (b) and (c) applies to someone living with entitled individuals both under and over 80; the model applies (c), as SI 2025/969 reg. 3(5) does for the Winter Fuel Payment. +values: + # SSI 2025/282 came into force on 11 October 2025 and applies to the + # qualifying week in September 2025. + 2025-01-01: + value: 152.55 + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Amendment Regulations 2025 (SSI 2025/282), reg. 9 (new reg. 10(6)(c)) + href: https://www.legislation.gov.uk/ssi/2025/282/regulation/9/made + 2026-04-01: + value: 158.35 + reference: + - title: The Social Security (Up-rating) (Miscellaneous Amendments) (Scotland) Regulations 2026 (SSI 2026/170), reg. 15(3)(g) + href: https://www.legislation.gov.uk/ssi/2026/170/regulation/15/made +metadata: + unit: currency-GBP + label: PAWHP shared amount 80 and over, living with 80 and over + uprating: gov.economic_assumptions.indices.obr.consumer_price_index diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher_shared_with_under_80.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher_shared_with_under_80.yaml new file mode 100644 index 0000000000..22941d5904 --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/higher_shared_with_under_80.yaml @@ -0,0 +1,18 @@ +description: Pension Age Winter Heating Payment for an individual aged 80 or over who is not entitled to a relevant benefit and lives with another entitled individual, none of whom has reached 80. Applies from the 2025 qualifying week. +values: + # SSI 2025/282 came into force on 11 October 2025 and applies to the + # qualifying week in September 2025. + 2025-01-01: + value: 203.40 + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Amendment Regulations 2025 (SSI 2025/282), reg. 9 (new reg. 10(6)(b)) + href: https://www.legislation.gov.uk/ssi/2025/282/regulation/9/made + 2026-04-01: + value: 211.15 + reference: + - title: The Social Security (Up-rating) (Miscellaneous Amendments) (Scotland) Regulations 2026 (SSI 2026/170), reg. 15(3)(f) + href: https://www.legislation.gov.uk/ssi/2026/170/regulation/15/made +metadata: + unit: currency-GBP + label: PAWHP shared amount 80 and over, living with under 80 + uprating: gov.economic_assumptions.indices.obr.consumer_price_index diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/lower.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/lower.yaml index 4ef7ba8026..cb4748ec94 100644 --- a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/lower.yaml +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/lower.yaml @@ -1,12 +1,23 @@ -description: Pension Age Winter Heating Payment lower (under-80) award paid to Scottish households containing someone of State Pension age who receives a qualifying benefit. +description: Pension Age Winter Heating Payment for an individual under 80 who is entitled to a relevant benefit and whose partner is under 80, or (from the 2025 qualifying week) who is not and does not live with any other entitled individual. values: - 2024-01-01: 200 + 2024-01-01: + value: 200 + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 10(c) (as made) + href: https://www.legislation.gov.uk/ssi/2024/351/regulation/10/made + 2025-04-01: + value: 203.40 + reference: + - title: The Social Security (Up-rating) (Miscellaneous Amendments) (Scotland) Regulations 2025 (SSI 2025/100), reg. 15(c) + href: https://www.legislation.gov.uk/ssi/2025/100/regulation/15/made + - title: The Winter Heating Assistance (Pension Age) (Scotland) Amendment Regulations 2025 (SSI 2025/282), reg. 9 (new reg. 10(4)(b) and (5)(a)) + href: https://www.legislation.gov.uk/ssi/2025/282/regulation/9/made + 2026-04-01: + value: 211.15 + reference: + - title: The Social Security (Up-rating) (Miscellaneous Amendments) (Scotland) Regulations 2026 (SSI 2026/170), reg. 15(3)(b) and (c) + href: https://www.legislation.gov.uk/ssi/2026/170/regulation/15/made metadata: unit: currency-GBP label: PAWHP lower (under-80) amount uprating: gov.economic_assumptions.indices.obr.consumer_price_index - reference: - - title: The Pension Age Winter Heating Payment (Scotland) Regulations 2024 (SSI 2024/250) - href: https://www.legislation.gov.uk/ssi/2024/250/contents/made - - title: Social Security Scotland — Pension Age Winter Heating Payment - href: https://www.mygov.scot/pension-age-winter-heating-payment \ No newline at end of file diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/lower_shared.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/lower_shared.yaml new file mode 100644 index 0000000000..e75c704469 --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/amount/lower_shared.yaml @@ -0,0 +1,18 @@ +description: Pension Age Winter Heating Payment for an individual under 80 who is not entitled to a relevant benefit and lives with another entitled individual. The regulations as made for the 2024 qualifying week had no shared amounts (only individuals entitled to a relevant benefit were entitled), so this amount applies from the 2025 qualifying week. +values: + # SSI 2025/282 came into force on 11 October 2025 and applies to the + # qualifying week in September 2025. + 2025-01-01: + value: 101.70 + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Amendment Regulations 2025 (SSI 2025/282), reg. 9 (new reg. 10(5)(b)) + href: https://www.legislation.gov.uk/ssi/2025/282/regulation/9/made + 2026-04-01: + value: 105.55 + reference: + - title: The Social Security (Up-rating) (Miscellaneous Amendments) (Scotland) Regulations 2026 (SSI 2026/170), reg. 15(3)(d) + href: https://www.legislation.gov.uk/ssi/2026/170/regulation/15/made +metadata: + unit: currency-GBP + label: PAWHP shared amount under 80 + uprating: gov.economic_assumptions.indices.obr.consumer_price_index diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/higher_age_requirement.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/higher_age_requirement.yaml index 86bd501762..42328f9bb5 100644 --- a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/higher_age_requirement.yaml +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/higher_age_requirement.yaml @@ -1,9 +1,9 @@ -description: Age at which a qualifying household becomes entitled to the higher PAWHP award. +description: Age from which an individual receives the higher Pension Age Winter Heating Payment amounts. values: 2000-01-01: 80 metadata: unit: year label: PAWHP higher amount age requirement reference: - - title: The Pension Age Winter Heating Payment (Scotland) Regulations 2024 (SSI 2024/250), reg. 5 - href: https://www.legislation.gov.uk/ssi/2024/250/regulation/5/made + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 10 (as made and as substituted by SSI 2025/282) + href: https://www.legislation.gov.uk/ssi/2024/351/regulation/10 diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/relevant_benefits.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/relevant_benefits.yaml new file mode 100644 index 0000000000..ae18d3969b --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/relevant_benefits.yaml @@ -0,0 +1,30 @@ +description: Benefits that count as a relevant benefit for the Pension Age Winter Heating Payment. For the 2024 qualifying week one was a condition of entitlement; from the 2025 qualifying week a person on one receives the full amount whoever they live with. A member of a couple is treated as entitled to a relevant benefit when the other member is, and the other member of a couple on one is not paid. +values: + 2024-01-01: + value: + - is_on_pension_credit + - is_on_income_based_jsa + - is_on_income_related_esa + - is_on_income_support + - is_on_universal_credit + - is_on_tax_credits + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 2 "relevant benefit" and reg. 7 (as made) + href: https://www.legislation.gov.uk/ssi/2024/351/regulation/2/made + # SSI 2025/195 reg. 9(2) omitted the tax credit words from 23 June 2025, + # before the qualifying week in September 2025. + 2025-01-01: + value: + - is_on_pension_credit + - is_on_income_based_jsa + - is_on_income_related_esa + - is_on_income_support + - is_on_universal_credit + reference: + - title: The Social Security (Miscellaneous Amendment) (Scotland) Regulations 2025 (SSI 2025/195), reg. 9(2) + href: https://www.legislation.gov.uk/ssi/2025/195/regulation/9/made + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 2 "relevant benefit" and reg. 2A (as amended) + href: https://www.legislation.gov.uk/ssi/2024/351/regulation/2 +metadata: + unit: list + label: PAWHP relevant benefits diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/require_benefits.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/require_benefits.yaml index ce39cde0d4..0f71bccc10 100644 --- a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/require_benefits.yaml +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/require_benefits.yaml @@ -1,16 +1,19 @@ -description: Whether receipt of a qualifying means-tested benefit is required for PAWHP entitlement. +description: Whether a relevant benefit is a condition of entitlement to the Pension Age Winter Heating Payment. It was for the 2024 qualifying week; from the 2025 qualifying week everyone of pensionable age resident in Scotland is entitled. values: 2000-01-01: false 2024-01-01: value: true reference: - - title: The Pension Age Winter Heating Payment (Scotland) Regulations 2024 (SSI 2024/250), reg. 4 - href: https://www.legislation.gov.uk/ssi/2024/250/regulation/4/made + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 5(a)(ii) and 7 (as made) + href: https://www.legislation.gov.uk/ssi/2024/351/regulation/7/made + # SSI 2025/282 came into force on 11 October 2025 and applies to the + # qualifying week in September 2025 (its reg. 17 keeps the 2024 week on + # the regulations as made). + 2025-01-01: + value: false + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Amendment Regulations 2025 (SSI 2025/282), reg. 4, 5 and 17 + href: https://www.legislation.gov.uk/ssi/2025/282/made metadata: unit: bool label: PAWHP means-tested benefits requirement - reference: - - title: The Pension Age Winter Heating Payment (Scotland) Regulations 2024 (SSI 2024/250) - href: https://www.legislation.gov.uk/ssi/2024/250/contents/made - - title: Scottish Government — Pension Age Winter Heating Payment to be paid to all pensioners (June 2025) - href: https://www.gov.scot/news/winter-fuel-payment-to-be-paid-to-all-pensioners/ diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/state_pension_age_requirement.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/state_pension_age_requirement.yaml index b8dbdfd15a..571699a326 100644 --- a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/state_pension_age_requirement.yaml +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/state_pension_age_requirement.yaml @@ -1,9 +1,9 @@ -description: Whether individuals must be of State Pension Age to qualify for the PAWHP. +description: Whether individuals must have reached pensionable age to qualify for the Pension Age Winter Heating Payment. values: 2000-01-01: true metadata: unit: bool label: PAWHP State Pension age requirement reference: - - title: The Pension Age Winter Heating Payment (Scotland) Regulations 2024 (SSI 2024/250), reg. 3 - href: https://www.legislation.gov.uk/ssi/2024/250/regulation/3/made + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 6 + href: https://www.legislation.gov.uk/ssi/2024/351/regulation/6 diff --git a/policyengine_uk/programs.yaml b/policyengine_uk/programs.yaml index f83e4acdd9..9dddf799d9 100644 --- a/policyengine_uk/programs.yaml +++ b/policyengine_uk/programs.yaml @@ -352,6 +352,7 @@ programs: variable: winter_fuel_payment parameter_prefix: gov.dwp.winter_fuel_payment verified_start_year: 2022 + notes: Great Britain to winter 2023-24, then England and Wales; Northern Ireland's mirror scheme uses the same parameters - id: ssmg name: Sure Start Maternity Grant @@ -581,3 +582,15 @@ programs: variable: scottish_child_payment parameter_prefix: gov.social_security_scotland.scottish_child_payment verified_start_year: 2022 + + - id: pawhp + name: Pension Age Winter Heating Payment + full_name: Pension Age Winter Heating Payment + category: Benefits + agency: Social Security Scotland + status: complete + coverage: Scotland + variable: pension_age_winter_heating_payment + parameter_prefix: gov.social_security_scotland.pawhp + verified_start_year: 2024 + notes: Replaced the Winter Fuel Payment in Scotland from winter 2024-25 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_allowance.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_allowance.yaml index 8167a7bf74..48390abfe4 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_allowance.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_allowance.yaml @@ -18,8 +18,10 @@ output: winter_fuel_allowance: 300 -- name: Household in Scotland gets no WFA (handled by Scottish Government) - period: 2023 +- name: Household in Scotland gets no WFA from the 2024 qualifying week (Pension Age Winter Heating Payment instead) + # SI 2024/869 and SI 2025/969 extend to England and Wales; SI 2000/729 + # covered Great Britain up to the 2023 qualifying week. + period: 2024 absolute_error_margin: 1 input: age: 67 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml new file mode 100644 index 0000000000..46a029bf5b --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml @@ -0,0 +1,480 @@ +# Winter Fuel Payment: who is on a relevant benefit, and what each person is +# paid. Expected values are worked from the regulations: +# - 2023 qualifying week: SI 2000/729 regs 2 and 3 (as in force 18.9.2023); +# - 2024: SI 2024/869 regs 2 to 4; +# - 2025 on: SI 2025/969 regs 1(3) and 2 to 4. +# Pension Credit and the other benefit-unit awards are entered on the benefit +# unit so that each case turns only on the receipt test. From 2025 the model +# stands in for the winter fuel payment charge (ITEPA 2003 s.681I) with its +# income test: a household passes when a member of pensionable age has total +# income below £35,000. + +- name: 2024 a non-dependant's income-related ESA does not entitle the pensioner + # The pensioner is not entitled to a relevant benefit, nor is any partner, + # so reg. 2(2)(b) is not met: no payment. The son's ESA is his own award + # in his own benefit unit and he is under pensionable age. + period: 2024 + input: + people: + pensioner: + age: 70 + total_income: 15_000 + son: + age: 40 + benunits: + pensioner_unit: + members: [pensioner] + pension_credit: 0 + tax_credits: 0 + son_unit: + members: [son] + esa_income: 4_000 + households: + household: + members: [pensioner, son] + country: ENGLAND + output: + is_on_winter_fuel_payment_relevant_benefit: [false, true] + winter_fuel_payment_eligible: [false, false] + winter_fuel_payment: [0, 0] + winter_fuel_allowance: 0 + +- name: 2024 a non-dependant who is neither claimant nor partner does not pass their ESA to the pensioner + # The adult is a member of the pensioner's benefit unit but neither the + # claimant nor a partner, so their award is their own (reg. 2(5) extends + # entitlement only to the other member of a couple). + period: 2024 + input: + people: + pensioner: + age: 70 + total_income: 15_000 + is_claimant_or_partner: true + adult: + age: 40 + is_claimant_or_partner: false + esa_income_reported: 4_000 + benunits: + unit: + members: [pensioner, adult] + pension_credit: 0 + tax_credits: 0 + households: + household: + members: [pensioner, adult] + country: ENGLAND + output: + is_on_income_related_esa: [false, true] + is_on_winter_fuel_payment_relevant_benefit: [false, true] + winter_fuel_payment: [0, 0] + winter_fuel_allowance: 0 + +- name: 2024 a pensioner on Pension Credit living with a non-dependant gets £200 + # Reg. 2(2)(b)(i) met through Pension Credit; under 80 and single: reg. 3(c). + period: 2024 + input: + people: + pensioner: + age: 70 + son: + age: 40 + benunits: + pensioner_unit: + members: [pensioner] + pension_credit: 1_000 + son_unit: + members: [son] + esa_income: 4_000 + households: + household: + members: [pensioner, son] + country: ENGLAND + output: + winter_fuel_payment: [200, 0] + winter_fuel_allowance: 200 + +- name: 2024 a Pension Credit couple gets one payment of £300 when one partner is 80 + # Both are entitled (reg. 2(5)); £300 for the 80-year-old (reg. 3(a)) or for + # the partner of one (reg. 3(b)); once paid to one member, the other is not + # entitled (reg. 4(1)(a)(i)). The model pays the elder. + period: 2024 + input: + people: + partner_1: + age: 76 + partner_2: + age: 82 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 1_500 + households: + household: + members: [partner_1, partner_2] + country: WALES + output: + is_on_winter_fuel_payment_relevant_benefit: [true, true] + winter_fuel_payment_eligible: [false, true] + winter_fuel_payment: [0, 300] + winter_fuel_allowance: 300 + +- name: 2024 two pensioners in separate benefit units each on Pension Credit get a payment each + # Each is a person entitled under reg. 2 and neither is the other's + # partner, so reg. 4(1)(a)(i) does not apply: 2 x £200. + period: 2024 + input: + people: + sister_1: + age: 72 + sister_2: + age: 75 + benunits: + unit_1: + members: [sister_1] + pension_credit: 1_000 + unit_2: + members: [sister_2] + pension_credit: 1_000 + households: + household: + members: [sister_1, sister_2] + country: ENGLAND + output: + winter_fuel_payment: [200, 200] + winter_fuel_allowance: 400 + +- name: 2024 a pensioner in a mixed-age couple on Universal Credit gets £200 + # Universal Credit is a relevant benefit (reg. 2(4)); the pensioner is + # entitled through the couple's joint award (reg. 2(5)). + period: 2024 + input: + people: + pensioner: + age: 67 + total_income: 9_000 + partner: + age: 60 + benunits: + couple: + members: [pensioner, partner] + universal_credit: 2_000 + pension_credit: 0 + households: + household: + members: [pensioner, partner] + country: ENGLAND + output: + is_on_universal_credit: [true, true] + winter_fuel_payment: [200, 0] + winter_fuel_allowance: 200 + +- name: 2024 a pensioner with a tax credit award of at least £26 gets £200 + # Reg. 2(2)(b)(ii): an award of child tax credit or working tax credit of + # not less than £26 for 2024-25. + period: 2024 + input: + people: + pensioner: + age: 67 + total_income: 9_000 + benunits: + unit: + members: [pensioner] + tax_credits: 500 + pension_credit: 0 + households: + household: + members: [pensioner] + country: ENGLAND + output: + is_on_tax_credits: [true] + winter_fuel_payment: [200] + winter_fuel_allowance: 200 + +- name: 2025 a non-dependant's income-related ESA does not exempt the pensioner from the income test + # Under SI 2025/969 the pensioner is entitled (reg. 2) to £200 (reg. 3(1)), + # but the winter fuel payment charge recovers it: total income over + # £35,000 and not entitled to a relevant benefit (ITEPA 2003 s.681I(1) and + # (4)). The son's ESA is his own award, so it does not exempt the pensioner. + period: 2025 + input: + people: + pensioner: + age: 70 + total_income: 50_000 + son: + age: 40 + benunits: + pensioner_unit: + members: [pensioner] + pension_credit: 0 + son_unit: + members: [son] + esa_income: 4_000 + households: + household: + members: [pensioner, son] + country: ENGLAND + output: + is_on_winter_fuel_payment_relevant_benefit: [false, true] + winter_fuel_payment: [0, 0] + winter_fuel_allowance: 0 + +- name: 2025 a pensioner under the income limit living with a non-dependant on ESA gets £200 + # Reg. 3(1): under 80 and not living with another person entitled (the son + # is under pensionable age). + period: 2025 + input: + people: + pensioner: + age: 70 + total_income: 15_000 + son: + age: 40 + benunits: + pensioner_unit: + members: [pensioner] + pension_credit: 0 + son_unit: + members: [son] + esa_income: 4_000 + households: + household: + members: [pensioner, son] + country: ENGLAND + output: + winter_fuel_payment: [200, 0] + winter_fuel_allowance: 200 + +- name: 2025 a pensioner on Pension Credit and one who is not, in separate benefit units + # The Pension Credit recipient: reg. 3(1), £200 (reg. 3(2) needs no relevant + # benefit). The other: no relevant benefit and living with a person + # entitled, reg. 3(2), £100. + period: 2025 + input: + people: + on_pension_credit: + age: 70 + not_on_pension_credit: + age: 72 + total_income: 20_000 + benunits: + unit_1: + members: [on_pension_credit] + pension_credit: 1_000 + unit_2: + members: [not_on_pension_credit] + pension_credit: 0 + households: + household: + members: [on_pension_credit, not_on_pension_credit] + country: ENGLAND + output: + winter_fuel_payment: [200, 100] + winter_fuel_allowance: 300 + +- name: 2025 an 85-year-old not on Pension Credit living with a 70-year-old who is + # The 70-year-old: reg. 3(1), £200. The 85-year-old: no relevant benefit, + # living with an entitled person under 80, reg. 3(5), £200. + period: 2025 + input: + people: + on_pension_credit: + age: 70 + aged_85: + age: 85 + total_income: 20_000 + benunits: + unit_1: + members: [on_pension_credit] + pension_credit: 1_000 + unit_2: + members: [aged_85] + pension_credit: 0 + households: + household: + members: [on_pension_credit, aged_85] + country: ENGLAND + output: + winter_fuel_payment: [200, 200] + winter_fuel_allowance: 400 + +- name: 2025 a couple aged 70 and 82 not on a relevant benefit gets £300 between them + # 70: reg. 3(2), £100. 82: living with an entitled person under 80, + # reg. 3(5), £200. + period: 2025 + input: + people: + partner_1: + age: 70 + total_income: 12_000 + partner_2: + age: 82 + total_income: 12_000 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 0 + households: + household: + members: [partner_1, partner_2] + country: ENGLAND + output: + winter_fuel_payment: [100, 200] + winter_fuel_allowance: 300 + +- name: 2025 a couple both over 80 not on a relevant benefit gets £150 each + # Reg. 3(6)(b)(i). + period: 2025 + input: + people: + partner_1: + age: 82 + total_income: 12_000 + partner_2: + age: 85 + total_income: 12_000 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 0 + households: + household: + members: [partner_1, partner_2] + country: ENGLAND + output: + winter_fuel_payment: [150, 150] + winter_fuel_allowance: 300 + +- name: 2025 an over-80 living with entitled people both over and under 80 gets £150 + # Reg. 3(5) is subject to reg. 3(6): the couple aged 82 and 85 each get + # £150; their 72-year-old lodger gets £100 (reg. 3(2)). + period: 2025 + input: + people: + partner_1: + age: 82 + total_income: 12_000 + partner_2: + age: 85 + total_income: 12_000 + lodger: + age: 72 + total_income: 12_000 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 0 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [partner_1, partner_2, lodger] + country: ENGLAND + output: + winter_fuel_payment: [150, 150, 100] + winter_fuel_allowance: 400 + +- name: 2025 a Pension Credit couple gets one payment + # The partner of the person paid is not entitled (reg. 4(2)(a)); the + # person paid is under 80 with a partner under 80: reg. 3(1), £200. + period: 2025 + input: + people: + partner_1: + age: 70 + partner_2: + age: 72 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 1_000 + households: + household: + members: [partner_1, partner_2] + country: ENGLAND + output: + winter_fuel_payment_eligible: [false, true] + winter_fuel_payment: [0, 200] + winter_fuel_allowance: 200 + +- name: 2023 Scotland's pensioners get the Winter Fuel Payment, not PAWHP + # SI 2000/729 reg. 2(1)(a)(i): ordinarily resident in Great Britain. The + # Pension Age Winter Heating Payment starts with the 2024 qualifying week. + period: 2023 + input: + people: + pensioner: + age: 70 + total_income: 15_000 + benunits: + unit: + members: [pensioner] + pension_credit: 0 + households: + household: + members: [pensioner] + country: SCOTLAND + output: + winter_fuel_payment: [200] + winter_fuel_allowance: 200 + pawhp: 0 + +- name: 2023 a pensioner on Pension Credit and one who is not, in separate benefit units + # SI 2000/729: the Pension Credit recipient, reg. 2(1)(i), £200; the other, + # reg. 2(1)(ii)(aa), £100. + period: 2023 + input: + people: + on_pension_credit: + age: 70 + not_on_pension_credit: + age: 72 + total_income: 20_000 + benunits: + unit_1: + members: [on_pension_credit] + pension_credit: 1_000 + unit_2: + members: [not_on_pension_credit] + pension_credit: 0 + households: + household: + members: [on_pension_credit, not_on_pension_credit] + country: ENGLAND + output: + winter_fuel_payment: [200, 100] + winter_fuel_allowance: 300 + +- name: 2023 Universal Credit is not a relevant benefit under the 2000 regulations + # Reg. 2(1)(ii) lists Pension Credit, income-based JSA and income-related + # ESA. Two pensioners in separate benefit units, one on Universal Credit: + # both get the shared £100. + period: 2023 + input: + people: + on_universal_credit: + age: 67 + total_income: 9_000 + partner: + age: 60 + other_pensioner: + age: 72 + total_income: 20_000 + benunits: + couple: + members: [on_universal_credit, partner] + universal_credit: 2_000 + pension_credit: 0 + unit_2: + members: [other_pensioner] + pension_credit: 0 + households: + household: + members: [on_universal_credit, partner, other_pensioner] + country: ENGLAND + output: + is_on_winter_fuel_payment_relevant_benefit: [false, false, false] + winter_fuel_payment: [100, 0, 100] + winter_fuel_allowance: 200 diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml new file mode 100644 index 0000000000..e15b4bc996 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml @@ -0,0 +1,366 @@ +# Pension Age Winter Heating Payment: who is on a relevant benefit, and what +# each individual is paid. Expected values are worked from the Winter +# Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI +# 2024/351): +# - 2024 qualifying week: as made (regs 5, 7, 9 and 10); +# - 2025: as amended by SSI 2025/282 (no relevant benefit condition; new +# reg. 10) with the amounts uprated by SSI 2025/100; +# - 2026: amounts uprated by SSI 2026/170. +# Pension Credit and the other benefit-unit awards are entered on the benefit +# unit so that each case turns only on the receipt test. + +- name: 2024 a non-dependant's income-related ESA does not entitle the pensioner + # Reg. 7: the pensioner is not entitled to a relevant benefit, nor is any + # partner (reg. 7(2)); the son's ESA is his own award. No payment. + period: 2024 + input: + people: + pensioner: + age: 70 + son: + age: 40 + benunits: + pensioner_unit: + members: [pensioner] + pension_credit: 0 + tax_credits: 0 + son_unit: + members: [son] + esa_income: 4_000 + households: + household: + members: [pensioner, son] + country: SCOTLAND + output: + is_on_pawhp_relevant_benefit: [false, true] + pawhp_eligible: [false, false] + pension_age_winter_heating_payment: [0, 0] + pawhp: 0 + winter_fuel_allowance: 0 + +- name: 2024 a pensioner on Pension Credit gets £200 + # Regs 5 to 8 met; reg. 10(c). + period: 2024 + input: + people: + pensioner: + age: 70 + son: + age: 40 + benunits: + pensioner_unit: + members: [pensioner] + pension_credit: 1_000 + son_unit: + members: [son] + esa_income: 4_000 + households: + household: + members: [pensioner, son] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [200, 0] + pawhp: 200 + +- name: 2024 a Pension Credit couple gets one payment of £300 when one partner is 80 + # Reg. 10(a) and (b); the other member is not entitled once one is paid + # (reg. 9(1)(d)). + period: 2024 + input: + people: + partner_1: + age: 75 + partner_2: + age: 81 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 1_500 + households: + household: + members: [partner_1, partner_2] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [0, 300] + pawhp: 300 + +- name: 2024 a pensioner in a mixed-age couple on Universal Credit gets £200 + # Universal Credit is a relevant benefit (reg. 2); the pensioner is entitled + # through the couple's joint award (reg. 7(2)). + period: 2024 + input: + people: + pensioner: + age: 67 + partner: + age: 60 + benunits: + couple: + members: [pensioner, partner] + universal_credit: 2_000 + pension_credit: 0 + households: + household: + members: [pensioner, partner] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [200, 0] + pawhp: 200 + +- name: 2024 a pensioner with a tax credit award of at least £26 gets £200 + # Reg. 2 "relevant benefit" as made: child tax credit or working tax credit + # where the award is not less than £26 for 2024-25. + period: 2024 + input: + people: + pensioner: + age: 67 + benunits: + unit: + members: [pensioner] + tax_credits: 500 + pension_credit: 0 + households: + household: + members: [pensioner] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [200] + pawhp: 200 + +- name: 2025 a single pensioner not on a relevant benefit gets £203.40 + # Reg. 10(5)(a): under 80, not entitled to a relevant benefit and not + # living with another entitled individual. + period: 2025 + input: + people: + pensioner: + age: 70 + benunits: + unit: + members: [pensioner] + pension_credit: 0 + households: + household: + members: [pensioner] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [203.40] + pawhp: 203.40 + +- name: 2025 a pensioner living with a non-dependant on ESA gets the single amount + # The son is under pensionable age, so not an entitled individual: reg. + # 10(5)(a), £203.40. His ESA is his own award and does not make the + # pensioner entitled to a relevant benefit. + period: 2025 + input: + people: + pensioner: + age: 70 + son: + age: 40 + benunits: + pensioner_unit: + members: [pensioner] + pension_credit: 0 + son_unit: + members: [son] + esa_income: 4_000 + households: + household: + members: [pensioner, son] + country: SCOTLAND + output: + is_on_pawhp_relevant_benefit: [false, true] + pension_age_winter_heating_payment: [203.40, 0] + pawhp: 203.40 + +- name: 2025 a couple under 80 not on a relevant benefit gets £101.70 each + # Reg. 10(5)(b)(i). + period: 2025 + input: + people: + partner_1: + age: 70 + partner_2: + age: 72 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 0 + households: + household: + members: [partner_1, partner_2] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [101.70, 101.70] + pawhp: 203.40 + +- name: 2025 a couple aged 70 and 82 not on a relevant benefit + # 70: reg. 10(5)(b)(i), £101.70. 82: living with an entitled individual + # under 80, reg. 10(6)(b), £203.40. + period: 2025 + input: + people: + partner_1: + age: 70 + partner_2: + age: 82 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 0 + households: + household: + members: [partner_1, partner_2] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [101.70, 203.40] + pawhp: 305.10 + +- name: 2025 a couple both over 80 not on a relevant benefit gets £152.55 each + # Reg. 10(6)(c)(i). + period: 2025 + input: + people: + partner_1: + age: 82 + partner_2: + age: 85 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 0 + households: + household: + members: [partner_1, partner_2] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [152.55, 152.55] + pawhp: 305.10 + +- name: 2025 an 82-year-old on Pension Credit gets £305.10 + # Reg. 10(2) and (4)(a)(i). + period: 2025 + input: + people: + pensioner: + age: 82 + benunits: + unit: + members: [pensioner] + pension_credit: 1_000 + households: + household: + members: [pensioner] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [305.10] + pawhp: 305.10 + +- name: 2025 a pensioner on Pension Credit and one who is not, in separate benefit units + # On Pension Credit: reg. 10(4)(b), £203.40 whoever they live with. The + # other: living with an entitled individual, reg. 10(5)(b)(i), £101.70. + period: 2025 + input: + people: + on_pension_credit: + age: 70 + not_on_pension_credit: + age: 72 + benunits: + unit_1: + members: [on_pension_credit] + pension_credit: 1_000 + unit_2: + members: [not_on_pension_credit] + pension_credit: 0 + households: + household: + members: [on_pension_credit, not_on_pension_credit] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [203.40, 101.70] + pawhp: 305.10 + +- name: 2026 a couple under 80 not on a relevant benefit gets £105.55 each + # Reg. 10(5)(b)(i) as uprated by SSI 2026/170 reg. 15(3)(d): two shared + # amounts make £211.10, 5p less than the single amount of £211.15. + period: 2026 + input: + people: + partner_1: + age: 70 + partner_2: + age: 72 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 0 + households: + household: + members: [partner_1, partner_2] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [105.55, 105.55] + pawhp: 211.10 + +- name: 2026 a single pensioner not on a relevant benefit gets £211.15 + # Reg. 10(5)(a) as uprated by SSI 2026/170 reg. 15(3)(c). + period: 2026 + input: + people: + pensioner: + age: 70 + benunits: + unit: + members: [pensioner] + pension_credit: 0 + households: + household: + members: [pensioner] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [211.15] + pawhp: 211.15 + +- name: 2026 a Pension Credit couple with a partner over 80 gets one payment of £316.70 + # Reg. 2A and 10(4)(a)(ii) as uprated by SSI 2026/170 reg. 15(3)(a); the + # other member is not entitled (reg. 9(d)). + period: 2026 + input: + people: + partner_1: + age: 70 + partner_2: + age: 82 + benunits: + couple: + members: [partner_1, partner_2] + pension_credit: 1_000 + households: + household: + members: [partner_1, partner_2] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [0, 316.70] + pawhp: 316.70 + +- name: The Winter Fuel Payment is not paid in Scotland from 2024 + # SI 2024/869 and SI 2025/969 extend to England and Wales only. + period: 2025 + input: + people: + pensioner: + age: 70 + benunits: + unit: + members: [pensioner] + pension_credit: 0 + households: + household: + members: [pensioner] + country: SCOTLAND + output: + winter_fuel_payment: [0] + winter_fuel_allowance: 0 diff --git a/policyengine_uk/tests/test_winter_heating_payment_properties.py b/policyengine_uk/tests/test_winter_heating_payment_properties.py new file mode 100644 index 0000000000..ac45df6c1b --- /dev/null +++ b/policyengine_uk/tests/test_winter_heating_payment_properties.py @@ -0,0 +1,400 @@ +"""The Winter Fuel Payment and the Pension Age Winter Heating Payment follow +each person's own receipt of a relevant benefit. + +Both schemes entitle and pay a person (SI 2000/729 reg 2; SI 2024/869 regs +2 to 4; SI 2025/969 regs 2 to 4; SSI 2024/351 regs 5, 9 and 10). A person is +on a relevant benefit through their own award or, for the claimant and +partner, their couple's award. Properties: + +- the model matches a reference implementation written directly from the + regulations, person by person, over households of up to three benefit + units, in every country and in the 2023 to 2026 qualifying weeks; +- adding a non-dependant under pensionable age, with or without a relevant + benefit of their own, never changes what anyone else is paid; +- from the 2025 qualifying week, a household whose pension-age members all + belong to one benefit unit receives the full amount for that unit + (higher if anyone is 80 or over), whether or not they are on a relevant + benefit: the shared amounts are halves (or, for two people over 80, a + half of the higher amount each). The one exception is PAWHP from April + 2026, where two shared amounts of £105.55 make £211.10, 5p below £211.15. + +Roles are given explicitly (is_claimant_or_partner) and the benefit-unit +awards are entered directly, so the properties test the winter payments +rather than role inference or the benefits' own means tests. Each example +builds many households in one simulation. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEARS = [2023, 2024, 2025, 2026] +COUNTRIES = ["ENGLAND", "WALES", "SCOTLAND", "NORTHERN_IRELAND"] +# Ages either side of pensionable age (66 to 67 over these years) and of 80. +PENSION_AGES = [67, 70, 79, 80, 85] +AGES = [40, 60, *PENSION_AGES] +INCOMES = [5_000, 20_000, 50_000] +AWARD = 1_000 +# The benefit-unit award a couple can be on, and the input that carries it. +AWARD_INPUTS = { + "PC": "pension_credit", + "IS": "income_support", + "JSA": "jsa_income", + "ESA": "esa_income", + "UC": "universal_credit", + "TC": "tax_credits", +} +# An award reported by a member who is neither claimant nor partner. +OWN_AWARD_INPUTS = { + "PC": "pension_credit_reported", + "ESA": "esa_income_reported", + "UC": "universal_credit_reported", +} + +# Relevant benefits by scheme and qualifying week, as listed in the +# regulations (not read from the model's parameters). +WFP_RELEVANT = { + # SI 2000/729 reg 2(1)(ii) and 3(1)(a)(i). + 2023: {"PC", "JSA", "ESA"}, + # SI 2024/869 reg 2(2)(b) and 2(4). + 2024: {"IS", "JSA", "PC", "ESA", "UC", "TC"}, + # SI 2025/969 reg 1(3). + 2025: {"IS", "JSA", "PC", "ESA", "UC"}, + 2026: {"IS", "JSA", "PC", "ESA", "UC"}, +} +PAWHP_RELEVANT = { + # SSI 2024/351 reg 2 as made. + 2024: {"PC", "JSA", "ESA", "IS", "UC", "TC"}, + # Tax credits omitted by SSI 2025/195 reg 9(2). + 2025: {"PC", "JSA", "ESA", "IS", "UC"}, + 2026: {"PC", "JSA", "ESA", "IS", "UC"}, +} +# (lower, higher, shared under 80, 80+ sharing with under 80 only, +# 80+ sharing with another 80+). +WFP_AMOUNTS = (200, 300, 100, 200, 150) +PAWHP_AMOUNTS = { + # SSI 2024/351 reg 10 as made: no shared amounts (only people on a + # relevant benefit were entitled). + 2024: (200, 300, None, None, None), + # SSI 2025/282 reg 9 (new reg 10); SSI 2025/100 reg 15. + 2025: (203.40, 305.10, 101.70, 203.40, 152.55), + # SSI 2026/170 reg 15(3). + 2026: (211.15, 316.70, 105.55, 211.15, 158.35), +} +INCOME_LIMIT = 35_000 + + +@st.composite +def benefit_units(draw): + """A single person or a couple, with the award they are on, if any.""" + size = draw(st.integers(1, 2)) + adults = [ + { + "age": draw(st.sampled_from(AGES)), + "total_income": draw(st.sampled_from(INCOMES)), + } + for _ in range(size) + ] + award = draw(st.sampled_from([None, None, *AWARD_INPUTS])) + return {"adults": adults, "award": award} + + +@st.composite +def other_members(draw): + """A member of the first benefit unit who is neither claimant nor partner.""" + return { + "age": draw(st.sampled_from(AGES)), + "total_income": draw(st.sampled_from(INCOMES)), + "own_award": draw(st.sampled_from([None, *OWN_AWARD_INPUTS])), + } + + +@st.composite +def households(draw): + return { + "country": draw(st.sampled_from(COUNTRIES)), + "units": draw(st.lists(benefit_units(), min_size=1, max_size=3)), + "other": draw(st.one_of(st.none(), other_members())), + } + + +def household_people(household): + """Each person as (inputs, benefit unit index, claimant or partner).""" + people = [] + for u, unit in enumerate(household["units"]): + for adult in unit["adults"]: + people.append((adult, u, True)) + if u == 0 and household["other"] is not None: + people.append((household["other"], u, False)) + return people + + +def situation(drawn, year): + people, benunits, hh = {}, {}, {} + for h, household in enumerate(drawn): + members = household_people(household) + names = [f"h{h}_p{i}" for i in range(len(members))] + for name, (inputs, _, cp) in zip(names, members): + person = { + "age": {year: inputs["age"]}, + "total_income": {year: inputs["total_income"]}, + "is_claimant_or_partner": {year: cp}, + } + own = inputs.get("own_award") + if own is not None: + person[OWN_AWARD_INPUTS[own]] = {year: AWARD} + people[name] = person + for u, unit in enumerate(household["units"]): + benunit = { + "members": [n for n, m in zip(names, members) if m[1] == u], + **{ + variable: {year: AWARD if unit["award"] == key else 0} + for key, variable in AWARD_INPUTS.items() + }, + } + benunits[f"h{h}_b{u}"] = benunit + hh[f"h{h}"] = {"members": names, "country": {year: household["country"]}} + return {"people": people, "benunits": benunits, "households": hh} + + +def reference_payments(household, year): + """WFP and PAWHP for each person, worked from the regulations. + + Everyone listed in AGES from 67 has reached pensionable age in these + years and everyone under 67 has not. + """ + country = household["country"] + people = household_people(household) + units = household["units"] + + def award_of(person): + inputs, u, cp = person + if cp: + return units[u]["award"] + return inputs.get("own_award") + + def scheme(resident, relevant, means_test, amounts): + lower, higher, shared, shared_80_with_under_80, shared_80_with_80 = amounts + on_relevant = [award_of(p) in relevant for p in people] + qualifies = [resident and p[0]["age"] >= 67 for p in people] + eligible = [q and (r or means_test) for q, r in zip(qualifies, on_relevant)] + # One payment for a couple on a relevant benefit: to the eldest who + # qualifies (first listed if the same age); the other is not entitled. + for u in range(len(units)): + couple = [ + i + for i, p in enumerate(people) + if p[1] == u and p[2] and qualifies[i] and on_relevant[i] + ] + if couple: + payee = max(couple, key=lambda i: (people[i][0]["age"], -i)) + for i in couple: + if i != payee: + eligible[i] = False + payments = [] + for i, (inputs, u, cp) in enumerate(people): + age = inputs["age"] + if not eligible[i]: + payments.append(0.0) + continue + if on_relevant[i]: + partner_80 = cp and any( + q[1] == u and q[2] and q[0]["age"] >= 80 for q in people + ) + payments.append(higher if age >= 80 or partner_80 else lower) + continue + others = [j for j in range(len(people)) if j != i and eligible[j]] + others_80 = [j for j in others if people[j][0]["age"] >= 80] + if age < 80: + payments.append(shared if others else lower) + elif others_80: + payments.append(shared_80_with_80) + elif others: + payments.append(shared_80_with_under_80) + else: + payments.append(higher) + return payments + + if year <= 2023: + wfp_resident = True + wfp_means = True + else: + wfp_resident = country in ("ENGLAND", "WALES", "NORTHERN_IRELAND") + if year == 2024: + wfp_means = False + else: + # The model's stand-in for the winter fuel payment charge. + wfp_means = country in ("ENGLAND", "WALES") and any( + p[0]["age"] >= 67 and p[0]["total_income"] < INCOME_LIMIT + for p in people + ) + wfp = scheme(wfp_resident, WFP_RELEVANT[year], wfp_means, WFP_AMOUNTS) + if year >= 2024 and country == "SCOTLAND": + pawhp = scheme(True, PAWHP_RELEVANT[year], year >= 2025, PAWHP_AMOUNTS[year]) + else: + pawhp = [0.0] * len(people) + return wfp, pawhp + + +SETTINGS = settings( + max_examples=12, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +@SETTINGS +@given(st.sampled_from(YEARS), st.lists(households(), min_size=1, max_size=8)) +def test_payments_match_reference(year, drawn): + sim = Simulation(situation=situation(drawn, year)) + wfp = sim.calculate("winter_fuel_payment", year) + pawhp = sim.calculate("pension_age_winter_heating_payment", year) + wfa_household = sim.calculate("winter_fuel_allowance", year) + pawhp_household = sim.calculate("pawhp", year) + start = 0 + for h, household in enumerate(drawn): + expected_wfp, expected_pawhp = reference_payments(household, year) + n = len(expected_wfp) + got_wfp = wfp[start : start + n] + got_pawhp = pawhp[start : start + n] + assert np.allclose(got_wfp, expected_wfp, atol=0.005), ( + year, + household, + got_wfp, + expected_wfp, + ) + assert np.allclose(got_pawhp, expected_pawhp, atol=0.005), ( + year, + household, + got_pawhp, + expected_pawhp, + ) + assert np.isclose(wfa_household[h], sum(expected_wfp), atol=0.01) + assert np.isclose(pawhp_household[h], sum(expected_pawhp), atol=0.01) + start += n + + +@st.composite +def non_dependants(draw): + """An adult under pensionable age, in a benefit unit of their own or as a + member of the first unit who is neither claimant nor partner, with or + without a relevant benefit of their own.""" + return { + "age": draw(st.sampled_from([19, 25, 40, 60])), + "own_unit": draw(st.booleans()), + "award": draw(st.sampled_from([None, "ESA", "PC", "UC"])), + } + + +def with_non_dependant(household, non_dependant): + """The household with the non-dependant added, and their position.""" + units = [dict(u) for u in household["units"]] + other = household["other"] + if non_dependant["own_unit"]: + units.append( + { + "adults": [{"age": non_dependant["age"], "total_income": 0}], + "award": non_dependant["award"], + } + ) + position = sum(len(u["adults"]) for u in household["units"]) + else: + other = { + "age": non_dependant["age"], + "total_income": 0, + "own_award": non_dependant["award"], + } + # Listed after the first unit's adults. + position = len(household["units"][0]["adults"]) + added = {"country": household["country"], "units": units, "other": other} + return added, position + + +@SETTINGS +@given( + st.sampled_from(YEARS), + st.lists( + st.tuples(households().filter(lambda h: h["other"] is None), non_dependants()), + min_size=1, + max_size=6, + ), +) +def test_non_dependants_never_change_anyone_elses_payment(year, drawn): + without = [household for household, _ in drawn] + added = [with_non_dependant(h, nd) for h, nd in drawn] + with_nd = [household for household, _ in added] + sim = Simulation(situation=situation(without + with_nd, year)) + sizes = [len(household_people(h)) for h in without + with_nd] + starts = np.cumsum([0] + sizes[:-1]) + k = len(drawn) + for variable in ["winter_fuel_payment", "pension_age_winter_heating_payment"]: + values = sim.calculate(variable, year) + for i in range(k): + before = values[starts[i] : starts[i] + sizes[i]] + after = values[starts[k + i] : starts[k + i] + sizes[k + i]] + position = added[i][1] + # The non-dependant is under pensionable age: never paid. + assert after[position] == 0, (variable, year, drawn[i], after) + # Everyone else is paid what they were paid without them. + kept = np.delete(after, position) + assert np.allclose(before, kept, atol=0.005), ( + variable, + year, + drawn[i], + before, + after, + ) + + +@st.composite +def one_unit_households(draw): + """Every pension-age member in one benefit unit, under the income limit.""" + size = draw(st.integers(1, 2)) + adults = [ + {"age": draw(st.sampled_from([60, *PENSION_AGES])), "total_income": 5_000} + for _ in range(size) + ] + if not any(a["age"] >= 67 for a in adults): + adults[0]["age"] = draw(st.sampled_from(PENSION_AGES)) + return { + "country": draw(st.sampled_from(["ENGLAND", "WALES", "SCOTLAND"])), + "units": [ + { + "adults": adults, + "award": draw(st.sampled_from([None, "PC", "UC", "ESA"])), + } + ], + "other": None, + } + + +@SETTINGS +@given( + st.sampled_from([2025, 2026]), + st.lists(one_unit_households(), min_size=1, max_size=8), +) +def test_one_benefit_unit_receives_the_full_amount(year, drawn): + sim = Simulation(situation=situation(drawn, year)) + wfa = sim.calculate("winter_fuel_allowance", year) + pawhp = sim.calculate("pawhp", year) + for h, household in enumerate(drawn): + adults = household["units"][0]["adults"] + any_80 = any(a["age"] >= 80 for a in adults) + if household["country"] == "SCOTLAND": + lower, higher, shared = PAWHP_AMOUNTS[year][:3] + expected = higher if any_80 else lower + pension_age = [a for a in adults if a["age"] >= 67] + relevant = household["units"][0]["award"] is not None + if not relevant and not any_80 and len(pension_age) == 2: + # Two shared amounts: equal to the single amount in 2025, + # 5p less from April 2026. + expected = 2 * shared + assert np.isclose(pawhp[h], expected, atol=0.005), (year, household) + assert abs(pawhp[h] - (higher if any_80 else lower)) <= 0.05 + assert wfa[h] == 0 + else: + assert wfa[h] == (300 if any_80 else 200), (year, household) + assert pawhp[h] == 0 diff --git a/policyengine_uk/utils/winter_heating.py b/policyengine_uk/utils/winter_heating.py new file mode 100644 index 0000000000..bc47d45e2a --- /dev/null +++ b/policyengine_uk/utils/winter_heating.py @@ -0,0 +1,76 @@ +"""Person-level rules shared by the Winter Fuel Payment and the Pension Age +Winter Heating Payment. + +Both schemes entitle and pay a person, and set the amount from that person's +own circumstances: + +- a person to whom a relevant benefit is paid receives the full amount, the + higher amount if they or their partner have reached 80 (SI 2000/729 reg + 2(1)(i) and 2(3); SI 2024/869 reg 3; SI 2025/969 reg 3(1), (3) and (4); + SSI 2024/351 reg 10, as made and as substituted by SSI 2025/282); +- a couple on a relevant benefit receives one payment: the partner of the + person paid is not entitled (SI 2000/729 reg 3(1)(a)(i); SI 2024/869 reg + 4(1)(a)(i); SI 2025/969 reg 4(2)(a); SSI 2024/351 reg 9(d)); +- anyone else receives a shared amount when they live with another person + entitled to a payment (SI 2000/729 reg 2(1)(ii) and 2(2)(b); SI 2025/969 + reg 3(2), (5) and (6); SSI 2024/351 reg 10(5) and (6) as substituted). + +Benefit units model couples and households model living together: everyone +in a household is taken to share it as their mutual home. Residential care, +long hospital stays and custody are not modelled. +""" + +from policyengine_core.model_api import select, where + + +def is_excluded_relevant_benefit_partner( + person, period, qualifies, on_relevant_benefit +): + """Whether the person is the partner of the person paid for their couple. + + A couple on a relevant benefit receives one payment. The model pays the + eldest member of the couple who qualifies (the claimant and partner of a + benefit unit whose award is a relevant benefit) and excludes the other; + the amount is the same whichever member is paid. + """ + age = person("age", period) + couple_member = ( + qualifies & person("is_claimant_or_partner", period) & on_relevant_benefit + ) + payee = couple_member & ( + person.get_rank(person.benunit, -age, condition=couple_member) == 0 + ) + return couple_member & ~payee + + +def winter_heating_payment_amount( + person, period, eligible, on_relevant_benefit, amount, higher_age +): + """The amount payable to each eligible person. + + ``amount`` is the scheme's amount parameter node, with ``lower`` and + ``higher`` (full amounts under and over the higher age), ``lower_shared``, + ``higher_shared_with_under_80`` and ``higher_shared_with_80_or_over``. + A person living with another eligible person who has reached the higher + age receives ``higher_shared_with_80_or_over`` even if they also live with + one who has not (SI 2025/969 reg 3(5) is subject to reg 3(6)). + """ + age = person("age", period) + aged_80 = age >= higher_age + claimant_or_partner = person("is_claimant_or_partner", period) + couple_aged_80 = claimant_or_partner & person.benunit.any( + claimant_or_partner & aged_80 + ) + relevant_benefit_amount = where( + aged_80 | couple_aged_80, amount.higher, amount.lower + ) + others = person.household.sum(eligible) - eligible + others_aged_80 = person.household.sum(eligible & aged_80) - (eligible & aged_80) + under_80_amount = where(others > 0, amount.lower_shared, amount.lower) + aged_80_amount = select( + [others_aged_80 > 0, others > 0], + [amount.higher_shared_with_80_or_over, amount.higher_shared_with_under_80], + default=amount.higher, + ) + other_amount = where(aged_80, aged_80_amount, under_80_amount) + return eligible * where(on_relevant_benefit, relevant_benefit_amount, other_amount) diff --git a/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py b/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py new file mode 100644 index 0000000000..98042710d3 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py @@ -0,0 +1,25 @@ +from policyengine_uk.model_api import * + + +class is_on_pension_credit(Variable): + value_type = bool + entity = Person + label = "on Pension Credit" + documentation = ( + "Whether State Pension Credit is payable to this person. Pension " + "Credit is awarded to a claimant for themselves and their partner " + "(SPCA 2002 ss.1 and 17), so the claimant and the partner are both on " + "it when their benefit unit's award (pension_credit) is positive. Any " + "other member of the benefit unit claims in their own right and is on " + "it only if they report an award themselves." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/ukpga/2002/16/section/1", + "https://www.legislation.gov.uk/ukpga/2002/16/section/17", + ) + + def formula(person, period, parameters): + couple_award = person.benunit("pension_credit", period) > 0 + own_award = person("pension_credit_reported", period) > 0 + return where(person("is_claimant_or_partner", period), couple_award, own_award) diff --git a/policyengine_uk/variables/gov/dwp/is_on_tax_credits.py b/policyengine_uk/variables/gov/dwp/is_on_tax_credits.py new file mode 100644 index 0000000000..802718f0a1 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_on_tax_credits.py @@ -0,0 +1,39 @@ +from policyengine_uk.model_api import * + + +class is_on_tax_credits(Variable): + value_type = bool + entity = Person + label = "on tax credits" + documentation = ( + "Whether an award of Child Tax Credit or Working Tax Credit is " + "payable to this person. A couple claims tax credits jointly (TCA " + "2002 s.3(3)(a)), so the claimant and the partner are both on them " + "when their benefit unit's award (tax_credits) is positive. An award " + "below the minimum (£26 a year) is not paid (SI 2002/2008 reg 9), so " + "a positive award is one of at least £26, the test the 2024-25 winter " + "fuel and pension age winter heating payments set. Any other member " + "of the benefit unit claims in their own right and is on them only if " + "they report awards of at least the minimum themselves while tax " + "credits are in payment." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/ukpga/2002/21/section/3", + "https://www.legislation.gov.uk/uksi/2002/2008/regulation/9", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.dwp.tax_credits + couple_award = person.benunit("tax_credits", period) > 0 + own_award = add( + person, + period, + ["child_tax_credit_reported", "working_tax_credit_reported"], + ) + own_award_payable = p.active & (own_award >= p.min_benefit) + return where( + person("is_claimant_or_partner", period), + couple_award, + own_award_payable, + ) diff --git a/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py b/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py new file mode 100644 index 0000000000..098e4f467b --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py @@ -0,0 +1,22 @@ +from policyengine_uk.model_api import * + + +class is_on_universal_credit(Variable): + value_type = bool + entity = Person + label = "on Universal Credit" + documentation = ( + "Whether Universal Credit is payable to this person. A couple claims " + "Universal Credit jointly (WRA 2012 s.2(1)(b)), so the claimant and " + "the partner are both on it when their benefit unit's award " + "(universal_credit) is positive. Any other member of the benefit " + "unit claims in their own right and is on it only if they report an " + "award themselves." + ) + definition_period = YEAR + reference = "https://www.legislation.gov.uk/ukpga/2012/5/section/2" + + def formula(person, period, parameters): + couple_award = person.benunit("universal_credit", period) > 0 + own_award = person("universal_credit_reported", period) > 0 + return where(person("is_claimant_or_partner", period), couple_award, own_award) diff --git a/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py b/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py new file mode 100644 index 0000000000..5648b83cfd --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py @@ -0,0 +1,29 @@ +from policyengine_uk.model_api import * + + +class is_on_winter_fuel_payment_relevant_benefit(Variable): + value_type = bool + entity = Person + label = "on a relevant benefit for the Winter Fuel Payment" + documentation = ( + "Whether a relevant benefit for the Winter Fuel Payment is paid to " + "this person. The relevant benefits are listed by period in " + "gov.dwp.winter_fuel_payment.eligibility.relevant_benefits. A " + "couple's award covers both partners, so the claimant and the partner " + "are on it when their benefit unit's award is positive (SI 2024/869 " + "reg 2(5) says so expressly; under SI 2000/729 and SI 2025/969 the " + "couple receives one payment either way). Any other member of the " + "benefit unit, such as a non-dependent adult, is on it only through " + "their own award: their relevant benefit never counts for anyone " + "else in the household." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2000/729/regulation/2", + "https://www.legislation.gov.uk/uksi/2024/869/regulation/2", + "https://www.legislation.gov.uk/uksi/2025/969/regulation/1", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.dwp.winter_fuel_payment.eligibility + return add(person, period, p.relevant_benefits) > 0 diff --git a/policyengine_uk/variables/gov/dwp/winter_fuel_allowance.py b/policyengine_uk/variables/gov/dwp/winter_fuel_allowance.py index f158779646..b0a404dec5 100644 --- a/policyengine_uk/variables/gov/dwp/winter_fuel_allowance.py +++ b/policyengine_uk/variables/gov/dwp/winter_fuel_allowance.py @@ -3,64 +3,13 @@ class winter_fuel_allowance(Variable): label = "Winter Fuel Allowance" + documentation = ( + "Winter Fuel Payments made to the members of this household " + "(winter_fuel_payment). Payments in Scotland from the 2024 " + "qualifying week are the Pension Age Winter Heating Payment (pawhp)." + ) entity = Household definition_period = YEAR value_type = float unit = GBP - - def formula(household, period, parameters): - in_scotland = household("country", period).decode_to_str() == "SCOTLAND" - age = household.members("age", period) - is_SP_age = household.members("is_SP_age", period) - wfp = parameters(period).gov.dwp.winter_fuel_payment - on_mtb = ( - add( - household, - period, - [ - "pension_credit", - "income_support", - "esa_income", - "jsa_income", - ], - ) - > 0 - ) - taxable_income = household.members("total_income", period) - is_SP_age = household.members("is_SP_age", period) - country = household("country", period).decode_to_str() - in_england_or_wales = np.isin(country, ["ENGLAND", "WALES"]) - meets_income_passport = ( - household.any( - is_SP_age - & ( - taxable_income - < wfp.eligibility.taxable_income_test.maximum_taxable_income - ) - ) - & in_england_or_wales - & wfp.eligibility.taxable_income_test.use_maximum_taxable_income - ) - - meets_mtb_requirement = ( - on_mtb | (not wfp.eligibility.require_benefits) | meets_income_passport - ) - meets_spa_requirement = household.any(is_SP_age) | ( - not wfp.eligibility.state_pension_age_requirement - ) - meets_higher_age_requirement = household.any( - age >= wfp.eligibility.higher_age_requirement - ) - qualifies_for_higher = ( - meets_mtb_requirement & meets_spa_requirement & meets_higher_age_requirement - ) - qualifies_for_lower = ( - meets_mtb_requirement - & meets_spa_requirement - & ~meets_higher_age_requirement - ) - - return ~in_scotland * ( - wfp.amount.higher * qualifies_for_higher - + wfp.amount.lower * qualifies_for_lower - ) + adds = ["winter_fuel_payment"] diff --git a/policyengine_uk/variables/gov/dwp/winter_fuel_payment.py b/policyengine_uk/variables/gov/dwp/winter_fuel_payment.py new file mode 100644 index 0000000000..33fda18f95 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/winter_fuel_payment.py @@ -0,0 +1,33 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.winter_heating import winter_heating_payment_amount + + +class winter_fuel_payment(Variable): + value_type = float + entity = Person + label = "Winter Fuel Payment" + documentation = ( + "The Winter Fuel Payment made to this person. A person on a relevant " + "benefit receives the full amount (the higher amount if they or their " + "partner have reached 80), once per couple. Anyone else receives the " + "full amount if no other person in their household is eligible, and " + "a shared amount otherwise." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2000/729/regulation/2", + "https://www.legislation.gov.uk/uksi/2024/869/regulation/3", + "https://www.legislation.gov.uk/uksi/2025/969/regulation/3", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.dwp.winter_fuel_payment + return winter_heating_payment_amount( + person, + period, + person("winter_fuel_payment_eligible", period), + person("is_on_winter_fuel_payment_relevant_benefit", period), + p.amount, + p.eligibility.higher_age_requirement, + ) diff --git a/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py b/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py new file mode 100644 index 0000000000..9b19032a38 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py @@ -0,0 +1,60 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.winter_heating import ( + is_excluded_relevant_benefit_partner, +) + + +class winter_fuel_payment_eligible(Variable): + value_type = bool + entity = Person + label = "eligible for the Winter Fuel Payment" + documentation = ( + "Whether this person is entitled to a Winter Fuel Payment: they have " + "reached pensionable age and live where the payment is made, they " + "are not the partner of the person paid for a couple on a relevant " + "benefit, and they meet the model's means condition. The means " + "condition is a relevant benefit of their own (or their couple's) " + "where gov.dwp.winter_fuel_payment.eligibility.require_benefits is " + "set, as for the 2024 qualifying week (SI 2024/869 reg 2(2)(b)). " + "From 2025 the regulations pay everyone of pensionable age (SI " + "2025/969 reg 2) and the winter fuel payment charge recovers the " + "payment from people with total income over £35,000 who are not on a " + "relevant benefit (ITEPA 2003 s.681I); the model represents the " + "charge with the household income test in " + "gov.dwp.winter_fuel_payment.eligibility.taxable_income_test." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2000/729/regulation/2", + "https://www.legislation.gov.uk/uksi/2024/869/regulation/2", + "https://www.legislation.gov.uk/uksi/2024/869/regulation/4", + "https://www.legislation.gov.uk/uksi/2025/969/regulation/2", + "https://www.legislation.gov.uk/uksi/2025/969/regulation/4", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.dwp.winter_fuel_payment.eligibility + country = person.household("country", period).decode_to_str() + resident = np.isin(country, p.countries) + is_SP_age = person("is_SP_age", period) + pension_age = is_SP_age | (not p.state_pension_age_requirement) + qualifies = resident & pension_age + on_relevant_benefit = person( + "is_on_winter_fuel_payment_relevant_benefit", period + ) + income_test = p.taxable_income_test + meets_income_test = ( + person.household.any( + is_SP_age + & (person("total_income", period) < income_test.maximum_taxable_income) + ) + & np.isin(country, ["ENGLAND", "WALES"]) + & income_test.use_maximum_taxable_income + ) + meets_means_condition = ( + on_relevant_benefit | (not p.require_benefits) | meets_income_test + ) + excluded = is_excluded_relevant_benefit_partner( + person, period, qualifies, on_relevant_benefit + ) + return qualifies & meets_means_condition & ~excluded diff --git a/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py b/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py new file mode 100644 index 0000000000..a8c5ec11dc --- /dev/null +++ b/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py @@ -0,0 +1,29 @@ +from policyengine_uk.model_api import * + + +class is_on_pawhp_relevant_benefit(Variable): + value_type = bool + entity = Person + label = "on a relevant benefit for the Pension Age Winter Heating Payment" + documentation = ( + "Whether this individual is entitled to a relevant benefit for the " + "Pension Age Winter Heating Payment. The relevant benefits are listed " + "by period in gov.social_security_scotland.pawhp.eligibility." + "relevant_benefits. A member of a couple is treated as entitled when " + "the other member is (SSI 2024/351 reg 7(2) as made, reg 10(8) as " + "substituted by SSI 2025/282, and reg 2A from April 2026), so the " + "claimant and the partner are on it when their benefit unit's award " + "is positive. Any other member of the benefit unit, such as a " + "non-dependent adult, is on it only through their own award: their " + "relevant benefit never counts for anyone else in the household." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/ssi/2024/351/regulation/2", + "https://www.legislation.gov.uk/ssi/2024/351/regulation/7/made", + "https://www.legislation.gov.uk/ssi/2025/282/regulation/9/made", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.social_security_scotland.pawhp.eligibility + return add(person, period, p.relevant_benefits) > 0 diff --git a/policyengine_uk/variables/gov/social_security_scotland/pawhp.py b/policyengine_uk/variables/gov/social_security_scotland/pawhp.py index 8cdbc70fbd..d1c565a7e7 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/pawhp.py +++ b/policyengine_uk/variables/gov/social_security_scotland/pawhp.py @@ -3,49 +3,12 @@ class pawhp(Variable): label = "Pension Age Winter Heating Payment" + documentation = ( + "Pension Age Winter Heating Payments made to the members of this " + "household (pension_age_winter_heating_payment)." + ) entity = Household definition_period = YEAR value_type = float unit = GBP - - def formula(household, period, parameters): - in_scotland = household("country", period).decode_to_str() == "SCOTLAND" - age = household.members("age", period) - is_SP_age = household.members("is_SP_age", period) - wfp = parameters(period).gov.social_security_scotland.pawhp - on_mtb = ( - add( - household, - period, - [ - "pension_credit", - "income_support", - "esa_income", - "jsa_income", - ], - ) - > 0 - ) - meets_mtb_requirement = on_mtb | ~wfp.eligibility.require_benefits - meets_spa_requirement = ( - household.any(is_SP_age) | ~wfp.eligibility.state_pension_age_requirement - ) - meets_higher_age_requirement = household.any( - age >= wfp.eligibility.higher_age_requirement - ) - qualifies_for_higher = ( - meets_mtb_requirement & meets_spa_requirement & meets_higher_age_requirement - ) - qualifies_for_lower = ( - meets_mtb_requirement - & meets_spa_requirement - & ~meets_higher_age_requirement - ) - - qualifies_for_base = ~meets_mtb_requirement & meets_spa_requirement - - return in_scotland * ( - wfp.amount.higher * qualifies_for_higher - + wfp.amount.lower * qualifies_for_lower - + wfp.amount.base * qualifies_for_base - ) + adds = ["pension_age_winter_heating_payment"] diff --git a/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py b/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py new file mode 100644 index 0000000000..a0bb9f1da1 --- /dev/null +++ b/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py @@ -0,0 +1,40 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.winter_heating import ( + is_excluded_relevant_benefit_partner, +) + + +class pawhp_eligible(Variable): + value_type = bool + entity = Person + label = "eligible for the Pension Age Winter Heating Payment" + documentation = ( + "Whether this individual is entitled to a Pension Age Winter Heating " + "Payment: they have reached pensionable age and live in Scotland, " + "they are not the other member of a couple on a relevant benefit " + "whose payment is made to their partner, and, for the 2024 " + "qualifying week, they are entitled to a relevant benefit." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/ssi/2024/351/regulation/5", + "https://www.legislation.gov.uk/ssi/2024/351/regulation/9", + "https://www.legislation.gov.uk/ssi/2025/282/regulation/7/made", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.social_security_scotland.pawhp + country = person.household("country", period).decode_to_str() + resident = p.active & (country == "SCOTLAND") + pension_age = person("is_SP_age", period) | ( + not p.eligibility.state_pension_age_requirement + ) + qualifies = resident & pension_age + on_relevant_benefit = person("is_on_pawhp_relevant_benefit", period) + meets_benefit_condition = on_relevant_benefit | ( + not p.eligibility.require_benefits + ) + excluded = is_excluded_relevant_benefit_partner( + person, period, qualifies, on_relevant_benefit + ) + return qualifies & meets_benefit_condition & ~excluded diff --git a/policyengine_uk/variables/gov/social_security_scotland/pension_age_winter_heating_payment.py b/policyengine_uk/variables/gov/social_security_scotland/pension_age_winter_heating_payment.py new file mode 100644 index 0000000000..aa3e0c0cca --- /dev/null +++ b/policyengine_uk/variables/gov/social_security_scotland/pension_age_winter_heating_payment.py @@ -0,0 +1,34 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.winter_heating import winter_heating_payment_amount + + +class pension_age_winter_heating_payment(Variable): + value_type = float + entity = Person + label = "Pension Age Winter Heating Payment" + documentation = ( + "The Pension Age Winter Heating Payment made to this individual. An " + "individual entitled to a relevant benefit receives the full amount " + "(the higher amount if they or their partner have reached 80), once " + "per couple. From the 2025 qualifying week anyone else receives the " + "full amount if they do not live with another entitled individual, " + "and a shared amount otherwise." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/ssi/2024/351/regulation/10/made", + "https://www.legislation.gov.uk/ssi/2025/282/regulation/9/made", + "https://www.legislation.gov.uk/ssi/2024/351/regulation/10", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.social_security_scotland.pawhp + return winter_heating_payment_amount( + person, + period, + person("pawhp_eligible", period), + person("is_on_pawhp_relevant_benefit", period), + p.amount, + p.eligibility.higher_age_requirement, + ) From fe6aff256f1b50d8894d20f027226708a5cc9e08 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 03:04:33 -0400 Subject: [PATCH 2/8] Cite ITEPA 2003 s.681I for the Winter Fuel Payment income test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The £35,000 limit comes from the winter fuel payment charge (FA 2026 Sch 10), not SI 2025/969, and require_benefits stays true from 2025 only as the model's stand-in for that charge; say so. Co-Authored-By: Claude Opus 5.5 --- .../eligibility/require_benefits.yaml | 10 +++++++--- .../taxable_income_test/maximum_taxable_income.yaml | 8 ++++---- .../use_maximum_taxable_income.yaml | 5 ++++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/require_benefits.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/require_benefits.yaml index dcf6c25586..a85099df07 100644 --- a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/require_benefits.yaml +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/require_benefits.yaml @@ -1,7 +1,7 @@ -description: Whether receipt of means-tested benefits is required to qualify for the Winter Fuel Payment. +description: Whether a relevant benefit is a condition of the Winter Fuel Payment. It was for the 2024 qualifying week. From the 2025 qualifying week the regulations pay everyone of pensionable age, and the winter fuel payment charge (ITEPA 2003 s.681I) recovers the payment from people with total income over £35,000 who are not on a relevant benefit. The model keeps this true from 2025 and lets people who are not on a relevant benefit through the income test in taxable_income_test, which stands in for the charge. values: 2000-01-01: false - 2024-01-01: + 2024-01-01: value: true metadata: reference: @@ -12,4 +12,8 @@ metadata: label: Winter Fuel Payment means-tested benefits requirement reference: - href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2 - title: The Social Fund Winter Fuel Payment Regulations 2000 \ No newline at end of file + title: The Social Fund Winter Fuel Payment Regulations 2000 + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 2 + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/2/made + - title: Finance Act 2026, Sch. 10 (ITEPA 2003 s.681I, winter fuel payment charge) + href: https://www.legislation.gov.uk/ukpga/2026/11/schedule/10 diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/maximum_taxable_income.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/maximum_taxable_income.yaml index 28652f1bf5..29e4138a20 100644 --- a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/maximum_taxable_income.yaml +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/maximum_taxable_income.yaml @@ -1,11 +1,11 @@ -description: Households in England or Wales with pensioners earning under this amount of yearly taxable income are eligible for the Winter Fuel Payment. +description: Households in England or Wales with pensioners earning under this amount of yearly taxable income are eligible for the Winter Fuel Payment. This stands in for the winter fuel payment charge, which recovers the payment from a person whose own total income exceeds £35,000 and who is not entitled to a relevant benefit. values: 2000-01-01: 0 2025-01-01: value: 35_000 reference: - - title: The Social Fund Winter Fuel Payment Regulations 2025 (SI 2025/969) - href: https://www.legislation.gov.uk/uksi/2025/969/made + - title: Finance Act 2026, Sch. 10 para. 1 (ITEPA 2003 s.681I(1)(b), for 2025-26 on) + href: https://www.legislation.gov.uk/ukpga/2026/11/schedule/10 metadata: unit: currency-GBP - label: Winter Fuel Payment maximum taxable income \ No newline at end of file + label: Winter Fuel Payment maximum taxable income diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/use_maximum_taxable_income.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/use_maximum_taxable_income.yaml index 5e8cf9a25b..594e55d980 100644 --- a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/use_maximum_taxable_income.yaml +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/use_maximum_taxable_income.yaml @@ -1,7 +1,10 @@ -description: When this is true, households in England or Wales with pensioners earning under the maximum taxable income are eligible for the Winter Fuel Payment. +description: When this is true, households in England or Wales with pensioners earning under the maximum taxable income are eligible for the Winter Fuel Payment. This stands in for the winter fuel payment charge on people with total income over £35,000 (ITEPA 2003 s.681I, from 2025-26). values: 2000-01-01: false 2025-01-01: true metadata: unit: bool label: WFA maximum taxable income eligibility + reference: + - title: Finance Act 2026, Sch. 10 para. 1 (ITEPA 2003 s.681I) + href: https://www.legislation.gov.uk/ukpga/2026/11/schedule/10 From 816bd338b290d0021bba619cfcda267ed15f4c20 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 05:29:52 -0400 Subject: [PATCH 3/8] =?UTF-8?q?Pay=20a=20couple's=20winter=20payment=20to?= =?UTF-8?q?=20the=20claimant;=20enforce=20the=20=C2=A326=20tax=20credit=20?= =?UTF-8?q?minimum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review findings on #2038: - The couple's payment went to the elder member. The law pays the person the relevant benefit is paid to (SI 2025/969 reg 4(2)(a); explanatory memorandum para 5.8), and that choice sets a third person's shared rate: an 82-year-old lodger gets £200 living with an entitled 70-year-old claimant, £150 with an entitled 85-year-old. The payee is now the member who reports the award, else the benefit-unit head, else the elder. - A tax credit award below £26 counted when entered on the benefit unit. Each scheme now has its own minimum_tax_credit_award (£26 for the 2024 week, infinite otherwise) applied to a new person-level tax_credit_award, replacing is_on_tax_credits. - Correct the SSI 2025/282 reg 17 comment and document the 2025 PAWHP couple rule as an interpretation. Co-Authored-By: Claude Opus 5.5 --- ...winter-heating-per-person-receipt.added.md | 2 +- .../eligibility/minimum_tax_credit_award.yaml | 22 ++++ .../eligibility/relevant_benefits.yaml | 3 +- .../eligibility/minimum_tax_credit_award.yaml | 17 +++ .../pawhp/eligibility/relevant_benefits.yaml | 19 +-- .../pawhp/eligibility/require_benefits.yaml | 4 +- .../relevant_benefit_receipt.yaml | 108 +++++++++++++++++- .../gov/social_security_scotland/pawhp.yaml | 51 +++++++++ .../test_winter_heating_payment_properties.py | 52 ++++++++- policyengine_uk/utils/winter_heating.py | 65 +++++++++-- .../variables/gov/dwp/is_on_tax_credits.py | 39 ------- ...on_winter_fuel_payment_relevant_benefit.py | 20 ++-- .../variables/gov/dwp/tax_credit_award.py | 27 +++++ .../gov/dwp/winter_fuel_payment_eligible.py | 7 +- .../is_on_pawhp_relevant_benefit.py | 20 ++-- .../pawhp_eligible.py | 16 ++- 16 files changed, 376 insertions(+), 96 deletions(-) create mode 100644 policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/minimum_tax_credit_award.yaml create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/minimum_tax_credit_award.yaml delete mode 100644 policyengine_uk/variables/gov/dwp/is_on_tax_credits.py create mode 100644 policyengine_uk/variables/gov/dwp/tax_credit_award.py diff --git a/changelog.d/winter-heating-per-person-receipt.added.md b/changelog.d/winter-heating-per-person-receipt.added.md index 6a162f060e..1d2733af49 100644 --- a/changelog.d/winter-heating-per-person-receipt.added.md +++ b/changelog.d/winter-heating-per-person-receipt.added.md @@ -1,2 +1,2 @@ -- Add `is_on_pension_credit`, `is_on_universal_credit` and `is_on_tax_credits`: whether the award is payable to the person, through their couple's award for the claimant and partner and through their own report for anyone else. +- Add `is_on_pension_credit` and `is_on_universal_credit` (whether the award is payable to the person) and `tax_credit_award` (the tax credit award payable to them): through their couple's award for the claimant and partner, and through their own report for anyone else. - Add person-level `winter_fuel_payment` and `pension_age_winter_heating_payment`, with `winter_fuel_payment_eligible`, `pawhp_eligible`, `is_on_winter_fuel_payment_relevant_benefit` and `is_on_pawhp_relevant_benefit`. The household `winter_fuel_allowance` and `pawhp` now add up their members' payments. diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/minimum_tax_credit_award.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/minimum_tax_credit_award.yaml new file mode 100644 index 0000000000..1b28832119 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/minimum_tax_credit_award.yaml @@ -0,0 +1,22 @@ +description: Smallest award of child tax credit or working tax credit that counts towards the Winter Fuel Payment. Tax credits counted only for the 2024 qualifying week; infinity means they do not count. +values: + 2000-01-01: + value: .inf + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2000, reg. 2(1)(ii) (as in force for the 2023 qualifying week) + href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2/2023-09-18 + # Dated 1 January so that the year's value is the one for its September + # qualifying week; SI 2024/869 came into force on 16 September 2024. + 2024-01-01: + value: 26 + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2024, reg. 2(2)(b)(ii) + href: https://www.legislation.gov.uk/uksi/2024/869/regulation/2/made + 2025-01-01: + value: .inf + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 1(3) "relevant benefit" + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/1/made +metadata: + unit: currency-GBP + label: Winter Fuel Payment minimum tax credit award diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/relevant_benefits.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/relevant_benefits.yaml index 9c8e5972a1..2a8765d902 100644 --- a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/relevant_benefits.yaml +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/relevant_benefits.yaml @@ -1,4 +1,4 @@ -description: Benefits that count as a relevant benefit for the Winter Fuel Payment. A person on one receives the full amount whoever they live with, and the partner of the person paid is not entitled. For the 2024 qualifying week only, a relevant benefit (or a tax credit award of at least £26) was also a condition of entitlement. +description: Benefits that count as a relevant benefit for the Winter Fuel Payment, other than tax credits (see minimum_tax_credit_award). A person on one receives the full amount whoever they live with, and the partner of the person paid is not entitled. For the 2024 qualifying week only, a relevant benefit was also a condition of entitlement. values: 2000-01-01: value: @@ -17,7 +17,6 @@ values: - is_on_pension_credit - is_on_income_related_esa - is_on_universal_credit - - is_on_tax_credits reference: - title: The Social Fund Winter Fuel Payment Regulations 2024, reg. 2(2)(b) and 2(4) "relevant benefit" href: https://www.legislation.gov.uk/uksi/2024/869/regulation/2/made diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/minimum_tax_credit_award.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/minimum_tax_credit_award.yaml new file mode 100644 index 0000000000..f0c95ab2c0 --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/minimum_tax_credit_award.yaml @@ -0,0 +1,17 @@ +description: Smallest award of child tax credit or working tax credit that counts as a relevant benefit for the Pension Age Winter Heating Payment. Tax credits counted only for the 2024 qualifying week; infinity means they do not count. +values: + 2024-01-01: + value: 26 + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 2 "relevant benefit" (as made) + href: https://www.legislation.gov.uk/ssi/2024/351/regulation/2/made + # SSI 2025/195 reg. 9(2) omitted the tax credit words from 23 June 2025, + # before the qualifying week in September 2025. + 2025-01-01: + value: .inf + reference: + - title: The Social Security (Miscellaneous Amendment) (Scotland) Regulations 2025 (SSI 2025/195), reg. 9(2) + href: https://www.legislation.gov.uk/ssi/2025/195/regulation/9/made +metadata: + unit: currency-GBP + label: PAWHP minimum tax credit award diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/relevant_benefits.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/relevant_benefits.yaml index ae18d3969b..f8f1a3090e 100644 --- a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/relevant_benefits.yaml +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/relevant_benefits.yaml @@ -1,4 +1,4 @@ -description: Benefits that count as a relevant benefit for the Pension Age Winter Heating Payment. For the 2024 qualifying week one was a condition of entitlement; from the 2025 qualifying week a person on one receives the full amount whoever they live with. A member of a couple is treated as entitled to a relevant benefit when the other member is, and the other member of a couple on one is not paid. +description: Benefits that count as a relevant benefit for the Pension Age Winter Heating Payment, other than tax credits (see minimum_tax_credit_award). For the 2024 qualifying week one was a condition of entitlement; from the 2025 qualifying week a person on one receives the full amount whoever they live with. A member of a couple is treated as entitled to a relevant benefit when the other member is, and the other member of a couple on one is not paid. values: 2024-01-01: value: @@ -7,23 +7,8 @@ values: - is_on_income_related_esa - is_on_income_support - is_on_universal_credit - - is_on_tax_credits reference: - - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 2 "relevant benefit" and reg. 7 (as made) - href: https://www.legislation.gov.uk/ssi/2024/351/regulation/2/made - # SSI 2025/195 reg. 9(2) omitted the tax credit words from 23 June 2025, - # before the qualifying week in September 2025. - 2025-01-01: - value: - - is_on_pension_credit - - is_on_income_based_jsa - - is_on_income_related_esa - - is_on_income_support - - is_on_universal_credit - reference: - - title: The Social Security (Miscellaneous Amendment) (Scotland) Regulations 2025 (SSI 2025/195), reg. 9(2) - href: https://www.legislation.gov.uk/ssi/2025/195/regulation/9/made - - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 2 "relevant benefit" and reg. 2A (as amended) + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 2 "relevant benefit" (as made and as amended) href: https://www.legislation.gov.uk/ssi/2024/351/regulation/2 metadata: unit: list diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/require_benefits.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/require_benefits.yaml index 0f71bccc10..6bf67cd340 100644 --- a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/require_benefits.yaml +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/require_benefits.yaml @@ -7,8 +7,8 @@ values: - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 5(a)(ii) and 7 (as made) href: https://www.legislation.gov.uk/ssi/2024/351/regulation/7/made # SSI 2025/282 came into force on 11 October 2025 and applies to the - # qualifying week in September 2025 (its reg. 17 keeps the 2024 week on - # the regulations as made). + # qualifying week in September 2025. Its reg. 17 keeps the regulations as + # they stood immediately before 11 October 2025 for the 2024 week. 2025-01-01: value: false reference: diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml index 46a029bf5b..f1805cf65a 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml @@ -187,10 +187,53 @@ members: [pensioner] country: ENGLAND output: - is_on_tax_credits: [true] + tax_credit_award: [500] + is_on_winter_fuel_payment_relevant_benefit: [true] winter_fuel_payment: [200] winter_fuel_allowance: 200 +- name: 2024 a tax credit award of £25 is below the £26 minimum + # Reg. 2(2)(b)(ii): "not less than £26". No other relevant benefit. + period: 2024 + input: + people: + pensioner: + age: 67 + total_income: 9_000 + benunits: + unit: + members: [pensioner] + tax_credits: 25 + pension_credit: 0 + households: + household: + members: [pensioner] + country: ENGLAND + output: + is_on_winter_fuel_payment_relevant_benefit: [false] + winter_fuel_payment: [0] + winter_fuel_allowance: 0 + +- name: 2024 a tax credit award of exactly £26 qualifies + period: 2024 + input: + people: + pensioner: + age: 67 + total_income: 9_000 + benunits: + unit: + members: [pensioner] + tax_credits: 26 + pension_credit: 0 + households: + household: + members: [pensioner] + country: ENGLAND + output: + is_on_winter_fuel_payment_relevant_benefit: [true] + winter_fuel_payment: [200] + - name: 2025 a non-dependant's income-related ESA does not exempt the pensioner from the income test # Under SI 2025/969 the pensioner is entitled (reg. 2) to £200 (reg. 3(1)), # but the winter fuel payment charge recovers it: total income over @@ -399,6 +442,69 @@ winter_fuel_payment: [0, 200] winter_fuel_allowance: 200 +- name: 2025 the couple's payment goes to the Pension Credit claimant, which sets a lodger's shared amount + # Pension Credit is paid to the claimant, aged 70; the partner, aged 85, is + # excluded (reg. 4(2)(a)). The claimant: reg. 3(3), £300 (partner aged 80). + # The lodger, aged 82 and on no relevant benefit, lives with an entitled + # person under 80 and no entitled person aged 80 (the partner is not + # entitled): reg. 3(5), £200. + period: 2025 + input: + people: + claimant: + age: 70 + pension_credit_reported: 1_000 + partner: + age: 85 + lodger: + age: 82 + total_income: 20_000 + benunits: + couple: + members: [claimant, partner] + pension_credit: 1_000 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [claimant, partner, lodger] + country: ENGLAND + output: + winter_fuel_payment_eligible: [true, false, true] + winter_fuel_payment: [300, 0, 200] + winter_fuel_allowance: 500 + +- name: 2025 without a reported award, the couple's payment goes to the benefit-unit head + # As above, with the award entered on the benefit unit only and the + # 70-year-old as head of the benefit unit (the claimant). + period: 2025 + input: + people: + claimant: + age: 70 + is_benunit_head: true + partner: + age: 85 + is_benunit_head: false + lodger: + age: 82 + total_income: 20_000 + benunits: + couple: + members: [claimant, partner] + pension_credit: 1_000 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [claimant, partner, lodger] + country: ENGLAND + output: + winter_fuel_payment: [300, 0, 200] + winter_fuel_allowance: 500 + - name: 2023 Scotland's pensioners get the Winter Fuel Payment, not PAWHP # SI 2000/729 reg. 2(1)(a)(i): ordinarily resident in Great Britain. The # Pension Age Winter Heating Payment starts with the 2024 qualifying week. diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml index e15b4bc996..5669da7bba 100644 --- a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml @@ -128,6 +128,27 @@ pension_age_winter_heating_payment: [200] pawhp: 200 +- name: 2024 a tax credit award of £25 is below the £26 minimum + # Reg. 2 "relevant benefit" as made: "where the award is not less than £26". + period: 2024 + input: + people: + pensioner: + age: 67 + benunits: + unit: + members: [pensioner] + tax_credits: 25 + pension_credit: 0 + households: + household: + members: [pensioner] + country: SCOTLAND + output: + is_on_pawhp_relevant_benefit: [false] + pension_age_winter_heating_payment: [0] + pawhp: 0 + - name: 2025 a single pensioner not on a relevant benefit gets £203.40 # Reg. 10(5)(a): under 80, not entitled to a relevant benefit and not # living with another entitled individual. @@ -283,6 +304,36 @@ pension_age_winter_heating_payment: [203.40, 101.70] pawhp: 305.10 +- name: 2025 the couple's payment goes to the Pension Credit claimant, which sets a lodger's shared amount + # The claimant, aged 70, is entitled to Pension Credit; reg. 10(4)(a)(ii): + # £305.10 (partner aged 85). The partner is not paid (reg. 9(d)). The + # lodger, aged 82, lives with an entitled individual under 80 and none + # aged 80: reg. 10(6)(b), £203.40. + period: 2025 + input: + people: + claimant: + age: 70 + pension_credit_reported: 1_000 + partner: + age: 85 + lodger: + age: 82 + benunits: + couple: + members: [claimant, partner] + pension_credit: 1_000 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [claimant, partner, lodger] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [305.10, 0, 203.40] + pawhp: 508.50 + - name: 2026 a couple under 80 not on a relevant benefit gets £105.55 each # Reg. 10(5)(b)(i) as uprated by SSI 2026/170 reg. 15(3)(d): two shared # amounts make £211.10, 5p less than the single amount of £211.15. diff --git a/policyengine_uk/tests/test_winter_heating_payment_properties.py b/policyengine_uk/tests/test_winter_heating_payment_properties.py index ac45df6c1b..325878fae5 100644 --- a/policyengine_uk/tests/test_winter_heating_payment_properties.py +++ b/policyengine_uk/tests/test_winter_heating_payment_properties.py @@ -46,6 +46,16 @@ "UC": "universal_credit", "TC": "tax_credits", } +# The award a claimant reports themselves: the couple's payment is made to +# them (SI 2025/969 reg 4(2)(a); explanatory memorandum para 5.8). +REPORTED_INPUTS = { + "PC": "pension_credit_reported", + "IS": "income_support_reported", + "JSA": "jsa_income_reported", + "ESA": "esa_income_reported", + "UC": "universal_credit_reported", + "TC": "working_tax_credit_reported", +} # An award reported by a member who is neither claimant nor partner. OWN_AWARD_INPUTS = { "PC": "pension_credit_reported", @@ -98,7 +108,10 @@ def benefit_units(draw): for _ in range(size) ] award = draw(st.sampled_from([None, None, *AWARD_INPUTS])) - return {"adults": adults, "award": award} + # The benefit-unit head, and the member (if any) who reports the award. + head = draw(st.integers(0, size - 1)) + reporter = draw(st.sampled_from([None, *range(size)])) if award else None + return {"adults": adults, "award": award, "head": head, "reporter": reporter} @st.composite @@ -121,10 +134,19 @@ def households(draw): def household_people(household): - """Each person as (inputs, benefit unit index, claimant or partner).""" + """Each person as (inputs, benefit unit index, claimant or partner). + + A claimant or partner's inputs carry whether they are the benefit-unit + head and whether they report their unit's award. + """ people = [] for u, unit in enumerate(household["units"]): - for adult in unit["adults"]: + for a, adult in enumerate(unit["adults"]): + adult = { + **adult, + "head": a == unit.get("head", 0), + "reports": a == unit.get("reporter"), + } people.append((adult, u, True)) if u == 0 and household["other"] is not None: people.append((household["other"], u, False)) @@ -141,11 +163,16 @@ def situation(drawn, year): "age": {year: inputs["age"]}, "total_income": {year: inputs["total_income"]}, "is_claimant_or_partner": {year: cp}, + "is_benunit_head": {year: bool(inputs.get("head", False))}, } own = inputs.get("own_award") if own is not None: person[OWN_AWARD_INPUTS[own]] = {year: AWARD} people[name] = person + for name, (inputs, u, cp) in zip(names, members): + if cp and inputs.get("reports"): + award = household["units"][u]["award"] + people[name][REPORTED_INPUTS[award]] = {year: AWARD} for u, unit in enumerate(household["units"]): benunit = { "members": [n for n, m in zip(names, members) if m[1] == u], @@ -180,8 +207,9 @@ def scheme(resident, relevant, means_test, amounts): on_relevant = [award_of(p) in relevant for p in people] qualifies = [resident and p[0]["age"] >= 67 for p in people] eligible = [q and (r or means_test) for q, r in zip(qualifies, on_relevant)] - # One payment for a couple on a relevant benefit: to the eldest who - # qualifies (first listed if the same age); the other is not entitled. + # One payment for a couple on a relevant benefit, to the claimant: the + # member who reports the award, else the benefit-unit head, else the + # elder (first listed if the same age). The other is not entitled. for u in range(len(units)): couple = [ i @@ -189,7 +217,19 @@ def scheme(resident, relevant, means_test, amounts): if p[1] == u and p[2] and qualifies[i] and on_relevant[i] ] if couple: - payee = max(couple, key=lambda i: (people[i][0]["age"], -i)) + payee = min( + couple, + key=lambda i: ( + 0 + if people[i][0]["reports"] + and units[people[i][1]]["award"] in relevant + else 1 + if people[i][0]["head"] + else 2, + -people[i][0]["age"], + i, + ), + ) for i in couple: if i != payee: eligible[i] = False diff --git a/policyengine_uk/utils/winter_heating.py b/policyengine_uk/utils/winter_heating.py index bc47d45e2a..2471ec3847 100644 --- a/policyengine_uk/utils/winter_heating.py +++ b/policyengine_uk/utils/winter_heating.py @@ -20,25 +20,76 @@ long hospital stays and custody are not modelled. """ -from policyengine_core.model_api import select, where +from policyengine_core.model_api import add, select, where + +# The reported award behind each relevant-benefit receipt variable: the +# member who reports it is the one the benefit is paid to. +REPORTED_AWARDS = { + "is_on_pension_credit": ["pension_credit_reported"], + "is_on_income_support": ["income_support_reported"], + "is_on_income_based_jsa": ["jsa_income_reported"], + "is_on_income_related_esa": ["esa_income_reported"], + "is_on_universal_credit": ["universal_credit_reported"], +} +TAX_CREDIT_REPORTS = ["child_tax_credit_reported", "working_tax_credit_reported"] + + +def is_on_relevant_benefit(person, period, eligibility): + """Whether a relevant benefit is paid to the person, or to their couple. + + ``eligibility`` is the scheme's eligibility parameter node: the benefits + in ``relevant_benefits``, and a tax credit award of at least + ``minimum_tax_credit_award`` (infinite when tax credits do not count). + """ + listed = add(person, period, eligibility.relevant_benefits) > 0 + tax_credits = ( + person("tax_credit_award", period) >= eligibility.minimum_tax_credit_award + ) + return listed | tax_credits + + +def reports_relevant_benefit(person, period, eligibility): + """Whether the person reports a relevant-benefit award of their own.""" + reports = [ + report + for variable in eligibility.relevant_benefits + for report in REPORTED_AWARDS.get(variable, []) + ] + own_award = add(person, period, reports) > 0 if reports else False + tax_credits = ( + add(person, period, TAX_CREDIT_REPORTS) >= eligibility.minimum_tax_credit_award + ) + return own_award | tax_credits def is_excluded_relevant_benefit_partner( - person, period, qualifies, on_relevant_benefit + person, period, qualifies, on_relevant_benefit, reports_relevant_benefit ): """Whether the person is the partner of the person paid for their couple. - A couple on a relevant benefit receives one payment. The model pays the - eldest member of the couple who qualifies (the claimant and partner of a - benefit unit whose award is a relevant benefit) and excludes the other; - the amount is the same whichever member is paid. + A couple on a relevant benefit receives one payment, made to the person + the benefit is paid to (the claimant), or to their partner when only the + partner qualifies; the partner of the person paid is not entitled. The + model takes the claimant to be the member of the couple who reports the + award; failing that, the benefit-unit head; failing that, the elder. The + choice matters beyond the couple: whether someone else in the household + lives with an entitled person aged 80 or over depends on whom the couple's + payment is made to (SI 2025/969 reg 3(5) and (6)). """ age = person("age", period) couple_member = ( qualifies & person("is_claimant_or_partner", period) & on_relevant_benefit ) + precedence = where( + reports_relevant_benefit, + 0, + where(person("is_benunit_head", period), 1, 2), + ) payee = couple_member & ( - person.get_rank(person.benunit, -age, condition=couple_member) == 0 + person.get_rank( + person.benunit, precedence * 1_000 - age, condition=couple_member + ) + == 0 ) return couple_member & ~payee diff --git a/policyengine_uk/variables/gov/dwp/is_on_tax_credits.py b/policyengine_uk/variables/gov/dwp/is_on_tax_credits.py deleted file mode 100644 index 802718f0a1..0000000000 --- a/policyengine_uk/variables/gov/dwp/is_on_tax_credits.py +++ /dev/null @@ -1,39 +0,0 @@ -from policyengine_uk.model_api import * - - -class is_on_tax_credits(Variable): - value_type = bool - entity = Person - label = "on tax credits" - documentation = ( - "Whether an award of Child Tax Credit or Working Tax Credit is " - "payable to this person. A couple claims tax credits jointly (TCA " - "2002 s.3(3)(a)), so the claimant and the partner are both on them " - "when their benefit unit's award (tax_credits) is positive. An award " - "below the minimum (£26 a year) is not paid (SI 2002/2008 reg 9), so " - "a positive award is one of at least £26, the test the 2024-25 winter " - "fuel and pension age winter heating payments set. Any other member " - "of the benefit unit claims in their own right and is on them only if " - "they report awards of at least the minimum themselves while tax " - "credits are in payment." - ) - definition_period = YEAR - reference = ( - "https://www.legislation.gov.uk/ukpga/2002/21/section/3", - "https://www.legislation.gov.uk/uksi/2002/2008/regulation/9", - ) - - def formula(person, period, parameters): - p = parameters(period).gov.dwp.tax_credits - couple_award = person.benunit("tax_credits", period) > 0 - own_award = add( - person, - period, - ["child_tax_credit_reported", "working_tax_credit_reported"], - ) - own_award_payable = p.active & (own_award >= p.min_benefit) - return where( - person("is_claimant_or_partner", period), - couple_award, - own_award_payable, - ) diff --git a/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py b/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py index 5648b83cfd..cddfa8d927 100644 --- a/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py +++ b/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py @@ -1,4 +1,5 @@ from policyengine_uk.model_api import * +from policyengine_uk.utils.winter_heating import is_on_relevant_benefit class is_on_winter_fuel_payment_relevant_benefit(Variable): @@ -8,14 +9,15 @@ class is_on_winter_fuel_payment_relevant_benefit(Variable): documentation = ( "Whether a relevant benefit for the Winter Fuel Payment is paid to " "this person. The relevant benefits are listed by period in " - "gov.dwp.winter_fuel_payment.eligibility.relevant_benefits. A " - "couple's award covers both partners, so the claimant and the partner " - "are on it when their benefit unit's award is positive (SI 2024/869 " - "reg 2(5) says so expressly; under SI 2000/729 and SI 2025/969 the " - "couple receives one payment either way). Any other member of the " - "benefit unit, such as a non-dependent adult, is on it only through " - "their own award: their relevant benefit never counts for anyone " - "else in the household." + "gov.dwp.winter_fuel_payment.eligibility.relevant_benefits; for the " + "2024 qualifying week a tax credit award of at least £26 also counts " + "(minimum_tax_credit_award). A couple's award covers both partners, " + "so the claimant and the partner are on it when their benefit unit's " + "award is positive (SI 2024/869 reg 2(5) says so expressly; under SI " + "2000/729 and SI 2025/969 the couple receives one payment either " + "way). Any other member of the benefit unit, such as a non-dependent " + "adult, is on it only through their own award: their relevant benefit " + "never counts for anyone else in the household." ) definition_period = YEAR reference = ( @@ -26,4 +28,4 @@ class is_on_winter_fuel_payment_relevant_benefit(Variable): def formula(person, period, parameters): p = parameters(period).gov.dwp.winter_fuel_payment.eligibility - return add(person, period, p.relevant_benefits) > 0 + return is_on_relevant_benefit(person, period, p) diff --git a/policyengine_uk/variables/gov/dwp/tax_credit_award.py b/policyengine_uk/variables/gov/dwp/tax_credit_award.py new file mode 100644 index 0000000000..7f589942d6 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/tax_credit_award.py @@ -0,0 +1,27 @@ +from policyengine_uk.model_api import * + + +class tax_credit_award(Variable): + value_type = float + entity = Person + label = "tax credit award" + documentation = ( + "The award of Child Tax Credit and Working Tax Credit payable to this " + "person. A couple claims tax credits jointly (TCA 2002 s.3(3)(a)), so " + "for the claimant and the partner it is their benefit unit's award " + "(tax_credits). Any other member of the benefit unit claims in their " + "own right, so for them it is the award they report themselves while " + "tax credits are in payment." + ) + definition_period = YEAR + unit = GBP + reference = "https://www.legislation.gov.uk/ukpga/2002/21/section/3" + + def formula(person, period, parameters): + couple_award = person.benunit("tax_credits", period) + own_award = parameters(period).gov.dwp.tax_credits.active * add( + person, + period, + ["child_tax_credit_reported", "working_tax_credit_reported"], + ) + return where(person("is_claimant_or_partner", period), couple_award, own_award) diff --git a/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py b/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py index 9b19032a38..12927fd4ad 100644 --- a/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py +++ b/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py @@ -1,6 +1,7 @@ from policyengine_uk.model_api import * from policyengine_uk.utils.winter_heating import ( is_excluded_relevant_benefit_partner, + reports_relevant_benefit, ) @@ -55,6 +56,10 @@ def formula(person, period, parameters): on_relevant_benefit | (not p.require_benefits) | meets_income_test ) excluded = is_excluded_relevant_benefit_partner( - person, period, qualifies, on_relevant_benefit + person, + period, + qualifies, + on_relevant_benefit, + reports_relevant_benefit(person, period, p), ) return qualifies & meets_means_condition & ~excluded diff --git a/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py b/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py index a8c5ec11dc..ee316ef08f 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py +++ b/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py @@ -1,4 +1,5 @@ from policyengine_uk.model_api import * +from policyengine_uk.utils.winter_heating import is_on_relevant_benefit class is_on_pawhp_relevant_benefit(Variable): @@ -8,14 +9,15 @@ class is_on_pawhp_relevant_benefit(Variable): documentation = ( "Whether this individual is entitled to a relevant benefit for the " "Pension Age Winter Heating Payment. The relevant benefits are listed " - "by period in gov.social_security_scotland.pawhp.eligibility." - "relevant_benefits. A member of a couple is treated as entitled when " - "the other member is (SSI 2024/351 reg 7(2) as made, reg 10(8) as " - "substituted by SSI 2025/282, and reg 2A from April 2026), so the " - "claimant and the partner are on it when their benefit unit's award " - "is positive. Any other member of the benefit unit, such as a " - "non-dependent adult, is on it only through their own award: their " - "relevant benefit never counts for anyone else in the household." + "in gov.social_security_scotland.pawhp.eligibility.relevant_benefits; " + "for the 2024 qualifying week a tax credit award of at least £26 also " + "counts (minimum_tax_credit_award). A member of a couple is treated " + "as entitled when the other member is (SSI 2024/351 reg 7(2) as made, " + "reg 10(8) as substituted by SSI 2025/282, and reg 2A from April " + "2026), so the claimant and the partner are on it when their benefit " + "unit's award is positive. Any other member of the benefit unit, such " + "as a non-dependent adult, is on it only through their own award: " + "their relevant benefit never counts for anyone else in the household." ) definition_period = YEAR reference = ( @@ -26,4 +28,4 @@ class is_on_pawhp_relevant_benefit(Variable): def formula(person, period, parameters): p = parameters(period).gov.social_security_scotland.pawhp.eligibility - return add(person, period, p.relevant_benefits) > 0 + return is_on_relevant_benefit(person, period, p) diff --git a/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py b/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py index a0bb9f1da1..ab0fec4fa7 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py +++ b/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py @@ -1,6 +1,7 @@ from policyengine_uk.model_api import * from policyengine_uk.utils.winter_heating import ( is_excluded_relevant_benefit_partner, + reports_relevant_benefit, ) @@ -13,7 +14,14 @@ class pawhp_eligible(Variable): "Payment: they have reached pensionable age and live in Scotland, " "they are not the other member of a couple on a relevant benefit " "whose payment is made to their partner, and, for the 2024 " - "qualifying week, they are entitled to a relevant benefit." + "qualifying week, they are entitled to a relevant benefit. For the " + "2025 qualifying week the couple rule (reg 10(8) as substituted by " + "SSI 2025/282) applied only within reg 10, so reg 9(d), which " + "excludes 'one member of a couple ... who is entitled to a relevant " + "benefit' once the other member is paid, read literally reaches only " + "a member entitled in their own right. The model makes one payment " + "per couple on a relevant benefit in 2025 too, as for the 2024 week " + "(reg 7(2)) and from April 2026 (reg 2A, inserted by SSI 2026/170)." ) definition_period = YEAR reference = ( @@ -35,6 +43,10 @@ def formula(person, period, parameters): not p.eligibility.require_benefits ) excluded = is_excluded_relevant_benefit_partner( - person, period, qualifies, on_relevant_benefit + person, + period, + qualifies, + on_relevant_benefit, + reports_relevant_benefit(person, period, p.eligibility), ) return qualifies & meets_benefit_condition & ~excluded From 071d5170b219ac4e041e2fb5f40bf3463a8815ce Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 07:08:12 -0400 Subject: [PATCH 4/8] =?UTF-8?q?Require=20a=20positive=20tax=20credit=20awa?= =?UTF-8?q?rd;=20cover=20the=20own-report=20and=20=C2=A326=20PAWHP=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 2 on #2038: - A minimum_tax_credit_award of 0 made everyone qualify (0 >= 0); the tax credit route now needs a positive award as well. - Tests: PAWHP £26 on the couple's award; for both schemes, a pensioner who is neither claimant nor partner reporting £25 (nothing) and £26 (£200); a reform test with the minimum at 0. - Nits: cite NISR 2024/160 for the NI £26 minimum, reg 9(1)(d) as made for one PAWHP payment per couple in 2024, and SI 2000/729 and SSI 2024/351 for why the payee matters to others; fail loudly on an unmapped relevant benefit; rename a shadowing argument; mark the 2025 reg 9(d) test comment as the model's reading. Co-Authored-By: Claude Opus 5.5 --- .../eligibility/minimum_tax_credit_award.yaml | 2 + .../relevant_benefit_receipt.yaml | 56 +++++++++++++ .../gov/social_security_scotland/pawhp.yaml | 78 ++++++++++++++++++- .../minimum_tax_credit_award.yaml | 38 +++++++++ policyengine_uk/utils/winter_heating.py | 19 +++-- .../pawhp_eligible.py | 4 +- 6 files changed, 185 insertions(+), 12 deletions(-) create mode 100644 policyengine_uk/tests/policy/reforms/parametric/winter_fuel_allowance/minimum_tax_credit_award.yaml diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/minimum_tax_credit_award.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/minimum_tax_credit_award.yaml index 1b28832119..b8f496e380 100644 --- a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/minimum_tax_credit_award.yaml +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/minimum_tax_credit_award.yaml @@ -12,6 +12,8 @@ values: reference: - title: The Social Fund Winter Fuel Payment Regulations 2024, reg. 2(2)(b)(ii) href: https://www.legislation.gov.uk/uksi/2024/869/regulation/2/made + - title: The Social Fund Winter Fuel Payment Regulations (Northern Ireland) 2024, reg. 2(2)(b)(ii) + href: https://www.legislation.gov.uk/nisr/2024/160/regulation/2/made 2025-01-01: value: .inf reference: diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml index f1805cf65a..7c650a46e6 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml @@ -234,6 +234,62 @@ is_on_winter_fuel_payment_relevant_benefit: [true] winter_fuel_payment: [200] +- name: 2024 a pensioner who is neither claimant nor partner reporting a tax credit award of £25 + # The pensioner is neither claimant nor partner in a 40-year-old's benefit + # unit, so only their own award counts; £25 is below the + # £26 minimum (reg. 2(2)(b)(ii)). + period: 2024 + input: + people: + adult: + age: 40 + is_claimant_or_partner: true + pensioner: + age: 70 + is_claimant_or_partner: false + working_tax_credit_reported: 25 + benunits: + unit: + members: [adult, pensioner] + tax_credits: 0 + pension_credit: 0 + universal_credit: 0 + households: + household: + members: [adult, pensioner] + country: ENGLAND + output: + tax_credit_award: [0, 25] + winter_fuel_payment: [0, 0] + +- name: 2024 a pensioner who is neither claimant nor partner reporting a tax credit award of £26 + # The pensioner is neither claimant nor partner in a 40-year-old's benefit + # unit, so only their own award counts; £26 meets the + # minimum (reg. 2(2)(b)(ii)) and reg. 3(c) gives £200. + period: 2024 + input: + people: + adult: + age: 40 + is_claimant_or_partner: true + pensioner: + age: 70 + is_claimant_or_partner: false + working_tax_credit_reported: 26 + benunits: + unit: + members: [adult, pensioner] + tax_credits: 0 + pension_credit: 0 + universal_credit: 0 + households: + household: + members: [adult, pensioner] + country: ENGLAND + output: + tax_credit_award: [0, 26] + winter_fuel_payment: [0, 200] + - name: 2025 a non-dependant's income-related ESA does not exempt the pensioner from the income test # Under SI 2025/969 the pensioner is entitled (reg. 2) to £200 (reg. 3(1)), # but the winter fuel payment charge recovers it: total income over diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml index 5669da7bba..86395ca936 100644 --- a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml @@ -149,6 +149,81 @@ pension_age_winter_heating_payment: [0] pawhp: 0 +- name: 2024 a tax credit award of exactly £26 qualifies + period: 2024 + input: + people: + pensioner: + age: 67 + benunits: + unit: + members: [pensioner] + tax_credits: 26 + pension_credit: 0 + households: + household: + members: [pensioner] + country: SCOTLAND + output: + is_on_pawhp_relevant_benefit: [true] + pension_age_winter_heating_payment: [200] + +- name: 2024 a pensioner who is neither claimant nor partner reporting a tax credit award of £25 + # The pensioner is neither claimant nor partner in a 40-year-old's benefit + # unit, so only their own award counts; £25 is below the + # £26 minimum (reg. 2 as made). + period: 2024 + input: + people: + adult: + age: 40 + is_claimant_or_partner: true + pensioner: + age: 70 + is_claimant_or_partner: false + working_tax_credit_reported: 25 + benunits: + unit: + members: [adult, pensioner] + tax_credits: 0 + pension_credit: 0 + universal_credit: 0 + households: + household: + members: [adult, pensioner] + country: SCOTLAND + output: + tax_credit_award: [0, 25] + pension_age_winter_heating_payment: [0, 0] + +- name: 2024 a pensioner who is neither claimant nor partner reporting a tax credit award of £26 + # The pensioner is neither claimant nor partner in a 40-year-old's benefit + # unit, so only their own award counts; £26 meets the + # minimum (reg. 2 as made) and reg. 10(c) gives £200. + period: 2024 + input: + people: + adult: + age: 40 + is_claimant_or_partner: true + pensioner: + age: 70 + is_claimant_or_partner: false + working_tax_credit_reported: 26 + benunits: + unit: + members: [adult, pensioner] + tax_credits: 0 + pension_credit: 0 + universal_credit: 0 + households: + household: + members: [adult, pensioner] + country: SCOTLAND + output: + tax_credit_award: [0, 26] + pension_age_winter_heating_payment: [0, 200] + - name: 2025 a single pensioner not on a relevant benefit gets £203.40 # Reg. 10(5)(a): under 80, not entitled to a relevant benefit and not # living with another entitled individual. @@ -306,7 +381,8 @@ - name: 2025 the couple's payment goes to the Pension Credit claimant, which sets a lodger's shared amount # The claimant, aged 70, is entitled to Pension Credit; reg. 10(4)(a)(ii): - # £305.10 (partner aged 85). The partner is not paid (reg. 9(d)). The + # £305.10 (partner aged 85). The partner is not paid, on the model's + # reading of reg. 9(d) for the 2025 week (see pawhp_eligible). The # lodger, aged 82, lives with an entitled individual under 80 and none # aged 80: reg. 10(6)(b), £203.40. period: 2025 diff --git a/policyengine_uk/tests/policy/reforms/parametric/winter_fuel_allowance/minimum_tax_credit_award.yaml b/policyengine_uk/tests/policy/reforms/parametric/winter_fuel_allowance/minimum_tax_credit_award.yaml new file mode 100644 index 0000000000..7de2f8c62c --- /dev/null +++ b/policyengine_uk/tests/policy/reforms/parametric/winter_fuel_allowance/minimum_tax_credit_award.yaml @@ -0,0 +1,38 @@ +- name: A tax credit minimum of zero does not qualify a pensioner with no tax credits + # Lowering minimum_tax_credit_award to 0 means any award counts; a person + # with no award still does not qualify. + period: 2024 + input: + gov.dwp.winter_fuel_payment.eligibility.minimum_tax_credit_award: 0 + gov.social_security_scotland.pawhp.eligibility.minimum_tax_credit_award: 0 + age: 70 + country: ENGLAND + pension_credit: 0 + tax_credits: 0 + output: + is_on_winter_fuel_payment_relevant_benefit: false + winter_fuel_allowance: 0 + +- name: A tax credit minimum of zero qualifies any positive award + period: 2024 + input: + gov.dwp.winter_fuel_payment.eligibility.minimum_tax_credit_award: 0 + age: 70 + country: ENGLAND + pension_credit: 0 + tax_credits: 10 + output: + is_on_winter_fuel_payment_relevant_benefit: true + winter_fuel_allowance: 200 + +- name: A tax credit minimum of zero does not qualify a Scottish pensioner with no tax credits + period: 2024 + input: + gov.social_security_scotland.pawhp.eligibility.minimum_tax_credit_award: 0 + age: 70 + country: SCOTLAND + pension_credit: 0 + tax_credits: 0 + output: + is_on_pawhp_relevant_benefit: false + pawhp: 0 diff --git a/policyengine_uk/utils/winter_heating.py b/policyengine_uk/utils/winter_heating.py index 2471ec3847..d94786373b 100644 --- a/policyengine_uk/utils/winter_heating.py +++ b/policyengine_uk/utils/winter_heating.py @@ -42,9 +42,8 @@ def is_on_relevant_benefit(person, period, eligibility): ``minimum_tax_credit_award`` (infinite when tax credits do not count). """ listed = add(person, period, eligibility.relevant_benefits) > 0 - tax_credits = ( - person("tax_credit_award", period) >= eligibility.minimum_tax_credit_award - ) + award = person("tax_credit_award", period) + tax_credits = (award > 0) & (award >= eligibility.minimum_tax_credit_award) return listed | tax_credits @@ -53,17 +52,16 @@ def reports_relevant_benefit(person, period, eligibility): reports = [ report for variable in eligibility.relevant_benefits - for report in REPORTED_AWARDS.get(variable, []) + for report in REPORTED_AWARDS[variable] ] own_award = add(person, period, reports) > 0 if reports else False - tax_credits = ( - add(person, period, TAX_CREDIT_REPORTS) >= eligibility.minimum_tax_credit_award - ) + reported = add(person, period, TAX_CREDIT_REPORTS) + tax_credits = (reported > 0) & (reported >= eligibility.minimum_tax_credit_award) return own_award | tax_credits def is_excluded_relevant_benefit_partner( - person, period, qualifies, on_relevant_benefit, reports_relevant_benefit + person, period, qualifies, on_relevant_benefit, reports ): """Whether the person is the partner of the person paid for their couple. @@ -74,14 +72,15 @@ def is_excluded_relevant_benefit_partner( award; failing that, the benefit-unit head; failing that, the elder. The choice matters beyond the couple: whether someone else in the household lives with an entitled person aged 80 or over depends on whom the couple's - payment is made to (SI 2025/969 reg 3(5) and (6)). + payment is made to (SI 2000/729 reg 2(2)(b); SI 2025/969 reg 3(5) and (6); + SSI 2024/351 reg 10(6) as substituted by SSI 2025/282). """ age = person("age", period) couple_member = ( qualifies & person("is_claimant_or_partner", period) & on_relevant_benefit ) precedence = where( - reports_relevant_benefit, + reports, 0, where(person("is_benunit_head", period), 1, 2), ) diff --git a/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py b/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py index ab0fec4fa7..0f3f7de4cd 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py +++ b/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py @@ -14,7 +14,9 @@ class pawhp_eligible(Variable): "Payment: they have reached pensionable age and live in Scotland, " "they are not the other member of a couple on a relevant benefit " "whose payment is made to their partner, and, for the 2024 " - "qualifying week, they are entitled to a relevant benefit. For the " + "qualifying week, they are entitled to a relevant benefit (reg 7, " + "with reg 7(2) treating both members of a couple as entitled, and " + "reg 9(1)(d) as made paying one of them). For the " "2025 qualifying week the couple rule (reg 10(8) as substituted by " "SSI 2025/282) applied only within reg 10, so reg 9(d), which " "excludes 'one member of a couple ... who is entitled to a relevant " From 4104aff94c3bf667bae5c8434f3103ef2afaf5d2 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 09:00:38 -0400 Subject: [PATCH 5/8] Read "paid to" and couple receipt separately for the winter heating payments #2027 now names the one member of a couple an income-related award is paid to. The winter heating instruments use two readings: - SI 2000/729, SI 2025/969 and NISR 2025/142 ask whether a relevant benefit "has been, or falls to be, paid to" P: the payee only for IS, JSA(IB), ESA(IR) and Pension Credit; both members for UC and tax credits. - SI 2024/869 reg 2(5), NISR 2024/160 reg 2(5) and SSI 2024/351 (reg 7(2), 10(8), 2A) count a couple member as on one when the other member is. New `partner_receipt_counts` parameters pick the reading per scheme and year. A couple on a relevant benefit still gets one payment: the couple's receipt decides the exclusion (SI 2000/729 reg 3(1)(a)(i), SI 2025/969 reg 4(2)(a), SI 2024/869 reg 4(1)(a)(i), SSI 2024/351 reg 9(d)). `is_on_pension_credit` now names the payee, as the legacy readers do. Without this, an ESA couple of pensionable age got two Winter Fuel Payments after #2027's change. New YAML cases cover mixed-age IS/JSA/ESA and UC couples under each instrument; the Hypothesis reference states both readings. Co-Authored-By: Claude Opus 5.5 --- .../winter-heating-per-person-payee.fixed.md | 1 + .../eligibility/partner_receipt_counts.yaml | 27 +++ .../eligibility/partner_receipt_counts.yaml | 22 +++ .../relevant_benefit_receipt.yaml | 156 ++++++++++++++++++ .../gov/social_security_scotland/pawhp.yaml | 74 +++++++++ .../test_winter_heating_payment_properties.py | 77 +++++++-- policyengine_uk/utils/winter_heating.py | 64 +++++-- .../variables/gov/dwp/is_on_pension_credit.py | 22 ++- ...on_winter_fuel_payment_relevant_benefit.py | 19 ++- .../is_on_pawhp_relevant_benefit.py | 7 +- 10 files changed, 422 insertions(+), 47 deletions(-) create mode 100644 changelog.d/winter-heating-per-person-payee.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/partner_receipt_counts.yaml create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/partner_receipt_counts.yaml diff --git a/changelog.d/winter-heating-per-person-payee.fixed.md b/changelog.d/winter-heating-per-person-payee.fixed.md new file mode 100644 index 0000000000..39b4f12faf --- /dev/null +++ b/changelog.d/winter-heating-per-person-payee.fixed.md @@ -0,0 +1 @@ +- Read "paid to" and "entitled through the couple" separately for the winter heating payments, now that the legacy award readers name the one member of a couple an award is paid to. Under SI 2000/729, SI 2025/969 and NISR 2025/142 a person is on a relevant benefit only when it is paid to them (Universal Credit and tax credits are joint awards). Under SI 2024/869 reg 2(5), NISR 2024/160 reg 2(5) and SSI 2024/351 (reg 7(2), 10(8) and 2A) a member of a couple counts as on one when the other member is (`partner_receipt_counts`). A couple on a relevant benefit still receives one payment. `is_on_pension_credit` names the member Pension Credit is paid to, as the other readers do. diff --git a/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/partner_receipt_counts.yaml b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/partner_receipt_counts.yaml new file mode 100644 index 0000000000..69f47640a7 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/partner_receipt_counts.yaml @@ -0,0 +1,27 @@ +description: Whether a member of a couple counts as on a relevant benefit for the Winter Fuel Payment when the other member is. The 2024 regulations say so. The 2000 and 2025 regulations instead ask whether a relevant benefit has been paid to the person, and income support, income-based jobseeker's allowance, income-related employment and support allowance and Pension Credit are paid to one member of a couple. +values: + 2000-01-01: + value: false + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2000, reg. 2(1)(ii) "paid to him" (as in force for the 2023 qualifying week) + href: https://www.legislation.gov.uk/uksi/2000/729/regulation/2/2023-09-18 + # Dated 1 January so that the year's value is the one for its September + # qualifying week; SI 2024/869 came into force on 16 September 2024. + 2024-01-01: + value: true + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2024, reg. 2(5) + href: https://www.legislation.gov.uk/uksi/2024/869/regulation/2/made + - title: The Social Fund Winter Fuel Payment Regulations (Northern Ireland) 2024, reg. 2(5) + href: https://www.legislation.gov.uk/nisr/2024/160/regulation/2/made + # SI 2025/969 came into force on 15 September 2025. + 2025-01-01: + value: false + reference: + - title: The Social Fund Winter Fuel Payment Regulations 2025, reg. 3(2)(a), (3)(a), (5)(a), (6)(a) and 4(2)(a) "paid to P" + href: https://www.legislation.gov.uk/uksi/2025/969/regulation/3/made + - title: The Social Fund Winter Fuel Payment Regulations (Northern Ireland) 2025, reg. 3 + href: https://www.legislation.gov.uk/nisr/2025/142/regulation/3 +metadata: + unit: bool + label: Winter Fuel Payment partner's relevant benefit counts diff --git a/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/partner_receipt_counts.yaml b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/partner_receipt_counts.yaml new file mode 100644 index 0000000000..f359fd009e --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/pawhp/eligibility/partner_receipt_counts.yaml @@ -0,0 +1,22 @@ +description: Whether a member of a couple counts as entitled to a relevant benefit for the Pension Age Winter Heating Payment when the other member is. +values: + 2024-01-01: + value: true + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Regulations 2024 (SSI 2024/351), reg. 7(2) (as made) + href: https://www.legislation.gov.uk/ssi/2024/351/regulation/7/made + # SSI 2025/282 came into force on 11 October 2025 and applies to the + # qualifying week in September 2025. + 2025-01-01: + value: true + reference: + - title: The Winter Heating Assistance (Pension Age) (Scotland) Amendment Regulations 2025 (SSI 2025/282), reg. 9 (new reg. 10(8)) + href: https://www.legislation.gov.uk/ssi/2025/282/regulation/9/made + 2026-04-01: + value: true + reference: + - title: The Social Security (Up-rating) (Miscellaneous Amendments) (Scotland) Regulations 2026 (SSI 2026/170), reg. 15 (new reg. 2A) + href: https://www.legislation.gov.uk/ssi/2026/170/regulation/15/made +metadata: + unit: bool + label: PAWHP partner's relevant benefit counts diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml index 7c650a46e6..808a45ad64 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml @@ -640,3 +640,159 @@ is_on_winter_fuel_payment_relevant_benefit: [false, false, false] winter_fuel_payment: [100, 0, 100] winter_fuel_allowance: 200 + +# A couple whose claimant is under pensionable age and whose partner is over +# it. Income-related ESA and income-based JSA are paid to the claimant (the +# benefit-unit head here), Universal Credit to the couple jointly. The +# lodger, aged 75, is in a benefit unit of their own and on no relevant +# benefit. + +- name: 2025 the partner of an income-related ESA claimant under pensionable age is paid at their own rate + # The partner is not excluded: reg. 4(2)(a) needs the person the benefit is + # paid to to have reached pensionable age. ESA is not paid to the partner, + # who lives with a person entitled (the lodger): reg. 3(2), £100. The + # lodger: reg. 3(2), £100. + period: 2025 + input: + people: + claimant: + age: 60 + is_benunit_head: true + partner: + age: 72 + is_benunit_head: false + lodger: + age: 75 + total_income: 20_000 + benunits: + couple: + members: [claimant, partner] + esa_income: 1_000 + pension_credit: 0 + universal_credit: 0 + income_support: 0 + jsa_income: 0 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [claimant, partner, lodger] + country: ENGLAND + output: + is_on_income_related_esa: [true, false, false] + is_on_winter_fuel_payment_relevant_benefit: [true, false, false] + winter_fuel_payment_eligible: [false, true, true] + winter_fuel_payment: [0, 100, 100] + winter_fuel_allowance: 200 + +- name: 2025 Universal Credit is paid to both members, so the partner keeps the full amount beside a lodger + # A joint award: the partner is on Universal Credit and gets £200 (reg. + # 3(1); reg. 3(2) applies only where no relevant benefit is paid to P). + # The lodger: reg. 3(2), £100. + period: 2025 + input: + people: + claimant: + age: 60 + is_benunit_head: true + partner: + age: 72 + is_benunit_head: false + lodger: + age: 75 + total_income: 20_000 + benunits: + couple: + members: [claimant, partner] + universal_credit: 1_000 + pension_credit: 0 + esa_income: 0 + income_support: 0 + jsa_income: 0 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [claimant, partner, lodger] + country: ENGLAND + output: + is_on_winter_fuel_payment_relevant_benefit: [true, true, false] + winter_fuel_payment: [0, 200, 100] + winter_fuel_allowance: 300 + +- name: 2024 the partner of an income-related ESA claimant under pensionable age is entitled through the couple + # Reg. 2(5) counts the claimant's ESA for the partner, so reg. 2(2)(b) is + # met: reg. 3(c), £200. The lodger is on no relevant benefit: nothing. + period: 2024 + input: + people: + claimant: + age: 60 + is_benunit_head: true + partner: + age: 72 + is_benunit_head: false + lodger: + age: 75 + total_income: 20_000 + benunits: + couple: + members: [claimant, partner] + esa_income: 1_000 + pension_credit: 0 + universal_credit: 0 + income_support: 0 + jsa_income: 0 + tax_credits: 0 + lodger_unit: + members: [lodger] + pension_credit: 0 + tax_credits: 0 + households: + household: + members: [claimant, partner, lodger] + country: ENGLAND + output: + is_on_income_related_esa: [true, false, false] + is_on_winter_fuel_payment_relevant_benefit: [true, true, false] + winter_fuel_payment: [0, 200, 0] + winter_fuel_allowance: 200 + +- name: 2023 the partner of an income-based JSA claimant under qualifying age is paid at their own rate + # SI 2000/729: the partner is not excluded (reg. 3(1)(a)(i) needs the + # person JSA is paid to to have attained the qualifying age). JSA has not + # been paid to the partner, who lives with a person to whom a payment is + # made (the lodger): reg. 2(1)(ii), £100. The lodger: £100. + period: 2023 + input: + people: + claimant: + age: 60 + is_benunit_head: true + partner: + age: 72 + is_benunit_head: false + lodger: + age: 75 + total_income: 20_000 + benunits: + couple: + members: [claimant, partner] + jsa_income: 1_000 + pension_credit: 0 + universal_credit: 0 + income_support: 0 + esa_income: 0 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [claimant, partner, lodger] + country: ENGLAND + output: + is_on_income_based_jsa: [true, false, false] + winter_fuel_payment: [0, 100, 100] + winter_fuel_allowance: 200 diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml index 86395ca936..df97cce57a 100644 --- a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/pawhp.yaml @@ -473,6 +473,80 @@ pension_age_winter_heating_payment: [0, 316.70] pawhp: 316.70 +- name: 2025 the partner of an income-related ESA claimant under pensionable age is treated as entitled + # Reg. 10(8) as substituted by SSI 2025/282: the partner is treated as + # entitled to a relevant benefit, so reg. 10(2) and (4)(b): £203.40, even + # beside an entitled lodger. The lodger, under 80, on no relevant benefit + # and living with another entitled individual: reg. 10(5)(b), £101.70. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 60 + is_benunit_head: true + partner: + age: 72 + is_benunit_head: false + lodger: + age: 75 + total_income: 20_000 + benunits: + couple: + members: [claimant, partner] + esa_income: 1_000 + pension_credit: 0 + universal_credit: 0 + income_support: 0 + jsa_income: 0 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [claimant, partner, lodger] + country: SCOTLAND + output: + is_on_pawhp_relevant_benefit: [true, true, false] + pension_age_winter_heating_payment: [0, 203.40, 101.70] + pawhp: 305.10 + +- name: 2024 the partner of an income-related ESA claimant under pensionable age is entitled through the couple + # Reg. 7(2) as made counts the claimant's ESA for the partner: reg. 10(c) + # as made, £200. The lodger is on no relevant benefit: nothing. + period: 2024 + input: + people: + claimant: + age: 60 + is_benunit_head: true + partner: + age: 72 + is_benunit_head: false + lodger: + age: 75 + total_income: 20_000 + benunits: + couple: + members: [claimant, partner] + esa_income: 1_000 + pension_credit: 0 + universal_credit: 0 + income_support: 0 + jsa_income: 0 + tax_credits: 0 + lodger_unit: + members: [lodger] + pension_credit: 0 + tax_credits: 0 + households: + household: + members: [claimant, partner, lodger] + country: SCOTLAND + output: + pension_age_winter_heating_payment: [0, 200, 0] + pawhp: 200 + - name: The Winter Fuel Payment is not paid in Scotland from 2024 # SI 2024/869 and SI 2025/969 extend to England and Wales only. period: 2025 diff --git a/policyengine_uk/tests/test_winter_heating_payment_properties.py b/policyengine_uk/tests/test_winter_heating_payment_properties.py index 325878fae5..36f1cfc4b5 100644 --- a/policyengine_uk/tests/test_winter_heating_payment_properties.py +++ b/policyengine_uk/tests/test_winter_heating_payment_properties.py @@ -2,9 +2,17 @@ each person's own receipt of a relevant benefit. Both schemes entitle and pay a person (SI 2000/729 reg 2; SI 2024/869 regs -2 to 4; SI 2025/969 regs 2 to 4; SSI 2024/351 regs 5, 9 and 10). A person is -on a relevant benefit through their own award or, for the claimant and -partner, their couple's award. Properties: +2 to 4; SI 2025/969 regs 2 to 4; SSI 2024/351 regs 5, 9 and 10). Whether a +person is on a relevant benefit depends on the instrument: + +- "paid to" the person (SI 2000/729, SI 2025/969): Income Support, + income-based JSA, income-related ESA and Pension Credit are paid to one + member of a couple, the one who reports the award or else the benefit-unit + head; Universal Credit and tax credits are joint awards, paid to both; +- through the couple (SI 2024/869 reg 2(5); SSI 2024/351 reg 7(2), 10(8) and + 2A): a member of a couple is on one when the other member is. + +Either way a couple on one receives a single payment. Properties: - the model matches a reference implementation written directly from the regulations, person by person, over households of up to three benefit @@ -56,12 +64,21 @@ "UC": "universal_credit_reported", "TC": "working_tax_credit_reported", } -# An award reported by a member who is neither claimant nor partner. +# An award reported by a member who is neither claimant nor partner. (An +# outside member's own income-related ESA or income-based JSA counts only if +# the award on their report is paid after the benefit unit's capital and +# eligibility screens, which the legacy award tests cover; the YAML tests +# cover the winter payments' side of it.) OWN_AWARD_INPUTS = { "PC": "pension_credit_reported", - "ESA": "esa_income_reported", "UC": "universal_credit_reported", } +# Inputs for an award an outside member reports. The non-dependant property +# also draws income-related ESA: whether or not it is paid, it must never +# change anyone else's payment. +OWN_REPORT_INPUTS = {**OWN_AWARD_INPUTS, "ESA": "esa_income_reported"} +# Awards paid jointly to both members of a couple. +JOINT_AWARDS = {"UC", "TC"} # Relevant benefits by scheme and qualifying week, as listed in the # regulations (not read from the model's parameters). @@ -167,7 +184,7 @@ def situation(drawn, year): } own = inputs.get("own_award") if own is not None: - person[OWN_AWARD_INPUTS[own]] = {year: AWARD} + person[OWN_REPORT_INPUTS[own]] = {year: AWARD} people[name] = person for name, (inputs, u, cp) in zip(names, members): if cp and inputs.get("reports"): @@ -202,23 +219,50 @@ def award_of(person): return units[u]["award"] return inputs.get("own_award") - def scheme(resident, relevant, means_test, amounts): + def is_payee(i): + """Whether the couple's award is paid to this claimant or partner: + the member who reports it, else the benefit-unit head.""" + inputs, u, _ = people[i] + reporter = units[u].get("reporter") + if reporter is not None: + return bool(inputs["reports"]) + return bool(inputs["head"]) + + def paid(relevant): + """Whether a relevant benefit is paid to each person.""" + return [ + award_of(p) in relevant + and (not p[2] or award_of(p) in JOINT_AWARDS or is_payee(i)) + for i, p in enumerate(people) + ] + + def through_couple(receipt): + """Each claimant or partner is on one when either of them is.""" + return [ + any(receipt[j] for j, q in enumerate(people) if q[1] == p[1] and q[2]) + if p[2] + else receipt[i] + for i, p in enumerate(people) + ] + + def scheme(resident, relevant, means_test, amounts, partner_counts): lower, higher, shared, shared_80_with_under_80, shared_80_with_80 = amounts - on_relevant = [award_of(p) in relevant for p in people] + couple = through_couple(paid(relevant)) + on_relevant = couple if partner_counts else paid(relevant) qualifies = [resident and p[0]["age"] >= 67 for p in people] eligible = [q and (r or means_test) for q, r in zip(qualifies, on_relevant)] # One payment for a couple on a relevant benefit, to the claimant: the # member who reports the award, else the benefit-unit head, else the # elder (first listed if the same age). The other is not entitled. for u in range(len(units)): - couple = [ + members = [ i for i, p in enumerate(people) - if p[1] == u and p[2] and qualifies[i] and on_relevant[i] + if p[1] == u and p[2] and qualifies[i] and couple[i] ] - if couple: + if members: payee = min( - couple, + members, key=lambda i: ( 0 if people[i][0]["reports"] @@ -230,7 +274,7 @@ def scheme(resident, relevant, means_test, amounts): i, ), ) - for i in couple: + for i in members: if i != payee: eligible[i] = False payments = [] @@ -270,9 +314,12 @@ def scheme(resident, relevant, means_test, amounts): p[0]["age"] >= 67 and p[0]["total_income"] < INCOME_LIMIT for p in people ) - wfp = scheme(wfp_resident, WFP_RELEVANT[year], wfp_means, WFP_AMOUNTS) + # Only SI 2024/869 (reg 2(5)) counts the other member's benefit. + wfp = scheme(wfp_resident, WFP_RELEVANT[year], wfp_means, WFP_AMOUNTS, year == 2024) if year >= 2024 and country == "SCOTLAND": - pawhp = scheme(True, PAWHP_RELEVANT[year], year >= 2025, PAWHP_AMOUNTS[year]) + pawhp = scheme( + True, PAWHP_RELEVANT[year], year >= 2025, PAWHP_AMOUNTS[year], True + ) else: pawhp = [0.0] * len(people) return wfp, pawhp diff --git a/policyengine_uk/utils/winter_heating.py b/policyengine_uk/utils/winter_heating.py index d94786373b..80e854c301 100644 --- a/policyengine_uk/utils/winter_heating.py +++ b/policyengine_uk/utils/winter_heating.py @@ -4,7 +4,7 @@ Both schemes entitle and pay a person, and set the amount from that person's own circumstances: -- a person to whom a relevant benefit is paid receives the full amount, the +- a person on a relevant benefit receives the full amount, the higher amount if they or their partner have reached 80 (SI 2000/729 reg 2(1)(i) and 2(3); SI 2024/869 reg 3; SI 2025/969 reg 3(1), (3) and (4); SSI 2024/351 reg 10, as made and as substituted by SSI 2025/282); @@ -15,6 +15,24 @@ entitled to a payment (SI 2000/729 reg 2(1)(ii) and 2(2)(b); SI 2025/969 reg 3(2), (5) and (6); SSI 2024/351 reg 10(5) and (6) as substituted). +Who is "on" a relevant benefit differs between the instruments: + +- SI 2000/729, SI 2025/969 and NISR 2025/142 ask whether the benefit "has + been, or falls to be, paid to" the person. Income Support, income-based + JSA, income-related ESA and Pension Credit are paid to one member of a + couple (is_on_income_support and the other readers name that member); + Universal Credit and tax credits are joint awards, so both members are on + them. +- SI 2024/869 reg 2(5), NISR 2024/160 reg 2(5) and SSI 2024/351 (reg 7(2) as + made, reg 10(8) as substituted by SSI 2025/282, reg 2A from April 2026) + treat a member of a couple as entitled when the other member is. + +Each scheme's eligibility node says which applies (partner_receipt_counts). +Either way a couple on a relevant benefit receives one payment, to the member +the benefit is paid to, or to the other member when only the other has +reached pensionable age (SI 2000/729 reg 3(1)(a)(i); SI 2024/869 reg +4(1)(a)(i); SI 2025/969 reg 4(2)(a); SSI 2024/351 reg 9(d)). + Benefit units model couples and households model living together: everyone in a household is taken to share it as their mutual home. Residential care, long hospital stays and custody are not modelled. @@ -34,17 +52,34 @@ TAX_CREDIT_REPORTS = ["child_tax_credit_reported", "working_tax_credit_reported"] +def couple_receipt(person, period, receipt): + """The receipt of the person's couple: for the claimant and the partner, + whether either of them is on a relevant benefit; for anyone else, their + own receipt.""" + claimant_or_partner = person("is_claimant_or_partner", period) + couple = person.benunit.any(claimant_or_partner & receipt) + return where(claimant_or_partner, couple, receipt) + + def is_on_relevant_benefit(person, period, eligibility): - """Whether a relevant benefit is paid to the person, or to their couple. + """Whether the person is on a relevant benefit for the scheme. ``eligibility`` is the scheme's eligibility parameter node: the benefits - in ``relevant_benefits``, and a tax credit award of at least - ``minimum_tax_credit_award`` (infinite when tax credits do not count). + in ``relevant_benefits``, a tax credit award of at least + ``minimum_tax_credit_award`` (infinite when tax credits do not count), + and ``partner_receipt_counts``: whether a member of a couple is on a + relevant benefit when the other member is. Without it, the person is on + one only if it is paid to them. """ listed = add(person, period, eligibility.relevant_benefits) > 0 award = person("tax_credit_award", period) tax_credits = (award > 0) & (award >= eligibility.minimum_tax_credit_award) - return listed | tax_credits + paid = listed | tax_credits + return where( + eligibility.partner_receipt_counts, + couple_receipt(person, period, paid), + paid, + ) def reports_relevant_benefit(person, period, eligibility): @@ -67,15 +102,20 @@ def is_excluded_relevant_benefit_partner( A couple on a relevant benefit receives one payment, made to the person the benefit is paid to (the claimant), or to their partner when only the - partner qualifies; the partner of the person paid is not entitled. The - model takes the claimant to be the member of the couple who reports the - award; failing that, the benefit-unit head; failing that, the elder. The - choice matters beyond the couple: whether someone else in the household - lives with an entitled person aged 80 or over depends on whom the couple's - payment is made to (SI 2000/729 reg 2(2)(b); SI 2025/969 reg 3(5) and (6); - SSI 2024/351 reg 10(6) as substituted by SSI 2025/282). + partner qualifies; the partner of the person paid is not entitled. Under + SI 2000/729 reg 3(1)(a)(i) and SI 2025/969 reg 4(2)(a) the partner is + excluded when the person the benefit is paid to has reached pensionable + age, so the couple's receipt decides this whichever instrument applies. + The model takes the claimant to be the member of the couple who reports + the award; failing that, the benefit-unit head; failing that, the elder, + as the legacy award readers do. The choice matters beyond the couple: + whether someone else in the household lives with an entitled person aged + 80 or over depends on whom the couple's payment is made to (SI 2000/729 + reg 2(2)(b); SI 2025/969 reg 3(5) and (6); SSI 2024/351 reg 10(6) as + substituted by SSI 2025/282). """ age = person("age", period) + on_relevant_benefit = couple_receipt(person, period, on_relevant_benefit) couple_member = ( qualifies & person("is_claimant_or_partner", period) & on_relevant_benefit ) diff --git a/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py b/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py index 98042710d3..0e4a2c9349 100644 --- a/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py +++ b/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py @@ -1,4 +1,7 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.dwp._legacy_award_payee import ( + is_payee_of_couple_award, +) class is_on_pension_credit(Variable): @@ -7,19 +10,24 @@ class is_on_pension_credit(Variable): label = "on Pension Credit" documentation = ( "Whether State Pension Credit is payable to this person. Pension " - "Credit is awarded to a claimant for themselves and their partner " - "(SPCA 2002 ss.1 and 17), so the claimant and the partner are both on " - "it when their benefit unit's award (pension_credit) is positive. Any " - "other member of the benefit unit claims in their own right and is on " - "it only if they report an award themselves." + "Credit is the claimant's entitlement, with their partner's income " + "and capital treated as theirs (SPCA 2002 ss.1 and 5), and is paid " + "to the claimant, not the partner. Of the claimant and partner, only " + "the payee is on it, meaning the one who reports the award, or the " + "claimant where neither does, and only while the benefit unit's award " + "(pension_credit) is positive, as for the legacy income-related " + "awards. Any other member of the benefit unit claims in their own " + "right and is on it only if they report an award themselves." ) definition_period = YEAR reference = ( "https://www.legislation.gov.uk/ukpga/2002/16/section/1", - "https://www.legislation.gov.uk/ukpga/2002/16/section/17", + "https://www.legislation.gov.uk/ukpga/2002/16/section/5", ) def formula(person, period, parameters): - couple_award = person.benunit("pension_credit", period) > 0 + couple_award = (person.benunit("pension_credit", period) > 0) & ( + is_payee_of_couple_award(person, period, "pension_credit_reported") + ) own_award = person("pension_credit_reported", period) > 0 return where(person("is_claimant_or_partner", period), couple_award, own_award) diff --git a/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py b/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py index cddfa8d927..74e4b44fe9 100644 --- a/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py +++ b/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py @@ -7,17 +7,18 @@ class is_on_winter_fuel_payment_relevant_benefit(Variable): entity = Person label = "on a relevant benefit for the Winter Fuel Payment" documentation = ( - "Whether a relevant benefit for the Winter Fuel Payment is paid to " - "this person. The relevant benefits are listed by period in " + "Whether this person is on a relevant benefit for the Winter Fuel " + "Payment. The relevant benefits are listed by period in " "gov.dwp.winter_fuel_payment.eligibility.relevant_benefits; for the " "2024 qualifying week a tax credit award of at least £26 also counts " - "(minimum_tax_credit_award). A couple's award covers both partners, " - "so the claimant and the partner are on it when their benefit unit's " - "award is positive (SI 2024/869 reg 2(5) says so expressly; under SI " - "2000/729 and SI 2025/969 the couple receives one payment either " - "way). Any other member of the benefit unit, such as a non-dependent " - "adult, is on it only through their own award: their relevant benefit " - "never counts for anyone else in the household." + "(minimum_tax_credit_award). Under SI 2000/729 and SI 2025/969 the " + "person is on one when it is paid to them: Income Support, " + "income-based JSA, income-related ESA and Pension Credit are paid to " + "one member of a couple, while Universal Credit and tax credits are " + "joint awards. For the 2024 qualifying week a member of a couple is " + "also on one when the other member is (SI 2024/869 reg 2(5); " + "partner_receipt_counts). A non-dependant's relevant benefit never " + "counts for anyone else in the household." ) definition_period = YEAR reference = ( diff --git a/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py b/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py index ee316ef08f..ce6957477e 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py +++ b/policyengine_uk/variables/gov/social_security_scotland/is_on_pawhp_relevant_benefit.py @@ -14,10 +14,9 @@ class is_on_pawhp_relevant_benefit(Variable): "counts (minimum_tax_credit_award). A member of a couple is treated " "as entitled when the other member is (SSI 2024/351 reg 7(2) as made, " "reg 10(8) as substituted by SSI 2025/282, and reg 2A from April " - "2026), so the claimant and the partner are on it when their benefit " - "unit's award is positive. Any other member of the benefit unit, such " - "as a non-dependent adult, is on it only through their own award: " - "their relevant benefit never counts for anyone else in the household." + "2026; partner_receipt_counts), so the claimant and the partner are " + "on it when either of them is. A non-dependant's relevant benefit " + "never counts for anyone else in the household." ) definition_period = YEAR reference = ( From 17ce2368a3088c323330ecead36ff496acdbdbb5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 11:28:12 -0400 Subject: [PATCH 6/8] Fold the payee changelog entry into the PR's own entries The "paid to" and couple readings are part of this PR's new person-level receipt test, not a fix to released behaviour, so the receipt entries now state them. The added entry no longer says Pension Credit counts for both members of a couple: it names the member it is paid to. Co-Authored-By: Claude Opus 5.5 --- changelog.d/winter-heating-per-person-payee.fixed.md | 1 - changelog.d/winter-heating-per-person-receipt.added.md | 2 +- changelog.d/winter-heating-per-person-receipt.fixed.md | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 changelog.d/winter-heating-per-person-payee.fixed.md diff --git a/changelog.d/winter-heating-per-person-payee.fixed.md b/changelog.d/winter-heating-per-person-payee.fixed.md deleted file mode 100644 index 39b4f12faf..0000000000 --- a/changelog.d/winter-heating-per-person-payee.fixed.md +++ /dev/null @@ -1 +0,0 @@ -- Read "paid to" and "entitled through the couple" separately for the winter heating payments, now that the legacy award readers name the one member of a couple an award is paid to. Under SI 2000/729, SI 2025/969 and NISR 2025/142 a person is on a relevant benefit only when it is paid to them (Universal Credit and tax credits are joint awards). Under SI 2024/869 reg 2(5), NISR 2024/160 reg 2(5) and SSI 2024/351 (reg 7(2), 10(8) and 2A) a member of a couple counts as on one when the other member is (`partner_receipt_counts`). A couple on a relevant benefit still receives one payment. `is_on_pension_credit` names the member Pension Credit is paid to, as the other readers do. diff --git a/changelog.d/winter-heating-per-person-receipt.added.md b/changelog.d/winter-heating-per-person-receipt.added.md index 1d2733af49..57bdd756a2 100644 --- a/changelog.d/winter-heating-per-person-receipt.added.md +++ b/changelog.d/winter-heating-per-person-receipt.added.md @@ -1,2 +1,2 @@ -- Add `is_on_pension_credit` and `is_on_universal_credit` (whether the award is payable to the person) and `tax_credit_award` (the tax credit award payable to them): through their couple's award for the claimant and partner, and through their own report for anyone else. +- Add `is_on_pension_credit` and `is_on_universal_credit` (whether the award is payable to the person) and `tax_credit_award` (the tax credit award payable to them). For the claimant and partner they read their couple's award: Pension Credit is paid to one member (the one who reports it, else the claimant), and Universal Credit and tax credits are joint awards. Anyone else is on an award only through their own report. - Add person-level `winter_fuel_payment` and `pension_age_winter_heating_payment`, with `winter_fuel_payment_eligible`, `pawhp_eligible`, `is_on_winter_fuel_payment_relevant_benefit` and `is_on_pawhp_relevant_benefit`. The household `winter_fuel_allowance` and `pawhp` now add up their members' payments. diff --git a/changelog.d/winter-heating-per-person-receipt.fixed.md b/changelog.d/winter-heating-per-person-receipt.fixed.md index cad767ddc7..c336ee492f 100644 --- a/changelog.d/winter-heating-per-person-receipt.fixed.md +++ b/changelog.d/winter-heating-per-person-receipt.fixed.md @@ -1,4 +1,4 @@ -- Test receipt of a relevant benefit for the Winter Fuel Payment and the Pension Age Winter Heating Payment person by person: the person's own award, or their couple's for the claimant and partner. A non-dependant's income-related ESA, income-based JSA, Income Support or Pension Credit no longer qualifies a pensioner in the same household, or exempts them from the Winter Fuel Payment income test. +- Test receipt of a relevant benefit for the Winter Fuel Payment and the Pension Age Winter Heating Payment person by person. Under SI 2000/729, SI 2025/969 and NISR 2025/142 a person is on a relevant benefit when it is paid to them: Income Support, income-based JSA, income-related ESA and Pension Credit are paid to one member of a couple, and Universal Credit and tax credits are joint awards. Under SI 2024/869 reg 2(5), NISR 2024/160 reg 2(5) and SSI 2024/351 a member of a couple is also on one when the other member is (`partner_receipt_counts`). A non-dependant's income-related ESA, income-based JSA, Income Support or Pension Credit no longer qualifies a pensioner in the same household, or exempts them from the Winter Fuel Payment income test. - Count Universal Credit as a relevant benefit for both payments, and a tax credit award of at least £26 for the 2024 qualifying week, as SI 2024/869, SI 2025/969 and SSI 2024/351 list them. - Pay each person the amount the regulations set: one payment for a couple on a relevant benefit, the full amount to anyone else living without another entitled person, and the shared amounts (£100, or £200 or £150 over 80, and the PAWHP equivalents) otherwise. Two benefit units in one household can now both be paid. - Pay the Winter Fuel Payment in Scotland up to the 2023 qualifying week (SI 2000/729 covered Great Britain) and the Pension Age Winter Heating Payment only from the 2024 week. From 4f2758c5d1d497ea35fd55f3f8d9d415f2c92ce8 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 12:24:05 -0400 Subject: [PATCH 7/8] Pay Pension Credit to a member over the qualifying age; ground UC's joint payment Answers review r4 on 17ce2368a. P1: is_on_pension_credit took the payee to be whichever member of the couple reports the award, else the benefit-unit head. A Pension Credit claimant must have attained the qualifying age (SPCA 2002 s.1(2)(b)), so a 75-year-old in a mixed-age couple whose 60-year-old partner reported the award lost the relevant-benefit rate, and a lodger's shared rate followed. is_payee_of_couple_award gains an optional can_claim limit (default unchanged for the legacy readers); Pension Credit passes has_attained_state_pension_credit_qualifying_age. Where neither member has attained it, both are considered as before. Three YAML cases pin the payee rule. They kill the surviving mutant that read Pension Credit for both members, and the mutant without the age limit. The Hypothesis reference states the same rule. P3: Universal Credit is awarded to a couple jointly (WRA 2012 s.1(2)(b)) and paid into an account the joint claimants nominate (Universal Credit etc. (Claims and Payments) Regulations 2013 reg 47(4)); reg 47(6) pays it wholly to one member only by special arrangement. So it falls to be paid to both, unlike the legacy awards, which are the claimant's alone. The docstrings, the YAML case, the Hypothesis reference and the changelog now say this instead of "joint awards, paid to both". Tax credits count only under the 2024 instruments, which count the couple either way. Nit: the one-unit property's docstring now names its scope (a single person or a couple with no one else in the household). Co-Authored-By: Claude Opus 5.5 --- ...winter-heating-per-person-receipt.added.md | 2 +- ...winter-heating-per-person-receipt.fixed.md | 2 +- .../relevant_benefit_receipt.yaml | 121 ++++++++++++++++-- .../test_winter_heating_payment_properties.py | 39 ++++-- policyengine_uk/utils/winter_heating.py | 12 +- .../variables/gov/dwp/_legacy_award_payee.py | 12 +- .../variables/gov/dwp/is_on_pension_credit.py | 24 +++- .../gov/dwp/is_on_universal_credit.py | 16 ++- ...on_winter_fuel_payment_relevant_benefit.py | 6 +- 9 files changed, 195 insertions(+), 39 deletions(-) diff --git a/changelog.d/winter-heating-per-person-receipt.added.md b/changelog.d/winter-heating-per-person-receipt.added.md index 57bdd756a2..5702719487 100644 --- a/changelog.d/winter-heating-per-person-receipt.added.md +++ b/changelog.d/winter-heating-per-person-receipt.added.md @@ -1,2 +1,2 @@ -- Add `is_on_pension_credit` and `is_on_universal_credit` (whether the award is payable to the person) and `tax_credit_award` (the tax credit award payable to them). For the claimant and partner they read their couple's award: Pension Credit is paid to one member (the one who reports it, else the claimant), and Universal Credit and tax credits are joint awards. Anyone else is on an award only through their own report. +- Add `is_on_pension_credit` and `is_on_universal_credit` (whether the award is payable to the person) and `tax_credit_award` (the tax credit award payable to them). For the claimant and partner they read their couple's award: Pension Credit is paid to one member (the one who reports it, else the claimant, among members who have attained the qualifying age), and Universal Credit and tax credits are joint awards. Anyone else is on an award only through their own report. - Add person-level `winter_fuel_payment` and `pension_age_winter_heating_payment`, with `winter_fuel_payment_eligible`, `pawhp_eligible`, `is_on_winter_fuel_payment_relevant_benefit` and `is_on_pawhp_relevant_benefit`. The household `winter_fuel_allowance` and `pawhp` now add up their members' payments. diff --git a/changelog.d/winter-heating-per-person-receipt.fixed.md b/changelog.d/winter-heating-per-person-receipt.fixed.md index c336ee492f..9d83d41914 100644 --- a/changelog.d/winter-heating-per-person-receipt.fixed.md +++ b/changelog.d/winter-heating-per-person-receipt.fixed.md @@ -1,4 +1,4 @@ -- Test receipt of a relevant benefit for the Winter Fuel Payment and the Pension Age Winter Heating Payment person by person. Under SI 2000/729, SI 2025/969 and NISR 2025/142 a person is on a relevant benefit when it is paid to them: Income Support, income-based JSA, income-related ESA and Pension Credit are paid to one member of a couple, and Universal Credit and tax credits are joint awards. Under SI 2024/869 reg 2(5), NISR 2024/160 reg 2(5) and SSI 2024/351 a member of a couple is also on one when the other member is (`partner_receipt_counts`). A non-dependant's income-related ESA, income-based JSA, Income Support or Pension Credit no longer qualifies a pensioner in the same household, or exempts them from the Winter Fuel Payment income test. +- Test receipt of a relevant benefit for the Winter Fuel Payment and the Pension Age Winter Heating Payment person by person. Under SI 2000/729, SI 2025/969 and NISR 2025/142 a person is on a relevant benefit when it is paid to them: Income Support, income-based JSA, income-related ESA and Pension Credit are paid to one member of a couple, while Universal Credit is awarded and paid to the couple jointly. Under SI 2024/869 reg 2(5), NISR 2024/160 reg 2(5) and SSI 2024/351 a member of a couple is also on one when the other member is (`partner_receipt_counts`). A non-dependant's income-related ESA, income-based JSA, Income Support or Pension Credit no longer qualifies a pensioner in the same household, or exempts them from the Winter Fuel Payment income test. - Count Universal Credit as a relevant benefit for both payments, and a tax credit award of at least £26 for the 2024 qualifying week, as SI 2024/869, SI 2025/969 and SSI 2024/351 list them. - Pay each person the amount the regulations set: one payment for a couple on a relevant benefit, the full amount to anyone else living without another entitled person, and the shared amounts (£100, or £200 or £150 over 80, and the PAWHP equivalents) otherwise. Two benefit units in one household can now both be paid. - Pay the Winter Fuel Payment in Scotland up to the 2023 qualifying week (SI 2000/729 covered Great Britain) and the Pension Age Winter Heating Payment only from the 2024 week. diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml index 808a45ad64..be4aaa5f7c 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml @@ -642,10 +642,13 @@ winter_fuel_allowance: 200 # A couple whose claimant is under pensionable age and whose partner is over -# it. Income-related ESA and income-based JSA are paid to the claimant (the -# benefit-unit head here), Universal Credit to the couple jointly. The -# lodger, aged 75, is in a benefit unit of their own and on no relevant -# benefit. +# it. Income-related ESA and income-based JSA are the claimant's own awards, +# paid to the claimant (the benefit-unit head here). Universal Credit is +# awarded to the couple jointly (WRA 2012 s.1(2)(b)) and paid into an +# account the joint claimants nominate (Universal Credit etc. (Claims and +# Payments) Regulations 2013 reg. 47(4)); reg. 47(6) pays it wholly to one +# member only by special arrangement. The lodger, aged 75, is in a benefit +# unit of their own and on no relevant benefit. - name: 2025 the partner of an income-related ESA claimant under pensionable age is paid at their own rate # The partner is not excluded: reg. 4(2)(a) needs the person the benefit is @@ -686,10 +689,12 @@ winter_fuel_payment: [0, 100, 100] winter_fuel_allowance: 200 -- name: 2025 Universal Credit is paid to both members, so the partner keeps the full amount beside a lodger - # A joint award: the partner is on Universal Credit and gets £200 (reg. - # 3(1); reg. 3(2) applies only where no relevant benefit is paid to P). - # The lodger: reg. 3(2), £100. +- name: 2025 Universal Credit falls to be paid to both joint claimants, so the partner keeps the full amount beside a lodger + # A joint award falls to be paid to both joint claimants, so it falls to + # be paid to the partner, who gets £200 (reg. 3(1); reg. 3(2) applies only + # where no relevant benefit has been, nor falls to be, paid to P). Unlike + # the ESA case above, the partner is a claimant of the award. The lodger: + # reg. 3(2), £100. period: 2025 input: people: @@ -796,3 +801,103 @@ is_on_income_based_jsa: [true, false, false] winter_fuel_payment: [0, 100, 100] winter_fuel_allowance: 200 + +# Pension Credit is paid to the claimant, who must have attained the +# qualifying age (SPCA 2002 s.1(2)(b)). + +- name: 2025 Pension Credit is paid only to the member of a couple who claims it + # Both have reached pensionable age and the 70-year-old reports the award, + # so it is paid to them, not to the partner who heads the benefit unit: + # reg. 3(1), £200. The partner is excluded: reg. 4(2)(a). + period: 2025 + input: + people: + claimant: + age: 70 + is_benunit_head: false + pension_credit_reported: 1_000 + partner: + age: 72 + is_benunit_head: true + benunits: + couple: + members: [claimant, partner] + pension_credit: 1_000 + households: + household: + members: [claimant, partner] + country: ENGLAND + output: + is_on_pension_credit: [true, false] + is_on_winter_fuel_payment_relevant_benefit: [true, false] + winter_fuel_payment: [200, 0] + winter_fuel_allowance: 200 + +- name: 2025 Pension Credit reported by a partner under the qualifying age is paid to the member over it + # A couple aged 75 and 60 on Pension Credit (a mixed-age couple whose award + # began before 15 May 2019). Only the 75-year-old can be the claimant, so + # Pension Credit is paid to them whoever reports it: reg. 3(1), £200. The + # 60-year-old has not reached pensionable age: nothing. The lodger, aged 70 + # and on no relevant benefit, lives with a person entitled: reg. 3(2), + # £100. + period: 2025 + input: + people: + older: + age: 75 + is_benunit_head: true + total_income: 20_000 + younger: + age: 60 + is_benunit_head: false + pension_credit_reported: 1_000 + lodger: + age: 70 + total_income: 20_000 + benunits: + couple: + members: [older, younger] + pension_credit: 1_000 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [older, younger, lodger] + country: ENGLAND + output: + is_on_pension_credit: [true, false, false] + winter_fuel_payment: [200, 0, 100] + winter_fuel_allowance: 300 + +- name: 2025 with no report, Pension Credit is paid to the member over the qualifying age even when the other heads the benefit unit + # As above, with the award entered for the couple and the 60-year-old as + # the benefit-unit head: £200, nothing and £100. + period: 2025 + input: + people: + older: + age: 75 + is_benunit_head: false + total_income: 20_000 + younger: + age: 60 + is_benunit_head: true + lodger: + age: 70 + total_income: 20_000 + benunits: + couple: + members: [older, younger] + pension_credit: 1_000 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [older, younger, lodger] + country: ENGLAND + output: + is_on_pension_credit: [true, false, false] + winter_fuel_payment: [200, 0, 100] + winter_fuel_allowance: 300 diff --git a/policyengine_uk/tests/test_winter_heating_payment_properties.py b/policyengine_uk/tests/test_winter_heating_payment_properties.py index 36f1cfc4b5..b0e1285243 100644 --- a/policyengine_uk/tests/test_winter_heating_payment_properties.py +++ b/policyengine_uk/tests/test_winter_heating_payment_properties.py @@ -8,7 +8,13 @@ - "paid to" the person (SI 2000/729, SI 2025/969): Income Support, income-based JSA, income-related ESA and Pension Credit are paid to one member of a couple, the one who reports the award or else the benefit-unit - head; Universal Credit and tax credits are joint awards, paid to both; + head (for Pension Credit, among the members who have attained the + qualifying age, SPCA 2002 s.1(2)(b)). Universal Credit is awarded to the + couple jointly and paid into an account the joint claimants nominate + (Universal Credit etc. (Claims and Payments) Regulations 2013 reg 47(4); + reg 47(6) pays it wholly to one member only by special arrangement), so it + is paid to both. Tax credits count only under the 2024 instruments, which + count the couple either way; - through the couple (SI 2024/869 reg 2(5); SSI 2024/351 reg 7(2), 10(8) and 2A): a member of a couple is on one when the other member is. @@ -19,8 +25,9 @@ units, in every country and in the 2023 to 2026 qualifying weeks; - adding a non-dependant under pensionable age, with or without a relevant benefit of their own, never changes what anyone else is paid; -- from the 2025 qualifying week, a household whose pension-age members all - belong to one benefit unit receives the full amount for that unit +- from the 2025 qualifying week, a household of a single person or a couple + (one benefit unit's claimant and partner, with no one else) receives the + full amount for that unit (higher if anyone is 80 or over), whether or not they are on a relevant benefit: the shared amounts are halves (or, for two people over 80, a half of the higher amount each). The one exception is PAWHP from April @@ -77,7 +84,9 @@ # also draws income-related ESA: whether or not it is paid, it must never # change anyone else's payment. OWN_REPORT_INPUTS = {**OWN_AWARD_INPUTS, "ESA": "esa_income_reported"} -# Awards paid jointly to both members of a couple. +# Awards made to a couple jointly and paid to both: Universal Credit (WRA 2012 +# s.1(2)(b); Claims and Payments Regulations 2013 reg 47(4) and (6)). Tax +# credits count only in the 2024 week, where the couple counts either way. JOINT_AWARDS = {"UC", "TC"} # Relevant benefits by scheme and qualifying week, as listed in the @@ -221,12 +230,22 @@ def award_of(person): def is_payee(i): """Whether the couple's award is paid to this claimant or partner: - the member who reports it, else the benefit-unit head.""" - inputs, u, _ = people[i] - reporter = units[u].get("reporter") - if reporter is not None: - return bool(inputs["reports"]) - return bool(inputs["head"]) + the member who reports it, else the benefit-unit head. A Pension + Credit claimant must have attained the qualifying age (SPCA 2002 + s.1(2)(b)), so where either member has, only such a member can be + paid it: the reporter or head among them, else the only one.""" + u = people[i][1] + couple = [j for j, q in enumerate(people) if q[1] == u and q[2]] + if units[u]["award"] == "PC": + able = [j for j in couple if people[j][0]["age"] >= 67] + couple = able or couple + if i not in couple: + return False + for role in ("reports", "head"): + chosen = [j for j in couple if people[j][0][role]] + if chosen: + return i in chosen + return i == max(couple, key=lambda j: (people[j][0]["age"], -j)) def paid(relevant): """Whether a relevant benefit is paid to each person.""" diff --git a/policyengine_uk/utils/winter_heating.py b/policyengine_uk/utils/winter_heating.py index 80e854c301..4ea768b737 100644 --- a/policyengine_uk/utils/winter_heating.py +++ b/policyengine_uk/utils/winter_heating.py @@ -19,10 +19,14 @@ - SI 2000/729, SI 2025/969 and NISR 2025/142 ask whether the benefit "has been, or falls to be, paid to" the person. Income Support, income-based - JSA, income-related ESA and Pension Credit are paid to one member of a - couple (is_on_income_support and the other readers name that member); - Universal Credit and tax credits are joint awards, so both members are on - them. + JSA, income-related ESA and Pension Credit are the claimant's awards, + paid to one member of a couple (is_on_income_support and the other readers + name that member). Universal Credit is awarded to a couple jointly (WRA + 2012 s.1(2)(b)) and paid into an account the joint claimants nominate + (Universal Credit etc. (Claims and Payments) Regulations 2013 reg 47(4)); + reg 47(6) pays it wholly to one member only by special arrangement. So it + falls to be paid to both members. Tax credits are relevant only under the + 2024 instruments below, so who they are paid to does not arise. - SI 2024/869 reg 2(5), NISR 2024/160 reg 2(5) and SSI 2024/351 (reg 7(2) as made, reg 10(8) as substituted by SSI 2025/282, reg 2A from April 2026) treat a member of a couple as entitled when the other member is. diff --git a/policyengine_uk/variables/gov/dwp/_legacy_award_payee.py b/policyengine_uk/variables/gov/dwp/_legacy_award_payee.py index 651d32fff1..51088a4ea5 100644 --- a/policyengine_uk/variables/gov/dwp/_legacy_award_payee.py +++ b/policyengine_uk/variables/gov/dwp/_legacy_award_payee.py @@ -3,7 +3,7 @@ from policyengine_uk.model_api import * -def is_payee_of_couple_award(person, period, reported): +def is_payee_of_couple_award(person, period, reported, can_claim=None): """Whether the claimant's or partner's family award is payable to this person. A person is on income-based JSA or income-related ESA on a day the allowance "is payable to him" (HB Regs 2006 reg 2(3) and (3A)); a @@ -11,8 +11,16 @@ def is_payee_of_couple_award(person, period, reported): award is paid to the claimant, not the partner. The payee is the claimant or partner who reports the award, or the claimant where neither does (an award entered directly). Another member's report never moves - the payee.""" + the payee. + + can_claim, if given, limits the payee to the members of the couple who + can be the claimant (for Pension Credit, those who have attained the + qualifying age). Where neither can, both are considered, so a positive + award entered for the couple is still paid to one of them.""" claimant_or_partner = person("is_claimant_or_partner", period) + if can_claim is not None: + able = claimant_or_partner & can_claim + claimant_or_partner = where(person.benunit.any(able), able, claimant_or_partner) reports = (person(reported, period) > 0) & claimant_or_partner head = claimant_or_partner & person("is_benunit_head", period) eldest = claimant_or_partner & ( diff --git a/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py b/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py index 0e4a2c9349..acd0b02013 100644 --- a/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py +++ b/policyengine_uk/variables/gov/dwp/is_on_pension_credit.py @@ -12,12 +12,15 @@ class is_on_pension_credit(Variable): "Whether State Pension Credit is payable to this person. Pension " "Credit is the claimant's entitlement, with their partner's income " "and capital treated as theirs (SPCA 2002 ss.1 and 5), and is paid " - "to the claimant, not the partner. Of the claimant and partner, only " - "the payee is on it, meaning the one who reports the award, or the " - "claimant where neither does, and only while the benefit unit's award " - "(pension_credit) is positive, as for the legacy income-related " - "awards. Any other member of the benefit unit claims in their own " - "right and is on it only if they report an award themselves." + "to the claimant, not the partner. The claimant must have attained " + "the qualifying age (s.1(2)(b)), so of the claimant and partner only " + "a member who has can be the payee: the one who reports the award, " + "or else the benefit-unit head, or else the elder. Where neither " + "member has attained it, the same order applies to both. They are on " + "it only while the benefit unit's award (pension_credit) is " + "positive, as for the legacy income-related awards. Any other member " + "of the benefit unit claims in their own right and is on it only if " + "they report an award themselves." ) definition_period = YEAR reference = ( @@ -27,7 +30,14 @@ class is_on_pension_credit(Variable): def formula(person, period, parameters): couple_award = (person.benunit("pension_credit", period) > 0) & ( - is_payee_of_couple_award(person, period, "pension_credit_reported") + is_payee_of_couple_award( + person, + period, + "pension_credit_reported", + can_claim=person( + "has_attained_state_pension_credit_qualifying_age", period + ), + ) ) own_award = person("pension_credit_reported", period) > 0 return where(person("is_claimant_or_partner", period), couple_award, own_award) diff --git a/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py b/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py index 098e4f467b..9a71bec44d 100644 --- a/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py +++ b/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py @@ -7,14 +7,22 @@ class is_on_universal_credit(Variable): label = "on Universal Credit" documentation = ( "Whether Universal Credit is payable to this person. A couple claims " - "Universal Credit jointly (WRA 2012 s.2(1)(b)), so the claimant and " - "the partner are both on it when their benefit unit's award " - "(universal_credit) is positive. Any other member of the benefit " + "Universal Credit jointly and it is awarded to them jointly (WRA 2012 " + "ss.1(2)(b) and 2(1)(b)), and paid into an account the joint " + "claimants nominate (Universal Credit etc. (Claims and Payments) " + "Regulations 2013 reg 47(4); reg 47(6) pays it wholly to one member " + "only by special arrangement). So the claimant and the partner are " + "both on it when their benefit unit's award (universal_credit) is " + "positive. Any other member of the benefit " "unit claims in their own right and is on it only if they report an " "award themselves." ) definition_period = YEAR - reference = "https://www.legislation.gov.uk/ukpga/2012/5/section/2" + reference = ( + "https://www.legislation.gov.uk/ukpga/2012/5/section/1", + "https://www.legislation.gov.uk/ukpga/2012/5/section/2", + "https://www.legislation.gov.uk/uksi/2013/380/regulation/47", + ) def formula(person, period, parameters): couple_award = person.benunit("universal_credit", period) > 0 diff --git a/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py b/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py index 74e4b44fe9..50ac663677 100644 --- a/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py +++ b/policyengine_uk/variables/gov/dwp/is_on_winter_fuel_payment_relevant_benefit.py @@ -14,8 +14,10 @@ class is_on_winter_fuel_payment_relevant_benefit(Variable): "(minimum_tax_credit_award). Under SI 2000/729 and SI 2025/969 the " "person is on one when it is paid to them: Income Support, " "income-based JSA, income-related ESA and Pension Credit are paid to " - "one member of a couple, while Universal Credit and tax credits are " - "joint awards. For the 2024 qualifying week a member of a couple is " + "one member of a couple, while Universal Credit is a joint award " + "paid to both (WRA 2012 s.1(2)(b); Universal Credit etc. (Claims and " + "Payments) Regulations 2013 reg 47(4) and (6)). For the 2024 " + "qualifying week a member of a couple is " "also on one when the other member is (SI 2024/869 reg 2(5); " "partner_receipt_counts). A non-dependant's relevant benefit never " "counts for anyone else in the household." From c12f8200aba57c91966a22c8133e3f780e22f5d0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 14:20:22 -0400 Subject: [PATCH 8/8] Pay a couple's winter payment to the member a benefit is paid to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answers review r5 on 4f2758c5d. P1: the couple's payee was the member who reports any relevant-benefit award, whether or not it is paid. A partner aged 67 whose ESA report the model pays nothing on took the payment from the 80-year-old the couple's Pension Credit is paid to: £200 instead of £300 (SI 2025/969 regs 3(4) and 4(2)(a)). The payee is now the member a relevant benefit is paid to (paid_relevant_benefit, read through the is_on_* variables). Between two it is paid to (a joint award), the member who reports the award paid to them, then the benefit-unit head, then the elder. P1: is_on_universal_credit put both members of a couple on the award, including a partner who cannot be a joint claimant (uc_is_ineligible_partner). The other member then claims as a single person (Universal Credit Regulations 2013 reg 3(3)) and the award is theirs alone. Such a partner aged 72, living with a 75-year-old lodger, got £200 instead of £100 (reg 3(2)). Two YAML regressions fail on 4f2758c5d and pass here. The Hypothesis reference ranks the payee the same way. Co-Authored-By: Claude Opus 5.5 --- ...winter-heating-per-person-receipt.added.md | 2 +- .../relevant_benefit_receipt.yaml | 72 +++++++++++++++++ .../test_winter_heating_payment_properties.py | 20 +++-- policyengine_uk/utils/winter_heating.py | 81 ++++++++++++------- .../gov/dwp/is_on_universal_credit.py | 10 ++- .../gov/dwp/winter_fuel_payment_eligible.py | 3 +- .../pawhp_eligible.py | 3 +- 7 files changed, 146 insertions(+), 45 deletions(-) diff --git a/changelog.d/winter-heating-per-person-receipt.added.md b/changelog.d/winter-heating-per-person-receipt.added.md index 5702719487..e0df90bb4e 100644 --- a/changelog.d/winter-heating-per-person-receipt.added.md +++ b/changelog.d/winter-heating-per-person-receipt.added.md @@ -1,2 +1,2 @@ -- Add `is_on_pension_credit` and `is_on_universal_credit` (whether the award is payable to the person) and `tax_credit_award` (the tax credit award payable to them). For the claimant and partner they read their couple's award: Pension Credit is paid to one member (the one who reports it, else the claimant, among members who have attained the qualifying age), and Universal Credit and tax credits are joint awards. Anyone else is on an award only through their own report. +- Add `is_on_pension_credit` and `is_on_universal_credit` (whether the award is payable to the person) and `tax_credit_award` (the tax credit award payable to them). For the claimant and partner they read their couple's award: Pension Credit is paid to one member (the one who reports it, else the claimant, among members who have attained the qualifying age), and Universal Credit and tax credits are joint awards (but not for a partner who cannot be a joint Universal Credit claimant, whose partner claims as a single person). Anyone else is on an award only through their own report. - Add person-level `winter_fuel_payment` and `pension_age_winter_heating_payment`, with `winter_fuel_payment_eligible`, `pawhp_eligible`, `is_on_winter_fuel_payment_relevant_benefit` and `is_on_pawhp_relevant_benefit`. The household `winter_fuel_allowance` and `pawhp` now add up their members' payments. diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml index be4aaa5f7c..37ebeebf31 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/winter_fuel_payment/relevant_benefit_receipt.yaml @@ -901,3 +901,75 @@ is_on_pension_credit: [true, false, false] winter_fuel_payment: [200, 0, 100] winter_fuel_allowance: 300 + +- name: 2025 a report of an award that is not paid does not move the couple's payment from the Pension Credit payee + # Pension Credit is paid to the 80-year-old head (no one reports it). The + # partner, 67, reports income-related ESA, but none is paid. So the + # payment goes to the Pension Credit payee: reg. 3(4), £300. The partner + # is excluded: reg. 4(2)(a). + period: 2025 + input: + people: + pension_credit_payee: + age: 80 + is_benunit_head: true + partner: + age: 67 + is_benunit_head: false + esa_income_reported: 1 + benunits: + couple: + members: [pension_credit_payee, partner] + pension_credit: 1_000 + esa_income: 0 + jsa_income: 0 + income_support: 0 + universal_credit: 0 + households: + household: + members: [pension_credit_payee, partner] + country: ENGLAND + output: + is_on_pension_credit: [true, false] + is_on_income_related_esa: [false, false] + winter_fuel_payment: [300, 0] + winter_fuel_allowance: 300 + +- name: 2025 Universal Credit claimed as a single person is not paid to the partner who cannot be a joint claimant + # The 60-year-old claims as a single person (Universal Credit Regulations + # 2013 reg. 3(3)), so the award is not paid to the 72-year-old partner, + # who lives with a person entitled (the lodger): reg. 3(2), £100. The + # partner is not excluded, as the claimant has not reached pensionable age + # (reg. 4(2)(a)). The lodger: reg. 3(2), £100. + period: 2025 + input: + people: + claimant: + age: 60 + is_benunit_head: true + partner: + age: 72 + is_benunit_head: false + uc_is_ineligible_partner: true + lodger: + age: 75 + total_income: 20_000 + benunits: + couple: + members: [claimant, partner] + universal_credit: 1_000 + pension_credit: 0 + esa_income: 0 + income_support: 0 + jsa_income: 0 + lodger_unit: + members: [lodger] + pension_credit: 0 + households: + household: + members: [claimant, partner, lodger] + country: ENGLAND + output: + is_on_universal_credit: [true, false, false] + winter_fuel_payment: [0, 100, 100] + winter_fuel_allowance: 200 diff --git a/policyengine_uk/tests/test_winter_heating_payment_properties.py b/policyengine_uk/tests/test_winter_heating_payment_properties.py index b0e1285243..d4c368a871 100644 --- a/policyengine_uk/tests/test_winter_heating_payment_properties.py +++ b/policyengine_uk/tests/test_winter_heating_payment_properties.py @@ -266,13 +266,16 @@ def through_couple(receipt): def scheme(resident, relevant, means_test, amounts, partner_counts): lower, higher, shared, shared_80_with_under_80, shared_80_with_80 = amounts - couple = through_couple(paid(relevant)) - on_relevant = couple if partner_counts else paid(relevant) + paid_to = paid(relevant) + couple = through_couple(paid_to) + on_relevant = couple if partner_counts else paid_to qualifies = [resident and p[0]["age"] >= 67 for p in people] eligible = [q and (r or means_test) for q, r in zip(qualifies, on_relevant)] - # One payment for a couple on a relevant benefit, to the claimant: the - # member who reports the award, else the benefit-unit head, else the - # elder (first listed if the same age). The other is not entitled. + # One payment for a couple on a relevant benefit, to the member it is + # paid to. Between two it is paid to (a joint award), or two it is + # paid to neither of, the member who reports the award paid to them, + # else the benefit-unit head, else the elder (first listed if the same + # age). The other is not entitled. for u in range(len(units)): members = [ i @@ -284,11 +287,14 @@ def scheme(resident, relevant, means_test, amounts, partner_counts): members, key=lambda i: ( 0 - if people[i][0]["reports"] + if paid_to[i] + and people[i][0]["reports"] and units[people[i][1]]["award"] in relevant else 1 + if paid_to[i] + else 2 if people[i][0]["head"] - else 2, + else 3, -people[i][0]["age"], i, ), diff --git a/policyengine_uk/utils/winter_heating.py b/policyengine_uk/utils/winter_heating.py index 4ea768b737..be21d41e27 100644 --- a/policyengine_uk/utils/winter_heating.py +++ b/policyengine_uk/utils/winter_heating.py @@ -65,20 +65,28 @@ def couple_receipt(person, period, receipt): return where(claimant_or_partner, couple, receipt) -def is_on_relevant_benefit(person, period, eligibility): - """Whether the person is on a relevant benefit for the scheme. +def paid_relevant_benefit(person, period, eligibility): + """Whether a relevant benefit for the scheme is paid to the person. ``eligibility`` is the scheme's eligibility parameter node: the benefits - in ``relevant_benefits``, a tax credit award of at least - ``minimum_tax_credit_award`` (infinite when tax credits do not count), - and ``partner_receipt_counts``: whether a member of a couple is on a - relevant benefit when the other member is. Without it, the person is on - one only if it is paid to them. + in ``relevant_benefits`` (read through the is_on_* variables, which name + the member of a couple an award is paid to), and a tax credit award of at + least ``minimum_tax_credit_award`` (infinite when tax credits do not + count). """ listed = add(person, period, eligibility.relevant_benefits) > 0 award = person("tax_credit_award", period) tax_credits = (award > 0) & (award >= eligibility.minimum_tax_credit_award) - paid = listed | tax_credits + return listed | tax_credits + + +def is_on_relevant_benefit(person, period, eligibility): + """Whether the person is on a relevant benefit for the scheme: it is paid + to them, or, where the scheme's ``partner_receipt_counts`` says a member + of a couple is on one when the other member is, to the other member of + their couple. + """ + paid = paid_relevant_benefit(person, period, eligibility) return where( eligibility.partner_receipt_counts, couple_receipt(person, period, paid), @@ -86,21 +94,25 @@ def is_on_relevant_benefit(person, period, eligibility): ) -def reports_relevant_benefit(person, period, eligibility): - """Whether the person reports a relevant-benefit award of their own.""" - reports = [ - report - for variable in eligibility.relevant_benefits - for report in REPORTED_AWARDS[variable] - ] - own_award = add(person, period, reports) > 0 if reports else False - reported = add(person, period, TAX_CREDIT_REPORTS) - tax_credits = (reported > 0) & (reported >= eligibility.minimum_tax_credit_award) - return own_award | tax_credits +def reports_paid_relevant_benefit(person, period, eligibility): + """Whether the person reports a relevant-benefit award that is paid to + them. A report of an award that is not paid (one the benefit's own tests + reduce to nothing, say) says nothing about who claims.""" + reports = False + for variable in eligibility.relevant_benefits: + reported = add(person, period, REPORTED_AWARDS[variable]) > 0 + reports = reports | (person(variable, period) & reported) + award = person("tax_credit_award", period) + tax_credits = ( + (award > 0) + & (award >= eligibility.minimum_tax_credit_award) + & (add(person, period, TAX_CREDIT_REPORTS) > 0) + ) + return reports | tax_credits def is_excluded_relevant_benefit_partner( - person, period, qualifies, on_relevant_benefit, reports + person, period, qualifies, on_relevant_benefit, eligibility ): """Whether the person is the partner of the person paid for their couple. @@ -110,23 +122,30 @@ def is_excluded_relevant_benefit_partner( SI 2000/729 reg 3(1)(a)(i) and SI 2025/969 reg 4(2)(a) the partner is excluded when the person the benefit is paid to has reached pensionable age, so the couple's receipt decides this whichever instrument applies. - The model takes the claimant to be the member of the couple who reports - the award; failing that, the benefit-unit head; failing that, the elder, - as the legacy award readers do. The choice matters beyond the couple: - whether someone else in the household lives with an entitled person aged - 80 or over depends on whom the couple's payment is made to (SI 2000/729 - reg 2(2)(b); SI 2025/969 reg 3(5) and (6); SSI 2024/351 reg 10(6) as - substituted by SSI 2025/282). + + The payment goes to the member a relevant benefit is paid to. Where it is + paid to both (a joint award), or to neither of those who qualify, the + model takes the claimant to be the member who reports the award paid to + them; failing that, the benefit-unit head; failing that, the elder. A + report of an award that is not paid does not count. The choice matters + beyond the couple: whether someone else in the household lives with an + entitled person aged 80 or over depends on whom the couple's payment is + made to (SI 2000/729 reg 2(2)(b); SI 2025/969 reg 3(5) and (6); SSI + 2024/351 reg 10(6) as substituted by SSI 2025/282). """ age = person("age", period) on_relevant_benefit = couple_receipt(person, period, on_relevant_benefit) couple_member = ( qualifies & person("is_claimant_or_partner", period) & on_relevant_benefit ) - precedence = where( - reports, - 0, - where(person("is_benunit_head", period), 1, 2), + precedence = select( + [ + reports_paid_relevant_benefit(person, period, eligibility), + paid_relevant_benefit(person, period, eligibility), + person("is_benunit_head", period), + ], + [0, 1, 2], + default=3, ) payee = couple_member & ( person.get_rank( diff --git a/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py b/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py index 9a71bec44d..b0e608d8ee 100644 --- a/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py +++ b/policyengine_uk/variables/gov/dwp/is_on_universal_credit.py @@ -13,7 +13,10 @@ class is_on_universal_credit(Variable): "Regulations 2013 reg 47(4); reg 47(6) pays it wholly to one member " "only by special arrangement). So the claimant and the partner are " "both on it when their benefit unit's award (universal_credit) is " - "positive. Any other member of the benefit " + "positive, except a partner who cannot be a joint claimant " + "(uc_is_ineligible_partner): the other member then claims as a " + "single person (Universal Credit Regulations 2013 reg 3(3)) and the " + "award is theirs alone. Any other member of the benefit " "unit claims in their own right and is on it only if they report an " "award themselves." ) @@ -21,10 +24,13 @@ class is_on_universal_credit(Variable): reference = ( "https://www.legislation.gov.uk/ukpga/2012/5/section/1", "https://www.legislation.gov.uk/ukpga/2012/5/section/2", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/3", "https://www.legislation.gov.uk/uksi/2013/380/regulation/47", ) def formula(person, period, parameters): - couple_award = person.benunit("universal_credit", period) > 0 + couple_award = (person.benunit("universal_credit", period) > 0) & ~person( + "uc_is_ineligible_partner", period + ) own_award = person("universal_credit_reported", period) > 0 return where(person("is_claimant_or_partner", period), couple_award, own_award) diff --git a/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py b/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py index 0404692856..ff957e3327 100644 --- a/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py +++ b/policyengine_uk/variables/gov/dwp/winter_fuel_payment_eligible.py @@ -1,7 +1,6 @@ from policyengine_uk.model_api import * from policyengine_uk.utils.winter_heating import ( is_excluded_relevant_benefit_partner, - reports_relevant_benefit, ) @@ -71,6 +70,6 @@ def formula(person, period, parameters): period, qualifies, on_relevant_benefit, - reports_relevant_benefit(person, period, p), + p, ) return qualifies & meets_means_condition & ~excluded diff --git a/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py b/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py index 0f3f7de4cd..0476c1d48b 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py +++ b/policyengine_uk/variables/gov/social_security_scotland/pawhp_eligible.py @@ -1,7 +1,6 @@ from policyengine_uk.model_api import * from policyengine_uk.utils.winter_heating import ( is_excluded_relevant_benefit_partner, - reports_relevant_benefit, ) @@ -49,6 +48,6 @@ def formula(person, period, parameters): period, qualifies, on_relevant_benefit, - reports_relevant_benefit(person, period, p.eligibility), + p.eligibility, ) return qualifies & meets_benefit_condition & ~excluded