From b967481e40357d85c837b0724f0b796cc6419f71 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 23:14:26 -0400 Subject: [PATCH 1/5] Share one carer attribution between the legacy severe disability premium and the Pension Credit addition Both rules ask whether anyone is entitled to and in receipt of Carer's Allowance or Carer Support Payment in respect of caring for the claimant or partner (HB Regs 2006 Sch 3 para 14(2)(a)(iii) and (2)(b); SPC Regs 2002 Sch I para 1(1)). The legacy premium counted only carers in the claimant's own benefit unit; the addition also attributed carers in other benefit units of the household. Both now read is_cared_for_by_carer_benefit_recipient, a person-level attribution over the people a carer benefit can be paid for (SSCBA 1992 s.70(2)). The residence tests stay separate (HB reg 3 non-dependants with para 14(4); SPC Sch I para 2). Fixes #1972 Co-Authored-By: Claude Opus 5.5 --- changelog.d/sdp-carer-attribution.fixed.md | 1 + .../qualifying_disability_benefits.yaml | 31 ++ .../family/severe_disability_premium.yaml | 184 ++++++++++++ ...evere_disability_residence_and_carers.yaml | 31 +- ..._cared_for_by_carer_benefit_recipient.yaml | 283 ++++++++++++++++++ ...t_severe_disability_addition_properties.py | 66 +++- ...st_severe_disability_premium_properties.py | 97 ++++-- ...isability_addition_qualifiers_cared_for.py | 102 ------- ...e_disability_minimum_guarantee_addition.py | 18 +- ...is_cared_for_by_carer_benefit_recipient.py | 109 +++++++ ..._severely_disabled_for_carers_allowance.py | 27 ++ .../demographic/severe_disability_premium.py | 29 +- 12 files changed, 818 insertions(+), 160 deletions(-) create mode 100644 changelog.d/sdp-carer-attribution.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/carers_allowance/qualifying_disability_benefits.yaml create mode 100644 policyengine_uk/tests/policy/baseline/household/demographic/is_cared_for_by_carer_benefit_recipient.yaml delete mode 100644 policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/num_severe_disability_addition_qualifiers_cared_for.py create mode 100644 policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py create mode 100644 policyengine_uk/variables/household/demographic/is_severely_disabled_for_carers_allowance.py diff --git a/changelog.d/sdp-carer-attribution.fixed.md b/changelog.d/sdp-carer-attribution.fixed.md new file mode 100644 index 0000000000..ceda1cf9a4 --- /dev/null +++ b/changelog.d/sdp-carer-attribution.fixed.md @@ -0,0 +1 @@ +The legacy severe disability premium and the Pension Credit severe disability addition now share one attribution of whom each carer benefit is paid for caring for (`is_cared_for_by_carer_benefit_recipient`), so a carer in another benefit unit of the household bars the legacy premium as it already barred the addition, even when that carer is ignored as a non-dependant. diff --git a/policyengine_uk/parameters/gov/dwp/carers_allowance/qualifying_disability_benefits.yaml b/policyengine_uk/parameters/gov/dwp/carers_allowance/qualifying_disability_benefits.yaml new file mode 100644 index 0000000000..b4b95cdf9c --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/carers_allowance/qualifying_disability_benefits.yaml @@ -0,0 +1,31 @@ +description: > + Benefits whose receipt makes a person severely disabled for Carer's + Allowance, so that a carer benefit may be paid for caring for them: + attendance allowance, the care component of disability living allowance at + the highest or middle rate, the daily living component of personal + independence payment and armed forces independence payment. The Scottish + equivalents are not separate model variables, and the constant attendance + allowances prescribed by regulation 3 of the Invalid Care Allowance + Regulations are not modelled. +metadata: + label: Carer's Allowance qualifying disability benefits + period: year + unit: list + reference: + - title: Social Security Contributions and Benefits Act 1992 s. 70(2) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/70 + - title: The Personal Independence Payment (Supplementary Provisions and Consequential Amendments) Regulations 2013 Sch. para. 5 + href: https://www.legislation.gov.uk/uksi/2013/388/schedule/paragraph/5 + - title: The Armed Forces and Reserve Forces (Compensation Scheme) (Consequential Provisions - Primary Legislation) Order 2013 art. 3(2) + href: https://www.legislation.gov.uk/uksi/2013/796/article/3 + - title: The Social Security (Invalid Care Allowance) Regulations 1976 reg. 3 + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/3 +values: + 1992-07-01: + - 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/severe_disability_premium.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/severe_disability_premium.yaml index ea89ee3350..f9182746bb 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 @@ -520,6 +520,190 @@ output: severe_disability_premium: [4474.60, 0] +# Carers in other benefit units (para 14(2)(a)(iii) and (2)(b), with 14(4)). +# The carer condition is separate from the non-dependant condition: a +# non-dependant whose presence is ignored under para 14(4) can still be the +# person who receives a carer benefit for caring for the claimant. Whom each +# carer benefit is paid for is attributed by +# is_cared_for_by_carer_benefit_recipient, shared with the Pension Credit +# severe disability addition. + +- name: A non-dependant ignored for receiving a qualifying benefit still bars the premium as the claimant's carer + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + son: + age: 25 + pip_dl_category: STANDARD + receives_carer_benefit: true + benunits: + claimant_family: + members: [claimant] + son_family: + members: [son] + households: + household: + members: [claimant, son] + output: + # Each is ignored as the other's non-dependant (para 14(4)(a)). The son's + # Carer's Allowance is for caring for the claimant, so the claimant fails + # para 14(2)(a)(iii); no one is paid for caring for the son. + has_non_dependant_for_severe_disability_premium: [false, false] + is_cared_for_by_carer_benefit_recipient: [true, false] + severe_disability_premium: [0, 4474.60] + +- name: A blind non-dependant who receives a carer benefit bars the premium + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + son: + age: 25 + is_blind: true + receives_carer_benefit: true + benunits: + claimant_family: + members: [claimant] + son_family: + members: [son] + households: + household: + members: [claimant, son] + output: + # The son is ignored as a non-dependant (para 14(4)(b)) but is the + # claimant's carer. + has_non_dependant_for_severe_disability_premium: [false, false] + is_cared_for_by_carer_benefit_recipient: [true, false] + severe_disability_premium: [0, 0] + +- name: A couple who both qualify get the single rate when an ignored non-dependant is paid for caring for one of them + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + partner: + age: 48 + pip_dl_category: ENHANCED + son: + age: 25 + pip_dl_category: STANDARD + receives_carer_benefit: true + benunits: + couple: + members: [claimant, partner] + son_family: + members: [son] + households: + household: + members: [claimant, partner, son] + output: + # One award is for one person: a carer for only one of the couple leaves + # the single rate (para 14(2)(b); Sch 3 para 20(6)(b)(i)). + is_cared_for_by_carer_benefit_recipient: [true, false, false] + severe_disability_premium: [4474.60, 4474.60] + +- name: A non-dependant who receives a carer benefit and has a severely disabled partner is treated as caring for their partner + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + son: + age: 30 + pip_dl_category: STANDARD + receives_carer_benefit: true + sons_partner: + age: 28 + pip_dl_category: STANDARD + benunits: + claimant_family: + members: [claimant] + son_family: + members: [son, sons_partner] + households: + household: + members: [claimant, son, sons_partner] + output: + # A carer cares within their own benefit unit first, so the claimant has + # no carer. The son and his partner both qualify with a carer paid for + # one of them: the single rate. + is_cared_for_by_carer_benefit_recipient: [false, false, true] + severe_disability_premium: [4474.60, 4474.60] + +- name: One carer's award bars the premium of only one of two other benefit units, the one with the eldest member + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 60 + pip_dl_category: STANDARD + younger: + age: 55 + pip_dl_category: STANDARD + carer: + age: 30 + pip_dl_category: STANDARD + receives_carer_benefit: true + benunits: + older_family: + members: [older] + younger_family: + members: [younger] + carer_family: + members: [carer] + households: + household: + members: [older, younger, carer] + output: + is_cared_for_by_carer_benefit_recipient: [true, false, false] + severe_disability_premium: [0, 4474.60, 4474.60] + +- name: The legacy premium and the Pension Credit addition read the same carer attribution + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + aa_category: LOWER + working_age: + age: 45 + pip_dl_category: STANDARD + carer: + age: 40 + pip_dl_category: STANDARD + receives_carer_benefit: true + benunits: + pensioner_family: + members: [pensioner] + working_age_family: + members: [working_age] + carer_family: + members: [carer] + households: + household: + members: [pensioner, working_age, carer] + output: + # The one carer benefit is for the pensioner under both rules, so the + # working-age claimant keeps the premium and the pensioner loses the + # addition. + is_cared_for_by_carer_benefit_recipient: [true, false, false] + severe_disability_premium: [0, 4474.60, 4474.60] + severe_disability_minimum_guarantee_addition: [0, 4474.60, 4474.60] + # Flow-through to the applicable amounts. - name: The premium is part of the legacy benefit premiums diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/severe_disability_residence_and_carers.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/severe_disability_residence_and_carers.yaml index 75e75aafbd..c5473a5028 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/severe_disability_residence_and_carers.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/severe_disability_residence_and_carers.yaml @@ -4,7 +4,8 @@ # (para 1(1)(a)(iii), (b)(ii) and (c)(iv)). 2026-27 weekly rates: one rate # 86.05 (4,474.60 a year), two rates 172.10 (8,949.20 a year). # Carer benefits are attributed as documented on -# num_severe_disability_addition_qualifiers_cared_for. +# is_cared_for_by_carer_benefit_recipient, which the legacy severe disability +# premium shares. - name: A single claimant on Attendance Allowance who lives alone gets one rate period: 2026 @@ -212,7 +213,7 @@ country: ENGLAND output: receives_carer_benefit: true - num_severe_disability_addition_qualifiers_cared_for: 0 + is_cared_for_by_carer_benefit_recipient: false severe_disability_minimum_guarantee_addition: 4_474.60 - name: Carer's Allowance paid to another household member stops a single claimant's addition @@ -241,7 +242,7 @@ members: [claimant, daughter] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [1, 0] + is_cared_for_by_carer_benefit_recipient: [true, false] severe_disability_minimum_guarantee_addition: [0, 4_474.60] - name: A couple who both qualify get one rate when the claimant receives Carer's Allowance @@ -267,7 +268,7 @@ members: [claimant, partner] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: 1 + is_cared_for_by_carer_benefit_recipient: [false, true] severe_disability_minimum_guarantee_addition: 4_474.60 - name: A couple who both qualify get one rate when another household member receives Carer's Allowance @@ -296,7 +297,7 @@ members: [claimant, partner, daughter] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [1, 0] + is_cared_for_by_carer_benefit_recipient: [true, false, false] severe_disability_minimum_guarantee_addition: [4_474.60, 4_474.60] - name: A couple get nothing when carer benefits are paid for both partners @@ -329,7 +330,7 @@ members: [claimant, partner, daughter] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [2, 0] + is_cared_for_by_carer_benefit_recipient: [true, true, false] severe_disability_minimum_guarantee_addition: [0, 4_474.60] - name: A qualifying partner's own Carer's Allowance does not stop the blind-partner route @@ -356,7 +357,7 @@ members: [claimant, partner] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: 0 + is_cared_for_by_carer_benefit_recipient: [false, false] severe_disability_minimum_guarantee_addition: 4_474.60 - name: The blind-partner route also needs no other adult resident @@ -649,7 +650,7 @@ members: [younger, elder, carer] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [0, 1, 0] + is_cared_for_by_carer_benefit_recipient: [false, true, false] severe_disability_minimum_guarantee_addition: [4_474.60, 0, 0] - name: Two carers in the household cover two benefit units @@ -685,7 +686,7 @@ members: [younger, elder, carer_1, carer_2] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [1, 1, 0, 0] + is_cared_for_by_carer_benefit_recipient: [true, true, false, false] severe_disability_minimum_guarantee_addition: [0, 0, 0, 0] - name: Two single pensioners who each receive Carer's Allowance care for each other @@ -713,7 +714,7 @@ members: [first, second] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [1, 1] + is_cared_for_by_carer_benefit_recipient: [true, true] severe_disability_minimum_guarantee_addition: [0, 0] - name: A resident who has just attained 18 and is not a qualifying young person counts @@ -804,7 +805,7 @@ members: [grandmother, daughter, child] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [0, 0] + is_cared_for_by_carer_benefit_recipient: [false, false, true] severe_disability_minimum_guarantee_addition: [4_474.60, 4_474.60] - name: A second carer in a pensioner's unit is attributed to a pensioner in another unit @@ -839,7 +840,7 @@ members: [first, carer_1, carer_2, second] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [1, 1] + is_cared_for_by_carer_benefit_recipient: [true, false, false, true] severe_disability_minimum_guarantee_addition: [0, 0] - name: When two benefit units' eldest members are the same age, the one whose eldest member comes first in person order takes a carer @@ -869,7 +870,7 @@ members: [listed_first, listed_second, carer] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [1, 0, 0] + is_cared_for_by_carer_benefit_recipient: [true, false, false] severe_disability_minimum_guarantee_addition: [0, 4_474.60, 0] - name: A carer who is the only qualifying member of their own unit cares for someone in another unit @@ -903,7 +904,7 @@ members: [grandmother, father, daughter] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [1, 0] + is_cared_for_by_carer_benefit_recipient: [true, false, false] severe_disability_minimum_guarantee_addition: [0, 0] - name: The two young carers cover both pensioners when each is in their own unit too @@ -941,5 +942,5 @@ members: [first, carer_1, carer_2, second] country: ENGLAND output: - num_severe_disability_addition_qualifiers_cared_for: [1, 0, 0, 1] + is_cared_for_by_carer_benefit_recipient: [true, false, false, true] severe_disability_minimum_guarantee_addition: [0, 0, 0, 0] diff --git a/policyengine_uk/tests/policy/baseline/household/demographic/is_cared_for_by_carer_benefit_recipient.yaml b/policyengine_uk/tests/policy/baseline/household/demographic/is_cared_for_by_carer_benefit_recipient.yaml new file mode 100644 index 0000000000..c4e84b774a --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/household/demographic/is_cared_for_by_carer_benefit_recipient.yaml @@ -0,0 +1,283 @@ +# Whom a carer benefit is paid for caring for: the carer condition of the +# legacy severe disability premium (HB Regs 2006 Sch 3 para 14(2)(a)(iii) and +# (2)(b)) and of the Pension Credit severe disability addition (SPC Regs 2002 +# Sch I para 1(1)). A carer benefit is paid for caring for a severely disabled +# person (SSCBA 1992 s.70(2)), one award per carer (s.70(7)) and one carer per +# severely disabled person (s.70(7ZA)). The data do not say whom a carer cares +# for, so each award is attributed: within the carer's own benefit unit first +# (a claimant or partner, then another member), then to a claimant or partner +# of another benefit unit in the household, eldest unit first. + +- name: Attendance Allowance, DLA care at the middle rate, PIP daily living and AFIP make a person severely disabled for Carer's Allowance + period: 2026 + input: + people: + attendance_allowance: + age: 70 + aa_category: LOWER + dla_care_middle: + age: 40 + dla_sc_category: MIDDLE + dla_care_lowest: + age: 40 + dla_sc_category: LOWER + pip_daily_living: + age: 40 + pip_dl_category: STANDARD + pip_mobility_only: + age: 40 + pip_m_category: ENHANCED + afip: + age: 40 + armed_forces_independence_payment: 1_000 + none: + age: 40 + output: + is_severely_disabled_for_carers_allowance: [true, true, false, true, false, true, false] + +- name: No one is their own carer + period: 2026 + input: + age: 40 + pip_dl_category: STANDARD + receives_carer_benefit: true + output: + is_cared_for_by_carer_benefit_recipient: false + +- name: A carer benefit is not paid for caring for someone who is not severely disabled + period: 2026 + input: + people: + claimant: + age: 40 + partner: + age: 40 + receives_carer_benefit: true + benunits: + couple: + members: [claimant, partner] + output: + is_cared_for_by_carer_benefit_recipient: [false, false] + +- name: A partner who receives a carer benefit cares for the claimant + period: 2026 + input: + people: + claimant: + age: 40 + pip_dl_category: STANDARD + partner: + age: 40 + receives_carer_benefit: true + benunits: + couple: + members: [claimant, partner] + output: + is_cared_for_by_carer_benefit_recipient: [true, false] + +- name: When both partners are severely disabled and one receives a carer benefit, the other is cared for + period: 2026 + input: + people: + claimant: + age: 40 + pip_dl_category: STANDARD + receives_carer_benefit: true + partner: + age: 40 + pip_dl_category: STANDARD + benunits: + couple: + members: [claimant, partner] + output: + is_cared_for_by_carer_benefit_recipient: [false, true] + +- name: When both partners receive a carer benefit, each cares for the other + period: 2026 + input: + people: + claimant: + age: 40 + pip_dl_category: STANDARD + receives_carer_benefit: true + partner: + age: 40 + pip_dl_category: STANDARD + receives_carer_benefit: true + benunits: + couple: + members: [claimant, partner] + output: + is_cared_for_by_carer_benefit_recipient: [true, true] + +- name: One carer for two severely disabled partners cares for the first in person order + period: 2026 + input: + people: + claimant: + age: 45 + pip_dl_category: STANDARD + partner: + age: 45 + pip_dl_category: STANDARD + young_carer: + age: 17 + receives_carer_benefit: true + benunits: + family: + members: [claimant, partner, young_carer] + output: + is_cared_for_by_carer_benefit_recipient: [true, false, false] + +- name: A parent who receives a carer benefit cares for their disabled child + period: 2026 + input: + people: + parent: + age: 40 + receives_carer_benefit: true + child: + age: 10 + dla_sc_category: MIDDLE + benunits: + family: + members: [parent, child] + output: + is_cared_for_by_carer_benefit_recipient: [false, true] + +- name: A carer cares for a severely disabled partner before a disabled child + period: 2026 + input: + people: + claimant: + age: 40 + pip_dl_category: STANDARD + partner: + age: 40 + receives_carer_benefit: true + child: + age: 10 + dla_sc_category: MIDDLE + benunits: + family: + members: [claimant, partner, child] + output: + is_cared_for_by_carer_benefit_recipient: [true, false, false] + +- name: Two carers in a benefit unit care for the severely disabled partner and the disabled child + period: 2026 + input: + people: + claimant: + age: 45 + pip_dl_category: STANDARD + partner: + age: 45 + receives_carer_benefit: true + young_carer: + age: 17 + receives_carer_benefit: true + child: + age: 10 + dla_sc_category: MIDDLE + benunits: + family: + members: [claimant, partner, young_carer, child] + output: + is_cared_for_by_carer_benefit_recipient: [true, false, false, true] + +- name: A carer with no one to care for in their own benefit unit cares for a claimant in another + period: 2026 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + son: + age: 25 + receives_carer_benefit: true + benunits: + claimant_family: + members: [claimant] + son_family: + members: [son] + households: + household: + members: [claimant, son] + output: + is_cared_for_by_carer_benefit_recipient: [true, false] + +- name: A carer in another benefit unit cares for a claimant or partner, not a disabled child + period: 2026 + input: + people: + parent: + age: 40 + child: + age: 10 + dla_sc_category: MIDDLE + grandparent: + age: 70 + receives_carer_benefit: true + benunits: + family: + members: [parent, child] + grandparent_family: + members: [grandparent] + households: + household: + members: [parent, child, grandparent] + output: + # The attribution places a carer from another benefit unit only with a + # claimant or partner, so the grandparent's award is for someone outside + # the household. + is_cared_for_by_carer_benefit_recipient: [false, false, false] + +- name: One carer's award is for one person, in the benefit unit with the eldest member + period: 2026 + input: + people: + younger: + age: 55 + pip_dl_category: STANDARD + older: + age: 60 + pip_dl_category: STANDARD + carer: + age: 30 + receives_carer_benefit: true + benunits: + younger_family: + members: [younger] + older_family: + members: [older] + carer_family: + members: [carer] + households: + household: + members: [younger, older, carer] + output: + is_cared_for_by_carer_benefit_recipient: [false, true, false] + +- name: A carer in another household is not attributed + period: 2026 + input: + people: + claimant: + age: 50 + pip_dl_category: STANDARD + daughter: + age: 25 + receives_carer_benefit: true + benunits: + claimant_family: + members: [claimant] + daughter_family: + members: [daughter] + households: + claimant_household: + members: [claimant] + daughter_household: + members: [daughter] + output: + is_cared_for_by_carer_benefit_recipient: [false, false] diff --git a/policyengine_uk/tests/test_severe_disability_addition_properties.py b/policyengine_uk/tests/test_severe_disability_addition_properties.py index c03142839b..f698e9f43d 100644 --- a/policyengine_uk/tests/test_severe_disability_addition_properties.py +++ b/policyengine_uk/tests/test_severe_disability_addition_properties.py @@ -27,12 +27,28 @@ unit's addition. 5. Symmetry: swapping the drawn attributes of a couple's two partners leaves their addition unchanged. +6. Shared carer attribution, differential: the claimants and partners that + is_cared_for_by_carer_benefit_recipient marks in each benefit unit are one + of the sets the reference's person-to-person matching allows. Structural: + only a person who is severely disabled for Carer's Allowance is marked, a + household has no more people cared for than carer benefits, and a + household's only carer is never marked as caring for themselves. +7. Cross-programme, differential: the legacy severe disability premium (HB + Regs 2006 Sch 3 para 14) reads the same attribution, and its qualifying + benefits, couple, blind-partner and carer rules and rates are those of the + addition. The two residence tests differ only over 16- to 19-year-olds + (HB reg 3 non-dependants against SPC Sch I para 2(2)(f)), so in a + household with no one of that age the premium equals the addition for + every benefit unit, carers in other benefit units included. """ import numpy as np from hypothesis import HealthCheck, given, settings from hypothesis import strategies as st -from severe_disability_addition_reference import reference_rates +from severe_disability_addition_reference import ( + household_carer_assignments, + reference_rates, +) from policyengine_uk import Simulation @@ -139,6 +155,10 @@ def calc(variable): "qualifies": calc("receives_severe_disability_addition_qualifying_benefit"), "carer": calc("receives_carer_benefit"), "is_couple": calc("is_couple"), + "cared_for": calc("is_cared_for_by_carer_benefit_recipient"), + "could_be_cared_for": calc("is_severely_disabled_for_carers_allowance"), + "addition": calc("severe_disability_minimum_guarantee_addition"), + "legacy_premium": calc("severe_disability_premium"), } @@ -279,3 +299,47 @@ def test_swapping_partners_leaves_a_couples_addition_unchanged(original): result = simulate([original, swapped]) before, after = rates_by_household(result) assert before[0] == after[0] + + +@PROPERTY_SETTINGS +@given(st.lists(household(), min_size=1, max_size=6)) +def test_shared_carer_attribution_matches_the_reference_matching(households): + result = simulate(households) + cared_for = result["cared_for"].astype(bool) + # Only someone a carer benefit can be paid for is marked. + assert not np.any(cared_for & ~result["could_be_cared_for"].astype(bool)) + np.testing.assert_array_equal(result["could_be_cared_for"], result["qualifies"]) + k = 0 + for h, layout in enumerate(result["layout"]): + allowed = household_carer_assignments(reference_household(result, h)) + names = [name for unit in layout for name, _, _ in unit] + marked = {name for i, name in enumerate(names) if cared_for[k + i]} + carers = int(result["carer"][k : k + len(names)].sum()) + # Each award is for one person. + assert len(marked) <= carers, (layout, marked) + if carers == 1 and len(names) > 0: + (carer,) = [n for i, n in enumerate(names) if result["carer"][k + i]] + assert carer not in marked, (layout, marked) + for b, unit in enumerate(layout): + claimants = {name for name, role, _ in unit if role == "cp"} + assert frozenset(marked & claimants) in allowed[b], ( + layout, + marked, + allowed[b], + ) + k += len(names) + + +def no_one_aged_16_to_19(units): + return not any( + 16 <= p["age"] <= 19 + for unit in units + for p in unit["claimants"] + unit["dependants"] + ) + + +@PROPERTY_SETTINGS +@given(st.lists(household().filter(no_one_aged_16_to_19), min_size=1, max_size=6)) +def test_legacy_premium_equals_the_addition_without_young_people(households): + result = simulate(households) + np.testing.assert_allclose(result["legacy_premium"], result["addition"], atol=0.01) diff --git a/policyengine_uk/tests/test_severe_disability_premium_properties.py b/policyengine_uk/tests/test_severe_disability_premium_properties.py index d34cc8461c..00af23dc76 100644 --- a/policyengine_uk/tests/test_severe_disability_premium_properties.py +++ b/policyengine_uk/tests/test_severe_disability_premium_properties.py @@ -15,15 +15,19 @@ 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). + the carer condition is the person-to-person matching of + severe_disability_addition_reference.household_carer_assignments, the + reference the Pension Credit severe disability addition is tested against + (the model does not observe who is cared for; both rules read the one + attribution, is_cared_for_by_carer_benefit_recipient). Carers in other + benefit units of the household are attributed too, so a non-dependant + whose presence is ignored can still bar the premium as the claimant's + carer. 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 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 +4. Metamorphic: paying anyone in the household 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. @@ -34,11 +38,10 @@ 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, event, example, given, settings from hypothesis import strategies as st +from severe_disability_addition_reference import household_carer_assignments from policyengine_uk import Simulation @@ -175,9 +178,41 @@ def simulate_before_after(units, changed): return premium[: len(units)], premium[len(units) :] +def claimants_and_partners_cared_for(unit): + """Every allowed set of the family's adults a carer benefit is paid for. + + The household as the shared carer reference reads it: the family's adults + are its claimant and partner, and each other person is a benefit unit of + their own, its claimant from age 16. People are listed in simulation + order. + """ + family = family_members(unit) + people, benunits = {}, {} + benunits["family"] = {"claimant_or_partner": [], "others": []} + for i, p in enumerate(family): + role = "claimant_or_partner" if i < len(unit["adults"]) else "others" + benunits["family"][role].append(("family", i)) + people[("family", i)] = p + for j, p in enumerate(unit["others"]): + role = "claimant_or_partner" if p["age"] >= 16 else "others" + benunits[("other", j)] = {"claimant_or_partner": [], "others": []} + benunits[("other", j)][role].append(("other", j)) + people[("other", j)] = p + people = { + pid: dict( + age=p["age"], + qualifying_benefit=qualifies(p), + receives_carer_benefit=p["carer"], + ) + for pid, p in people.items() + } + allowed = household_carer_assignments({"people": people, "benunits": benunits}) + return [{i for _, i in cared_for} for cared_for in allowed["family"]] + + def oracle(unit): """The premium by the statutory rule, written independently of the model.""" - adults, family = unit["adults"], family_members(unit) + adults = unit["adults"] # 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). @@ -198,17 +233,18 @@ def oracle(unit): 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 + # Which qualifying members a carer benefit is paid for caring for. Where + # the attribution leaves a choice between partners, the premium must not + # depend on it. + premiums = set() + for cared_for in claimants_and_partners_cared_for(unit): + cared_for = len(cared_for & set(qualifying)) + if len(adults) == 2 and len(qualifying) == 2: + premiums.add((2 - cared_for) * SINGLE) + else: + premiums.add(SINGLE if cared_for == 0 else 0.0) + assert len(premiums) == 1, (unit, premiums) + return premiums.pop() def _adult(benefit, blind=False, carer=False): @@ -246,6 +282,20 @@ def _adult(benefit, blind=False, carer=False): young=[dict(_adult("none"), age=19, in_education=True, entry_age=17)], others=[], ), + # A non-dependant who receives a qualifying benefit is ignored as a + # resident (para 14(4)(a)) but, receiving a carer benefit, is the + # claimant's carer: no premium. + dict( + adults=[_adult("pip_standard")], + children=[], + others=[_adult("pip_standard", carer=True)], + ), + # The same for a couple who both qualify: the single rate. + dict( + adults=[_adult("pip_standard"), _adult("aa_lower")], + children=[], + others=[_adult("none", blind=True, carer=True)], + ), ] ) def test_premium_matches_statutory_oracle_and_range(units): @@ -272,13 +322,14 @@ def test_counted_non_dependant_removes_premium(units, age): @PROPERTY_SETTINGS -@given(st.lists(households(), min_size=1, max_size=10), st.integers(0, 2)) +@given(st.lists(households(), min_size=1, max_size=10), st.integers(0, 4)) def test_carer_benefit_never_increases_premium(units, index): def pay_carer(unit): family = family_members(unit) - i = index % len(family) - family = [dict(p, carer=p["carer"] or k == i) for k, p in enumerate(family)] - return with_family(unit, family) + everyone = family + unit["others"] + i = index % len(everyone) + paid = [dict(p, carer=p["carer"] or k == i) for k, p in enumerate(everyone)] + return dict(with_family(unit, paid[: len(family)]), others=paid[len(family) :]) before, after = simulate_before_after(units, [pay_carer(u) for u in units]) assert np.all(after <= before + 0.01) diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/num_severe_disability_addition_qualifiers_cared_for.py b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/num_severe_disability_addition_qualifiers_cared_for.py deleted file mode 100644 index da7e25d637..0000000000 --- a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/num_severe_disability_addition_qualifiers_cared_for.py +++ /dev/null @@ -1,102 +0,0 @@ -from policyengine_uk.model_api import * - - -class num_severe_disability_addition_qualifiers_cared_for(Variable): - value_type = int - entity = BenUnit - label = ( - "Qualifying claimants or partners with a carer benefit paid for caring for them" - ) - documentation = ( - "How many of the claimant and partner who receive a qualifying " - "disability benefit have someone entitled to and in receipt of " - "Carer's Allowance or Carer Support Payment in respect of caring for " - "them. The data do not say whom a carer cares for. A carer benefit " - "needs the person cared for to receive a qualifying disability " - "benefit, and each award is for one person, who has only one such " - "carer (SSCBA 1992 s.70(7) and (7ZA)), so each carer is attributed as " - "follows. No one is their own carer. A carer cares for someone in " - "their own benefit unit who receives a qualifying benefit if there is " - "one: a qualifying claimant or partner first, then another member such " - "as a disabled child, each such person having one carer. Every other " - "carer in the household, including one left over in their own unit, " - "is allocated to a qualifying claimant or partner not yet cared for in " - "another benefit unit, one each, taking the units in descending order " - "of their eldest member's age (ties in person order, the order people " - "are listed in the input or dataset). Carers outside the household, " - "and Universal Credit awards that include the carer element, are not " - "counted. Reading Universal Credit here would be circular, because the " - "Universal Credit non-dependant deduction exemption reads Pension " - "Credit; a Universal Credit carer in the household already stops the " - "addition through the residence condition unless their presence is " - "ignored. Receipt is receives_carer_benefit, so it inherits " - "carers_allowance, which does not apply the overlapping-benefit rule " - "that stops Carer's Allowance being paid alongside a higher State " - "Pension." - ) - definition_period = YEAR - reference = ( - "https://www.legislation.gov.uk/uksi/2002/1792/schedule/I/paragraph/1", - "https://www.legislation.gov.uk/uksi/2002/1792/regulation/6", - "https://www.legislation.gov.uk/ukpga/1992/4/section/70", - ) - - def formula(benunit, period, parameters): - person = benunit.members - claimant_or_partner = person("is_claimant_or_partner", period) - receives = person( - "receives_severe_disability_addition_qualifying_benefit", period - ) - qualifies = claimant_or_partner & receives - dependant_receives = ~claimant_or_partner & receives - carer = person("receives_carer_benefit", period) - qualifying = benunit.sum(qualifies) - receiving_dependants = benunit.sum(dependant_receives) - # People in the carer's own unit the carer could be caring for. - qualifiers_in_own_unit = benunit.project(qualifying) - qualifies - dependants_in_own_unit = ( - benunit.project(receiving_dependants) - dependant_receives - ) - cares_for_a_qualifier = carer & (qualifiers_in_own_unit > 0) - cares_within_unit = carer & ( - (qualifiers_in_own_unit + dependants_in_own_unit) > 0 - ) - # Qualifying claimants and partners first, one carer each. - cared_for_within = min_(qualifying, benunit.sum(cares_for_a_qualifier)) - not_yet_cared_for = qualifying - cared_for_within - placed_within = min_( - benunit.sum(cares_within_unit), qualifying + receiving_dependants - ) - # Every other carer benefit is for someone in another unit. - outside_here = benunit.sum(carer) - placed_within - outside_members = where( - person.get_rank(person.benunit, -person("age", period)) == 0, - benunit.project(outside_here), - 0, - ) - outside_in_household = benunit.max(person.household.sum(outside_members)) - # Order the household's units by their eldest member (ties by person order) - # and give each unit, in turn, carers from the other units. - age = person("age", period) - representative = person.get_rank(person.benunit, -age) == 0 - order = person.get_rank(person.household, -age, condition=representative) - unit_order = benunit.max(where(representative, order, -1)) - allocated_in_household = np.zeros_like(outside_here) - cared_for_from_other_units = np.zeros_like(outside_here) - for rank in range(int(np.max(unit_order, initial=-1)) + 1): - available = max_( - 0, - min_( - outside_in_household - outside_here, - outside_in_household - allocated_in_household, - ), - ) - allocated = where(unit_order == rank, min_(not_yet_cared_for, available), 0) - cared_for_from_other_units = cared_for_from_other_units + allocated - allocated_by_representative = where( - representative, benunit.project(allocated), 0 - ) - allocated_in_household = allocated_in_household + benunit.max( - person.household.sum(allocated_by_representative) - ) - return cared_for_within + cared_for_from_other_units diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/severe_disability_minimum_guarantee_addition.py b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/severe_disability_minimum_guarantee_addition.py index 9e8d299a53..56cf7b0bca 100644 --- a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/severe_disability_minimum_guarantee_addition.py +++ b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/severe_disability_minimum_guarantee_addition.py @@ -23,10 +23,11 @@ class severe_disability_minimum_guarantee_addition(Variable): "either partner, to qualify where the other partner is blind. Not " "modelled: the hospital-patient deeming (para 1(2)(b)-(bd)) " "and backdating (para 1(2)(a), (c)); sight regained within 28 weeks " - "(para 1(3)); polygamous marriages; and the unmodelled para 2 and 3 " - "exceptions and carer attribution described on " - "is_counted_resident_for_severe_disability_addition and " - "num_severe_disability_addition_qualifiers_cared_for. Blindness " + "(para 1(3)); polygamous marriages; and the para 2 and 3 exceptions " + "listed on is_counted_resident_for_severe_disability_addition. Whom " + "a carer benefit is paid for caring for is attributed by " + "is_cared_for_by_carer_benefit_recipient, which the legacy severe " + "disability premium shares. Blindness " "(is_blind) is not in the survey data, so in microsimulation the " "blind-partner route never applies and a blind resident is counted." ) @@ -54,8 +55,13 @@ def formula(benunit, period, parameters): other_partner_blind = benunit.any( claimant_or_partner & ~qualifies & person("is_blind", period) ) - cared_for = benunit( - "num_severe_disability_addition_qualifiers_cared_for", period + # How many qualifying claimants and partners someone receives a carer + # benefit for caring for (the attribution is shared with the legacy + # severe disability premium). + cared_for = benunit.sum( + claimant_or_partner + & qualifies + & person("is_cared_for_by_carer_benefit_recipient", period) ) # Para 1(1)(b) with reg 6(5): two rates, less one for each partner a # carer benefit is paid for (none if paid for both). Para 1(1)(c): one diff --git a/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py new file mode 100644 index 0000000000..289e4777e8 --- /dev/null +++ b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py @@ -0,0 +1,109 @@ +from policyengine_uk.model_api import * + + +class is_cared_for_by_carer_benefit_recipient(Variable): + value_type = bool + entity = Person + label = "Someone receives a carer benefit for caring for this person" + documentation = ( + "Someone is entitled to and in receipt of Carer's Allowance or Carer " + "Support Payment in respect of caring for this person: the carer " + "condition of the legacy severe disability premium (HB Regs 2006 Sch 3 " + "para 14(2)(a)(iii) and (2)(b); IS, ESA and JSA alike) and of the " + "Pension Credit severe disability addition (SPC Regs 2002 Sch I para " + "1(1)). The data say who receives a carer benefit but not whom they " + "care for, so each award is attributed. A carer benefit is paid for " + "caring for a person who is severely disabled for Carer's Allowance " + "(is_severely_disabled_for_carers_allowance), a carer has one award " + "(SSCBA 1992 s.70(7)), and only one person is paid for caring for the " + "same severely disabled person (s.70(7ZA); both rules were in s.70(7) " + "before 16 November 2023). No one is their own carer. A carer cares " + "for someone in their own benefit unit if they can: a claimant or " + "partner first, then another member such as a disabled child. Every " + "other carer in the household, including one left over in their own " + "unit, cares for a claimant or partner not yet cared for in another " + "benefit unit, taking the units in descending order of their eldest " + "member's age (ties in person order, the order people are listed in " + "the input or dataset). Where only one of two people can have a " + "carer, it is one who is not a carer themselves, then the first in " + "person order; no count depends on that choice. Carers outside the " + "household are not observed. A Universal Credit award that includes " + "the carer element also counts in law but is not attributed here: " + "reading Universal Credit would be circular, because the Universal " + "Credit non-dependant deduction exemption reads Pension Credit. Such a " + "carer living in the household already bars both the premium and the " + "addition through their residence conditions unless their presence " + "is ignored. Receipt is receives_carer_benefit, so it inherits " + "carers_allowance, which does not apply the overlapping-benefit rule " + "that stops Carer's Allowance being paid alongside a higher State " + "Pension." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/ukpga/1992/4/section/70", + "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14", + "https://www.legislation.gov.uk/uksi/2002/1792/schedule/I/paragraph/1", + "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): + claimant_or_partner = person("is_claimant_or_partner", period) + could_be_cared_for = person("is_severely_disabled_for_carers_allowance", period) + carer = person("receives_carer_benefit", period) + age = person("age", period) + # Who takes a carer when not everyone can: someone who is not a carer + # themselves, then person order (get_rank breaks ties by position). + priority = carer.astype(int) + + # Within the benefit unit: claimants and partners, then other members. + cp_target = claimant_or_partner & could_be_cared_for + cp_targets = person.benunit.sum(cp_target) + targets = person.benunit.sum(could_be_cared_for) + can_care_for_cp = carer & (cp_targets - cp_target > 0) + can_care_within = carer & (targets - could_be_cared_for > 0) + cp_cared_for_within = min_(cp_targets, person.benunit.sum(can_care_for_cp)) + cared_for_within = min_(targets, person.benunit.sum(can_care_within)) + cp_within = cp_target & ( + person.get_rank(person.benunit, priority, condition=cp_target) + < cp_cared_for_within + ) + other_target = could_be_cared_for & ~claimant_or_partner + other_within = other_target & ( + person.get_rank(person.benunit, priority, condition=other_target) + < cared_for_within - cp_cared_for_within + ) + + # Every carer not placed in their own unit cares for a claimant or + # partner of another unit. Units take them in descending order of + # their eldest member's age, each taking as many as it needs from the + # carers of other units that are not yet placed. + left_over = person.benunit.sum(carer) - cared_for_within + eldest = person.get_rank(person.benunit, -age) == 0 + left_over_in_household = person.household.sum(where(eldest, left_over, 0)) + needed = cp_targets - cp_cared_for_within + unit_order = person.benunit.max( + where(eldest, person.get_rank(person.household, -age, condition=eldest), -1) + ) + from_other_units = np.zeros_like(needed) + placed_in_household = np.zeros_like(needed) + for rank in range(int(np.max(unit_order, initial=-1)) + 1): + available = max_( + 0, + min_( + left_over_in_household - left_over, + left_over_in_household - placed_in_household, + ), + ) + taken = where(unit_order == rank, min_(needed, available), 0) + from_other_units = from_other_units + taken + placed_in_household = placed_in_household + person.household.sum( + where(eldest, taken, 0) + ) + cp_not_yet = cp_target & ~cp_within + cp_from_other_units = cp_not_yet & ( + person.get_rank(person.benunit, priority, condition=cp_not_yet) + < from_other_units + ) + return cp_within | other_within | cp_from_other_units diff --git a/policyengine_uk/variables/household/demographic/is_severely_disabled_for_carers_allowance.py b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_carers_allowance.py new file mode 100644 index 0000000000..8d70f6a67c --- /dev/null +++ b/policyengine_uk/variables/household/demographic/is_severely_disabled_for_carers_allowance.py @@ -0,0 +1,27 @@ +from policyengine_uk.model_api import * + + +class is_severely_disabled_for_carers_allowance(Variable): + value_type = bool + entity = Person + label = "Severely disabled person for Carer's Allowance" + documentation = ( + "A severely disabled person within SSCBA 1992 s.70(2): one for whom " + "attendance allowance, the care component of disability living " + "allowance at the highest or middle rate, the daily living component " + "of personal independence payment, or armed forces independence " + "payment is payable. A carer benefit can only be paid for caring for " + "such a person. The Scottish equivalents, which Carer Support Payment " + "also accepts, are not separate model variables, and the constant " + "attendance allowances prescribed by the Invalid Care Allowance " + "Regulations 1976 reg 3 are not modelled." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/ukpga/1992/4/section/70", + "https://www.legislation.gov.uk/uksi/1976/409/regulation/3", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.dwp.carers_allowance + return add(person, period, p.qualifying_disability_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 cc54020a5d..fa173834ab 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -19,8 +19,11 @@ class severe_disability_premium(Variable): "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." + "paid for both. Whom a carer benefit is paid for caring for is " + "attributed by is_cared_for_by_carer_benefit_recipient, shared with " + "the Pension Credit severe disability addition; the law also counts " + "a Universal Credit award with the carer element, which the model " + "does not." ) definition_period = YEAR reference = ( @@ -34,12 +37,11 @@ class severe_disability_premium(Variable): unit = GBP 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 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. + # Whom a carer benefit is paid for caring for is attributed by + # is_cared_for_by_carer_benefit_recipient, which the Pension Credit + # severe disability addition shares. 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) @@ -51,9 +53,10 @@ def formula(benunit, period, parameters): 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) + cared_for = benunit.sum( + qualifying_member + & person("is_cared_for_by_carer_benefit_recipient", period) + ) # 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 @@ -63,10 +66,10 @@ def formula(benunit, period, parameters): couple, (qualifying == 1) & partner_treated_as_absent, qualifying >= 1, - ) & (carers_for_qualifying_member == 0) + ) & (cared_for == 0) rates = where( couple & (qualifying >= 2), - max_(2 - carers, 0), + max_(2 - cared_for, 0), single_condition, ) weekly_amount = select( From 1c30130b05dc33b6dc92f18d274ba4dc2092c84b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 23:31:58 -0400 Subject: [PATCH 2/5] Address review: cite s.70(7ZA)-(7ZC), test other members and three flags, pin the qualifying lists - The attribution docstring says an unplaced award is for someone outside the household, and notes the s.70(1A) run-on. - The person-level property now checks other members (such as disabled children) against the reference's within-unit matching. - YAML cases for three claimant-or-partner flags. - A test that the Carer's Allowance, legacy premium and Pension Credit qualifying lists are the same model variables from 8 April 2013. Co-Authored-By: Claude Opus 5.5 --- .../qualifying_disability_benefits.yaml | 2 +- ...evere_disability_residence_and_carers.yaml | 1 + ..._cared_for_by_carer_benefit_recipient.yaml | 80 ++++++++++++++++++- ...t_severe_disability_addition_properties.py | 45 ++++++++++- ...is_cared_for_by_carer_benefit_recipient.py | 28 ++++--- 5 files changed, 138 insertions(+), 18 deletions(-) diff --git a/policyengine_uk/parameters/gov/dwp/carers_allowance/qualifying_disability_benefits.yaml b/policyengine_uk/parameters/gov/dwp/carers_allowance/qualifying_disability_benefits.yaml index b4b95cdf9c..19c81815df 100644 --- a/policyengine_uk/parameters/gov/dwp/carers_allowance/qualifying_disability_benefits.yaml +++ b/policyengine_uk/parameters/gov/dwp/carers_allowance/qualifying_disability_benefits.yaml @@ -16,7 +16,7 @@ metadata: href: https://www.legislation.gov.uk/ukpga/1992/4/section/70 - title: The Personal Independence Payment (Supplementary Provisions and Consequential Amendments) Regulations 2013 Sch. para. 5 href: https://www.legislation.gov.uk/uksi/2013/388/schedule/paragraph/5 - - title: The Armed Forces and Reserve Forces (Compensation Scheme) (Consequential Provisions - Primary Legislation) Order 2013 art. 3(2) + - title: "The Armed Forces and Reserve Forces (Compensation Scheme) (Consequential Provisions: Primary Legislation) Order 2013 art. 3(2)" href: https://www.legislation.gov.uk/uksi/2013/796/article/3 - title: The Social Security (Invalid Care Allowance) Regulations 1976 reg. 3 href: https://www.legislation.gov.uk/uksi/1976/409/regulation/3 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/severe_disability_residence_and_carers.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/severe_disability_residence_and_carers.yaml index c5473a5028..e10994ac62 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/severe_disability_residence_and_carers.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/severe_disability_residence_and_carers.yaml @@ -773,6 +773,7 @@ members: [first, second, third] country: ENGLAND output: + is_cared_for_by_carer_benefit_recipient: [true, true, true] severe_disability_minimum_guarantee_addition: 0 - name: A carer whose own disabled child qualifies is treated as caring for the child diff --git a/policyengine_uk/tests/policy/baseline/household/demographic/is_cared_for_by_carer_benefit_recipient.yaml b/policyengine_uk/tests/policy/baseline/household/demographic/is_cared_for_by_carer_benefit_recipient.yaml index c4e84b774a..2b067dd810 100644 --- a/policyengine_uk/tests/policy/baseline/household/demographic/is_cared_for_by_carer_benefit_recipient.yaml +++ b/policyengine_uk/tests/policy/baseline/household/demographic/is_cared_for_by_carer_benefit_recipient.yaml @@ -3,7 +3,7 @@ # (2)(b)) and of the Pension Credit severe disability addition (SPC Regs 2002 # Sch I para 1(1)). A carer benefit is paid for caring for a severely disabled # person (SSCBA 1992 s.70(2)), one award per carer (s.70(7)) and one carer per -# severely disabled person (s.70(7ZA)). The data do not say whom a carer cares +# severely disabled person (s.70(7ZA) to (7ZC)). The data do not say whom a carer cares # for, so each award is attributed: within the carer's own benefit unit first # (a claimant or partner, then another member), then to a claimant or partner # of another benefit unit in the household, eldest unit first. @@ -281,3 +281,81 @@ members: [daughter] output: is_cared_for_by_carer_benefit_recipient: [false, false] + +- name: Three members flagged as claimant or partner, each a carer, each care for another + period: 2026 + input: + people: + first: + age: 75 + aa_category: HIGHER + receives_carer_benefit: true + is_claimant_or_partner: true + second: + age: 74 + aa_category: HIGHER + receives_carer_benefit: true + is_claimant_or_partner: true + third: + age: 73 + aa_category: HIGHER + receives_carer_benefit: true + is_claimant_or_partner: true + benunits: + benunit: + members: [first, second, third] + output: + is_cared_for_by_carer_benefit_recipient: [true, true, true] + +- name: Three members flagged as claimant or partner with one carer in another benefit unit, who cares for the first in person order + period: 2026 + input: + people: + first: + age: 75 + aa_category: HIGHER + is_claimant_or_partner: true + second: + age: 74 + aa_category: HIGHER + is_claimant_or_partner: true + third: + age: 73 + aa_category: HIGHER + is_claimant_or_partner: true + carer: + age: 40 + receives_carer_benefit: true + benunits: + benunit: + members: [first, second, third] + carer_family: + members: [carer] + households: + household: + members: [first, second, third, carer] + output: + is_cared_for_by_carer_benefit_recipient: [true, false, false, false] + +- name: Three members flagged as claimant or partner, one of them the only carer, who cares for the first other one + period: 2026 + input: + people: + first: + age: 75 + aa_category: HIGHER + receives_carer_benefit: true + is_claimant_or_partner: true + second: + age: 74 + aa_category: HIGHER + is_claimant_or_partner: true + third: + age: 73 + aa_category: HIGHER + is_claimant_or_partner: true + benunits: + benunit: + members: [first, second, third] + output: + is_cared_for_by_carer_benefit_recipient: [false, true, false] diff --git a/policyengine_uk/tests/test_severe_disability_addition_properties.py b/policyengine_uk/tests/test_severe_disability_addition_properties.py index f698e9f43d..86c2659998 100644 --- a/policyengine_uk/tests/test_severe_disability_addition_properties.py +++ b/policyengine_uk/tests/test_severe_disability_addition_properties.py @@ -29,10 +29,12 @@ their addition unchanged. 6. Shared carer attribution, differential: the claimants and partners that is_cared_for_by_carer_benefit_recipient marks in each benefit unit are one - of the sets the reference's person-to-person matching allows. Structural: - only a person who is severely disabled for Carer's Allowance is marked, a - household has no more people cared for than carer benefits, and a - household's only carer is never marked as caring for themselves. + of the sets the reference's person-to-person matching allows, and the + number of other members marked (such as disabled children) is the number + the reference's within-unit matching covers. Structural: only a person + who is severely disabled for Carer's Allowance is marked, a household has + no more people cared for than carer benefits, and a household's only + carer is never marked as caring for themselves. 7. Cross-programme, differential: the legacy severe disability premium (HB Regs 2006 Sch 3 para 14) reads the same attribution, and its qualifying benefits, couple, blind-partner and carer rules and rates are those of the @@ -46,6 +48,7 @@ from hypothesis import HealthCheck, given, settings from hypothesis import strategies as st from severe_disability_addition_reference import ( + _best_within_unit, household_carer_assignments, reference_rates, ) @@ -320,6 +323,7 @@ def test_shared_carer_attribution_matches_the_reference_matching(households): if carers == 1 and len(names) > 0: (carer,) = [n for i, n in enumerate(names) if result["carer"][k + i]] assert carer not in marked, (layout, marked) + reference = reference_household(result, h) for b, unit in enumerate(layout): claimants = {name for name, role, _ in unit if role == "cp"} assert frozenset(marked & claimants) in allowed[b], ( @@ -327,6 +331,20 @@ def test_shared_carer_attribution_matches_the_reference_matching(households): marked, allowed[b], ) + # Other members (such as a disabled child) are cared for only by + # carers of their own unit, after its claimants and partners. + others = {name for name, role, _ in unit if role != "cp"} + unit_carers = sum( + reference["people"][n]["receives_carer_benefit"] + for n in claimants | others + ) + within_cp, left_over = _best_within_unit( + reference["people"], reference["benunits"][b] + ) + assert len(marked & others) == unit_carers - left_over - within_cp, ( + layout, + marked, + ) k += len(names) @@ -343,3 +361,22 @@ def no_one_aged_16_to_19(units): def test_legacy_premium_equals_the_addition_without_young_people(households): result = simulate(households) np.testing.assert_allclose(result["legacy_premium"], result["addition"], atol=0.01) + + +def test_the_three_qualifying_benefit_lists_agree(): + """The attribution's candidates are the Carer's Allowance list (SSCBA 1992 + s.70(2)); the premium and the addition each count their own qualifying + claimants and partners among those marked. In the model's variables the + three lists are the same from 8 April 2013, when PIP and AFIP joined + s.70(2), which is what makes the two programmes' cared-for counts agree.""" + from policyengine_uk import CountryTaxBenefitSystem + + parameters = CountryTaxBenefitSystem().parameters + for instant in ("2013-04-08", "2020-01-01", "2026-01-01", "2030-01-01"): + dwp = parameters(instant).gov.dwp + carers_allowance = set(dwp.carers_allowance.qualifying_disability_benefits) + legacy = set(dwp.disability_premia.severe_qualifying_benefits) + pension_credit = set( + dwp.pension_credit.guarantee_credit.severe_disability.relevant_benefits + ) + assert carers_allowance == legacy == pension_credit, instant diff --git a/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py index 289e4777e8..bad997eecd 100644 --- a/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py +++ b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py @@ -16,18 +16,22 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "caring for a person who is severely disabled for Carer's Allowance " "(is_severely_disabled_for_carers_allowance), a carer has one award " "(SSCBA 1992 s.70(7)), and only one person is paid for caring for the " - "same severely disabled person (s.70(7ZA); both rules were in s.70(7) " - "before 16 November 2023). No one is their own carer. A carer cares " - "for someone in their own benefit unit if they can: a claimant or " - "partner first, then another member such as a disabled child. Every " - "other carer in the household, including one left over in their own " - "unit, cares for a claimant or partner not yet cared for in another " - "benefit unit, taking the units in descending order of their eldest " - "member's age (ties in person order, the order people are listed in " - "the input or dataset). Where only one of two people can have a " - "carer, it is one who is not a carer themselves, then the first in " - "person order; no count depends on that choice. Carers outside the " - "household are not observed. A Universal Credit award that includes " + "same severely disabled person (s.70(7ZA) to (7ZC); both rules were " + "in s.70(7) before 16 November 2023). No one is their own carer. A " + "carer cares for someone in their own benefit unit if they can: a " + "claimant or partner first, then another member such as a disabled " + "child. Every other carer in the household, including one left over " + "in their own unit, cares for a claimant or partner not yet cared for " + "in another benefit unit if there is one, taking the units in " + "descending order of their eldest member's age (ties in person order, " + "the order people are listed in the input or dataset); otherwise the " + "award is taken to be for someone outside the household. Where not " + "everyone can have a carer, those who are not carers themselves come " + "first, then person order; the number cared for in a benefit unit " + "does not depend on that choice. Carers outside the household are not " + "observed, and nor is a carer whose allowance runs on for up to eight " + "weeks after the person they cared for has died (s.70(1A)). A " + "Universal Credit award that includes " "the carer element also counts in law but is not attributed here: " "reading Universal Credit would be circular, because the Universal " "Credit non-dependant deduction exemption reads Pension Credit. Such a " From 7af8b302f48efa9b16c2d9468a868a02faafc941 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 23:23:38 -0400 Subject: [PATCH 3/5] Say that claimant-first carer attribution can withhold a premium The data do not say whom a carer cares for. Putting claimants and partners first, and placing left-over carers only with claimants and partners of other units, withholds the premium or Pension Credit addition where the carer in fact cares for a disabled child. Co-Authored-By: Claude Opus 5.5 --- .../is_cared_for_by_carer_benefit_recipient.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py index bad997eecd..a9e510f4d1 100644 --- a/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py +++ b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py @@ -25,7 +25,13 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "in another benefit unit if there is one, taking the units in " "descending order of their eldest member's age (ties in person order, " "the order people are listed in the input or dataset); otherwise the " - "award is taken to be for someone outside the household. Where not " + "award is taken to be for someone outside the household. The data " + "cannot say whether a carer cares for a claimant or partner or for " + "another disabled member, such as a child, so putting claimants and " + "partners first, and placing left-over carers only with claimants and " + "partners of other units, is a choice: where the carer in fact cares " + "for the child, it withholds a premium or addition the law would pay. " + "Where not " "everyone can have a carer, those who are not carers themselves come " "first, then person order; the number cared for in a benefit unit " "does not depend on that choice. Carers outside the household are not " From 016a6069e92996b94341a36f984c344ed1b5f942 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 09:08:18 -0400 Subject: [PATCH 4/5] Say the attribution can also cut a couple's premium to the single rate Co-Authored-By: Claude Opus 5.5 --- .../is_cared_for_by_carer_benefit_recipient.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py index a9e510f4d1..f321616545 100644 --- a/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py +++ b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py @@ -30,15 +30,16 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "another disabled member, such as a child, so putting claimants and " "partners first, and placing left-over carers only with claimants and " "partners of other units, is a choice: where the carer in fact cares " - "for the child, it withholds a premium or addition the law would pay. " - "Where not " + "for the child and the claimant or partner otherwise qualifies, it " + "withholds a premium or addition the law would pay, or for a couple " + "pays the single rate instead of the double rate. Where not " "everyone can have a carer, those who are not carers themselves come " "first, then person order; the number cared for in a benefit unit " "does not depend on that choice. Carers outside the household are not " "observed, and nor is a carer whose allowance runs on for up to eight " "weeks after the person they cared for has died (s.70(1A)). A " - "Universal Credit award that includes " - "the carer element also counts in law but is not attributed here: " + "Universal Credit award that includes the carer element also counts " + "in law but is not attributed here: " "reading Universal Credit would be circular, because the Universal " "Credit non-dependant deduction exemption reads Pension Credit. Such a " "carer living in the household already bars both the premium and the " From ab929c4833db89504362b420c56bc87c78631196 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 15:26:17 -0400 Subject: [PATCH 5/5] Say that carer receipt is after the overlapping-benefit adjustment Main's #1994 reduces Carer's Allowance and Carer Support Payment by overlapping benefits, so receives_carer_benefit is already the amount in payment. The documentation still said the rule was not applied. Co-Authored-By: Claude Opus 5.5 --- .../is_cared_for_by_carer_benefit_recipient.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py index f321616545..6772dfa69b 100644 --- a/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py +++ b/policyengine_uk/variables/household/demographic/is_cared_for_by_carer_benefit_recipient.py @@ -44,10 +44,11 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "Credit non-dependant deduction exemption reads Pension Credit. Such a " "carer living in the household already bars both the premium and the " "addition through their residence conditions unless their presence " - "is ignored. Receipt is receives_carer_benefit, so it inherits " - "carers_allowance, which does not apply the overlapping-benefit rule " - "that stops Carer's Allowance being paid alongside a higher State " - "Pension." + "is ignored. Receipt is receives_carer_benefit: a positive Carer's " + "Allowance or Carer Support Payment after the overlapping-benefit " + "adjustment. A carer whose award an overlapping benefit, such as a " + "higher State Pension, reduces to nil is entitled but not in receipt, " + "so is not attributed." ) definition_period = YEAR reference = (