From 02c2a5297a4de3b74a19b30aeba7d78b891cf49f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 14:04:30 -0400 Subject: [PATCH 1/7] Give the legacy severe disability premium its statutory conditions The working-age severe disability premium in the Income Support, Housing Benefit and Council Tax Reduction applicable amounts counted any adult flagged by is_severely_disabled_for_benefits, the tax credit condition (DLA care highest rate, PIP daily living enhanced rate, or any AFCS payment), and paid a couple the double rate when either adult was flagged. HB Regs 2006 Sch 3 paras 14 and 20(6), IS Regs 1987 Sch 2 paras 13 and 15(5), ESA Regs 2008 Sch 4 paras 6 and 11(2) and JSA Regs 1996 Sch 1 paras 15 and 20(6) qualify a claimant on Attendance Allowance (either rate), the DLA care component at the middle or highest rate, the PIP daily living component at either rate, or Armed Forces Independence Payment. A couple needs both partners to qualify, unless the other partner is blind, who is then treated as absent and the single rate applies. No non-dependant aged 18 or over may reside with them (ignoring non-dependants who receive a qualifying benefit or are blind), and no carer benefit may be paid for caring for them: a couple who both qualify get the double rate with no carer paid for either, and the single rate with one paid for only one. is_severely_disabled_for_benefits stays for the CTC disability element and the WTC severe disability element (CTC Regs 2002 reg 8; WTC Regs 2002 reg 17). It now counts higher-rate Attendance Allowance (WTC reg 17(2)) and Armed Forces Independence Payment instead of any AFCS payment. Co-Authored-By: Claude Opus 5.5 --- ...ing-age-severe-disability-premium.fixed.md | 1 + .../severe_non_dependant_age.yaml | 15 + .../severe_qualifying_benefits.yaml | 31 ++ .../income_support/legacy_premiums.yaml | 17 +- .../is_severely_disabled_for_benefits.yaml | 70 +++ .../family/severe_disability_premium.yaml | 413 ++++++++++++++++++ ...st_severe_disability_premium_properties.py | 243 +++++++++++ ...aimants_or_partners_for_legacy_benefits.py | 22 - ...dependant_for_severe_disability_premium.py | 37 ++ .../is_severely_disabled_for_benefits.py | 47 +- ...e_disability_premium_qualifying_benefit.py | 32 ++ .../demographic/severe_disability_premium.py | 76 +++- 12 files changed, 949 insertions(+), 55 deletions(-) create mode 100644 changelog.d/working-age-severe-disability-premium.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/disability_premia/severe_non_dependant_age.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/is_severely_disabled_for_benefits.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml create mode 100644 policyengine_uk/tests/test_severe_disability_premium_properties.py delete mode 100644 policyengine_uk/variables/gov/dwp/num_severely_disabled_claimants_or_partners_for_legacy_benefits.py create mode 100644 policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py create mode 100644 policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py diff --git a/changelog.d/working-age-severe-disability-premium.fixed.md b/changelog.d/working-age-severe-disability-premium.fixed.md new file mode 100644 index 0000000000..58d4168268 --- /dev/null +++ b/changelog.d/working-age-severe-disability-premium.fixed.md @@ -0,0 +1 @@ +The legacy benefit severe disability premium now qualifies on the benefits the Regulations list (Attendance Allowance, the DLA care component at the middle or highest rate, the PIP daily living component at either rate, and Armed Forces Independence Payment), pays a couple only when both partners qualify or the other partner is blind, pays the single rate when a carer benefit is paid for one partner, and is barred by a non-dependant aged 18 or over or by a carer benefit paid for the claimant. The tax credit severe disability flag now counts higher-rate Attendance Allowance and Armed Forces Independence Payment instead of all Armed Forces Compensation Scheme payments. diff --git a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_non_dependant_age.yaml b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_non_dependant_age.yaml new file mode 100644 index 0000000000..66e066512a --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_non_dependant_age.yaml @@ -0,0 +1,15 @@ +description: > + A claimant loses the legacy severe disability premium if a non-dependant of + at least this age normally resides with them, unless that non-dependant + receives a severe disability premium qualifying benefit or is blind. +metadata: + label: Legacy benefit severe disability premium non-dependant age + period: year + unit: year + reference: + - title: The Housing Benefit Regulations 2006 Sch. 3 para. 14(2)(a)(ii), (2)(b)(iii) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14 + - title: The Income Support (General) Regulations 1987 Sch. 2 para. 13(2)(a)(ii), (2)(b)(iii) + href: https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13 +values: + 2006-03-06: 18 diff --git a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml new file mode 100644 index 0000000000..e4e715221b --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml @@ -0,0 +1,31 @@ +description: > + Benefits whose receipt makes a claimant or partner severely disabled for the + legacy severe disability premium, and whose receipt by a non-dependant means + that non-dependant is ignored in the premium's residence condition. The + Regulations list attendance allowance (either rate), the care component of + disability living allowance at the highest or middle rate, the daily living + component of personal independence payment at either rate, armed forces + independence payment, and the Scottish equivalents. The Scottish benefits + are not separate model variables. +metadata: + label: Legacy benefit severe disability premium qualifying benefits + period: year + unit: list + reference: + - title: The Housing Benefit Regulations 2006 Sch. 3 para. 14(2)(a)(i) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14 + - title: The Income Support (General) Regulations 1987 Sch. 2 para. 13(2)(a)(i) + href: https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13 + - title: The Employment and Support Allowance Regulations 2008 Sch. 4 para. 6(2)(a)(i) + href: https://www.legislation.gov.uk/uksi/2008/794/schedule/4/paragraph/6 + - title: The Jobseeker's Allowance Regulations 1996 Sch. 1 para. 15(2)(a)(i) + href: https://www.legislation.gov.uk/uksi/1996/207/schedule/1/paragraph/15 +values: + 2006-03-06: + - attendance_allowance + - dla_sc_middle_plus + 2013-04-08: + - attendance_allowance + - dla_sc_middle_plus + - pip_dl + - armed_forces_independence_payment diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml index 141319853e..0455adf7b5 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml @@ -14,6 +14,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true + pip_dl_category: STANDARD output: # The claimant qualifies in their own right: no statutory age-18 floor. disability_premium: 2332.20 @@ -28,6 +29,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true + pip_dl_category: STANDARD output: disability_premium: 2332.20 enhanced_disability_premium: 1144 @@ -46,6 +48,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true + pip_dl_category: STANDARD benunits: family: members: [parent, young_person] @@ -69,6 +72,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true + pip_dl_category: STANDARD benunits: family: members: [parent, young_person] @@ -91,16 +95,17 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true + pip_dl_category: STANDARD benunits: family: members: [claimant, partner] output: disability_premium: 3328 enhanced_disability_premium: 1632.80 - # Deliberately preserve the existing severe-premium approximation: - # one qualifying partner currently triggers the full couple rate. - # IS Sch 2 para 13 / HB Sch 3 para 14 require both to qualify. - severe_disability_premium: 8949.20 + # IS Sch 2 para 13(2)(b) / HB Sch 3 para 14(2)(b) require both partners + # to qualify; the claimant receives no qualifying benefit and is not + # blind, so there is no severe disability premium. + severe_disability_premium: 0 - name: Disabled lone parent with a young person keeps single legacy premiums period: 2026 @@ -113,6 +118,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true + pip_dl_category: STANDARD young_person: age: 19 is_in_non_advanced_education: true @@ -136,6 +142,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true + pip_dl_category: STANDARD benunits: family: members: [parent, child] @@ -154,11 +161,13 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true + pip_dl_category: STANDARD partner: age: 32 is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true + pip_dl_category: STANDARD benunits: family: members: [claimant, partner] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/is_severely_disabled_for_benefits.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/is_severely_disabled_for_benefits.yaml new file mode 100644 index 0000000000..cbc0d7f8a4 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/is_severely_disabled_for_benefits.yaml @@ -0,0 +1,70 @@ +# Severe disability conditions of the Child Tax Credit disability element +# (CTC Regs 2002 reg 8(1)(b), (3)-(5)) and the Working Tax Credit severe +# disability element (WTC Regs 2002 reg 17(2)-(4)). + +- name: DLA care component at the highest rate is severe + period: 2026 + input: + dla_sc_category: HIGHER + output: + is_severely_disabled_for_benefits: true + +- name: DLA care component at the middle rate is not severe + period: 2026 + input: + dla_sc_category: MIDDLE + output: + is_severely_disabled_for_benefits: false + +- name: PIP daily living component at the enhanced rate is severe + period: 2026 + input: + pip_dl_category: ENHANCED + output: + is_severely_disabled_for_benefits: true + +- name: PIP daily living component at the standard rate is not severe + period: 2026 + input: + pip_dl_category: STANDARD + output: + is_severely_disabled_for_benefits: false + +- name: Attendance Allowance at the higher rate is severe (WTC reg 17(2)) + period: 2026 + input: + aa_category: HIGHER + output: + is_severely_disabled_for_benefits: true + +- name: Attendance Allowance at the lower rate is not severe + period: 2026 + input: + aa_category: LOWER + output: + is_severely_disabled_for_benefits: false + +- name: Armed Forces Independence Payment is severe + period: 2026 + input: + armed_forces_independence_payment: 1_000 + output: + is_severely_disabled_for_benefits: true + +- name: Other Armed Forces Compensation Scheme payments are not severe + period: 2026 + input: + afcs_reported: 1_000 + output: + is_severely_disabled_for_benefits: false + +- name: WTC severe disability element for a claimant on higher-rate Attendance Allowance + period: 2024 + absolute_error_margin: 0.01 + input: + age: 70 + aa_category: HIGHER + is_WTC_eligible: true + output: + # WTC Regs 2002 reg 17(2) and Sch 2 para 6: 1,705 a year in 2024-25. + WTC_severely_disabled_element: 1_705 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml new file mode 100644 index 0000000000..7adcb6b843 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml @@ -0,0 +1,413 @@ +# Legacy benefit severe disability premium: HB Regs 2006 Sch 3 paras 14 and +# 20(6); IS Regs 1987 Sch 2 paras 13 and 15(5); ESA Regs 2008 Sch 4 paras 6 +# and 11(2); JSA Regs 1996 Sch 1 paras 15 and 20(6). +# Expected values are the 2026-27 weekly rates times 52, hand-computed from +# the Schedules' rate tables: single rate 86.05 * 52 = 4474.60; double rate +# for a couple who both qualify with no carer paid for either +# 172.10 * 52 = 8949.20. + +# Qualifying benefits (para 14(2)(a)(i) and its counterparts). + +- name: Attendance Allowance at the lower rate qualifies + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + aa_category: LOWER + output: + receives_severe_disability_premium_qualifying_benefit: true + severe_disability_premium: 4474.60 + +- name: Attendance Allowance at the higher rate qualifies + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + aa_category: HIGHER + output: + severe_disability_premium: 4474.60 + +- name: DLA care component at the middle rate qualifies + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + dla_sc_category: MIDDLE + output: + receives_severe_disability_premium_qualifying_benefit: true + severe_disability_premium: 4474.60 + +- name: DLA care component at the highest rate qualifies + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + dla_sc_category: HIGHER + output: + severe_disability_premium: 4474.60 + +- name: DLA care component at the lowest rate does not qualify + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + dla_sc_category: LOWER + output: + receives_severe_disability_premium_qualifying_benefit: false + severe_disability_premium: 0 + +- name: PIP daily living component at the standard rate qualifies + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + pip_dl_category: STANDARD + output: + receives_severe_disability_premium_qualifying_benefit: true + severe_disability_premium: 4474.60 + +- name: PIP daily living component at the enhanced rate qualifies + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + pip_dl_category: ENHANCED + output: + severe_disability_premium: 4474.60 + +- name: PIP mobility component alone does not qualify + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + pip_m_category: ENHANCED + output: + severe_disability_premium: 0 + +- name: Armed Forces Independence Payment qualifies + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + armed_forces_independence_payment: 1_000 + output: + receives_severe_disability_premium_qualifying_benefit: true + severe_disability_premium: 4474.60 + +- name: Other Armed Forces Compensation Scheme payments do not qualify + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + afcs_reported: 1_000 + output: + receives_severe_disability_premium_qualifying_benefit: false + severe_disability_premium: 0 + +- name: The tax credit severe disability flag alone does not qualify + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + is_severely_disabled_for_benefits: true + output: + severe_disability_premium: 0 + +- name: No qualifying benefit, no premium + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + output: + severe_disability_premium: 0 + +# Couples (para 14(2)(b), (3) and (4); para 20(6)(b)). + +- name: Couple who both qualify with no carer get the double rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + pip_dl_category: STANDARD + partner: + age: 42 + dla_sc_category: MIDDLE + benunits: + family: + members: [claimant, partner] + output: + severe_disability_premium: 8949.20 + +- name: Couple where only one partner qualifies get nothing + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + pip_dl_category: ENHANCED + partner: + age: 42 + benunits: + family: + members: [claimant, partner] + output: + severe_disability_premium: 0 + +- name: A blind partner who does not qualify is treated as absent + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + pip_dl_category: ENHANCED + partner: + age: 42 + is_blind: true + benunits: + family: + members: [claimant, partner] + output: + # Para 14(3): the claimant is tested as single and gets the single rate. + severe_disability_premium: 4474.60 + +- name: A blind partner who is the qualifying member's carer bars the premium + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + pip_dl_category: ENHANCED + partner: + age: 42 + is_blind: true + receives_carer_benefit: true + benunits: + family: + members: [claimant, partner] + output: + severe_disability_premium: 0 + +- name: Couple who both qualify with a carer paid for one get the single rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + pip_dl_category: STANDARD + partner: + age: 42 + aa_category: LOWER + receives_carer_benefit: true + benunits: + family: + members: [claimant, partner] + output: + # Para 20(6)(b)(i): a carer's allowance is paid for only one of them. + severe_disability_premium: 4474.60 + +- name: Couple who both qualify with a carer paid for each get nothing + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + pip_dl_category: STANDARD + receives_carer_benefit: true + partner: + age: 42 + aa_category: LOWER + receives_carer_benefit: true + benunits: + family: + members: [claimant, partner] + output: + severe_disability_premium: 0 + +# Carers (para 14(2)(a)(iii)). + +- name: A single claimant's own carer benefit is not paid for caring for them + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + pip_dl_category: STANDARD + receives_carer_benefit: true + output: + severe_disability_premium: 4474.60 + +- name: A carer benefit paid to a young person in the family bars a single claimant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + pip_dl_category: STANDARD + child: + age: 17 + receives_carer_benefit: true + benunits: + family: + members: [parent, child] + output: + severe_disability_premium: 0 + +# Non-dependants (para 14(2)(a)(ii) and (2)(b)(iii); para 14(4)). + +- name: A non-dependant aged 18 or over bars the premium + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + son: + age: 25 + benunits: + claimant_family: + members: [claimant] + son_family: + members: [son] + households: + household: + members: [claimant, son] + output: + has_non_dependant_for_severe_disability_premium: [true, false] + severe_disability_premium: [0, 0] + +- name: A non-dependant who receives a qualifying benefit is ignored + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + son: + age: 25 + pip_dl_category: STANDARD + benunits: + claimant_family: + members: [claimant] + son_family: + members: [son] + households: + household: + members: [claimant, son] + output: + # Each is the other's non-dependant, and each is ignored because they + # receive a qualifying benefit, so both get the single rate. + has_non_dependant_for_severe_disability_premium: [false, false] + severe_disability_premium: [4474.60, 4474.60] + +- name: A blind non-dependant is ignored + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + son: + age: 25 + is_blind: true + benunits: + claimant_family: + members: [claimant] + son_family: + members: [son] + households: + household: + members: [claimant, son] + output: + severe_disability_premium: [4474.60, 0] + +- name: A non-dependant aged 17 does not bar the premium + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + lodger: + age: 17 + benunits: + claimant_family: + members: [claimant] + lodger_family: + members: [lodger] + households: + household: + members: [claimant, lodger] + output: + severe_disability_premium: [4474.60, 0] + +- name: A disabled adult living with their parents has non-dependants + period: 2026 + absolute_error_margin: 0.01 + input: + people: + disabled_adult: + age: 30 + dla_sc_category: HIGHER + mother: + age: 58 + father: + age: 60 + benunits: + adult_family: + members: [disabled_adult] + parents: + members: [mother, father] + households: + household: + members: [disabled_adult, mother, father] + output: + # HB/IS reg 3: close relatives living together are non-dependants. + severe_disability_premium: [0, 0] + +- name: People in other households are not non-dependants + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + neighbour: + age: 25 + benunits: + claimant_family: + members: [claimant] + neighbour_family: + members: [neighbour] + households: + claimant_household: + members: [claimant] + neighbour_household: + members: [neighbour] + output: + severe_disability_premium: [4474.60, 0] + +# Flow-through to the applicable amounts. + +- name: The premium is part of the legacy benefit premiums + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + pip_dl_category: STANDARD + output: + disability_premium: 0 + enhanced_disability_premium: 0 + carer_premium: 0 + benefits_premiums: 4474.60 diff --git a/policyengine_uk/tests/test_severe_disability_premium_properties.py b/policyengine_uk/tests/test_severe_disability_premium_properties.py new file mode 100644 index 0000000000..c27e0c9844 --- /dev/null +++ b/policyengine_uk/tests/test_severe_disability_premium_properties.py @@ -0,0 +1,243 @@ +"""Property-based tests for the legacy severe disability premium. + +HB Regs 2006 Sch 3 para 14 (with IS Regs 1987 Sch 2 para 13, ESA Regs 2008 +Sch 4 para 6 and JSA Regs 1996 Sch 1 para 15) pay the premium to a claimant +who receives a qualifying benefit; for a couple, both partners must qualify +unless the other partner is blind. No non-dependant aged 18 or over may +reside with them (ignoring non-dependants who receive a qualifying benefit or +are blind), and no one may receive a carer benefit for caring for them. A +couple who both qualify get the double rate when no carer benefit is paid for +either, and the single rate when one is paid for only one of them. + +Invariants, for any generated population of households: + +1. Range: the premium is 0, the single rate or the double rate, and the double + rate goes only to a couple who both qualify. +2. Oracle: the premium equals an independent statement of the rule in which + the carer condition is a brute-force assignment of each carer award to a + benefit-unit member other than its recipient (the model does not observe + who is cared for, so it takes the assignment that covers the most + qualifying members). +3. Metamorphic: adding a household member aged 18 or over, in another benefit + unit, who receives no qualifying benefit and is not blind, removes the + premium. +4. Metamorphic: paying anyone in the benefit unit a carer benefit never + increases the premium. +5. Metamorphic: giving anyone in the household a qualifying benefit, or making + them blind, never reduces the premium. +6. Differential: for a family with no children, no carer and no one else in + the household, a single claimant or a couple who both or neither qualify + get exactly the Pension Credit severe disability addition, which lists the + same qualifying benefits (SPC Regs 2002 Sch I para 1; reg 6(5)). +""" + +from itertools import product + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEAR = 2026 +WEEKS = 52 +SINGLE = 86.05 * WEEKS +DOUBLE = 172.10 * WEEKS +PROPERTY_SETTINGS = settings( + max_examples=12, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + +# (inputs, qualifies) for each benefit a person may receive. +BENEFITS = { + "none": ({}, False), + "aa_lower": ({"aa_category": "LOWER"}, True), + "aa_higher": ({"aa_category": "HIGHER"}, True), + "dla_lowest": ({"dla_sc_category": "LOWER"}, False), + "dla_middle": ({"dla_sc_category": "MIDDLE"}, True), + "dla_highest": ({"dla_sc_category": "HIGHER"}, True), + "pip_standard": ({"pip_dl_category": "STANDARD"}, True), + "pip_enhanced": ({"pip_dl_category": "ENHANCED"}, True), + "pip_mobility_only": ({"pip_m_category": "ENHANCED"}, False), + "afip": ({"armed_forces_independence_payment": 1_000.0}, True), + "afcs_other": ({"afcs_reported": 1_000.0}, False), +} +benefit = st.sampled_from(sorted(BENEFITS)) +# Adults aged 20 to 60 and children under 16 are the claimant and partner, and +# their children, under any reading of the benefit unit (18 and 19 year olds +# can be qualifying young persons). +ADULT_AGE = st.integers(20, 60) + + +@st.composite +def person(draw, age): + return dict( + age=draw(age), + benefit=draw(benefit), + blind=draw(st.booleans()), + carer=draw(st.booleans()), + ) + + +@st.composite +def households(draw): + adults = draw(st.lists(person(ADULT_AGE), min_size=1, max_size=2)) + children = draw(st.lists(person(st.integers(0, 15)), max_size=1)) + others = draw(st.lists(person(st.integers(15, 70)), max_size=2)) + return dict(adults=adults, children=children, others=others) + + +def qualifies(p): + return BENEFITS[p["benefit"]][1] + + +def person_inputs(p): + inputs = {"age": {YEAR: p["age"]}, "is_blind": {YEAR: p["blind"]}} + inputs["receives_carer_benefit"] = {YEAR: p["carer"]} + for name, value in BENEFITS[p["benefit"]][0].items(): + inputs[name] = {YEAR: value} + return inputs + + +def simulate(units, variables=("severe_disability_premium",)): + """One simulation for many households; returns each primary family's values.""" + people, benunits, hh = {}, {}, {} + for i, unit in enumerate(units): + family = [] + for j, p in enumerate(unit["adults"] + unit["children"]): + people[f"h{i}_f{j}"] = person_inputs(p) + family.append(f"h{i}_f{j}") + benunits[f"h{i}_family"] = {"members": family} + members = list(family) + for j, p in enumerate(unit["others"]): + people[f"h{i}_o{j}"] = person_inputs(p) + benunits[f"h{i}_o{j}"] = {"members": [f"h{i}_o{j}"]} + members.append(f"h{i}_o{j}") + hh[f"h{i}"] = {"members": members} + sim = Simulation( + situation={"people": people, "benunits": benunits, "households": hh} + ) + ids = list(sim.populations["benunit"].ids) + rows = [ids.index(f"h{i}_family") for i in range(len(units))] + return {v: np.asarray(sim.calculate(v, YEAR))[rows] for v in variables} + + +def oracle(unit): + """The premium by the statutory rule, written independently of the model.""" + adults, family = unit["adults"], unit["adults"] + unit["children"] + blocking = [ + o + for o in unit["others"] + if o["age"] >= 18 and not qualifies(o) and not o["blind"] + ] + if blocking: + return 0.0 + qualifying = [i for i, a in enumerate(adults) if qualifies(a)] + if len(adults) == 2 and len(qualifying) == 1: + other = adults[1 - qualifying[0]] + if not other["blind"]: + return 0.0 + elif len(adults) == 2 and len(qualifying) == 0: + return 0.0 + elif len(adults) == 1 and not qualifying: + return 0.0 + # Assign each carer award to one family member other than its recipient, + # and count the most qualifying members that can be covered. + carers = [i for i, p in enumerate(family) if p["carer"]] + cared_for = 0 + for assignment in product(range(len(family)), repeat=len(carers)): + if any(c == target for c, target in zip(carers, assignment)): + continue + cared_for = max(cared_for, len(set(assignment) & set(qualifying))) + if len(adults) == 2 and len(qualifying) == 2: + return (2 - cared_for) * SINGLE + return SINGLE if cared_for == 0 else 0.0 + + +@PROPERTY_SETTINGS +@given(st.lists(households(), min_size=1, max_size=10)) +def test_premium_matches_statutory_oracle_and_range(units): + premium = simulate(units)["severe_disability_premium"] + for unit, value in zip(units, premium): + assert np.isclose(value, oracle(unit), atol=0.01), unit + assert any(np.isclose(value, x, atol=0.01) for x in (0, SINGLE, DOUBLE)) + if np.isclose(value, DOUBLE, atol=0.01): + assert len(unit["adults"]) == 2 + assert all(qualifies(a) for a in unit["adults"]) + + +@PROPERTY_SETTINGS +@given(st.lists(households(), min_size=1, max_size=10), ADULT_AGE) +def test_counted_non_dependant_removes_premium(units, age): + non_dependant = dict(age=age, benefit="none", blind=False, carer=False) + added = [dict(u, others=u["others"] + [non_dependant]) for u in units] + premium = simulate(added)["severe_disability_premium"] + assert np.allclose(premium, 0) + + +@PROPERTY_SETTINGS +@given(st.lists(households(), min_size=1, max_size=10), st.integers(0, 2)) +def test_carer_benefit_never_increases_premium(units, index): + def pay_carer(unit): + family = unit["adults"] + unit["children"] + i = index % len(family) + family = [dict(p, carer=p["carer"] or k == i) for k, p in enumerate(family)] + n = len(unit["adults"]) + return dict(unit, adults=family[:n], children=family[n:]) + + before = simulate(units)["severe_disability_premium"] + after = simulate([pay_carer(u) for u in units])["severe_disability_premium"] + assert np.all(after <= before + 0.01) + + +@PROPERTY_SETTINGS +@given( + st.lists(households(), min_size=1, max_size=10), + st.integers(0, 4), + st.sampled_from(["pip_standard", "blind"]), +) +def test_qualifying_benefit_or_blindness_never_reduces_premium(units, index, change): + def apply(unit): + everyone = unit["adults"] + unit["children"] + unit["others"] + i = index % len(everyone) + changed = [ + dict(p, **({"blind": True} if change == "blind" else {"benefit": change})) + if k == i + else p + for k, p in enumerate(everyone) + ] + a, c = len(unit["adults"]), len(unit["children"]) + return dict( + unit, + adults=changed[:a], + children=changed[a : a + c], + others=changed[a + c :], + ) + + before = simulate(units)["severe_disability_premium"] + after = simulate([apply(u) for u in units])["severe_disability_premium"] + assert np.all(after >= before - 0.01) + + +@PROPERTY_SETTINGS +@given(st.lists(st.lists(person(ADULT_AGE), min_size=1, max_size=2), min_size=1)) +def test_matches_pension_credit_severe_disability_addition(families): + units = [] + for adults in families: + adults = [dict(a, carer=False) for a in adults] + if len(adults) == 2 and qualifies(adults[0]) != qualifies(adults[1]): + # The Pension Credit formula does not yet apply the couple rule + # (PolicyEngine/policyengine-uk#1896 adds it). + adults = [dict(a, benefit=adults[0]["benefit"]) for a in adults] + units.append(dict(adults=adults, children=[], others=[])) + values = simulate( + units, + ("severe_disability_premium", "severe_disability_minimum_guarantee_addition"), + ) + assert np.allclose( + values["severe_disability_premium"], + values["severe_disability_minimum_guarantee_addition"], + atol=0.01, + ) diff --git a/policyengine_uk/variables/gov/dwp/num_severely_disabled_claimants_or_partners_for_legacy_benefits.py b/policyengine_uk/variables/gov/dwp/num_severely_disabled_claimants_or_partners_for_legacy_benefits.py deleted file mode 100644 index 2d6f4987ad..0000000000 --- a/policyengine_uk/variables/gov/dwp/num_severely_disabled_claimants_or_partners_for_legacy_benefits.py +++ /dev/null @@ -1,22 +0,0 @@ -from policyengine_uk.model_api import * - - -class num_severely_disabled_claimants_or_partners_for_legacy_benefits(Variable): - value_type = int - entity = BenUnit - label = "Severely disabled claimants or partners for legacy benefits" - documentation = ( - "Claimants and partners satisfying the model's existing " - "is_severely_disabled_for_benefits indicator. This count does not " - "implement the separate statutory non-dependant or carer conditions." - ) - definition_period = YEAR - reference = ( - "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", - "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14", - ) - - def formula(benunit, period, parameters): - claimant_or_partner = benunit.members("is_claimant_or_partner", period) - disabled = benunit.members("is_severely_disabled_for_benefits", period) - return benunit.sum(claimant_or_partner & disabled) diff --git a/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py new file mode 100644 index 0000000000..ad3ebccf38 --- /dev/null +++ b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py @@ -0,0 +1,37 @@ +from policyengine_uk.model_api import * + + +class has_non_dependant_for_severe_disability_premium(Variable): + value_type = bool + entity = BenUnit + label = "Has a non-dependant who blocks the severe disability premium" + documentation = ( + "A non-dependant aged 18 or over normally resides with the claimant, " + "which bars the legacy severe disability premium. Non-dependants who " + "receive a severe disability premium qualifying benefit, or who are " + "blind, are ignored. Every household member aged 18 or over outside " + "the benefit unit is treated as a non-dependant. The model cannot " + "identify the people the Regulations exclude from that definition " + "(joint occupiers, and commercial lodgers or landlords who are not " + "close relatives), or someone who is treated as blind for 28 weeks " + "after regaining their sight." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/3", + "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14", + "https://www.legislation.gov.uk/uksi/1987/1967/regulation/3", + "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", + ) + + def formula(benunit, period, parameters): + p = parameters(period).gov.dwp.disability_premia + person = benunit.members + counted = ( + (person("age", period) >= p.severe_non_dependant_age) + & ~person("receives_severe_disability_premium_qualifying_benefit", period) + & ~person("is_blind", period) + ) + # Counted residents of the household, less those in this benefit unit. + in_household = benunit.max(person.household.sum(counted)) + return in_household > benunit.sum(counted) diff --git a/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py index c648487f98..81eba3a7a1 100644 --- a/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py +++ b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py @@ -4,21 +4,48 @@ class is_severely_disabled_for_benefits(Variable): value_type = bool entity = Person - label = "Has a severe disability" - documentation = "Whether this person is severely disabled for benefits purposes" + label = "Severely disabled for the tax credit severe disability elements" + documentation = ( + "Meets the severe disability condition of the Child Tax Credit " + "disability element (for a child or qualifying young person) and the " + "Working Tax Credit severe disability element (for a claimant): the " + "care component of disability living allowance at the highest rate, " + "the daily living component of personal independence payment at the " + "enhanced rate, attendance allowance at the higher rate (a Working " + "Tax Credit condition; children cannot receive it) or armed forces " + "independence payment. Other Armed Forces Compensation Scheme " + "payments do not count. The Scottish equivalents and benefit that " + "would be payable but for a hospital stay are not modelled. The " + "legacy severe disability premium has its own, wider list " + "(receives_severe_disability_premium_qualifying_benefit)." + ) definition_period = YEAR - reference = "Child Tax Credit Regulations 2002 s. 8" + reference = ( + "https://www.legislation.gov.uk/uksi/2002/2007/regulation/8", + "https://www.legislation.gov.uk/uksi/2002/2005/regulation/17", + ) def formula(person, period, parameters): - benefit = parameters(period).gov.dwp + dwp = parameters(period).gov.dwp THRESHOLD_SAFETY_GAP = 10 * WEEKS_IN_YEAR - paragraph_3 = ( + dla_care_highest = ( person("dla_sc", period) - >= benefit.dla.self_care.higher * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP + >= dwp.dla.self_care.higher * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP ) - paragraph_4 = ( + pip_daily_living_enhanced = ( person("pip_dl", period) - >= benefit.pip.daily_living.enhanced * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP + >= dwp.pip.daily_living.enhanced * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP + ) + attendance_allowance_higher = ( + person("attendance_allowance", period) + >= dwp.attendance_allowance.higher * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP + ) + armed_forces_independence_payment = ( + person("armed_forces_independence_payment", period) > 0 + ) + return ( + dla_care_highest + | pip_daily_living_enhanced + | attendance_allowance_higher + | armed_forces_independence_payment ) - paragraph_5 = person("afcs", period) > 0 - return sum([paragraph_3, paragraph_4, paragraph_5]) > 0 diff --git a/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py new file mode 100644 index 0000000000..b4bd270715 --- /dev/null +++ b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py @@ -0,0 +1,32 @@ +from policyengine_uk.model_api import * + + +class receives_severe_disability_premium_qualifying_benefit(Variable): + value_type = bool + entity = Person + label = "Receives a severe disability premium qualifying benefit" + documentation = ( + "In receipt of a benefit that makes a claimant or partner severely " + "disabled for the legacy severe disability premium: attendance " + "allowance (either rate), the care component of disability living " + "allowance at the highest or middle rate, the daily living component " + "of personal independence payment at either rate, or armed forces " + "independence payment. The same receipt makes a non-dependant " + "ignored in the premium's residence condition. Armed Forces " + "Compensation Scheme payments other than armed forces independence " + "payment do not qualify. The Scottish equivalents (adult disability " + "payment, pension age disability payment and Scottish adult " + "disability living allowance) are not separate model variables, and " + "the hospital and concessionary-payment rules are not modelled." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14", + "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", + "https://www.legislation.gov.uk/uksi/2008/794/schedule/4/paragraph/6", + "https://www.legislation.gov.uk/uksi/1996/207/schedule/1/paragraph/15", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.dwp.disability_premia + return add(person, period, p.severe_qualifying_benefits) > 0 diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index d2de6c640c..95a5543add 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -6,31 +6,69 @@ class severe_disability_premium(Variable): entity = BenUnit label = "Severe disability premium" documentation = ( - "Legacy benefit severe disability premium using the model's existing " - "severe-disability indicator for the claimant or partner. The current " - "approximation pays the couple rate when either partner qualifies; " - "the statutory requirement for both partners to qualify and the " - "non-dependant and carer exclusions remain unmodelled." + "Legacy benefit severe disability premium. A single claimant qualifies " + "if they receive a qualifying benefit; a couple qualifies only if both " + "partners do, or if one does and the other is blind (the blind " + "partner is then treated as absent and the single rate applies). " + "There must be no non-dependant aged 18 or over, and no one may " + "receive a carer benefit for caring for a qualifying partner. A " + "couple who both qualify get the double rate when no carer benefit is " + "paid for either of them, and the single rate when one is paid for " + "only one of them." ) definition_period = YEAR reference = ( - "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14", + "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/20", + "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", + "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/15", + "https://www.legislation.gov.uk/uksi/2008/794/schedule/4/paragraph/6", + "https://www.legislation.gov.uk/uksi/1996/207/schedule/1/paragraph/15", ) unit = GBP def formula(benunit, period, parameters): - dis = parameters(period).gov.dwp.disability_premia - single = benunit("is_single", period.this_year) - couple = benunit("is_couple", period.this_year) - single_premium = single * dis.severe_single - couple_premium = couple * dis.severe_couple - has_severely_disabled_claimant_or_partner = ( - benunit( - "num_severely_disabled_claimants_or_partners_for_legacy_benefits", - period, - ) - > 0 + # The person cared for is not observed. A carer benefit paid to + # someone in the benefit unit is assumed to be for a claimant or + # partner other than its recipient, one person per award; carers + # outside the benefit unit are not observed. The hospital rules that + # keep a patient treated as in receipt (and then pay the single rate) + # are not modelled. + p = parameters(period).gov.dwp.disability_premia + person = benunit.members + claimant_or_partner = person("is_claimant_or_partner", period) + qualifies = person( + "receives_severe_disability_premium_qualifying_benefit", period + ) + qualifying_member = claimant_or_partner & qualifies + qualifying = benunit.sum(qualifying_member) + partner_treated_as_absent = benunit.any( + claimant_or_partner & ~qualifies & person("is_blind", period) + ) + carer = person("receives_carer_benefit", period) + carers = benunit.sum(carer) + carers_for_qualifying_member = benunit.sum(carer & ~qualifying_member) + # Number of single rates payable: two for a couple who both qualify + # with no carer paid for either, one when a carer is paid for only + # one of them, and one for a single claimant (or a couple whose + # other partner is blind) with no carer paid for them. + couple = benunit("is_couple", period) + single_condition = where( + couple, + (qualifying == 1) & partner_treated_as_absent, + qualifying >= 1, + ) & (carers_for_qualifying_member == 0) + rates = where( + couple & (qualifying >= 2), + max_(2 - carers, 0), + single_condition, + ) + weekly_amount = select( + [rates >= 2, rates == 1], + [p.severe_couple, p.severe_single], + default=0, + ) + no_non_dependant = ~benunit( + "has_non_dependant_for_severe_disability_premium", period ) - weekly_amount = single_premium + couple_premium - return weekly_amount * WEEKS_IN_YEAR * has_severely_disabled_claimant_or_partner + return weekly_amount * WEEKS_IN_YEAR * no_non_dependant From 7f3737296a09f3264eb07bf0525166861a9efa2b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 16:26:55 -0400 Subject: [PATCH 2/7] Pin the premium to the claimant and partner, and cover the double rate Adds YAML cases where an 18 or 19 year old qualifying young person is in the family: they are neither a partner nor a non-dependant, so they cannot make a couple qualify, be the blind partner treated as absent, or stop a lone parent qualifying. The legacy-premium fixtures from #1896 now use PIP daily living at the enhanced rate, which qualifies for the premium and is also severe for tax credits. The oracle property draws carers less often and has explicit double-rate and blind-partner examples, with events that show which branches ran; the Pension Credit differential says it compares two encodings of one rule, on the cases every version of that addition agrees on. Co-Authored-By: Claude Opus 5.5 --- .../income_support/legacy_premiums.yaml | 18 ++-- .../family/severe_disability_premium.yaml | 97 +++++++++++++++++++ ...st_severe_disability_premium_properties.py | 38 +++++++- 3 files changed, 139 insertions(+), 14 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml index 0455adf7b5..a301c8d08c 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml @@ -14,7 +14,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true - pip_dl_category: STANDARD + pip_dl_category: ENHANCED output: # The claimant qualifies in their own right: no statutory age-18 floor. disability_premium: 2332.20 @@ -29,7 +29,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true - pip_dl_category: STANDARD + pip_dl_category: ENHANCED output: disability_premium: 2332.20 enhanced_disability_premium: 1144 @@ -48,7 +48,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true - pip_dl_category: STANDARD + pip_dl_category: ENHANCED benunits: family: members: [parent, young_person] @@ -72,7 +72,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true - pip_dl_category: STANDARD + pip_dl_category: ENHANCED benunits: family: members: [parent, young_person] @@ -95,7 +95,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true - pip_dl_category: STANDARD + pip_dl_category: ENHANCED benunits: family: members: [claimant, partner] @@ -118,7 +118,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true - pip_dl_category: STANDARD + pip_dl_category: ENHANCED young_person: age: 19 is_in_non_advanced_education: true @@ -142,7 +142,7 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true - pip_dl_category: STANDARD + pip_dl_category: ENHANCED benunits: family: members: [parent, child] @@ -161,13 +161,13 @@ is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true - pip_dl_category: STANDARD + pip_dl_category: ENHANCED partner: age: 32 is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true - pip_dl_category: STANDARD + pip_dl_category: ENHANCED benunits: family: members: [claimant, partner] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml index 7adcb6b843..3fbb498018 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml @@ -230,6 +230,103 @@ output: severe_disability_premium: 0 +# The claimant and partner, not every adult in the benefit unit (para 14(2); +# HB Regs reg 2 "family"). An 18 or 19 year old qualifying young person is a +# member of the family, neither a partner nor a non-dependant. + +- name: A lone parent qualifies alongside an 18-year-old young person + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + pip_dl_category: STANDARD + young_person: + age: 18 + is_in_non_advanced_education: true + benunits: + family: + members: [parent, young_person] + output: + severe_disability_premium: 4474.60 + +- name: A qualifying young person does not make a couple both qualify + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + is_parent: true + pip_dl_category: STANDARD + partner: + age: 42 + is_parent: true + young_person: + age: 18 + is_in_non_advanced_education: true + dla_sc_category: MIDDLE + benunits: + family: + members: [claimant, partner, young_person] + output: + severe_disability_premium: 0 + +- name: A blind young person is not a partner treated as absent + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + is_parent: true + pip_dl_category: STANDARD + partner: + age: 42 + is_parent: true + young_person: + age: 18 + is_in_non_advanced_education: true + is_blind: true + benunits: + family: + members: [claimant, partner, young_person] + output: + severe_disability_premium: 0 + +- name: A qualifying young person does not qualify the couple + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + is_parent: true + is_blind: true + partner: + age: 42 + is_parent: true + young_person: + age: 19 + is_in_non_advanced_education: true + pip_dl_category: STANDARD + benunits: + family: + members: [claimant, partner, young_person] + output: + severe_disability_premium: 0 + +- name: A 17-year-old single claimant qualifies in their own right + period: 2026 + absolute_error_margin: 0.01 + input: + age: 17 + pip_dl_category: STANDARD + output: + severe_disability_premium: 4474.60 + # Carers (para 14(2)(a)(iii)). - name: A single claimant's own carer benefit is not paid for caring for them diff --git a/policyengine_uk/tests/test_severe_disability_premium_properties.py b/policyengine_uk/tests/test_severe_disability_premium_properties.py index c27e0c9844..298aa6a82d 100644 --- a/policyengine_uk/tests/test_severe_disability_premium_properties.py +++ b/policyengine_uk/tests/test_severe_disability_premium_properties.py @@ -28,13 +28,14 @@ 6. Differential: for a family with no children, no carer and no one else in the household, a single claimant or a couple who both or neither qualify get exactly the Pension Credit severe disability addition, which lists the - same qualifying benefits (SPC Regs 2002 Sch I para 1; reg 6(5)). + same qualifying benefits (SPC Regs 2002 Sch I para 1; reg 6(5)). This keeps + two encodings of the same rule in step; it is not an independent oracle. """ from itertools import product import numpy as np -from hypothesis import HealthCheck, given, settings +from hypothesis import HealthCheck, event, example, given, settings from hypothesis import strategies as st from policyengine_uk import Simulation @@ -77,7 +78,9 @@ def person(draw, age): age=draw(age), benefit=draw(benefit), blind=draw(st.booleans()), - carer=draw(st.booleans()), + # Carer benefits are rarer than not, so couples who both qualify with + # no carer (the double rate) are generated as well. + carer=draw(st.sampled_from([False, False, False, True])), ) @@ -156,11 +159,35 @@ def oracle(unit): return SINGLE if cared_for == 0 else 0.0 +def _adult(benefit, blind=False, carer=False): + return dict(age=40, benefit=benefit, blind=blind, carer=carer) + + @PROPERTY_SETTINGS @given(st.lists(households(), min_size=1, max_size=10)) +@example( + [ + # Both qualify, no carer: the double rate. + dict( + adults=[_adult("pip_standard"), _adult("aa_lower")], + children=[], + others=[], + ), + # One qualifies, the other is blind: the single rate. + dict( + adults=[_adult("dla_middle"), _adult("none", blind=True)], + children=[], + others=[], + ), + ] +) def test_premium_matches_statutory_oracle_and_range(units): premium = simulate(units)["severe_disability_premium"] for unit, value in zip(units, premium): + event( + f"{len(unit['adults'])} adult(s), premium " + f"{'double' if value > SINGLE + 1 else 'single' if value > 1 else 'none'}" + ) assert np.isclose(value, oracle(unit), atol=0.01), unit assert any(np.isclose(value, x, atol=0.01) for x in (0, SINGLE, DOUBLE)) if np.isclose(value, DOUBLE, atol=0.01): @@ -228,8 +255,9 @@ def test_matches_pension_credit_severe_disability_addition(families): for adults in families: adults = [dict(a, carer=False) for a in adults] if len(adults) == 2 and qualifies(adults[0]) != qualifies(adults[1]): - # The Pension Credit formula does not yet apply the couple rule - # (PolicyEngine/policyengine-uk#1896 adds it). + # Restricted to the cases every version of the Pension Credit + # addition agrees on: versions differ on a couple with one + # qualifying partner, and on carers. adults = [dict(a, benefit=adults[0]["benefit"]) for a in adults] units.append(dict(adults=adults, children=[], others=[])) values = simulate( From 09aef1478bfb36cb9fb0ba3cf9cdeccf2fb9ef28 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 18:17:25 -0400 Subject: [PATCH 3/7] Treat only the claimant's family as never a non-dependant A benefit-unit member aged 18 or over who is neither the claimant, the partner, nor a child or qualifying young person (HB Regs reg 19; SSCBA s.142) is a non-dependant under reg 3 and bars the premium; the test previously exempted every benefit-unit member. Adds a YAML case for an 18-year-old not in education, and gives #1896's 19-year-old fixtures the entry-condition input that makes them qualifying young persons. Documentation now states the carer condition as the Regulations do (caring for a single claimant; the couple rates), notes that only the claimant's blind partner is treated as absent and that the model assumes the qualifying partner claims (HB Regs reg 82(1)), lists child disability payment among the unmodelled Scottish benefits, notes that the tax credit flag also gates UC's higher disabled child addition, and fixes the JSA pinpoint. The property tests compute each before/after pair in one simulation, and the Pension Credit differential now covers carers and couples with one qualifying partner, since #1896's addition applies the same rules. Co-Authored-By: Claude Opus 5.5 --- .../severe_qualifying_benefits.yaml | 5 ++- .../income_support/legacy_premiums.yaml | 2 + .../family/severe_disability_premium.yaml | 27 ++++++++++++- ...st_severe_disability_premium_properties.py | 40 +++++++++---------- ...dependant_for_severe_disability_premium.py | 26 ++++++++---- .../is_severely_disabled_for_benefits.py | 5 ++- ...e_disability_premium_qualifying_benefit.py | 9 +++-- .../demographic/severe_disability_premium.py | 26 +++++++----- 8 files changed, 95 insertions(+), 45 deletions(-) diff --git a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml index e4e715221b..444daec682 100644 --- a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml +++ b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml @@ -6,7 +6,8 @@ description: > disability living allowance at the highest or middle rate, the daily living component of personal independence payment at either rate, armed forces independence payment, and the Scottish equivalents. The Scottish benefits - are not separate model variables. + are not separate model variables, and the lists differ slightly between + instruments. metadata: label: Legacy benefit severe disability premium qualifying benefits period: year @@ -18,7 +19,7 @@ metadata: href: https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13 - title: The Employment and Support Allowance Regulations 2008 Sch. 4 para. 6(2)(a)(i) href: https://www.legislation.gov.uk/uksi/2008/794/schedule/4/paragraph/6 - - title: The Jobseeker's Allowance Regulations 1996 Sch. 1 para. 15(2)(a)(i) + - title: The Jobseeker's Allowance Regulations 1996 Sch. 1 para. 15(1)(a), (2)(a), (4)(a) href: https://www.legislation.gov.uk/uksi/1996/207/schedule/1/paragraph/15 values: 2006-03-06: diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml index a301c8d08c..2714eacf05 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support/legacy_premiums.yaml @@ -69,6 +69,7 @@ young_person: age: 19 is_in_non_advanced_education: true + age_started_or_accepted_current_education_or_training: 17 is_disabled_for_benefits: true is_enhanced_disabled_for_benefits: true is_severely_disabled_for_benefits: true @@ -122,6 +123,7 @@ young_person: age: 19 is_in_non_advanced_education: true + age_started_or_accepted_current_education_or_training: 17 benunits: family: members: [parent, young_person] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml index 3fbb498018..ea89ee3350 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml @@ -311,6 +311,7 @@ young_person: age: 19 is_in_non_advanced_education: true + age_started_or_accepted_current_education_or_training: 17 pip_dl_category: STANDARD benunits: family: @@ -318,6 +319,29 @@ output: severe_disability_premium: 0 +- name: An 18-year-old in the benefit unit who is not a young person is a non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_parent: true + pip_dl_category: STANDARD + adult_child: + age: 18 + is_in_non_advanced_education: false + is_in_approved_training: false + benunits: + family: + members: [parent, adult_child] + output: + # Not in education or training, so not a qualifying young person (HB + # Regs reg 19; SSCBA s.142): not in the family, so a non-dependant. + is_child_or_qualifying_young_person_for_child_benefit: [false, false] + has_non_dependant_for_severe_disability_premium: true + severe_disability_premium: 0 + - name: A 17-year-old single claimant qualifies in their own right period: 2026 absolute_error_margin: 0.01 @@ -469,7 +493,8 @@ household: members: [disabled_adult, mother, father] output: - # HB/IS reg 3: close relatives living together are non-dependants. + # HB/IS reg 3: parents living with the claimant are non-dependants + # unless an exclusion applies (for example joint occupiers). severe_disability_premium: [0, 0] - name: People in other households are not non-dependants diff --git a/policyengine_uk/tests/test_severe_disability_premium_properties.py b/policyengine_uk/tests/test_severe_disability_premium_properties.py index 298aa6a82d..ec5619efb2 100644 --- a/policyengine_uk/tests/test_severe_disability_premium_properties.py +++ b/policyengine_uk/tests/test_severe_disability_premium_properties.py @@ -5,9 +5,10 @@ who receives a qualifying benefit; for a couple, both partners must qualify unless the other partner is blind. No non-dependant aged 18 or over may reside with them (ignoring non-dependants who receive a qualifying benefit or -are blind), and no one may receive a carer benefit for caring for them. A -couple who both qualify get the double rate when no carer benefit is paid for -either, and the single rate when one is paid for only one of them. +are blind). A single claimant must have no one paid a carer benefit for caring +for them; a couple who both qualify get the double rate when no carer benefit +is paid for either, the single rate when one is paid for only one of them, and +nothing when carers are paid for both. Invariants, for any generated population of households: @@ -25,10 +26,10 @@ increases the premium. 5. Metamorphic: giving anyone in the household a qualifying benefit, or making them blind, never reduces the premium. -6. Differential: for a family with no children, no carer and no one else in - the household, a single claimant or a couple who both or neither qualify - get exactly the Pension Credit severe disability addition, which lists the - same qualifying benefits (SPC Regs 2002 Sch I para 1; reg 6(5)). This keeps +6. Differential: for a family with no children and no one else in the + household, the premium equals the Pension Credit severe disability + addition, which has the same qualifying benefits, couple, blind-partner and + carer rules and rates (SPC Regs 2002 Sch I para 1; reg 6(5)). This keeps two encodings of the same rule in step; it is not an independent oracle. """ @@ -127,6 +128,12 @@ def simulate(units, variables=("severe_disability_premium",)): return {v: np.asarray(sim.calculate(v, YEAR))[rows] for v in variables} +def simulate_before_after(units, changed): + """The premium for the original and changed households, in one simulation.""" + premium = simulate(units + changed)["severe_disability_premium"] + return premium[: len(units)], premium[len(units) :] + + def oracle(unit): """The premium by the statutory rule, written independently of the model.""" adults, family = unit["adults"], unit["adults"] + unit["children"] @@ -214,8 +221,7 @@ def pay_carer(unit): n = len(unit["adults"]) return dict(unit, adults=family[:n], children=family[n:]) - before = simulate(units)["severe_disability_premium"] - after = simulate([pay_carer(u) for u in units])["severe_disability_premium"] + before, after = simulate_before_after(units, [pay_carer(u) for u in units]) assert np.all(after <= before + 0.01) @@ -243,23 +249,17 @@ def apply(unit): others=changed[a + c :], ) - before = simulate(units)["severe_disability_premium"] - after = simulate([apply(u) for u in units])["severe_disability_premium"] + before, after = simulate_before_after(units, [apply(u) for u in units]) assert np.all(after >= before - 0.01) @PROPERTY_SETTINGS @given(st.lists(st.lists(person(ADULT_AGE), min_size=1, max_size=2), min_size=1)) def test_matches_pension_credit_severe_disability_addition(families): - units = [] - for adults in families: - adults = [dict(a, carer=False) for a in adults] - if len(adults) == 2 and qualifies(adults[0]) != qualifies(adults[1]): - # Restricted to the cases every version of the Pension Credit - # addition agrees on: versions differ on a couple with one - # qualifying partner, and on carers. - adults = [dict(a, benefit=adults[0]["benefit"]) for a in adults] - units.append(dict(adults=adults, children=[], others=[])) + # PolicyEngine/policyengine-uk#1896's Pension Credit addition applies the + # same couple, blind-partner and carer rules, so every family with no one + # else in the household is compared, carers and mixed couples included. + units = [dict(adults=adults, children=[], others=[]) for adults in families] values = simulate( units, ("severe_disability_premium", "severe_disability_minimum_guarantee_addition"), diff --git a/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py index ad3ebccf38..a28e08b27c 100644 --- a/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py @@ -9,16 +9,21 @@ class has_non_dependant_for_severe_disability_premium(Variable): "A non-dependant aged 18 or over normally resides with the claimant, " "which bars the legacy severe disability premium. Non-dependants who " "receive a severe disability premium qualifying benefit, or who are " - "blind, are ignored. Every household member aged 18 or over outside " - "the benefit unit is treated as a non-dependant. The model cannot " - "identify the people the Regulations exclude from that definition " - "(joint occupiers, and commercial lodgers or landlords who are not " - "close relatives), or someone who is treated as blind for 28 weeks " - "after regaining their sight." + "blind, are ignored. Every household member aged 18 or over is " + "treated as a non-dependant unless they are in the claimant's family: " + "the claimant, their partner, and children and qualifying young " + "persons (the Child Benefit definition that the Regulations' " + "'young person' uses). The model cannot identify the other people the " + "Regulations exclude from the definition (for example joint " + "occupiers, commercial lodgers or landlords, and carers engaged by a " + "charity), apply the 12-week rule for someone who moves in to care " + "(IS, ESA and JSA only), or treat someone as blind for 28 weeks after " + "regaining their sight." ) definition_period = YEAR reference = ( "https://www.legislation.gov.uk/uksi/2006/213/regulation/3", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/19", "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14", "https://www.legislation.gov.uk/uksi/1987/1967/regulation/3", "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", @@ -32,6 +37,11 @@ def formula(benunit, period, parameters): & ~person("receives_severe_disability_premium_qualifying_benefit", period) & ~person("is_blind", period) ) - # Counted residents of the household, less those in this benefit unit. + # The claimant's family is never a non-dependant (HB Regs reg 3(2)(a); + # "young person" is a Child Benefit qualifying young person, reg 19). + family = person("is_claimant_or_partner", period) | person( + "is_child_or_qualifying_young_person_for_child_benefit", period + ) + # Counted residents of the household, less the claimant's family. in_household = benunit.max(person.household.sum(counted)) - return in_household > benunit.sum(counted) + return in_household > benunit.sum(counted & family) diff --git a/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py index 81eba3a7a1..90a81af832 100644 --- a/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py +++ b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py @@ -17,7 +17,10 @@ class is_severely_disabled_for_benefits(Variable): "payments do not count. The Scottish equivalents and benefit that " "would be payable but for a hospital stay are not modelled. The " "legacy severe disability premium has its own, wider list " - "(receives_severe_disability_premium_qualifying_benefit)." + "(receives_severe_disability_premium_qualifying_benefit). The flag " + "also gates the Universal Credit higher disabled child addition, " + "whose condition (UC Regs 2013 reg 24(2)(b)) adds blindness and has " + "no armed forces independence payment limb; that is not yet separated." ) definition_period = YEAR reference = ( diff --git a/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py index b4bd270715..e508c4a959 100644 --- a/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py +++ b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py @@ -15,9 +15,12 @@ class receives_severe_disability_premium_qualifying_benefit(Variable): "ignored in the premium's residence condition. Armed Forces " "Compensation Scheme payments other than armed forces independence " "payment do not qualify. The Scottish equivalents (adult disability " - "payment, pension age disability payment and Scottish adult " - "disability living allowance) are not separate model variables, and " - "the hospital and concessionary-payment rules are not modelled." + "payment, pension age disability payment, Scottish adult disability " + "living allowance and, in Housing Benefit, the care component of " + "child disability payment) are not separate model variables; the " + "lists differ slightly between instruments (pension age disability " + "payment is not in the IS and JSA single-claimant lists). The " + "hospital and concessionary-payment rules are not modelled." ) definition_period = YEAR reference = ( diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index 95a5543add..cc54020a5d 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -9,12 +9,18 @@ class severe_disability_premium(Variable): "Legacy benefit severe disability premium. A single claimant qualifies " "if they receive a qualifying benefit; a couple qualifies only if both " "partners do, or if one does and the other is blind (the blind " - "partner is then treated as absent and the single rate applies). " - "There must be no non-dependant aged 18 or over, and no one may " - "receive a carer benefit for caring for a qualifying partner. A " - "couple who both qualify get the double rate when no carer benefit is " - "paid for either of them, and the single rate when one is paid for " - "only one of them." + "partner is then treated as absent and the single rate applies). The " + "Regulations treat only the claimant's blind partner as absent; the " + "model assumes the qualifying partner is the claimant, as a couple " + "can arrange for Housing Benefit (HB Regs reg 82(1)). There must be " + "no non-dependant aged 18 or over, other than one who receives a " + "qualifying benefit or is blind. A single claimant (or one whose blind " + "partner is treated as absent) must have no one paid a carer benefit " + "for caring for them. A couple who both qualify get the double rate " + "when no carer benefit is paid for either of them, the single rate " + "when one is paid for only one of them, and nothing when carers are " + "paid for both. The law also counts a Universal Credit award with the " + "carer element, which the model does not." ) definition_period = YEAR reference = ( @@ -30,10 +36,10 @@ class severe_disability_premium(Variable): def formula(benunit, period, parameters): # The person cared for is not observed. A carer benefit paid to # someone in the benefit unit is assumed to be for a claimant or - # partner other than its recipient, one person per award; carers - # outside the benefit unit are not observed. The hospital rules that - # keep a patient treated as in receipt (and then pay the single rate) - # are not modelled. + # partner other than its recipient, one person per award. Carers in + # other benefit units are not counted, and carers in other households + # are not observed. The hospital rules that keep a patient treated as + # in receipt (and then pay the single rate) are not modelled. p = parameters(period).gov.dwp.disability_premia person = benunit.members claimant_or_partner = person("is_claimant_or_partner", period) From 9dd451481d4903115426f81a6a5932fa94d7c1ae Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 22:06:17 -0400 Subject: [PATCH 4/7] Property-test the claimant's family and document its limits The property tests now generate a 16-19 year old in the benefit unit, in or out of education and with an entry age either side of 19, and the oracle treats one who is not a qualifying young person as a non-dependant (HB Regs regs 3, 19). Explicit examples cover an 18-year-old not in education (no premium) and a 19-year-old qualifying young person (single rate); restoring the old all-benefit-unit exemption fails them. The non-dependant docstring now says that a 19-year-old is a young person only with a known entry age under 19, and that the young person's own-claim exclusion (HB reg 19(2), IS reg 14(2)) is approximated by receives_benefits_in_own_right. The qualifying-benefit docstring places child disability payment in Housing Benefit and the Scottish CTR scheme. Co-Authored-By: Claude Opus 5.5 --- ...st_severe_disability_premium_properties.py | 90 +++++++++++++++---- ...dependant_for_severe_disability_premium.py | 8 +- ...e_disability_premium_qualifying_benefit.py | 5 +- 3 files changed, 83 insertions(+), 20 deletions(-) diff --git a/policyengine_uk/tests/test_severe_disability_premium_properties.py b/policyengine_uk/tests/test_severe_disability_premium_properties.py index ec5619efb2..d34cc8461c 100644 --- a/policyengine_uk/tests/test_severe_disability_premium_properties.py +++ b/policyengine_uk/tests/test_severe_disability_premium_properties.py @@ -21,7 +21,8 @@ qualifying members). 3. Metamorphic: adding a household member aged 18 or over, in another benefit unit, who receives no qualifying benefit and is not blind, removes the - premium. + premium. The oracle also treats a benefit-unit member aged 18 or 19 who is + not a qualifying young person as a non-dependant (HB Regs regs 3, 19). 4. Metamorphic: paying anyone in the benefit unit a carer benefit never increases the premium. 5. Metamorphic: giving anyone in the household a qualifying benefit, or making @@ -85,21 +86,61 @@ def person(draw, age): ) +@st.composite +def young_person(draw): + # A 16-19 year old in the benefit unit, who is a qualifying young person + # only if in non-advanced education and (at 19) started before 19. + return dict( + draw(person(st.integers(16, 19))), + in_education=draw(st.booleans()), + entry_age=draw(st.sampled_from([17, 19])), + ) + + @st.composite def households(draw): - adults = draw(st.lists(person(ADULT_AGE), min_size=1, max_size=2)) + young = draw(st.lists(young_person(), max_size=1)) + # With a 16-19 year old present, adults aged 40+ make them the head's + # presumed child under #1896's claimant and partner inference. + adult_age = st.integers(40, 60) if young else ADULT_AGE + adults = draw(st.lists(person(adult_age), min_size=1, max_size=2)) children = draw(st.lists(person(st.integers(0, 15)), max_size=1)) others = draw(st.lists(person(st.integers(15, 70)), max_size=2)) - return dict(adults=adults, children=children, others=others) + return dict(adults=adults, children=children, young=young, others=others) + + +def family_members(unit): + return unit["adults"] + unit["children"] + unit.get("young", []) + + +def with_family(unit, family): + a, c = len(unit["adults"]), len(unit["children"]) + return dict( + unit, adults=family[:a], children=family[a : a + c], young=family[a + c :] + ) def qualifies(p): return BENEFITS[p["benefit"]][1] +def is_qualifying_young_person(p): + """Child Benefit qualifying young person (SSCBA s.142), as HB reg 19 uses.""" + return ( + 16 <= p["age"] < 20 + and p.get("in_education", False) + and (p["age"] < 19 or p.get("entry_age", 99) < 19) + ) + + def person_inputs(p): inputs = {"age": {YEAR: p["age"]}, "is_blind": {YEAR: p["blind"]}} inputs["receives_carer_benefit"] = {YEAR: p["carer"]} + if "in_education" in p: + inputs["is_in_non_advanced_education"] = {YEAR: p["in_education"]} + inputs["age_started_or_accepted_current_education_or_training"] = { + YEAR: p["entry_age"] + } for name, value in BENEFITS[p["benefit"]][0].items(): inputs[name] = {YEAR: value} return inputs @@ -110,7 +151,7 @@ def simulate(units, variables=("severe_disability_premium",)): people, benunits, hh = {}, {}, {} for i, unit in enumerate(units): family = [] - for j, p in enumerate(unit["adults"] + unit["children"]): + for j, p in enumerate(family_members(unit)): people[f"h{i}_f{j}"] = person_inputs(p) family.append(f"h{i}_f{j}") benunits[f"h{i}_family"] = {"members": family} @@ -136,11 +177,15 @@ def simulate_before_after(units, changed): def oracle(unit): """The premium by the statutory rule, written independently of the model.""" - adults, family = unit["adults"], unit["adults"] + unit["children"] + adults, family = unit["adults"], family_members(unit) + # Non-dependants: other benefit units in the household, and anyone in the + # benefit unit who is not the claimant, partner, a child or a qualifying + # young person (HB Regs regs 3(2)(a), 19). + candidates = unit["others"] + [ + y for y in unit.get("young", []) if not is_qualifying_young_person(y) + ] blocking = [ - o - for o in unit["others"] - if o["age"] >= 18 and not qualifies(o) and not o["blind"] + o for o in candidates if o["age"] >= 18 and not qualifies(o) and not o["blind"] ] if blocking: return 0.0 @@ -186,6 +231,21 @@ def _adult(benefit, blind=False, carer=False): children=[], others=[], ), + # An 18-year-old in the benefit unit, not in education: a + # non-dependant, so no premium. + dict( + adults=[_adult("pip_standard")], + children=[], + young=[dict(_adult("none"), age=18, in_education=False, entry_age=17)], + others=[], + ), + # A 19-year-old qualifying young person: family, so the single rate. + dict( + adults=[_adult("pip_standard")], + children=[], + young=[dict(_adult("none"), age=19, in_education=True, entry_age=17)], + others=[], + ), ] ) def test_premium_matches_statutory_oracle_and_range(units): @@ -215,11 +275,10 @@ def test_counted_non_dependant_removes_premium(units, age): @given(st.lists(households(), min_size=1, max_size=10), st.integers(0, 2)) def test_carer_benefit_never_increases_premium(units, index): def pay_carer(unit): - family = unit["adults"] + unit["children"] + family = family_members(unit) i = index % len(family) family = [dict(p, carer=p["carer"] or k == i) for k, p in enumerate(family)] - n = len(unit["adults"]) - return dict(unit, adults=family[:n], children=family[n:]) + return with_family(unit, family) before, after = simulate_before_after(units, [pay_carer(u) for u in units]) assert np.all(after <= before + 0.01) @@ -233,7 +292,8 @@ def pay_carer(unit): ) def test_qualifying_benefit_or_blindness_never_reduces_premium(units, index, change): def apply(unit): - everyone = unit["adults"] + unit["children"] + unit["others"] + family = family_members(unit) + everyone = family + unit["others"] i = index % len(everyone) changed = [ dict(p, **({"blind": True} if change == "blind" else {"benefit": change})) @@ -241,12 +301,8 @@ def apply(unit): else p for k, p in enumerate(everyone) ] - a, c = len(unit["adults"]), len(unit["children"]) return dict( - unit, - adults=changed[:a], - children=changed[a : a + c], - others=changed[a + c :], + with_family(unit, changed[: len(family)]), others=changed[len(family) :] ) before, after = simulate_before_after(units, [apply(u) for u in units]) diff --git a/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py index a28e08b27c..77653a3c89 100644 --- a/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py @@ -13,7 +13,13 @@ class has_non_dependant_for_severe_disability_premium(Variable): "treated as a non-dependant unless they are in the claimant's family: " "the claimant, their partner, and children and qualifying young " "persons (the Child Benefit definition that the Regulations' " - "'young person' uses). The model cannot identify the other people the " + "'young person' uses). A 19-year-old is a young person only if their " + "course started, or they enrolled or were accepted, before 19 " + "(age_started_or_accepted_current_education_or_training; unknown " + "counts as not met). The Regulations' exclusion of young people who " + "claim benefits themselves (HB Regs reg 19(2), IS Regs reg 14(2)) is " + "approximated by receives_benefits_in_own_right. The model cannot " + "identify the other people the " "Regulations exclude from the definition (for example joint " "occupiers, commercial lodgers or landlords, and carers engaged by a " "charity), apply the 12-week rule for someone who moves in to care " diff --git a/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py index e508c4a959..ebe5033875 100644 --- a/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py +++ b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py @@ -16,8 +16,9 @@ class receives_severe_disability_premium_qualifying_benefit(Variable): "Compensation Scheme payments other than armed forces independence " "payment do not qualify. The Scottish equivalents (adult disability " "payment, pension age disability payment, Scottish adult disability " - "living allowance and, in Housing Benefit, the care component of " - "child disability payment) are not separate model variables; the " + "living allowance and, in Housing Benefit and the Scottish Council " + "Tax Reduction scheme, the care component of child disability " + "payment) are not separate model variables; the " "lists differ slightly between instruments (pension age disability " "payment is not in the IS and JSA single-claimant lists). The " "hospital and concessionary-payment rules are not modelled." From e1806628f3580adf19489a250629025aa56de1be Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 13:16:36 -0400 Subject: [PATCH 5/7] Read Attendance Allowance from its category; document omitted provisions Close the round-4 review findings on #1946: - is_severely_disabled_for_benefits reads higher-rate Attendance Allowance from aa_category, which the Enhanced FRS holds, and compares an amount with the higher rate less GBP 10 a week only when no category is given. The documentation says what the dataset holds and that the fallback is an approximation (WTC Regs 2002 reg 17(2) has no tolerance). YAML cases pin the category's precedence and both sides of the amount threshold in 2024 and 2026. - Document provisions the model does not apply, with citations: carer benefits withheld under Social Security Fraud Act 2001 s. 6B or 7 (HB Sch 3 para 14(7), IS Sch 2 para 13(5), ESA Sch 4 para 6(8), JSA Sch 1 para 15(9)); the care-leaver exception to "young person" (HB reg 19(2)(c), IS reg 14(2)(c)); and the JSA 1991 savings (JSA Sch 1 para 15(8)). - Document that the qualifying-benefit list uses Great Britain's 8 April 2013 date, while Northern Ireland added AFIP on 24 December 2013 and PIP on 20 June 2016 (S.R. 2006/405 Sch 4 para 14). - Describe the property-test oracle as a separately written implementation that shares the model's carer-attribution and blind-partner assumptions, not a statement of the statutory rule. Co-Authored-By: Claude Opus 5.5 --- ...g-age-severe-disability-premium.changed.md | 1 + .../severe_qualifying_benefits.yaml | 13 +++++- .../is_severely_disabled_for_benefits.yaml | 41 +++++++++++++++++++ ...st_severe_disability_premium_properties.py | 22 ++++++---- ...dependant_for_severe_disability_premium.py | 10 ++++- .../is_severely_disabled_for_benefits.py | 26 ++++++++++-- .../demographic/severe_disability_premium.py | 15 ++++++- 7 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 changelog.d/working-age-severe-disability-premium.changed.md diff --git a/changelog.d/working-age-severe-disability-premium.changed.md b/changelog.d/working-age-severe-disability-premium.changed.md new file mode 100644 index 0000000000..d6a320c253 --- /dev/null +++ b/changelog.d/working-age-severe-disability-premium.changed.md @@ -0,0 +1 @@ +The tax credit severe disability flag reads Attendance Allowance from its award category, comparing an amount with the higher rate only when no category is given. diff --git a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml index 444daec682..ef06782c69 100644 --- a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml +++ b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml @@ -7,7 +7,16 @@ description: > component of personal independence payment at either rate, armed forces independence payment, and the Scottish equivalents. The Scottish benefits are not separate model variables, and the lists differ slightly between - instruments. + instruments. The 8 April 2013 date is Great Britain's: S.I. 2013/388 added + the PIP daily living component and S.I. 2013/591 added armed forces + independence payment. In Northern Ireland the Housing Benefit Regulations + (S.R. 2006/405 Sch. 4 para. 14) added armed forces independence payment on + 24 December 2013 (S.I. 2013/3021) and the PIP daily living component on + 20 June 2016 (S.R. 2016/228). The model applies the Great Britain list + throughout the UK, so for Northern Ireland the list before 20 June 2016 is + approximate. The Northern Ireland Income Support Regulations (S.R. + 1987/459) have no revised text on legislation.gov.uk, and their dates were + not checked. metadata: label: Legacy benefit severe disability premium qualifying benefits period: year @@ -21,6 +30,8 @@ metadata: href: https://www.legislation.gov.uk/uksi/2008/794/schedule/4/paragraph/6 - title: The Jobseeker's Allowance Regulations 1996 Sch. 1 para. 15(1)(a), (2)(a), (4)(a) href: https://www.legislation.gov.uk/uksi/1996/207/schedule/1/paragraph/15 + - title: The Housing Benefit Regulations (Northern Ireland) 2006 Sch. 4 para. 14(2)(a)(i) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/4/paragraph/14 values: 2006-03-06: - attendance_allowance diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/is_severely_disabled_for_benefits.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/is_severely_disabled_for_benefits.yaml index cbc0d7f8a4..2934198a36 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/is_severely_disabled_for_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/is_severely_disabled_for_benefits.yaml @@ -68,3 +68,44 @@ output: # WTC Regs 2002 reg 17(2) and Sch 2 para 6: 1,705 a year in 2024-25. WTC_severely_disabled_element: 1_705 + +# Attendance Allowance is read from its award category. Only when no category +# is given is an amount compared with the annual higher rate less GBP 10 a +# week: an approximation, since WTC reg 17(2) has no tolerance. +- name: The Attendance Allowance category decides over an amount entered with it + period: 2026 + input: + aa_category: LOWER + attendance_allowance: 5_959.20 + output: + is_severely_disabled_for_benefits: false + +- name: Attendance Allowance amount with no category, at the 2024 threshold, is severe + period: 2024 + input: + # 108.55 x 52 - 10 x 52 = 5,124.60. + attendance_allowance: 5_124.60 + output: + is_severely_disabled_for_benefits: true + +- name: Attendance Allowance amount with no category, just below the 2024 threshold, is not severe + period: 2024 + input: + attendance_allowance: 5_124.59 + output: + is_severely_disabled_for_benefits: false + +- name: Attendance Allowance amount with no category, at the 2026 threshold, is severe + period: 2026 + input: + # 114.60 x 52 - 10 x 52 = 5,439.20. + attendance_allowance: 5_439.20 + output: + is_severely_disabled_for_benefits: true + +- name: Attendance Allowance amount with no category, just below the 2026 threshold, is not severe + period: 2026 + input: + attendance_allowance: 5_439.19 + output: + is_severely_disabled_for_benefits: false diff --git a/policyengine_uk/tests/test_severe_disability_premium_properties.py b/policyengine_uk/tests/test_severe_disability_premium_properties.py index d34cc8461c..62f8272595 100644 --- a/policyengine_uk/tests/test_severe_disability_premium_properties.py +++ b/policyengine_uk/tests/test_severe_disability_premium_properties.py @@ -14,11 +14,15 @@ 1. Range: the premium is 0, the single rate or the double rate, and the double rate goes only to a couple who both qualify. -2. Oracle: the premium equals an independent statement of the rule in which - the carer condition is a brute-force assignment of each carer award to a - benefit-unit member other than its recipient (the model does not observe - who is cared for, so it takes the assignment that covers the most - qualifying members). +2. Oracle: the premium equals a second, separately written implementation + of the rule, with its own benefit classification and a carer condition + that brute-forces the assignment of each carer award to a benefit-unit + member other than its recipient. It is algorithmically independent of the + model but not of the model's assumptions: it shares the carer attribution + (the model does not observe who is cared for, so both take the assignment + that covers the most qualifying members) and the symmetric blind-partner + rule (either partner may be the claimant). Agreement shows the model + implements those assumptions, not that they match the Regulations. 3. Metamorphic: adding a household member aged 18 or over, in another benefit unit, who receives no qualifying benefit and is not blind, removes the premium. The oracle also treats a benefit-unit member aged 18 or 19 who is @@ -176,7 +180,11 @@ def simulate_before_after(units, changed): def oracle(unit): - """The premium by the statutory rule, written independently of the model.""" + """The premium from a separately written implementation of the rule. + + Independent of the model's code, but it shares the model's carer + attribution and symmetric blind-partner assumptions. + """ adults, family = unit["adults"], family_members(unit) # Non-dependants: other benefit units in the household, and anyone in the # benefit unit who is not the claimant, partner, a child or a qualifying @@ -248,7 +256,7 @@ def _adult(benefit, blind=False, carer=False): ), ] ) -def test_premium_matches_statutory_oracle_and_range(units): +def test_premium_matches_independent_oracle_and_range(units): premium = simulate(units)["severe_disability_premium"] for unit, value in zip(units, premium): event( diff --git a/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py index 77653a3c89..aacc28f900 100644 --- a/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py @@ -18,7 +18,13 @@ class has_non_dependant_for_severe_disability_premium(Variable): "(age_started_or_accepted_current_education_or_training; unknown " "counts as not met). The Regulations' exclusion of young people who " "claim benefits themselves (HB Regs reg 19(2), IS Regs reg 14(2)) is " - "approximated by receives_benefits_in_own_right. The model cannot " + "approximated by receives_benefits_in_own_right. Their exclusion of " + "a young person to whom section 6 of the Children (Leaving Care) Act " + "2000 applies (HB Regs reg 19(2)(c), IS Regs reg 14(2)(c)) is not " + "modelled. The people in section 6(2)(a) to (bb) are children aged 16 " + "or 17, below the non-dependant age, so for them the omission cannot " + "change the premium; the people prescribed under section 6(4) " + "(looked after in Scotland) were not checked. The model cannot " "identify the other people the " "Regulations exclude from the definition (for example joint " "occupiers, commercial lodgers or landlords, and carers engaged by a " @@ -32,7 +38,9 @@ class has_non_dependant_for_severe_disability_premium(Variable): "https://www.legislation.gov.uk/uksi/2006/213/regulation/19", "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14", "https://www.legislation.gov.uk/uksi/1987/1967/regulation/3", + "https://www.legislation.gov.uk/uksi/1987/1967/regulation/14", "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", + "https://www.legislation.gov.uk/ukpga/2000/35/section/6", ) def formula(benunit, period, parameters): diff --git a/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py index 90a81af832..e511685eeb 100644 --- a/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py +++ b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py @@ -15,8 +15,19 @@ class is_severely_disabled_for_benefits(Variable): "Tax Credit condition; children cannot receive it) or armed forces " "independence payment. Other Armed Forces Compensation Scheme " "payments do not count. The Scottish equivalents and benefit that " - "would be payable but for a hospital stay are not modelled. The " - "legacy severe disability premium has its own, wider list " + "would be payable but for a hospital stay are not modelled. " + "Attendance allowance is read from its award category (aa_category), " + "which the Enhanced FRS holds. Only where no category is given, as " + "when a household enters an attendance allowance amount directly, is " + "the amount compared with the annual higher rate, less £10 a week; " + "the Regulations have no such tolerance, so that fallback is an " + "approximation. The disability living allowance and personal " + "independence payment tests read amounts, which the model computes " + "from their categories, with the same tolerance for amounts entered " + "directly. The Enhanced FRS also stores this flag, and dataset runs " + "use the stored value instead of this formula " + "(policyengine-uk-data#494 aligns its definition with this one). " + "The legacy severe disability premium has its own, wider list " "(receives_severe_disability_premium_qualifying_benefit). The flag " "also gates the Universal Credit higher disabled child addition, " "whose condition (UC Regs 2013 reg 24(2)(b)) adds blindness and has " @@ -39,9 +50,16 @@ def formula(person, period, parameters): person("pip_dl", period) >= dwp.pip.daily_living.enhanced * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP ) - attendance_allowance_higher = ( + # WTC Regs 2002 reg 17(2): "an attendance allowance at the higher + # rate". The award category decides; an amount is read only when no + # category is given. + aa_category = person("aa_category", period) + aa_categories = aa_category.possible_values + attendance_allowance_higher = where( + aa_category == aa_categories.NONE, person("attendance_allowance", period) - >= dwp.attendance_allowance.higher * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP + >= dwp.attendance_allowance.higher * WEEKS_IN_YEAR - THRESHOLD_SAFETY_GAP, + aa_category == aa_categories.HIGHER, ) armed_forces_independence_payment = ( person("armed_forces_independence_payment", period) > 0 diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index cc54020a5d..c62b5f09a6 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -20,7 +20,18 @@ class severe_disability_premium(Variable): "when no carer benefit is paid for either of them, the single rate " "when one is paid for only one of them, and nothing when carers are " "paid for both. The law also counts a Universal Credit award with the " - "carer element, which the model does not." + "carer element, which the model does not. Not modelled: a carer's " + "allowance or carer element that would be paid but for a loss of " + "benefit restriction under section 6B or 7 of the Social Security " + "Fraud Act 2001 also counts (HB Regs 2006 Sch 3 para 14(7), IS Regs " + "1987 Sch 2 para 13(5), ESA Regs 2008 Sch 4 para 6(8), JSA Regs 1996 " + "Sch 1 para 15(9)); the model has no input for such a restriction, so " + "where one applies it pays the premium the law withholds. The " + "Jobseeker's Allowance savings for people protected by the Income " + "Support (General) Amendment (No. 6) Regulations 1991 (JSA Regs 1996 " + "Sch 1 para 15(8)) are not modelled; income-based Jobseeker's " + "Allowance is a reported amount in the model, so this changes no " + "modelled payment." ) definition_period = YEAR reference = ( @@ -30,6 +41,8 @@ class severe_disability_premium(Variable): "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/15", "https://www.legislation.gov.uk/uksi/2008/794/schedule/4/paragraph/6", "https://www.legislation.gov.uk/uksi/1996/207/schedule/1/paragraph/15", + "https://www.legislation.gov.uk/ukpga/2001/11/section/6B", + "https://www.legislation.gov.uk/ukpga/2001/11/section/7", ) unit = GBP From c81331a60387788778bbad895d8084ec5b82ecc9 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 17:36:45 -0400 Subject: [PATCH 6/7] Apply Northern Ireland's commencement dates to the severe disability premium list Northern Ireland added armed forces independence payment on 24 December 2013 (S.I. 2013/3021) and the PIP daily living component on 20 June 2016 (S.R. 2016/228), later than Great Britain's 8 April 2013. A separate parameter holds the Northern Ireland list and the variable selects by household country. Documentation notes what the model omits. Co-Authored-By: Claude Opus 5.5 --- ...g-age-severe-disability-premium.changed.md | 2 +- .../severe_qualifying_benefits.yaml | 19 ++- ..._qualifying_benefits_northern_ireland.yaml | 57 +++++++++ ...evere_disability_premium_commencement.yaml | 112 ++++++++++++++++++ ...st_severe_disability_premium_properties.py | 23 +++- ...dependant_for_severe_disability_premium.py | 6 +- .../is_severely_disabled_for_benefits.py | 8 +- ...e_disability_premium_qualifying_benefit.py | 21 +++- .../demographic/severe_disability_premium.py | 2 +- 9 files changed, 231 insertions(+), 19 deletions(-) create mode 100644 policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits_northern_ireland.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium_commencement.yaml diff --git a/changelog.d/working-age-severe-disability-premium.changed.md b/changelog.d/working-age-severe-disability-premium.changed.md index d6a320c253..527fdbb906 100644 --- a/changelog.d/working-age-severe-disability-premium.changed.md +++ b/changelog.d/working-age-severe-disability-premium.changed.md @@ -1 +1 @@ -The tax credit severe disability flag reads Attendance Allowance from its award category, comparing an amount with the higher rate only when no category is given. +- Read Attendance Allowance from its dataset award category when supplied, retained an explicitly approximate amount fallback otherwise, and applied Northern Ireland's separate AFIP and PIP commencement dates to the legacy severe disability premium. diff --git a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml index ef06782c69..c5c5104405 100644 --- a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml +++ b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml @@ -9,14 +9,11 @@ description: > are not separate model variables, and the lists differ slightly between instruments. The 8 April 2013 date is Great Britain's: S.I. 2013/388 added the PIP daily living component and S.I. 2013/591 added armed forces - independence payment. In Northern Ireland the Housing Benefit Regulations - (S.R. 2006/405 Sch. 4 para. 14) added armed forces independence payment on - 24 December 2013 (S.I. 2013/3021) and the PIP daily living component on - 20 June 2016 (S.R. 2016/228). The model applies the Great Britain list - throughout the UK, so for Northern Ireland the list before 20 June 2016 is - approximate. The Northern Ireland Income Support Regulations (S.R. - 1987/459) have no revised text on legislation.gov.uk, and their dates were - not checked. + independence payment. This list applies in Great Britain; the + severe_qualifying_benefits_northern_ireland parameter holds Northern + Ireland's separate commencement dates. The model selects the list using + household country and applies the list at the annual period's start, + without within-year proration. metadata: label: Legacy benefit severe disability premium qualifying benefits period: year @@ -30,8 +27,10 @@ metadata: href: https://www.legislation.gov.uk/uksi/2008/794/schedule/4/paragraph/6 - title: The Jobseeker's Allowance Regulations 1996 Sch. 1 para. 15(1)(a), (2)(a), (4)(a) href: https://www.legislation.gov.uk/uksi/1996/207/schedule/1/paragraph/15 - - title: The Housing Benefit Regulations (Northern Ireland) 2006 Sch. 4 para. 14(2)(a)(i) - href: https://www.legislation.gov.uk/nisr/2006/405/schedule/4/paragraph/14 + - title: The Personal Independence Payment (Supplementary Provisions and Consequential Amendments) Regulations 2013 reg. 2 + href: https://www.legislation.gov.uk/uksi/2013/388/regulation/2 + - title: "The Armed Forces and Reserve Forces Compensation Scheme (Consequential Provisions: Subordinate Legislation) Order 2013 art. 2" + href: https://www.legislation.gov.uk/uksi/2013/591/article/2 values: 2006-03-06: - attendance_allowance diff --git a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits_northern_ireland.yaml b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits_northern_ireland.yaml new file mode 100644 index 0000000000..d26c473216 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits_northern_ireland.yaml @@ -0,0 +1,57 @@ +description: > + Benefits whose receipt qualifies a claimant or partner for the legacy severe + disability premium, or exempts a non-dependant from its residence condition, + in Northern Ireland. Attendance allowance at either rate and the disability + living allowance care component at the middle or highest rate qualify. + Armed forces independence payment was added from 24 December 2013 by + S.I. 2013/3021, and the personal independence payment daily living component + at either rate from 20 June 2016 by S.R. 2016/228. The amendments cover the + Northern Ireland Income Support, Jobseeker's Allowance, Housing Benefit and + Employment and Support Allowance regulations. The model applies the list at + the annual period's start, without within-year proration. The initial value + date follows the shared model baseline, not the commencement of S.R. + 2006/405 on 20 November 2006. Northern Ireland historical coverage before + that commencement has not been verified. +metadata: + label: Northern Ireland legacy severe disability premium qualifying benefits + period: year + unit: list + preserve_calendar_dates: true + reference: + - title: The Housing Benefit Regulations (Northern Ireland) 2006 reg. 1(1) + href: https://www.legislation.gov.uk/nisr/2006/405/regulation/1/made + - title: The Housing Benefit Regulations (Northern Ireland) 2006 Sch. 4 para. 14 + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/4/paragraph/14 + - title: S.I. 2013/3021 art. 1 (commencement) + href: https://www.legislation.gov.uk/uksi/2013/3021/article/1 + - title: S.I. 2013/3021 art. 14(5)(c) (Income Support) + href: https://www.legislation.gov.uk/uksi/2013/3021/article/14/5/c + - title: S.I. 2013/3021 art. 17(7)(c) (Jobseeker's Allowance) + href: https://www.legislation.gov.uk/uksi/2013/3021/article/17/7/c + - title: S.I. 2013/3021 art. 26(6)(c) (Housing Benefit) + href: https://www.legislation.gov.uk/uksi/2013/3021/article/26/6/c + - title: S.I. 2013/3021 art. 28(5)(b) (Employment and Support Allowance) + href: https://www.legislation.gov.uk/uksi/2013/3021/article/28/5/b + - title: S.R. 2016/228 reg. 1 (commencement) + href: https://www.legislation.gov.uk/nisr/2016/228/regulation/1 + - title: S.R. 2016/228 reg. 11(5)(c) (Income Support) + href: https://www.legislation.gov.uk/nisr/2016/228/regulation/11/5/c + - title: S.R. 2016/228 reg. 14(7)(c) (Jobseeker's Allowance) + href: https://www.legislation.gov.uk/nisr/2016/228/regulation/14/7/c + - title: S.R. 2016/228 reg. 30(7)(c) (Housing Benefit) + href: https://www.legislation.gov.uk/nisr/2016/228/regulation/30/7/c + - title: S.R. 2016/228 reg. 34(5)(b) (Employment and Support Allowance) + href: https://www.legislation.gov.uk/nisr/2016/228/regulation/34/5/b +values: + 2006-03-06: + - attendance_allowance + - dla_sc_middle_plus + 2013-12-24: + - attendance_allowance + - dla_sc_middle_plus + - armed_forces_independence_payment + 2016-06-20: + - attendance_allowance + - dla_sc_middle_plus + - pip_dl + - armed_forces_independence_payment diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium_commencement.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium_commencement.yaml new file mode 100644 index 0000000000..9564351306 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium_commencement.yaml @@ -0,0 +1,112 @@ +# Dates verified in the amending instruments: GB S.I. 2013/388 reg 2 and +# S.I. 2013/591 art 2; NI S.I. 2013/3021 art 1 and S.R. 2016/228 reg 1. +# Calendar-year cases exercise the list at 1 January: AFIP added on +# 24 December 2013 enters the 2014 snapshot; PIP added on 20 June 2016 enters +# the 2017 snapshot. These cases do not assert within-year payment proration. +# Pre-2015 cases supply receipt amounts directly because the AA and PIP rate +# parameters begin in 2015; they test qualifying lists, not historical rates. + +- name: GB PIP and AFIP do not qualify in the January 2013 snapshot + period: 2013 + input: + country: ENGLAND + attendance_allowance: 0 + pip_dl: 1 + armed_forces_independence_payment: 1 + output: + receives_severe_disability_premium_qualifying_benefit: false + +- name: GB PIP qualifies in the January 2014 snapshot + period: 2014 + input: + country: ENGLAND + attendance_allowance: 0 + pip_dl: 1 + output: + receives_severe_disability_premium_qualifying_benefit: true + +- name: GB AFIP qualifies in the January 2014 snapshot + period: 2014 + input: + country: ENGLAND + attendance_allowance: 0 + pip_dl: 0 + armed_forces_independence_payment: 1 + output: + receives_severe_disability_premium_qualifying_benefit: true + +- name: NI PIP and AFIP do not qualify in the January 2013 snapshot + period: 2013 + input: + country: NORTHERN_IRELAND + attendance_allowance: 0 + pip_dl: 1 + armed_forces_independence_payment: 1 + output: + receives_severe_disability_premium_qualifying_benefit: false + +- name: NI PIP does not use the GB January 2014 snapshot + period: 2014 + input: + country: NORTHERN_IRELAND + attendance_allowance: 0 + pip_dl: 1 + output: + receives_severe_disability_premium_qualifying_benefit: false + +- name: NI AFIP qualifies in the January 2014 snapshot + period: 2014 + input: + country: NORTHERN_IRELAND + attendance_allowance: 0 + pip_dl: 0 + armed_forces_independence_payment: 1 + output: + receives_severe_disability_premium_qualifying_benefit: true + +- name: NI AFIP remains qualifying in the January 2017 snapshot + period: 2017 + input: + country: NORTHERN_IRELAND + armed_forces_independence_payment: 1 + output: + receives_severe_disability_premium_qualifying_benefit: true + +- name: NI PIP does not qualify in the January 2016 snapshot + period: 2016 + input: + country: NORTHERN_IRELAND + pip_dl: 1 + output: + receives_severe_disability_premium_qualifying_benefit: false + +- name: NI PIP qualifies in the January 2017 snapshot + period: 2017 + input: + country: NORTHERN_IRELAND + pip_dl: 1 + output: + receives_severe_disability_premium_qualifying_benefit: true + +- name: Contemporary NI PIP and GB AFIP qualify in separate households + period: 2026 + input: + people: + pip_recipient: + pip_dl: 1 + afip_recipient: + armed_forces_independence_payment: 1 + benunits: + pip_family: + members: [pip_recipient] + afip_family: + members: [afip_recipient] + households: + ni_household: + members: [pip_recipient] + country: NORTHERN_IRELAND + gb_household: + members: [afip_recipient] + country: ENGLAND + output: + receives_severe_disability_premium_qualifying_benefit: [true, true] diff --git a/policyengine_uk/tests/test_severe_disability_premium_properties.py b/policyengine_uk/tests/test_severe_disability_premium_properties.py index 62f8272595..5c84379bf7 100644 --- a/policyengine_uk/tests/test_severe_disability_premium_properties.py +++ b/policyengine_uk/tests/test_severe_disability_premium_properties.py @@ -44,7 +44,7 @@ from hypothesis import HealthCheck, event, example, given, settings from hypothesis import strategies as st -from policyengine_uk import Simulation +from policyengine_uk import Simulation, parameters YEAR = 2026 WEEKS = 52 @@ -57,6 +57,27 @@ suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], ) + +def test_northern_ireland_commencements_survive_parameter_processing(): + # Fiscal-year conversion must not erase these midyear commencements. + for benefit, before, commencement in ( + ("armed_forces_independence_payment", "2013-12-23", "2013-12-24"), + ("pip_dl", "2016-06-19", "2016-06-20"), + ): + assert ( + benefit + not in parameters( + before + ).gov.dwp.disability_premia.severe_qualifying_benefits_northern_ireland + ) + assert ( + benefit + in parameters( + commencement + ).gov.dwp.disability_premia.severe_qualifying_benefits_northern_ireland + ) + + # (inputs, qualifies) for each benefit a person may receive. BENEFITS = { "none": ({}, False), diff --git a/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py index aacc28f900..5c6d489a77 100644 --- a/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/has_non_dependant_for_severe_disability_premium.py @@ -24,7 +24,10 @@ class has_non_dependant_for_severe_disability_premium(Variable): "modelled. The people in section 6(2)(a) to (bb) are children aged 16 " "or 17, below the non-dependant age, so for them the omission cannot " "change the premium; the people prescribed under section 6(4) " - "(looked after in Scotland) were not checked. The model cannot " + "(looked after in Scotland) are also under 18 (S.I. 2004/747 reg " + "2(2)(a)). The care-leaver bursary and current local-authority care " + "inputs do not identify the historical care status needed for " + "section 6. The model cannot " "identify the other people the " "Regulations exclude from the definition (for example joint " "occupiers, commercial lodgers or landlords, and carers engaged by a " @@ -41,6 +44,7 @@ class has_non_dependant_for_severe_disability_premium(Variable): "https://www.legislation.gov.uk/uksi/1987/1967/regulation/14", "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", "https://www.legislation.gov.uk/ukpga/2000/35/section/6", + "https://www.legislation.gov.uk/uksi/2004/747/regulation/2", ) def formula(benunit, period, parameters): diff --git a/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py index e511685eeb..2a2ca1b08d 100644 --- a/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py +++ b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_benefits.py @@ -6,18 +6,20 @@ class is_severely_disabled_for_benefits(Variable): entity = Person label = "Severely disabled for the tax credit severe disability elements" documentation = ( - "Meets the severe disability condition of the Child Tax Credit " + "Approximates the severe disability condition of the Child Tax Credit " "disability element (for a child or qualifying young person) and the " "Working Tax Credit severe disability element (for a claimant): the " "care component of disability living allowance at the highest rate, " "the daily living component of personal independence payment at the " "enhanced rate, attendance allowance at the higher rate (a Working " - "Tax Credit condition; children cannot receive it) or armed forces " + "Tax Credit condition) or armed forces " "independence payment. Other Armed Forces Compensation Scheme " "payments do not count. The Scottish equivalents and benefit that " "would be payable but for a hospital stay are not modelled. " "Attendance allowance is read from its award category (aa_category), " - "which the Enhanced FRS holds. Only where no category is given, as " + "which the Enhanced FRS holds after its data pipeline infers it from " + "reported amounts; it is not a directly observed award status. " + "Only where no category is given, as " "when a household enters an attendance allowance amount directly, is " "the amount compared with the annual higher rate, less £10 a week; " "the Regulations have no such tolerance, so that fallback is an " diff --git a/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py index ebe5033875..8072b5a4a6 100644 --- a/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py +++ b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py @@ -21,7 +21,14 @@ class receives_severe_disability_premium_qualifying_benefit(Variable): "payment) are not separate model variables; the " "lists differ slightly between instruments (pension age disability " "payment is not in the IS and JSA single-claimant lists). The " - "hospital and concessionary-payment rules are not modelled." + "hospital and concessionary-payment rules are not modelled. " + "Household country selects the qualifying-benefit list: Northern " + "Ireland adds armed forces independence payment from 24 December " + "2013 and PIP daily living from 20 June 2016; Great Britain adds " + "both from 8 April 2013. The model uses the list at the annual " + "period's start without within-year proration. The initial parameter " + "value date preserves the shared model baseline; Northern Ireland " + "historical coverage before 20 November 2006 has not been verified." ) definition_period = YEAR reference = ( @@ -29,8 +36,18 @@ class receives_severe_disability_premium_qualifying_benefit(Variable): "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", "https://www.legislation.gov.uk/uksi/2008/794/schedule/4/paragraph/6", "https://www.legislation.gov.uk/uksi/1996/207/schedule/1/paragraph/15", + "https://www.legislation.gov.uk/nisr/2006/405/schedule/4/paragraph/14", + "https://www.legislation.gov.uk/nisr/2006/405/regulation/1/made", + "https://www.legislation.gov.uk/uksi/2013/3021/article/1", + "https://www.legislation.gov.uk/nisr/2016/228/regulation/1", ) def formula(person, period, parameters): p = parameters(period).gov.dwp.disability_premia - return add(person, period, p.severe_qualifying_benefits) > 0 + country = person.household("country", period) + northern_ireland = country == country.possible_values.NORTHERN_IRELAND + return where( + northern_ireland, + add(person, period, p.severe_qualifying_benefits_northern_ireland) > 0, + add(person, period, p.severe_qualifying_benefits) > 0, + ) diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index c62b5f09a6..6d68fdf7ce 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -26,7 +26,7 @@ class severe_disability_premium(Variable): "Fraud Act 2001 also counts (HB Regs 2006 Sch 3 para 14(7), IS Regs " "1987 Sch 2 para 13(5), ESA Regs 2008 Sch 4 para 6(8), JSA Regs 1996 " "Sch 1 para 15(9)); the model has no input for such a restriction, so " - "where one applies it pays the premium the law withholds. The " + "where one applies it can pay a premium the law withholds. The " "Jobseeker's Allowance savings for people protected by the Income " "Support (General) Amendment (No. 6) Regulations 1991 (JSA Regs 1996 " "Sch 1 para 15(8)) are not modelled; income-based Jobseeker's " From 8d0b53bec471ac02f08d03b7ac74331a1e2005a5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 23:15:36 -0400 Subject: [PATCH 7/7] Read the Northern Ireland list like the GB list; fix a link and a mis-pinned case Drop preserve_calendar_dates from the Northern Ireland qualifying list so it is read as at 30 April from 2015, as the GB list is; a bare-year reform now lands in the same model year for both. The regression test checks year values. Fix a 404 link to HB Sch 3 Part 4, and stop pinning a disability premium of nil for a PIP recipient, which only followed from an unset input. Co-Authored-By: Claude Opus 5.5 --- .../severe_qualifying_benefits.yaml | 5 +-- ..._qualifying_benefits_northern_ireland.yaml | 6 ++-- .../family/severe_disability_premium.yaml | 7 ++-- ...evere_disability_premium_commencement.yaml | 18 +++++----- ...st_severe_disability_premium_properties.py | 36 ++++++++++--------- ...e_disability_premium_qualifying_benefit.py | 5 +-- .../demographic/severe_disability_premium.py | 2 +- 7 files changed, 43 insertions(+), 36 deletions(-) diff --git a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml index c5c5104405..e95c4a9421 100644 --- a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml +++ b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits.yaml @@ -12,8 +12,9 @@ description: > independence payment. This list applies in Great Britain; the severe_qualifying_benefits_northern_ireland parameter holds Northern Ireland's separate commencement dates. The model selects the list using - household country and applies the list at the annual period's start, - without within-year proration. + household country. Like other parameters, it reads the list as at 30 April + of each year from 2015 and at 1 January before that, and does not prorate a + change made during a year. metadata: label: Legacy benefit severe disability premium qualifying benefits period: year diff --git a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits_northern_ireland.yaml b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits_northern_ireland.yaml index d26c473216..e4c3b9d8cf 100644 --- a/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits_northern_ireland.yaml +++ b/policyengine_uk/parameters/gov/dwp/disability_premia/severe_qualifying_benefits_northern_ireland.yaml @@ -7,8 +7,9 @@ description: > S.I. 2013/3021, and the personal independence payment daily living component at either rate from 20 June 2016 by S.R. 2016/228. The amendments cover the Northern Ireland Income Support, Jobseeker's Allowance, Housing Benefit and - Employment and Support Allowance regulations. The model applies the list at - the annual period's start, without within-year proration. The initial value + Employment and Support Allowance regulations. Like other parameters, the model + reads the list as at 30 April of each year from 2015 and at 1 January before + that, and does not prorate a change made during a year. The initial value date follows the shared model baseline, not the commencement of S.R. 2006/405 on 20 November 2006. Northern Ireland historical coverage before that commencement has not been verified. @@ -16,7 +17,6 @@ metadata: label: Northern Ireland legacy severe disability premium qualifying benefits period: year unit: list - preserve_calendar_dates: true reference: - title: The Housing Benefit Regulations (Northern Ireland) 2006 reg. 1(1) href: https://www.legislation.gov.uk/nisr/2006/405/regulation/1/made diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml index ea89ee3350..fffdd43830 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml @@ -523,13 +523,16 @@ # Flow-through to the applicable amounts. - name: The premium is part of the legacy benefit premiums + # Isolates the severe premium. PIP also qualifies for the disability premium + # (HB Regs 2006 Sch 3 para 13(1)(a)(i)), but its flag is_disabled_for_benefits + # is an input with no formula (the Enhanced FRS supplies it), so it is set + # false here rather than left to its default. period: 2026 absolute_error_margin: 0.01 input: age: 40 pip_dl_category: STANDARD + is_disabled_for_benefits: false output: - disability_premium: 0 - enhanced_disability_premium: 0 carer_premium: 0 benefits_premiums: 4474.60 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium_commencement.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium_commencement.yaml index 9564351306..a45e73835c 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium_commencement.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium_commencement.yaml @@ -6,7 +6,7 @@ # Pre-2015 cases supply receipt amounts directly because the AA and PIP rate # parameters begin in 2015; they test qualifying lists, not historical rates. -- name: GB PIP and AFIP do not qualify in the January 2013 snapshot +- name: GB PIP and AFIP do not qualify in model year 2013 period: 2013 input: country: ENGLAND @@ -16,7 +16,7 @@ output: receives_severe_disability_premium_qualifying_benefit: false -- name: GB PIP qualifies in the January 2014 snapshot +- name: GB PIP qualifies in model year 2014 period: 2014 input: country: ENGLAND @@ -25,7 +25,7 @@ output: receives_severe_disability_premium_qualifying_benefit: true -- name: GB AFIP qualifies in the January 2014 snapshot +- name: GB AFIP qualifies in model year 2014 period: 2014 input: country: ENGLAND @@ -35,7 +35,7 @@ output: receives_severe_disability_premium_qualifying_benefit: true -- name: NI PIP and AFIP do not qualify in the January 2013 snapshot +- name: NI PIP and AFIP do not qualify in model year 2013 period: 2013 input: country: NORTHERN_IRELAND @@ -45,7 +45,7 @@ output: receives_severe_disability_premium_qualifying_benefit: false -- name: NI PIP does not use the GB January 2014 snapshot +- name: NI PIP does not qualify in model year 2014, unlike GB period: 2014 input: country: NORTHERN_IRELAND @@ -54,7 +54,7 @@ output: receives_severe_disability_premium_qualifying_benefit: false -- name: NI AFIP qualifies in the January 2014 snapshot +- name: NI AFIP qualifies in model year 2014 period: 2014 input: country: NORTHERN_IRELAND @@ -64,7 +64,7 @@ output: receives_severe_disability_premium_qualifying_benefit: true -- name: NI AFIP remains qualifying in the January 2017 snapshot +- name: NI AFIP remains qualifying in model year 2017 period: 2017 input: country: NORTHERN_IRELAND @@ -72,7 +72,7 @@ output: receives_severe_disability_premium_qualifying_benefit: true -- name: NI PIP does not qualify in the January 2016 snapshot +- name: NI PIP does not qualify in model year 2016 period: 2016 input: country: NORTHERN_IRELAND @@ -80,7 +80,7 @@ output: receives_severe_disability_premium_qualifying_benefit: false -- name: NI PIP qualifies in the January 2017 snapshot +- name: NI PIP qualifies in model year 2017 period: 2017 input: country: NORTHERN_IRELAND diff --git a/policyengine_uk/tests/test_severe_disability_premium_properties.py b/policyengine_uk/tests/test_severe_disability_premium_properties.py index 5c84379bf7..3326a19210 100644 --- a/policyengine_uk/tests/test_severe_disability_premium_properties.py +++ b/policyengine_uk/tests/test_severe_disability_premium_properties.py @@ -58,24 +58,26 @@ ) -def test_northern_ireland_commencements_survive_parameter_processing(): - # Fiscal-year conversion must not erase these midyear commencements. - for benefit, before, commencement in ( - ("armed_forces_independence_payment", "2013-12-23", "2013-12-24"), - ("pip_dl", "2016-06-19", "2016-06-20"), +def _qualifying_list(year, name): + return getattr(parameters(str(year)).gov.dwp.disability_premia, name) + + +def test_northern_ireland_commencements_by_model_year(): + # The model reads the list as at 1 January before 2015 and 30 April from + # 2015, so AFIP (24 December 2013) first qualifies in 2014 and PIP daily + # living (20 June 2016) in 2017. The Great Britain list adds both from + # 8 April 2013, so from 2014. + ni = "severe_qualifying_benefits_northern_ireland" + for benefit, last_year_without, first_year_with in ( + ("armed_forces_independence_payment", 2013, 2014), + ("pip_dl", 2016, 2017), ): - assert ( - benefit - not in parameters( - before - ).gov.dwp.disability_premia.severe_qualifying_benefits_northern_ireland - ) - assert ( - benefit - in parameters( - commencement - ).gov.dwp.disability_premia.severe_qualifying_benefits_northern_ireland - ) + assert benefit not in _qualifying_list(last_year_without, ni) + assert benefit in _qualifying_list(first_year_with, ni) + for benefit in ("armed_forces_independence_payment", "pip_dl"): + gb = "severe_qualifying_benefits" + assert benefit not in _qualifying_list(2013, gb) + assert benefit in _qualifying_list(2014, gb) # (inputs, qualifies) for each benefit a person may receive. diff --git a/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py index 8072b5a4a6..be318a73d9 100644 --- a/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py +++ b/policyengine_uk/variables/household/demographic/receives_severe_disability_premium_qualifying_benefit.py @@ -25,8 +25,9 @@ class receives_severe_disability_premium_qualifying_benefit(Variable): "Household country selects the qualifying-benefit list: Northern " "Ireland adds armed forces independence payment from 24 December " "2013 and PIP daily living from 20 June 2016; Great Britain adds " - "both from 8 April 2013. The model uses the list at the annual " - "period's start without within-year proration. The initial parameter " + "both from 8 April 2013. The model reads each list as at 30 April " + "of the year from 2015 and at 1 January before that, without " + "within-year proration. The initial parameter " "value date preserves the shared model baseline; Northern Ireland " "historical coverage before 20 November 2006 has not been verified." ) diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index 6d68fdf7ce..4621a87000 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -36,7 +36,7 @@ class severe_disability_premium(Variable): definition_period = YEAR reference = ( "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14", - "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/20", + "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/part/4", "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/13", "https://www.legislation.gov.uk/uksi/1987/1967/schedule/2/paragraph/15", "https://www.legislation.gov.uk/uksi/2008/794/schedule/4/paragraph/6",