From 196fea803bceb68354f25deb708f48dc6e3a8b87 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 31 Oct 2026 20:26:15 -0400 Subject: [PATCH 01/21] Count Universal Credit carer-element awards in the severe disability carer test and Scottish working-age CTR carer premium The severe disability premium, the pension-age premium and the Pension Credit severe disability addition now treat a person as cared for when a carer's Universal Credit award includes the carer element, read from the claim and the award's conditions (has_uc_award_with_carer_element) because the award itself depends on Pension Credit. Scotland's working-age council tax reduction pays the carer premium for an award of Universal Credit which includes the carer element (SSI 2021/249 Sch 1 para 6(1)(c)) from 1 April 2022, through council_tax_reduction_carer_premium. Co-Authored-By: Claude Opus 5.5 --- ...carer_premium_counts_uc_carer_element.yaml | 15 ++++ ...e_disability_minimum_guarantee_addition.py | 4 +- .../has_uc_award_with_carer_element.py | 46 ++++++++++++ .../council_tax_reduction_carer_premium.py | 73 +++++++++++++++++++ .../council_tax_reduction_premiums.py | 16 +++- .../household/demographic/carer_premium.py | 11 ++- ...is_cared_for_by_carer_benefit_recipient.py | 67 +++++++++++------ .../pension_age_severe_disability_premium.py | 5 +- .../demographic/severe_disability_premium.py | 8 +- 9 files changed, 214 insertions(+), 31 deletions(-) create mode 100644 policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/carer_premium_counts_uc_carer_element.yaml create mode 100644 policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py create mode 100644 policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/carer_premium_counts_uc_carer_element.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/carer_premium_counts_uc_carer_element.yaml new file mode 100644 index 0000000000..ad47b6bfb2 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/carer_premium_counts_uc_carer_element.yaml @@ -0,0 +1,15 @@ +description: Scotland's working-age Council Tax Reduction scheme pays the carer premium to an applicant or partner whose award of Universal Credit includes the carer element if this is true, and only on entitlement to Carer's Allowance or Carer Support Payment otherwise. +values: + 2013-04-01: false + 2022-04-01: true +metadata: + unit: bool + period: year + label: Scottish working-age Council Tax Reduction carer premium counts the Universal Credit carer element + reference: + - title: The Council Tax Reduction (Scotland) Regulations 2021 Sch. 1 para. 6(1)(c) + href: https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/6 + - title: The Council Tax Reduction (Scotland) Regulations 2021 reg. 1 (in force 1 April 2022) + href: https://www.legislation.gov.uk/ssi/2021/249/regulation/1 + - title: The Council Tax Reduction (Scotland) Regulations 2012 Sch. 1 para. 14 (persons under pension age, to 31 March 2022) + href: https://www.legislation.gov.uk/ssi/2012/303/schedule/1/paragraph/14/2021-04-01 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 56cf7b0bca..cb7369c9ef 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 @@ -27,7 +27,9 @@ class severe_disability_minimum_guarantee_addition(Variable): "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 " + "disability premium shares; a carer benefit there includes an award " + "of Universal Credit which includes the carer element (para 1(4)), " + "read from the award's conditions rather than its amount. Blindness " "(is_blind) is not in the survey data, so in microsimulation the " "blind-partner route never applies and a blind resident is counted." ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py new file mode 100644 index 0000000000..e442d0cb7b --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py @@ -0,0 +1,46 @@ +from policyengine_uk.model_api import * + + +class has_uc_award_with_carer_element(Variable): + value_type = bool + entity = Person + label = "has a Universal Credit award with the carer element, read before the award" + documentation = ( + "Whether this person has an award of Universal Credit which includes " + "the carer element (Universal Credit Regulations 2013 reg 29) in " + "respect of them, read from the conditions of the award rather than " + "its amount: the family claims Universal Credit " + "(claims_universal_credit), meets the age, pension-route and capital " + "conditions (is_uc_eligible), and the award includes the carer " + "element in respect of this claimant " + "(uc_carer_element_included_for_claimant). It is the carer condition " + "of the legacy severe disability premium (HB Regs 2006 Sch 3 para " + "14(2)(a)(iii) and (b)) and of the Pension Credit severe disability " + "addition (SPC Regs 2002 Sch I para 1(1) and (4)), through " + "is_cared_for_by_carer_benefit_recipient. Those cannot read the award " + "itself: the housing costs element's non-dependant deduction reads " + "Pension Credit, which reads the addition. A claimant whose income " + "tapers the award to nil fails the financial conditions (Welfare " + "Reform Act 2012 s.5(1)(b) and (2)(b); the minimum is one penny, reg " + "17), so has no award in law, but still counts here. So does a family " + "that forgoes Universal Credit for Tax-Free Childcare " + "(forgoes_universal_credit_for_tax_free_childcare reads the award). " + "receives_uc_carer_element is the same test on a positive award, for " + "readers that can read it." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2013/376/regulation/29", + "https://www.legislation.gov.uk/ukpga/2012/5/section/5", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/17", + "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/14", + "https://www.legislation.gov.uk/uksi/2002/1792/schedule/I/paragraph/1", + ) + + def formula(person, period, parameters): + benunit = person.benunit + on_claim = benunit("claims_universal_credit", period) & benunit( + "is_uc_eligible", period + ) + included = person("uc_carer_element_included_for_claimant", period) + return on_claim & included diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py new file mode 100644 index 0000000000..5579a2b6af --- /dev/null +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py @@ -0,0 +1,73 @@ +from policyengine_uk.model_api import * +from policyengine_uk.variables.household.demographic.country import Country + + +class council_tax_reduction_carer_premium(Variable): + value_type = float + entity = BenUnit + label = "Working-age CTR carer premium" + documentation = ( + "The carer premium in a working-age Council Tax Reduction applicable " + "amount. Outside Scotland's working-age scheme it is carer_premium. " + "Scotland's scheme (SSI 2021/249 Sch 1 paras 5 and 6, from 1 April " + "2022) pays it in respect of each applicant or partner who has " + "regular and substantial caring responsibilities for a severely " + "disabled person. Para 6(1) defines those as (a) receiving Carer's " + "Allowance or Carer Support Payment, (b) being entitled to one that " + "the overlapping benefits rules reduce to nil, or (c) having an award " + "of Universal Credit which includes the carer element. Limbs (a) and " + "(b) are carer_premium's test (is_entitled_to_carer_benefit); limb (c) " + "is receives_uc_carer_element, which needs a positive award, since an " + "income that tapers the award to nil leaves no award (Welfare Reform " + "Act 2012 s.5(1)(b), UC Regs 2013 reg 17). A person with both an " + "allowance and a carer element qualifies once: the premium is for each " + "partner who qualifies (para 5(3)), not for each award. Partners " + "caring for the same severely disabled person get one premium (para " + "5(3) and (4); partners_care_for_same_severely_disabled_person), as in " + "carer_premium. Para 5(4) then pays it in respect of the one whose " + "Universal Credit award includes the carer element, then one in " + "receipt of an allowance, then one entitled to an allowance reduced " + "to nil; that choice does not change the amount. Before 1 April 2022 " + "the scheme for persons under pension age, SSI 2012/303 Sch 1 para " + "14, tested entitlement to Carer's Allowance only " + "(carer_premium_counts_uc_carer_element)." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/5", + "https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/6", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/1", + "https://www.legislation.gov.uk/ssi/2012/303/schedule/1/paragraph/14/2021-04-01", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/29", + "https://www.legislation.gov.uk/ukpga/2012/5/section/5", + ) + + def formula(benunit, period, parameters): + carer_premium = benunit("carer_premium", period) + scotland = parameters( + period + ).gov.local_authorities.scotland.council_tax_reduction.working_age + if not scotland.carer_premium_counts_uc_carer_element: + return carer_premium + scottish_working_age = ( + benunit.household("country", period) == Country.SCOTLAND + ) & ~benunit("council_tax_reduction_pensioner", period) + person = benunit.members + claimant_or_partner = person("is_claimant_or_partner", period) + # SSI 2021/249 Sch 1 para 6(1)(a) and (b), as in carer_premium, or + # (c) an award of Universal Credit which includes the carer element. + caring = person("is_entitled_to_carer_benefit", period) | person( + "receives_uc_carer_element", period + ) + qualifying_carers = benunit.sum(claimant_or_partner & caring) + same_person = benunit("partners_care_for_same_severely_disabled_person", period) + qualifying_carers = where( + same_person, min_(qualifying_carers, 1), qualifying_carers + ) + amount = parameters(period).gov.dwp.carer_premium.single + return where( + scottish_working_age, + qualifying_carers * amount * WEEKS_IN_YEAR, + carer_premium, + ) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py index d6bc318621..ae8018c0e3 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py @@ -22,6 +22,9 @@ class council_tax_reduction_premiums(Variable): "her non-dependant (everyone else in the household, since she has no " "partner or children, unless they receive a qualifying benefit or are " "blind); and the carer premium if she is entitled to a carer benefit. " + "At working age the benefit unit's carer premium is " + "council_tax_reduction_carer_premium, which in Scotland also counts " + "an award of Universal Credit which includes the carer element. " "Whom a carer benefit is paid for caring for comes from " "is_cared_for_by_carer_benefit_recipient, which gives a carer in her " "own benefit unit to her but a carer in another benefit unit only to " @@ -40,13 +43,16 @@ class council_tax_reduction_premiums(Variable): "https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/1", "https://www.legislation.gov.uk/uksi/2006/213/schedule/3", "https://www.legislation.gov.uk/uksi/2006/214/schedule/3", + "https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/6", ) def formula(benunit, period, parameters): pensioner = benunit("council_tax_reduction_pensioner", period) # The working-age severe disability premium takes Council Tax # Reduction's own family test (council_tax_reduction_severe_disability_ - # premium, which differs from the shared premium in Scotland). + # premium), and the working-age carer premium its own caring test + # (council_tax_reduction_carer_premium); both differ from the shared + # premiums only in Scotland. benefit_unit_premiums = where( pensioner, benunit("pension_age_benefits_premiums", period), @@ -54,6 +60,10 @@ def formula(benunit, period, parameters): + ( benunit("council_tax_reduction_severe_disability_premium", period) - benunit("severe_disability_premium", period) + ) + + ( + benunit("council_tax_reduction_carer_premium", period) + - benunit("carer_premium", period) ), ) head_applies_alone = benunit("council_tax_reduction_head_applies_alone", period) @@ -86,6 +96,10 @@ def head_has(condition): ) & (others_counted == 0) ) * disability.severe_single + # A head who applies alone holds no Universal Credit award of her own + # (the benefit unit's award is its claimant's and partner's), so + # Scotland's carer element limb (council_tax_reduction_carer_premium) + # never reaches her. carer_premium = ( head_has(person("is_entitled_to_carer_benefit", period)) * carer.single ) diff --git a/policyengine_uk/variables/household/demographic/carer_premium.py b/policyengine_uk/variables/household/demographic/carer_premium.py index fa9c6e1d24..bc9adbff80 100644 --- a/policyengine_uk/variables/household/demographic/carer_premium.py +++ b/policyengine_uk/variables/household/demographic/carer_premium.py @@ -25,7 +25,10 @@ class carer_premium(Variable): "person caps the benefit unit at one amount. The premium enters the " "Income Support, Housing Benefit and council tax reduction applicable " "amounts; the model takes income-based JSA and income-related ESA " - "from reported awards." + "from reported awards. Scotland's working-age council tax reduction " + "also pays it for an award of Universal Credit which includes the " + "carer element (council_tax_reduction_carer_premium); no other " + "scheme's carer premium has that limb." ) definition_period = YEAR reference = ( @@ -57,8 +60,10 @@ def formula(benunit, period, parameters): # tests caring responsibilities and pays each partner who qualifies. # Para 6(1) defines those as (a) receiving either allowance, (b) being # entitled to one reduced to nil by the overlapping benefits rules, or - # (c) a Universal Credit award with the carer element; the model - # applies (a) and (b) but not (c) (#2124). + # (c) a Universal Credit award with the carer element. This premium + # applies (a) and (b), which every scheme shares; Scottish working-age + # council tax reduction reads council_tax_reduction_carer_premium, + # which adds (c). claimant_or_partner = benunit.members("is_claimant_or_partner", period) entitled = benunit.members("is_entitled_to_carer_benefit", period) qualifying_carers = benunit.sum(claimant_or_partner & entitled) 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 6772dfa69b..3598aa6d40 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 @@ -7,17 +7,20 @@ class is_cared_for_by_carer_benefit_recipient(Variable): 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 " + "Support Payment, or has an award of Universal Credit which includes " + "the carer element, 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) to (7ZC); both rules were " - "in s.70(7) before 16 November 2023). No one is their own carer. A " + "1(1) and (4)). The data say who receives a carer benefit but not whom " + "they care for, so each award is attributed. A carer benefit or carer " + "element is paid for caring for a person who is severely disabled for " + "Carer's Allowance (is_severely_disabled_for_carers_allowance; UC Regs " + "2013 reg 30), a carer has one award (SSCBA 1992 s.70(7)), and only " + "one person has an allowance or a carer element for caring for the " + "same severely disabled person (s.70(7ZA) to (7ZC) and (7A); 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 " @@ -37,18 +40,24 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "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 " - "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: 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." + "weeks after the person they cared for has died (s.70(1A)). Receipt " + "is receives_carer_benefit: a positive Carer's Allowance or Carer " + "Support Payment after the overlapping-benefit adjustment. A carer " + "whose allowance an overlapping benefit, such as a higher State " + "Pension, reduces to nil is entitled but not in receipt, so is not " + "attributed for the allowance. A carer element award is " + "has_uc_award_with_carer_element, read from the claim and the award's " + "conditions rather than its amount, because the Universal Credit " + "non-dependant deduction exemption reads Pension Credit, which reads " + "the addition. So a carer whose income tapers the award to nil, who " + "has no award in law (Welfare Reform Act 2012 s.5), is still " + "attributed, and can withhold a premium or addition the law would " + "pay. A carer with both an allowance and a carer element is one " + "carer. Partners who care for the same severely disabled person " + "(partners_care_for_same_severely_disabled_person) have one relevant " + "entitlement between them (s.70(7ZA) and (7A)), so where one of them " + "receives a carer benefit, the other's carer element is not a second " + "carer." ) definition_period = YEAR reference = ( @@ -58,12 +67,28 @@ class is_cared_for_by_carer_benefit_recipient(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/uksi/2013/376/regulation/29", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/30", + "https://www.legislation.gov.uk/ukpga/2012/5/section/5", ) 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) + receives = person("receives_carer_benefit", period) + # An award of Universal Credit which includes the carer element counts + # as a carer too. A partner's carer element is not a second carer for + # a couple caring for the same person where one of them receives a + # carer benefit: they have one relevant entitlement between them + # (SSCBA 1992 s.70(7ZA) and (7A)). + same_person = person.benunit( + "partners_care_for_same_severely_disabled_person", period + ) + partner_receives = person.benunit.any(claimant_or_partner & receives) + carer_element = person("has_uc_award_with_carer_element", period) & ~( + same_person & partner_receives + ) + carer = receives | carer_element 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). diff --git a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py index 5a41077d36..4e0fb09af5 100644 --- a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py @@ -20,8 +20,9 @@ class pension_age_severe_disability_premium(Variable): "qualifies and the other is blind, the qualifying partner is taken to " "claim, as a couple may agree under HB(SPC) reg 63(1) and the CTR " "schemes (England SI 2012/2885 Sch 8 para 4(1)); carers are attributed " - "by is_cared_for_by_carer_benefit_recipient, which does not count " - "Universal Credit carer-element awards. The Pension Credit severe " + "by is_cared_for_by_carer_benefit_recipient, which counts a Universal " + "Credit award with the carer element from the award's conditions " + "rather than its amount. The Pension Credit severe " "disability addition has the same conditions apart from its wider " "residence exceptions, but its own amount parameter, so a Pension " "Credit reform does not move this premium." diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index d748f85fb4..0147125bfc 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -21,9 +21,11 @@ class severe_disability_premium(Variable): "when one is paid for only one of them, and nothing when carers are " "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. Not modelled: a carer's " + "the Pension Credit severe disability addition. It counts an award " + "of Universal Credit which includes the carer element from the " + "award's conditions rather than its amount " + "(has_uc_award_with_carer_element), so a carer whose income tapers " + "the award to nil still bars the premium. 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 " From 2e6ae55d79f9dd32986e6929fb62b7faabde8ff1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 31 Oct 2026 23:24:24 -0400 Subject: [PATCH 02/21] Test Universal Credit carer-element awards in the carer test and the Scottish carer premium YAML cases with the arithmetic worked from the 2026-27 rates: the blind carer of #2120, a carer inside and outside the benefit unit, a carer on Carer's Allowance and Universal Credit, partners caring for the same person, the nil award, and the Scottish carer of #2124 in 2021, 2022 and 2026 with English and Welsh controls. Property tests check that the limb changes nothing without a carer element, never raises the severe disability premium or addition, never lowers the carer premium, and that every reader calculates on one simulation in any order. Co-Authored-By: Claude Opus 5.5 --- changelog.d/2120.fixed.md | 1 + .../council_tax_reduction_carer_premium.yaml | 249 +++++++++++ .../uc_carer_element_carer_test.yaml | 254 +++++++++++ .../test_uc_carer_element_award_properties.py | 403 ++++++++++++++++++ 4 files changed, 907 insertions(+) create mode 100644 changelog.d/2120.fixed.md create mode 100644 policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml create mode 100644 policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml create mode 100644 policyengine_uk/tests/test_uc_carer_element_award_properties.py diff --git a/changelog.d/2120.fixed.md b/changelog.d/2120.fixed.md new file mode 100644 index 0000000000..3e754bdf83 --- /dev/null +++ b/changelog.d/2120.fixed.md @@ -0,0 +1 @@ +The model now withholds the severe disability premium and the Pension Credit severe disability addition when a carer's Universal Credit award includes the carer element for the claimant or partner, and pays the carer premium in Scotland's working-age council tax reduction for such an award from 1 April 2022 (#2120, #2124). diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml new file mode 100644 index 0000000000..2e7a1bbaeb --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml @@ -0,0 +1,249 @@ +# Scotland's working-age council tax reduction includes the carer premium +# where the applicant or partner has regular and substantial caring +# responsibilities for a severely disabled person (SSI 2021/249 Sch 1 para +# 5(2)), which para 6(1)(c) extends to "an award of universal credit which +# includes the carer element described in regulation 29 of the 2013 +# Regulations". No other scheme's carer premium has that limb: the Housing +# Benefit, English and Welsh schedules test entitlement to Carer's Allowance +# or Carer Support Payment (HB Regs 2006 Sch 3 para 17; WSI 2013/3029 Sch 7 +# para 14), as did Scotland's scheme for persons under pension age before +# 1 April 2022 (SSI 2012/303 Sch 1 para 14). +# +# Amounts: carer premium £48.15 a week from 1 April 2026 (SSI 2021/249 Sch 1 +# para 5(1), substituted by SSI 2026/56) and £38.85 in 2022-23; personal +# allowance for a single person aged 25 or over £95.55 (2026-27) and £77.00 +# (2022-23); Universal Credit carer element £209.34 a month (2026-27) and +# £168.81 (2022-23), standard allowance £424.90 and £334.91. + +- name: The issue's Scottish carer aged 40 with 35 hours and no Carer's Allowance claim gets the carer premium + # Para 6(1)(c): the award includes the carer element, 209.34 x 12 = + # 2,512.08, so the applicable amount is (95.55 + 48.15) x 52 = 7,472.40, + # not 95.55 x 52 = 4,968.60. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + output: + uc_carer_element: 209.34 * 12 + universal_credit: (424.90 + 209.34) * 12 + receives_uc_carer_element: true + is_entitled_to_carer_benefit: false + carer_premium: 0 + council_tax_reduction_carer_premium: 48.15 * 52 + council_tax_reduction_applicable_amount: (95.55 + 48.15) * 52 + +- name: The Scottish carer premium for a carer element starts with the 2021 Regulations on 1 April 2022 + # Model year 2022 is 2022-23, read at 30 April 2022. Carer element + # 168.81 x 12 = 2,025.72; carer premium 38.85 x 52 = 2,020.20; applicable + # amount (77.00 + 38.85) x 52. + period: 2022 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + output: + uc_carer_element: 168.81 * 12 + council_tax_reduction_carer_premium: 38.85 * 52 + council_tax_reduction_applicable_amount: (77.00 + 38.85) * 52 + +- name: Before 1 April 2022 the Scottish scheme for persons under pension age tested Carer's Allowance only + # SSI 2012/303 Sch 1 para 14: "entitled to a carer's allowance". Model + # year 2021 is 2021-22, read at 30 April 2021, so the carer element, + # 163.73 x 12 = 1,964.76, gives no premium. + period: 2021 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + output: + uc_carer_element: 163.73 * 12 + council_tax_reduction_carer_premium: 0 + +- name: The same carer in England gets no carer premium for the carer element + # England: the working-age premiums follow the Housing Benefit schedule, + # whose carer premium needs entitlement to Carer's Allowance (HB Regs 2006 + # Sch 3 para 17(1)). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: ENGLAND + output: + uc_carer_element: 209.34 * 12 + receives_uc_carer_element: true + council_tax_reduction_carer_premium: 0 + council_tax_reduction_applicable_amount: 95.55 * 52 + +- name: The same carer in Wales gets no carer premium for the carer element + # WSI 2013/3029 Sch 7 para 14(1): "entitled to a carer's allowance". + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: WALES + output: + uc_carer_element: 209.34 * 12 + receives_uc_carer_element: true + council_tax_reduction_carer_premium: 0 + council_tax_reduction_applicable_amount: 95.55 * 52 + +- name: A Scottish carer whose income tapers the award to nil has no award and no carer premium + # With £20,000 of earnings the award is nil, so there is no award (Welfare + # Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17) and para 6(1)(c) does not + # apply, though the maximum amount still includes the carer element. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + employment_income: 20_000 + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + output: + uc_carer_element: 209.34 * 12 + universal_credit: 0 + receives_uc_carer_element: false + council_tax_reduction_carer_premium: 0 + +- name: A Scottish carer with both Carer Support Payment and the carer element gets one premium + # The premium is paid in respect of each person who qualifies (para 5(3)), + # not for each award: 48.15 x 52 = 2,503.80, not twice that. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + care_hours: 35 + would_claim_carers_allowance: true + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + output: + receives_carer_benefit: true + receives_uc_carer_element: true + carer_premium: 48.15 * 52 + council_tax_reduction_carer_premium: 48.15 * 52 + +- name: Scottish partners caring for the same person get one premium, whichever holds the carer element + # Both care 35 hours. The younger receives Carer Support Payment and the + # joint award includes the carer element for the elder (UC Regs 2013 reg + # 29(3)). Para 5(3) and (4) pay one premium, in respect of the one whose + # award includes the carer element: 48.15 x 52 = 2,503.80. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + elder: + age: 45 + care_hours: 35 + would_claim_carers_allowance: false + younger: + age: 40 + care_hours: 35 + would_claim_carers_allowance: true + benunits: + couple: + members: [elder, younger] + households: + household: + members: [elder, younger] + country: SCOTLAND + region: SCOTLAND + output: + partners_care_for_same_severely_disabled_person: true + receives_carer_benefit: [false, true] + receives_uc_carer_element: [true, false] + council_tax_reduction_carer_premium: 48.15 * 52 + +- name: A Scottish pensioner's schedule has no carer element limb + # SSI 2012/319 Sch 1 para 10(1): entitlement to Carer's Allowance or Carer + # Support Payment. The working-age variable keeps the shared premium. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 70 + care_hours: 35 + would_claim_carers_allowance: false + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + output: + council_tax_reduction_pensioner: true + council_tax_reduction_carer_premium: 0 + council_tax_reduction_premiums: 0 diff --git a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml new file mode 100644 index 0000000000..39b4892cd1 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml @@ -0,0 +1,254 @@ +# An award of Universal Credit which includes the carer element counts as a +# carer benefit in the carer condition of the legacy severe disability premium +# (HB Regs 2006 Sch 3 para 14(2)(a)(iii) and (b); HB(SPC) Regs 2006 Sch 3 +# para 6(2)) and of the Pension Credit severe disability addition (SPC Regs +# 2002 Sch I para 1(1)(a)(iii) and (b), with para 1(4)): no premium or +# addition is paid for a person someone "has an award of universal credit which +# includes the carer element in respect of caring for". The carer element is +# for regular and substantial caring for a severely disabled person, whether or +# not the carer claims Carer's Allowance (UC Regs 2013 regs 29 and 30(2)). +# +# Amounts for 2026-27 (DWP benefit and pension rates 2026 to 2027; the Social +# Security Benefits Up-rating Order 2026, SI 2026/148): severe disability +# premium and addition £86.05 a week, couple premium £172.10, Carer's Allowance +# £86.45; Universal Credit standard allowance £424.90 a month (single, 25 or +# over) and £666.97 (joint, either 25 or over), carer element £209.34 a month. + +- name: A blind carer's Universal Credit carer element bars the premium of the person they care for + # The carer is a non-dependant who is blind, so the non-dependant condition + # ignores them (para 14(4)(b)); their award bars the premium instead. + # Carer element 209.34 x 12 = 2,512.08; award (424.90 + 209.34) x 12 = + # 7,610.88. Without the limb the premium was 86.05 x 52 = 4,474.60. + period: 2026 + input: + people: + disabled: + age: 50 + pip_dl_category: STANDARD + carer: + age: 40 + is_blind: true + care_hours: 35 + would_claim_carers_allowance: false + benunits: + disabled_unit: + members: [disabled] + would_claim_uc: false + carer_unit: + members: [carer] + households: + household: + members: [disabled, carer] + country: ENGLAND + output: + uc_carer_element: [0, 2_512.08] + universal_credit: [0, 7_610.88] + has_uc_award_with_carer_element: [false, true] + is_cared_for_by_carer_benefit_recipient: [true, false] + severe_disability_premium: [0, 0] + +- name: A partner whose joint award includes the carer element leaves a couple who both qualify the single rate + # Para 14(2)(b)(iii): a carer element in respect of caring for only one of + # the couple gives one single rate, 86.05 x 52 = 4,474.60, where no carer + # gave the double rate, 172.10 x 52 = 8,949.20. Joint award (666.97 + + # 209.34) x 12 = 10,515.72. + period: 2026 + input: + people: + claimant: + age: 45 + pip_dl_category: STANDARD + partner: + age: 40 + pip_dl_category: STANDARD + care_hours: 35 + would_claim_carers_allowance: false + benunits: + couple: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + country: ENGLAND + output: + uc_carer_element: 2_512.08 + universal_credit: 10_515.72 + has_uc_award_with_carer_element: [false, true] + is_cared_for_by_carer_benefit_recipient: [true, false] + severe_disability_premium: 4_474.60 + +- name: A son's Universal Credit carer element in another benefit unit bars a pensioner's severe disability addition + # SPC Regs 2002 Sch I para 1(1)(a)(iii) and (4). The son is blind, so the + # residence condition ignores him (para 2). Without the limb the addition + # was 86.05 x 52 = 4,474.60, and so was the pension-age Housing Benefit + # premium (HB(SPC) Regs 2006 Sch 3 para 6(2)(a)(iii)). + period: 2026 + input: + people: + pensioner: + age: 75 + aa_category: HIGHER + son: + age: 40 + is_blind: true + care_hours: 35 + would_claim_carers_allowance: false + benunits: + pensioner_unit: + members: [pensioner] + son_unit: + members: [son] + households: + household: + members: [pensioner, son] + country: ENGLAND + output: + universal_credit: [0, 7_610.88] + has_uc_award_with_carer_element: [false, true] + is_cared_for_by_carer_benefit_recipient: [true, false] + severe_disability_minimum_guarantee_addition: [0, 0] + pension_age_severe_disability_premium: [0, 0] + +- name: A carer on both Carer's Allowance and Universal Credit is one carer + # The carer receives Carer's Allowance, 86.45 x 52 = 4,495.40, and a + # Universal Credit award with the carer element, (424.90 + 209.34) x 12 - + # 4,495.40 = 3,115.48. They care for one person (SSCBA 1992 s.70(7) and + # (7ZA), (7A)), so the couple who both qualify keep one rate of the + # addition, 86.05 x 52 = 4,474.60 (SPC Regs 2002 reg 6(5)), as with the + # allowance alone. + period: 2026 + input: + people: + husband: + age: 78 + aa_category: HIGHER + wife: + age: 76 + aa_category: HIGHER + carer: + age: 40 + is_blind: true + care_hours: 35 + would_claim_carers_allowance: true + benunits: + couple: + members: [husband, wife] + carer_unit: + members: [carer] + households: + household: + members: [husband, wife, carer] + country: ENGLAND + output: + carers_allowance: [0, 0, 4_495.40] + universal_credit: [0, 3_115.48] + receives_carer_benefit: [false, false, true] + has_uc_award_with_carer_element: [false, false, true] + is_cared_for_by_carer_benefit_recipient: [true, false, false] + severe_disability_minimum_guarantee_addition: [4_474.60, 0] + +- name: A partner's carer element is not a second carer where the other partner receives the allowance for the same person + # Both partners care 35 hours; the younger claims Carer's Allowance and the + # joint award includes the carer element for the elder (UC Regs 2013 reg + # 29(3); the model takes the elder). Partners caring for the same severely + # disabled person have one relevant entitlement between them (SSCBA 1992 + # s.70(7ZA) and (7A)), so they care for one person, the disabled child, and + # not also the grandmother in another benefit unit. + period: 2026 + input: + people: + elder: + age: 45 + care_hours: 35 + would_claim_carers_allowance: false + younger: + age: 40 + care_hours: 35 + would_claim_carers_allowance: true + child: + age: 8 + dla_sc_category: MIDDLE + grandmother: + age: 80 + aa_category: HIGHER + benunits: + family: + members: [elder, younger, child] + grandmother_unit: + members: [grandmother] + households: + household: + members: [elder, younger, child, grandmother] + country: ENGLAND + output: + partners_care_for_same_severely_disabled_person: [true, true] + receives_carer_benefit: [false, true, false, false] + has_uc_award_with_carer_element: [true, false, false, false] + is_cared_for_by_carer_benefit_recipient: [false, false, true, false] + +- name: "Disclosed limit: a carer whose income tapers the award to nil still bars the premium" + # With £25,000 of earnings the carer's award is nil (income reduction + # 7,610.88 against a maximum of 7,610.88), so in law there is no award + # (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17) and the premium, + # 86.05 x 52 = 4,474.60, is payable. The model reads the award from its + # conditions, because the award reads Pension Credit, which reads the + # addition, so it withholds the premium. + period: 2026 + input: + people: + disabled: + age: 50 + pip_dl_category: STANDARD + carer: + age: 40 + is_blind: true + care_hours: 35 + would_claim_carers_allowance: false + employment_income: 25_000 + benunits: + disabled_unit: + members: [disabled] + would_claim_uc: false + carer_unit: + members: [carer] + households: + household: + members: [disabled, carer] + country: ENGLAND + output: + universal_credit: [0, 0] + receives_uc_carer_element: [false, false] + has_uc_award_with_carer_element: [false, true] + severe_disability_premium: [0, 0] + +- name: Without a Universal Credit claim there is no carer element award + # The same carer as in the first case, in a family that does not claim + # Universal Credit and does not claim Carer's Allowance: no one has an award + # in respect of caring for the disabled person, who keeps the premium, + # 86.05 x 52 = 4,474.60. + period: 2026 + input: + people: + disabled: + age: 50 + pip_dl_category: STANDARD + carer: + age: 40 + is_blind: true + care_hours: 35 + would_claim_carers_allowance: false + benunits: + disabled_unit: + members: [disabled] + would_claim_uc: false + carer_unit: + members: [carer] + would_claim_uc: false + households: + household: + members: [disabled, carer] + country: ENGLAND + output: + has_uc_award_with_carer_element: [false, false] + is_cared_for_by_carer_benefit_recipient: [false, false] + severe_disability_premium: [4_474.60, 0] diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py new file mode 100644 index 0000000000..d02cedc855 --- /dev/null +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -0,0 +1,403 @@ +"""Universal Credit carer-element awards as a carer benefit. + +HB Regs 2006 Sch 3 para 14(2)(a)(iii) and (b) (with the IS, JSA, ESA, +pension-age Housing Benefit and council tax reduction schedules) and SPC Regs +2002 Sch I para 1(1) and (4) withhold the severe disability premium and the +Pension Credit severe disability addition where someone "has an award of +universal credit which includes the carer element in respect of caring for" +the claimant or partner. Scotland's working-age council tax reduction (SSI +2021/249 Sch 1 paras 5 and 6(1)(c), from 1 April 2022) pays the carer premium +to an applicant or partner with such an award. + +The model reads the award from its conditions (has_uc_award_with_carer_element) +for the severe disability carer test, since the award itself reads Pension +Credit, and from the positive award (receives_uc_carer_element) for the +Scottish carer premium. Each example simulates the households as drawn, and +again with has_uc_award_with_carer_element supplied as false for everyone, the +attribution as it ran before the limb (its carers are then +receives_carer_benefit alone). The Scottish premium's counterpart without the +limb is carer_premium. + +Invariants, for any generated population of households: + +1. No change without a carer element: where no one has + has_uc_award_with_carer_element or receives_uc_carer_element, the + attribution, the severe disability premiums and the addition equal their + values without the limb, and the Scottish premium equals carer_premium, + exactly. +2. Never raises: no carer element raises the severe disability premium (the + shared, council tax reduction and pension-age premiums) or the Pension + Credit addition. +3. Never lowers, and only in Scotland: council_tax_reduction_carer_premium is + never below carer_premium, and differs from it only for a Scottish + working-age family. +4. Bounds: the Scottish premium is a whole number of weekly amounts, at most + the number of claimants and partners, and at most one where partners care + for the same person; a person with both an allowance and a carer element + counts once. +5. The read before the award misses no award: receives_uc_carer_element + implies has_uc_award_with_carer_element. +6. Attribution: no one is their own carer, everyone cared for is severely + disabled for Carer's Allowance, and a household has no more people cared + for than carers (carer benefit recipients and carer element holders). +7. No cycle: every reader calculates on one simulation, whichever is requested + first. + +The no-change invariant compares the two code paths in this tree; the +differential against origin/main itself runs on hand-built datasets outside +the test suite (see the pull request). +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, example, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEAR = 2026 +WEEKS = 52 +CARER_PREMIUM = 48.15 +PROPERTY_SETTINGS = settings( + max_examples=5, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + +SEVERE_READERS = ( + "severe_disability_premium", + "severe_disability_premium_before_non_dependant_test", + "council_tax_reduction_severe_disability_premium", + "pension_age_severe_disability_premium", + "severe_disability_minimum_guarantee_addition", +) +CARER_READERS = ( + "carer_premium", + "council_tax_reduction_carer_premium", + "council_tax_reduction_premiums", +) +BENUNIT_READERS = SEVERE_READERS + CARER_READERS +PERSON_READERS = ( + "is_cared_for_by_carer_benefit_recipient", + "has_uc_award_with_carer_element", + "receives_uc_carer_element", +) +# Every reader of the attribution or the carer element award, and the +# programmes that read them, for the cycle check. +ALL_READERS = BENUNIT_READERS + ( + "is_cared_for_by_carer_benefit_recipient", + "has_uc_award_with_carer_element", + "receives_uc_carer_element", + "universal_credit", + "pension_credit", + "housing_benefit", + "income_support", + "council_tax_reduction", + "household_net_income", +) + + +DISABILITY = { + "none": {}, + "pip_standard": {"pip_dl_category": "STANDARD"}, + "aa_higher": {"aa_category": "HIGHER"}, + "dla_middle": {"dla_sc_category": "MIDDLE"}, +} + + +@st.composite +def adult(draw, age): + return dict( + age=draw(age), + disability=draw(st.sampled_from(["none", "none", "pip_standard", "aa_higher"])), + blind=draw(st.sampled_from([False, False, True])), + # The qualifying hours, with or without a Carer's Allowance claim: the + # Universal Credit carer element needs no claim (UC Regs 2013 reg + # 30(2)), and its test ignores the allowance's earnings limit. + care_hours=draw(st.sampled_from([0, 35, 35])), + would_claim_carers_allowance=draw(st.booleans()), + earnings=draw(st.sampled_from([0, 0, 8_000, 25_000])), + ) + + +@st.composite +def benefit_unit(draw): + # Working-age or pension-age adults, partners within ten years of each + # other, so no adult reads as the other's child. + eldest = draw(st.integers(20, 80)) + ages = st.integers(max(18, eldest - 10), eldest) + adults = [draw(adult(st.just(eldest)))] + adults += draw(st.lists(adult(ages), max_size=1)) + children = draw( + st.lists( + st.fixed_dictionaries( + dict( + age=st.integers(0, 15), + disability=st.sampled_from(["none", "dla_middle"]), + ) + ), + max_size=1, + ) + ) + return dict(adults=adults, children=children) + + +@st.composite +def households(draw): + return dict( + country=draw(st.sampled_from(["ENGLAND", "SCOTLAND", "WALES"])), + units=draw(st.lists(benefit_unit(), min_size=1, max_size=3)), + ) + + +def situation(population, without_limb=()): + """One situation; households in without_limb have no carer element award.""" + people, benunits, hh = {}, {}, {} + for h, household in enumerate(population): + members = [] + for u, unit in enumerate(household["units"]): + unit_members = [] + for a, p in enumerate(unit["adults"]): + name = f"h{h}_u{u}_a{a}" + people[name] = { + "age": {YEAR: p["age"]}, + "is_blind": {YEAR: p["blind"]}, + "care_hours": {YEAR: p["care_hours"]}, + "would_claim_carers_allowance": { + YEAR: p["would_claim_carers_allowance"] + }, + "employment_income": {YEAR: p["earnings"]}, + "is_claimant_or_partner": {YEAR: True}, + **{k: {YEAR: v} for k, v in DISABILITY[p["disability"]].items()}, + } + if h in without_limb: + people[name]["has_uc_award_with_carer_element"] = {YEAR: False} + unit_members.append(name) + for c, p in enumerate(unit["children"]): + name = f"h{h}_u{u}_c{c}" + people[name] = { + "age": {YEAR: p["age"]}, + "is_claimant_or_partner": {YEAR: False}, + **{k: {YEAR: v} for k, v in DISABILITY[p["disability"]].items()}, + } + unit_members.append(name) + benunits[f"h{h}_u{u}"] = {"members": unit_members} + members += unit_members + hh[f"h{h}"] = {"members": members, "country": {YEAR: household["country"]}} + return {"people": people, "benunits": benunits, "households": hh} + + +def with_and_without_limb(population, variables): + """Each variable for the households as drawn, and without the limb. + + Without the limb, has_uc_award_with_carer_element is supplied as false + for everyone, in a second simulation: a variable supplied for some people + takes its default, not its formula, for the rest. + """ + sim = Simulation(situation=situation(population)) + sim_without = Simulation( + situation=situation(population, without_limb=range(len(population))) + ) + return sim, { + v: ( + np.asarray(sim.calculate(v, YEAR)), + np.asarray(sim_without.calculate(v, YEAR)), + ) + for v in variables + } + + +def household_index(sim, entity): + """The household each benefit unit or person belongs to, by position.""" + population = sim.populations[entity] + names = population.ids + return np.array([int(str(name).split("_")[0][1:]) for name in names]) + + +def single(age=40, **inputs): + return dict( + dict( + age=age, + disability="none", + blind=False, + care_hours=0, + would_claim_carers_allowance=False, + earnings=0, + ), + **inputs, + ) + + +EXAMPLES = [ + # The blind carer: a Universal Credit carer element in another benefit + # unit bars the disabled person's premium. + dict( + country="ENGLAND", + units=[ + dict(adults=[single(50, disability="pip_standard")], children=[]), + dict(adults=[single(40, blind=True, care_hours=35)], children=[]), + ], + ), + # The same carer earning enough to taper the award to nil. + dict( + country="ENGLAND", + units=[ + dict(adults=[single(50, disability="pip_standard")], children=[]), + dict( + adults=[single(40, blind=True, care_hours=35, earnings=25_000)], + children=[], + ), + ], + ), + # The Scottish carer of the issue, aged 40 with 35 hours and no claim. + dict(country="SCOTLAND", units=[dict(adults=[single(care_hours=35)], children=[])]), + # A Scottish couple, one claiming the allowance, both caring. + dict( + country="SCOTLAND", + units=[ + dict( + adults=[ + single(45, care_hours=35), + single(40, care_hours=35, would_claim_carers_allowance=True), + ], + children=[dict(age=6, disability="dla_middle")], + ) + ], + ), + # A pensioner cared for by a son on a qualifying benefit. + dict( + country="WALES", + units=[ + dict(adults=[single(75, disability="aa_higher")], children=[]), + dict( + adults=[single(40, disability="pip_standard", care_hours=35)], + children=[], + ), + ], + ), +] + + +def test_issue_households_read_the_carer_element_award(): + # #2120: the blind carer's award includes the carer element, 209.34 x 12, + # so the disabled person has no severe disability premium (86.05 x 52 + # without it). #2124: the Scottish carer's applicable amount includes the + # carer premium, (95.55 + 48.15) x 52, not 95.55 x 52. Only variables + # that predate the fix are read, so the test also runs on the old code. + sim = Simulation(situation=situation(EXAMPLES[:1] + EXAMPLES[2:3])) + severe = sim.calculate("severe_disability_premium", YEAR) + carer_element = sim.calculate("uc_carer_element", YEAR) + applicable = sim.calculate("council_tax_reduction_applicable_amount", YEAR) + # Benefit units: the disabled person, the blind carer, the Scottish carer. + assert carer_element == pytest.approx([0, 209.34 * 12, 209.34 * 12]) + assert severe[0] == 0 + assert applicable[2] == pytest.approx((95.55 + 48.15) * WEEKS) + + +@PROPERTY_SETTINGS +@given(st.lists(households(), min_size=1, max_size=4)) +@example(EXAMPLES) +def test_the_carer_element_limb_moves_only_its_readers_and_only_one_way(population): + variables = SEVERE_READERS + ( + "carer_premium", + "council_tax_reduction_carer_premium", + "council_tax_reduction_pensioner", + "partners_care_for_same_severely_disabled_person", + "is_cared_for_by_carer_benefit_recipient", + "is_severely_disabled_for_carers_allowance", + "has_uc_award_with_carer_element", + "receives_uc_carer_element", + "receives_carer_benefit", + "is_claimant_or_partner", + ) + sim, values = with_and_without_limb(population, variables) + drawn = {v: pair[0] for v, pair in values.items()} + without = {v: pair[1] for v, pair in values.items()} + n_benunits = len(drawn["carer_premium"]) + benunit_household = household_index(sim, "benunit") + person_household = household_index(sim, "person") + + # 5. The read before the award misses no award. + has_award = drawn["has_uc_award_with_carer_element"] + positive_award = drawn["receives_uc_carer_element"] + assert not (positive_award & ~has_award).any() + + # 1. No change without a carer element, household by household. + touched = list(set(person_household[has_award | positive_award])) + untouched_units = ~np.isin(benunit_household, touched) + untouched_people = ~np.isin(person_household, touched) + for v in SEVERE_READERS: + assert (drawn[v][untouched_units] == without[v][untouched_units]).all(), v + cared_for = drawn["is_cared_for_by_carer_benefit_recipient"] + cared_for_without = without["is_cared_for_by_carer_benefit_recipient"] + assert (cared_for[untouched_people] == cared_for_without[untouched_people]).all() + ctr = drawn["council_tax_reduction_carer_premium"] + shared = drawn["carer_premium"] + assert (ctr[untouched_units] == shared[untouched_units]).all() + + # 2. A carer element never raises the severe disability premium or the + # addition, and never removes anyone from the cared-for set. + for v in SEVERE_READERS: + assert (drawn[v] <= without[v]).all(), v + assert not (cared_for_without & ~cared_for).any() + + # 3. The Scottish premium never lowers the carer premium and moves it + # only in Scotland at working age. + assert (ctr >= shared).all() + countries = sim.calculate("country", YEAR) + if hasattr(countries, "decode_to_str"): + countries = countries.decode_to_str() + country = dict(zip(sim.populations["household"].ids, np.asarray(countries))) + scottish = np.array([country[f"h{h}"] == "SCOTLAND" for h in benunit_household]) + scottish_working_age = scottish & ~drawn["council_tax_reduction_pensioner"] + assert (ctr[~scottish_working_age] == shared[~scottish_working_age]).all() + + # 4. Whole weekly amounts, at most one per claimant or partner, one where + # partners care for the same person. + weekly = ctr / (CARER_PREMIUM * WEEKS) + assert np.allclose(weekly, np.round(weekly)) + benunit_position = { + name: i for i, name in enumerate(sim.populations["benunit"].ids) + } + person_benunit = [ + benunit_position[name.rsplit("_", 1)[0]] + for name in sim.populations["person"].ids + ] + claimants = np.bincount( + person_benunit, weights=drawn["is_claimant_or_partner"], minlength=n_benunits + ) + assert (np.round(weekly) <= claimants).all() + same_person = drawn["partners_care_for_same_severely_disabled_person"] + assert (np.round(weekly[same_person]) <= 1).all() + + # 6. Everyone cared for is severely disabled for Carer's Allowance; no + # household has more people cared for than carers, and a lone carer is + # not cared for. + assert not (cared_for & ~drawn["is_severely_disabled_for_carers_allowance"]).any() + carer = drawn["receives_carer_benefit"] | has_award + for h in np.unique(person_household): + in_h = person_household == h + assert cared_for[in_h].sum() <= carer[in_h].sum() + if carer[in_h].sum() == 1: + assert not (cared_for & carer)[in_h].any() + + +READER_ORDERS = st.permutations(ALL_READERS) + + +@settings( + max_examples=3, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.lists(households(), min_size=1, max_size=3), READER_ORDERS) +@example(EXAMPLES, ALL_READERS) +@example(EXAMPLES, tuple(reversed(ALL_READERS))) +def test_every_reader_calculates_on_one_simulation_in_any_order(population, order): + # A CycleError, or the recursion it guards, would fail this test. + sim = Simulation(situation=situation(population)) + for variable in order: + values = np.asarray(sim.calculate(variable, YEAR)) + assert np.isfinite(values.astype(float)).all(), variable From 8604456522ce12361a6339aa139e56826bae7de2 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 02:03:15 -0500 Subject: [PATCH 03/21] Gate the carer element award on an upper bound and date Scotland's nil-award rule has_uc_award_with_carer_element now also needs a positive uc_award_upper_bound: the award before any non-dependant deduction, worked in the award's own order of operations so it never falls below universal_credit_pre_benefit_cap. A carer whose income tapers the award to nil has no award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17) and no longer bars the severe disability premium or the Pension Credit addition. Where universal_credit is stored or neutralized, the test reads the award itself (receives_uc_carer_element). Scotland's working-age carer premium counts a nil award with the carer element from 1 April 2022 to 31 March 2025, when SSI 2021/249 reg 4 defined an award of universal credit to include one with a value of zero (SSI 2022/52 reg 3), and reads the positive award from 1 April 2025 (SSI 2025/24 reg 8). The new parameter uc_award_includes_zero_value_award carries the dates. Co-Authored-By: Claude Opus 5.5 --- .../uc_award_includes_zero_value_award.yaml | 18 ++ .../council_tax_reduction_carer_premium.yaml | 123 ++++++++- .../uc_carer_element_carer_test.yaml | 142 +++++++++- .../test_uc_carer_element_award_properties.py | 243 +++++++++++++++++- policyengine_uk/utils/supplied_inputs.py | 1 + ...e_disability_minimum_guarantee_addition.py | 3 +- .../has_uc_award_with_carer_element.py | 59 +++-- .../universal_credit/uc_award_upper_bound.py | 97 +++++++ .../council_tax_reduction_carer_premium.py | 83 ++++-- .../household/demographic/carer_premium.py | 8 +- ...is_cared_for_by_carer_benefit_recipient.py | 16 +- .../pension_age_severe_disability_premium.py | 5 +- .../demographic/severe_disability_premium.py | 5 +- 13 files changed, 715 insertions(+), 88 deletions(-) create mode 100644 policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml create mode 100644 policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml new file mode 100644 index 0000000000..c9037e7852 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml @@ -0,0 +1,18 @@ +description: Scotland's working-age Council Tax Reduction scheme treats an award of Universal Credit with a value of zero as an award of Universal Credit if this is true, so a nil award that includes the carer element meets the carer premium's caring condition. +values: + 2013-04-01: false + 2022-04-01: true + 2025-04-01: false +metadata: + unit: bool + period: year + label: Scottish working-age Council Tax Reduction counts a Universal Credit award with a value of zero + reference: + - title: The Council Tax Reduction (Scotland) Regulations 2021 reg. 4 ("award of universal credit" includes a universal credit award with a value of zero), as in force to 31 March 2025 + href: https://www.legislation.gov.uk/ssi/2021/249/regulation/4/2024-04-30 + - title: The Council Tax Reduction (Scotland) Amendment Regulations 2022 (SSI 2022/52) reg. 3 (inserts that definition from 31 March 2022) + href: https://www.legislation.gov.uk/ssi/2022/52/regulation/3/made + - title: The Council Tax Reduction (Miscellaneous Amendment) (Scotland) Regulations 2025 (SSI 2025/24) regs. 1 and 8 (substitute "means an award of universal credit made under Part 1 of the 2012 Act" from 1 April 2025) + href: https://www.legislation.gov.uk/ssi/2025/24/regulation/8/made + - title: The Council Tax Reduction (Scotland) Regulations 2021 reg. 10A (awards reduced to nil by a sanction, and reclaims under the Claims and Payments Regulations 2013 reg. 32A, from 1 April 2025) + href: https://www.legislation.gov.uk/ssi/2021/249/regulation/10A diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml index 2e7a1bbaeb..788cbac06f 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml @@ -7,13 +7,21 @@ # Benefit, English and Welsh schedules test entitlement to Carer's Allowance # or Carer Support Payment (HB Regs 2006 Sch 3 para 17; WSI 2013/3029 Sch 7 # para 14), as did Scotland's scheme for persons under pension age before -# 1 April 2022 (SSI 2012/303 Sch 1 para 14). +# 1 April 2022 (SSI 2012/303 Sch 1 para 14). From 1 April 2022 to 31 March +# 2025 an "award of universal credit" included "a universal credit award with +# a value of zero" (reg 4, as amended by SSI 2022/52 reg 3); from 1 April 2025 +# it "means an award of universal credit made under Part 1 of the 2012 Act" +# (SSI 2025/24 reg 8), and an income that tapers the award to nil leaves no +# award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17). # # Amounts: carer premium £48.15 a week from 1 April 2026 (SSI 2021/249 Sch 1 -# para 5(1), substituted by SSI 2026/56) and £38.85 in 2022-23; personal -# allowance for a single person aged 25 or over £95.55 (2026-27) and £77.00 -# (2022-23); Universal Credit carer element £209.34 a month (2026-27) and -# £168.81 (2022-23), standard allowance £424.90 and £334.91. +# para 5(1), substituted by SSI 2026/56), £46.40 in 2025-26, £45.60 in +# 2024-25 and £38.85 in 2022-23 (para 17, as substituted by SSI 2022/52 reg +# 25(c)(ii); para 5(1) kept £37.70 until SSI 2023/38 amended both from +# 1 April 2023); personal allowance for a single person aged 25 or over +# £95.55 (2026-27) and £77.00 (2022-23); Universal Credit carer element +# £209.34 a month (2026-27), £201.68 (2025-26), £198.31 (2024-25) and £168.81 +# (2022-23), standard allowance £424.90, £400.14, £393.45 and £334.91. - name: The issue's Scottish carer aged 40 with 35 hours and no Carer's Allowance claim gets the carer premium # Para 6(1)(c): the award includes the carer element, 209.34 x 12 = @@ -142,9 +150,10 @@ council_tax_reduction_applicable_amount: 95.55 * 52 - name: A Scottish carer whose income tapers the award to nil has no award and no carer premium - # With £20,000 of earnings the award is nil, so there is no award (Welfare - # Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17) and para 6(1)(c) does not - # apply, though the maximum amount still includes the carer element. + # With £20,000 of earnings the award is nil, so in 2026 there is no award + # (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17) and para 6(1)(c) + # does not apply, though the maximum amount still includes the carer + # element. period: 2026 absolute_error_margin: 0.01 input: @@ -168,6 +177,69 @@ receives_uc_carer_element: false council_tax_reduction_carer_premium: 0 +- name: In 2024 a Scottish carer's nil award still counts as an award with the carer element + # The same carer in 2024-25. The maximum amount is (393.45 + 198.31) x 12 + # = 7,101.12, and the taper on £20,000 of earnings less income tax and + # National Insurance takes more, so the award is nil. Until 31 March 2025 + # an "award of universal credit" included one "with a value of zero" (reg + # 4), so para 6(1)(c) applies: carer premium 45.60 x 52 = 2,371.20. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + employment_income: 20_000 + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + output: + uc_carer_element: 198.31 * 12 + universal_credit: 0 + receives_uc_carer_element: false + is_entitled_to_carer_benefit: false + carer_premium: 0 + council_tax_reduction_carer_premium: 45.60 * 52 + +- name: From 1 April 2025 the same Scottish carer's nil award is no award + # The same carer in 2025-26. The maximum amount is (400.14 + 201.68) x 12 = + # 7,221.84, and the taper again takes more, so the award is nil. From 1 + # April 2025 an award of universal credit is one "made under Part 1 of the + # 2012 Act" (SSI 2025/24 reg 8), and an income that tapers it to nil leaves + # no award, so para 6(1)(c) does not apply: no carer premium. Neither of + # reg 10A's nil awards (a sanction, or a reclaim under the UC Claims and + # Payments Regs 2013 reg 32A) arises. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + employment_income: 20_000 + benunits: + carer_unit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + output: + uc_carer_element: 201.68 * 12 + universal_credit: 0 + receives_uc_carer_element: false + is_entitled_to_carer_benefit: false + council_tax_reduction_carer_premium: 0 + - name: A Scottish carer with both Carer Support Payment and the carer element gets one premium # The premium is paid in respect of each person who qualifies (para 5(3)), # not for each award: 48.15 x 52 = 2,503.80, not twice that. @@ -247,3 +319,38 @@ council_tax_reduction_pensioner: true council_tax_reduction_carer_premium: 0 council_tax_reduction_premiums: 0 + +- name: A dependant's caring does not raise the Scottish carer premium + # Para 5(2) includes the premium where "an applicant or the applicant's + # partner" has caring responsibilities. The son receives Carer Support + # Payment for caring for his grandmother, but he is neither the applicant + # nor a partner, so his parent's family gets no carer premium (48.15 x 52 + # = 2,503.80 if his caring counted). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + son: + age: 17 + care_hours: 35 + would_claim_carers_allowance: true + grandmother: + age: 80 + aa_category: HIGHER + benunits: + family: + members: [parent, son] + grandmother_unit: + members: [grandmother] + households: + household: + members: [parent, son, grandmother] + country: SCOTLAND + region: SCOTLAND + output: + is_claimant_or_partner: [true, false, true] + receives_carer_benefit: [false, true, false] + carer_premium: [0, 0] + council_tax_reduction_carer_premium: [0, 0] diff --git a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml index 39b4892cd1..66edc75834 100644 --- a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml +++ b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml @@ -186,14 +186,20 @@ has_uc_award_with_carer_element: [true, false, false, false] is_cared_for_by_carer_benefit_recipient: [false, false, true, false] -- name: "Disclosed limit: a carer whose income tapers the award to nil still bars the premium" - # With £25,000 of earnings the carer's award is nil (income reduction - # 7,610.88 against a maximum of 7,610.88), so in law there is no award - # (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17) and the premium, - # 86.05 x 52 = 4,474.60, is payable. The model reads the award from its - # conditions, because the award reads Pension Credit, which reads the - # addition, so it withholds the premium. +- name: A carer whose income tapers the award to nil has no award and does not bar the premium + # The carer's maximum amount is (424.90 + 209.34) x 12 = 7,610.88. Earnings + # of £25,000 less income tax (25,000 - 12,570) x 0.20 = 2,486.00 and + # National Insurance (25,000 - 12,570) x 0.08 = 994.40 leave 21,519.60, and + # with no work allowance the taper takes 0.55 x 21,519.60 = 11,835.78. That + # exceeds the maximum amount even with no non-dependant deduction + # (uc_award_upper_bound; the disabled person's family, which does not + # claim, has a bound of its standard allowance, 424.90 x 12 = 5,098.80), so + # the award is nil and there is no award (Welfare Reform Act 2012 s.5(1)(b); + # UC Regs 2013 reg 17). No one has an award in + # respect of caring for the disabled person, who keeps the premium, 86.05 x + # 52 = 4,474.60. period: 2026 + absolute_error_margin: 0.01 input: people: disabled: @@ -216,9 +222,131 @@ members: [disabled, carer] country: ENGLAND output: + uc_earned_income: [0, 21_519.60] universal_credit: [0, 0] + uc_award_upper_bound: [5_098.80, 0] receives_uc_carer_element: [false, false] + has_uc_award_with_carer_element: [false, false] + is_cared_for_by_carer_benefit_recipient: [false, false] + severe_disability_premium: [4_474.60, 0] + +- name: A renting carer's housing costs element keeps an award that bars the premium + # The carer of the case above, earning £25,000, rents from the council at + # £6,000 a year. The housing costs element raises the maximum amount to + # 7,610.88 + 6,000 = 13,610.88, and the taper of 11,835.78 leaves an award + # of 1,775.10. The disabled person, on the PIP daily living component, + # brings no non-dependant deduction (UC Regs 2013 Sch 4 para 16(2)(e)), so + # the award equals its bound. It includes the carer element and bars the + # premium. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + is_blind: true + care_hours: 35 + would_claim_carers_allowance: false + employment_income: 25_000 + is_household_head: true + disabled: + age: 50 + pip_dl_category: STANDARD + benunits: + carer_unit: + members: [carer] + disabled_unit: + members: [disabled] + would_claim_uc: false + households: + household: + members: [carer, disabled] + country: ENGLAND + tenure_type: RENT_FROM_COUNCIL + rent: 6_000 + output: + uc_housing_costs_element: [6_000, 0] + universal_credit: [1_775.10, 0] + uc_award_upper_bound: [1_775.10, 5_098.80] + has_uc_award_with_carer_element: [true, false] + is_cared_for_by_carer_benefit_recipient: [false, true] + severe_disability_premium: [0, 0] + +- name: A son whose income tapers the award to nil does not bar a pensioner's severe disability addition + # The son of the third case earns £20,000: less income tax 1,486.00 and + # National Insurance 594.40, the taper takes 0.55 x 17,919.60 = 9,855.78, + # more than his maximum amount, 7,610.88. He has no award, so the addition + # and the pension-age Housing Benefit premium stay at 86.05 x 52 = + # 4,474.60 (SPC Regs 2002 Sch I para 1(1)(a)(iii); HB(SPC) Regs 2006 Sch 3 + # para 6(2)(a)(iii)). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 75 + aa_category: HIGHER + son: + age: 40 + is_blind: true + care_hours: 35 + would_claim_carers_allowance: false + employment_income: 20_000 + benunits: + pensioner_unit: + members: [pensioner] + son_unit: + members: [son] + households: + household: + members: [pensioner, son] + country: ENGLAND + output: + uc_earned_income: [0, 17_919.60] + universal_credit: [0, 0] + uc_award_upper_bound: [0, 0] + has_uc_award_with_carer_element: [false, false] + is_cared_for_by_carer_benefit_recipient: [false, false] + severe_disability_minimum_guarantee_addition: [4_474.60, 0] + pension_age_severe_disability_premium: [4_474.60, 0] + +- name: A stored award is read as the award, even without a claim + # The carer of the first case earns £20,000, which tapers the award the + # model would work out to nil, and neither family claims. Universal Credit + # is supplied instead: nil for the disabled person's family and £100 for + # the carer's. A stored award reads no other variable, so the carer test + # reads it (receives_uc_carer_element): the carer's award includes the + # carer element and bars the premium. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + disabled: + age: 50 + pip_dl_category: STANDARD + carer: + age: 40 + is_blind: true + care_hours: 35 + would_claim_carers_allowance: false + employment_income: 20_000 + benunits: + disabled_unit: + members: [disabled] + claims_universal_credit: false + universal_credit: 0 + carer_unit: + members: [carer] + claims_universal_credit: false + universal_credit: 100 + households: + household: + members: [disabled, carer] + country: ENGLAND + output: + receives_uc_carer_element: [false, true] has_uc_award_with_carer_element: [false, true] + is_cared_for_by_carer_benefit_recipient: [true, false] severe_disability_premium: [0, 0] - name: Without a Universal Credit claim there is no carer element award diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index d02cedc855..96b91f99e3 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -9,16 +9,19 @@ 2021/249 Sch 1 paras 5 and 6(1)(c), from 1 April 2022) pays the carer premium to an applicant or partner with such an award. -The model reads the award from its conditions (has_uc_award_with_carer_element) -for the severe disability carer test, since the award itself reads Pension -Credit, and from the positive award (receives_uc_carer_element) for the -Scottish carer premium. Each example simulates the households as drawn, and +The model reads the award from its conditions and an upper bound on it +(has_uc_award_with_carer_element, uc_award_upper_bound) for the severe +disability carer test, since the award itself reads Pension Credit, unless the +simulation stores the award. Scotland's carer premium reads the positive award +(receives_uc_carer_element), or from 1 April 2022 to 31 March 2025, when an +award of nil counted, the award's conditions. Each example simulates the +households as drawn, and again with has_uc_award_with_carer_element supplied as false for everyone, the attribution as it ran before the limb (its carers are then receives_carer_benefit alone). The Scottish premium's counterpart without the limb is carer_premium. -Invariants, for any generated population of households: +Invariants, on generated households under baseline law: 1. No change without a carer element: where no one has has_uc_award_with_carer_element or receives_uc_carer_element, the @@ -35,13 +38,18 @@ the number of claimants and partners, and at most one where partners care for the same person; a person with both an allowance and a carer element counts once. -5. The read before the award misses no award: receives_uc_carer_element - implies has_uc_award_with_carer_element. +5. Where the bound is nil, the award is nil: uc_award_upper_bound is at least + universal_credit_pre_benefit_cap, which is at least universal_credit, so + receives_uc_carer_element implies has_uc_award_with_carer_element. That + implication also holds where the simulation stores the award. 6. Attribution: no one is their own carer, everyone cared for is severely disabled for Carer's Allowance, and a household has no more people cared for than carers (carer benefit recipients and carer element holders). 7. No cycle: every reader calculates on one simulation, whichever is requested - first. + first, and also where the simulation stores the award. + +A reform that makes is_uc_eligible read Pension Credit would cycle through the +attribution, which reads eligibility; these tests do not cover reforms. The no-change invariant compares the two code paths in this tree; the differential against origin/main itself runs on hand-built datasets outside @@ -89,6 +97,7 @@ "is_cared_for_by_carer_benefit_recipient", "has_uc_award_with_carer_element", "receives_uc_carer_element", + "uc_award_upper_bound", "universal_credit", "pension_credit", "housing_benefit", @@ -145,8 +154,23 @@ def benefit_unit(draw): @st.composite def households(draw): + # Renters pay the housing costs element, and the household head's family + # bears a deduction for each non-dependant in another benefit unit, which + # the upper bound leaves out. + tenure, rent = draw( + st.sampled_from( + [ + ("OWNED_OUTRIGHT", 0), + ("RENT_PRIVATELY", 6_000), + ("RENT_PRIVATELY", 12_000), + ("RENT_FROM_COUNCIL", 6_000), + ] + ) + ) return dict( country=draw(st.sampled_from(["ENGLAND", "SCOTLAND", "WALES"])), + tenure=tenure, + rent=rent, units=draw(st.lists(benefit_unit(), min_size=1, max_size=3)), ) @@ -185,6 +209,9 @@ def situation(population, without_limb=()): benunits[f"h{h}_u{u}"] = {"members": unit_members} members += unit_members hh[f"h{h}"] = {"members": members, "country": {YEAR: household["country"]}} + if "tenure" in household: + hh[f"h{h}"]["tenure_type"] = {YEAR: household["tenure"]} + hh[f"h{h}"]["rent"] = {YEAR: household["rent"]} return {"people": people, "benunits": benunits, "households": hh} @@ -310,6 +337,9 @@ def test_the_carer_element_limb_moves_only_its_readers_and_only_one_way(populati "receives_uc_carer_element", "receives_carer_benefit", "is_claimant_or_partner", + "uc_award_upper_bound", + "universal_credit_pre_benefit_cap", + "universal_credit", ) sim, values = with_and_without_limb(population, variables) drawn = {v: pair[0] for v, pair in values.items()} @@ -318,7 +348,12 @@ def test_the_carer_element_limb_moves_only_its_readers_and_only_one_way(populati benunit_household = household_index(sim, "benunit") person_household = household_index(sim, "person") - # 5. The read before the award misses no award. + # 5. Where the bound is nil the award is nil, exactly, so the carer test + # misses no award. + bound = drawn["uc_award_upper_bound"] + award = drawn["universal_credit_pre_benefit_cap"] + assert (bound >= award).all() + assert (award >= drawn["universal_credit"]).all() has_award = drawn["has_uc_award_with_carer_element"] positive_award = drawn["receives_uc_carer_element"] assert not (positive_award & ~has_award).any() @@ -401,3 +436,193 @@ def test_every_reader_calculates_on_one_simulation_in_any_order(population, orde for variable in order: values = np.asarray(sim.calculate(variable, YEAR)) assert np.isfinite(values.astype(float)).all(), variable + + +def stored_award_situation(stored=True): + """The issue's blind carer on £20,000, which tapers the award the model + works out to nil, with neither family claiming; the award is supplied + instead: nil for the disabled person's family, £100 for the carer's.""" + sim_situation = situation( + [ + dict( + country="ENGLAND", + units=[ + dict(adults=[single(50, disability="pip_standard")], children=[]), + dict( + adults=[single(40, blind=True, care_hours=35, earnings=20_000)], + children=[], + ), + ], + ) + ] + ) + for unit, award in (("h0_u0", 0), ("h0_u1", 100)): + sim_situation["benunits"][unit]["claims_universal_credit"] = {YEAR: False} + if stored: + sim_situation["benunits"][unit]["universal_credit"] = {YEAR: award} + return sim_situation + + +@pytest.mark.parametrize("order", [ALL_READERS, tuple(reversed(ALL_READERS))]) +def test_a_stored_award_is_the_award_and_reads_no_cycle(order): + # A stored award reads no other variable, so the carer test reads it: the + # carer's £100 award includes the carer element and bars the disabled + # person's premium, 86.05 x 52 = 4,474.60 without it. Every reader then + # calculates on the one simulation (invariants 5 and 7). + sim = Simulation(situation=stored_award_situation()) + for variable in order: + values = np.asarray(sim.calculate(variable, YEAR)) + assert np.isfinite(values.astype(float)).all(), variable + has_award = sim.calculate("has_uc_award_with_carer_element", YEAR) + receives = sim.calculate("receives_uc_carer_element", YEAR) + assert receives.tolist() == [False, True] + assert has_award.tolist() == [False, True] + assert sim.calculate("is_cared_for_by_carer_benefit_recipient", YEAR).tolist() == [ + True, + False, + ] + assert sim.calculate("severe_disability_premium", YEAR).tolist() == [0, 0] + # Without the stored award neither family claims, so no one has an award. + unstored = Simulation(situation=stored_award_situation(stored=False)) + assert not unstored.calculate("has_uc_award_with_carer_element", YEAR).any() + assert unstored.calculate("severe_disability_premium", YEAR)[0] == pytest.approx( + 86.05 * WEEKS + ) + + +def test_a_reform_that_abolishes_universal_credit_leaves_no_carer_element_award(): + # A neutralized award reads no other variable and is nil, so no one has + # an award and the blind carer of the issue no longer bars the premium. + from policyengine_core.reforms import Reform + + class AbolishUniversalCredit(Reform): + def apply(self): + self.neutralize_variable("universal_credit") + + sim = Simulation(situation=situation(EXAMPLES[:1]), reform=AbolishUniversalCredit) + assert not sim.calculate("has_uc_award_with_carer_element", YEAR).any() + assert sim.calculate("severe_disability_premium", YEAR)[0] == pytest.approx( + 86.05 * WEEKS + ) + + +def require(condition, message): + # A broken premise is an error, not the disclosed failure. + if not condition: + raise RuntimeError(message) + + +def non_dependant_situation(): + """A carer (40) who rents jointly with the disabled person (50) she cares + for, each liable for half of £12,000, and a blind non-dependant (30) + earning £20,000. The carer earns £27,000.""" + people = { + "carer": { + "age": {YEAR: 40}, + "care_hours": {YEAR: 35}, + "would_claim_carers_allowance": {YEAR: False}, + "employment_income": {YEAR: 27_000}, + "is_household_head": {YEAR: True}, + }, + "disabled": {"age": {YEAR: 50}, "pip_dl_category": {YEAR: "STANDARD"}}, + "non_dependant": { + "age": {YEAR: 30}, + "is_blind": {YEAR: True}, + "employment_income": {YEAR: 20_000}, + }, + } + benunits = { + "carer_unit": {"members": ["carer"], "share_of_household_rent": {YEAR: 0.5}}, + "disabled_unit": { + "members": ["disabled"], + "would_claim_uc": {YEAR: False}, + "liable_for_share_of_household_rent": {YEAR: True}, + "share_of_household_rent": {YEAR: 0.5}, + "severe_disability_premium_joint_occupier_exclusion_applies": {YEAR: True}, + }, + "non_dependant_unit": { + "members": ["non_dependant"], + "would_claim_uc": {YEAR: False}, + }, + } + household = { + "members": ["carer", "disabled", "non_dependant"], + "country": {YEAR: "ENGLAND"}, + "tenure_type": {YEAR: "RENT_PRIVATELY"}, + "rent": {YEAR: 12_000}, + } + return {"people": people, "benunits": benunits, "households": {"h": household}} + + +def tax_free_childcare_situation(): + """A blind carer (35) earning £60,000 with a child (3) in £12,000 of + childcare, and the disabled person (60) she cares for in another family + of the household.""" + people = { + "carer": { + "age": {YEAR: 35}, + "is_blind": {YEAR: True}, + "care_hours": {YEAR: 35}, + "would_claim_carers_allowance": {YEAR: False}, + "employment_income": {YEAR: 60_000}, + }, + "child": {"age": {YEAR: 3}, "childcare_expenses": {YEAR: 12_000}}, + "disabled": {"age": {YEAR: 60}, "pip_dl_category": {YEAR: "STANDARD"}}, + } + benunits = { + "carer_unit": {"members": ["carer", "child"]}, + "disabled_unit": {"members": ["disabled"], "would_claim_uc": {YEAR: False}}, + } + household = { + "members": ["carer", "child", "disabled"], + "country": {YEAR: "ENGLAND"}, + } + return {"people": people, "benunits": benunits, "households": {"h": household}} + + +@pytest.mark.xfail( + raises=AssertionError, + strict=True, + reason=( + "Disclosed limit: the carer test reads the award before the " + "non-dependant deduction, which takes this award to nil." + ), +) +def test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award(): + # The non-dependant's deduction (UC Regs 2013 Sch 4 paras 13 and 16; a + # blind non-dependant has no exemption, and the carer is no exempt renter + # under para 15) takes the carer's award to nil, so she has no award + # (Welfare Reform Act 2012 s.5(1)(b); reg 17). The disabled person is a + # joint tenant, not her non-dependant (para 9(2)(d)), and she is not his + # (HB Regs 2006 reg 3(2)(d)); a blind non-dependant does not bar the + # premium (Sch 3 para 14(4)). So the law pays him the premium. + sim = Simulation(situation=non_dependant_situation()) + award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) + bound = sim.calculate("uc_award_upper_bound", YEAR) + deductions = sim.calculate("uc_non_dep_deductions", YEAR) + require(award[0] == 0 and bound[0] > 0, "the carer's award is not nil") + require(deductions[0] > bound[0], "the deduction does not take it to nil") + severe = sim.calculate("severe_disability_premium", YEAR) + assert severe[1] == pytest.approx(86.05 * WEEKS) + + +@pytest.mark.xfail( + raises=AssertionError, + strict=True, + reason=( + "Disclosed limit: the carer test cannot read whether a family takes " + "Tax-Free Childcare instead, which reads the award." + ), +) +def test_a_family_that_takes_tax_free_childcare_instead_has_no_award(): + # The family is better off with the Tax-Free Childcare top-up, so it + # takes no award (forgoes_universal_credit_for_tax_free_childcare); with + # no award, no one has one in respect of caring for the disabled person, + # and the law pays him the premium. + sim = Simulation(situation=tax_free_childcare_situation()) + forgoes = sim.calculate("forgoes_universal_credit_for_tax_free_childcare", YEAR) + award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) + bound = sim.calculate("uc_award_upper_bound", YEAR) + require(forgoes[0] and award[0] == 0 and bound[0] > 0, "the family claims") + severe = sim.calculate("severe_disability_premium", YEAR) + assert severe[1] == pytest.approx(86.05 * WEEKS) diff --git a/policyengine_uk/utils/supplied_inputs.py b/policyengine_uk/utils/supplied_inputs.py index 38897f81bf..3e1bdd4ed2 100644 --- a/policyengine_uk/utils/supplied_inputs.py +++ b/policyengine_uk/utils/supplied_inputs.py @@ -85,6 +85,7 @@ "trading_loss", "uc_carer_element", "uc_LCWRA_element", + "universal_credit", "universal_childcare_entitlement", } ) 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 cb7369c9ef..9afa73c9ae 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 @@ -29,7 +29,8 @@ class severe_disability_minimum_guarantee_addition(Variable): "is_cared_for_by_carer_benefit_recipient, which the legacy severe " "disability premium shares; a carer benefit there includes an award " "of Universal Credit which includes the carer element (para 1(4)), " - "read from the award's conditions rather than its amount. Blindness " + "read from the award's conditions and the award before any " + "non-dependant deduction (has_uc_award_with_carer_element). Blindness " "(is_blind) is not in the survey data, so in microsimulation the " "blind-partner route never applies and a blind resident is counted." ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py index e442d0cb7b..fe18c62e1d 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py @@ -1,32 +1,45 @@ from policyengine_uk.model_api import * +from policyengine_uk.utils.supplied_inputs import stored_as_input + + +def award_reads_no_other_variable(simulation, period): + """Whether reading universal_credit for this period reads no other + variable: a reform neutralized it, or an input or a scenario stored its + value for the period.""" + variable = simulation.tax_benefit_system.get_variable("universal_credit") + return variable.is_neutralized or stored_as_input( + simulation.populations["benunit"], "universal_credit", period + ) class has_uc_award_with_carer_element(Variable): value_type = bool entity = Person - label = "has a Universal Credit award with the carer element, read before the award" + label = "has a Universal Credit award with the carer element" documentation = ( "Whether this person has an award of Universal Credit which includes " "the carer element (Universal Credit Regulations 2013 reg 29) in " - "respect of them, read from the conditions of the award rather than " - "its amount: the family claims Universal Credit " - "(claims_universal_credit), meets the age, pension-route and capital " - "conditions (is_uc_eligible), and the award includes the carer " - "element in respect of this claimant " - "(uc_carer_element_included_for_claimant). It is the carer condition " - "of the legacy severe disability premium (HB Regs 2006 Sch 3 para " - "14(2)(a)(iii) and (b)) and of the Pension Credit severe disability " - "addition (SPC Regs 2002 Sch I para 1(1) and (4)), through " - "is_cared_for_by_carer_benefit_recipient. Those cannot read the award " - "itself: the housing costs element's non-dependant deduction reads " - "Pension Credit, which reads the addition. A claimant whose income " - "tapers the award to nil fails the financial conditions (Welfare " - "Reform Act 2012 s.5(1)(b) and (2)(b); the minimum is one penny, reg " - "17), so has no award in law, but still counts here. So does a family " - "that forgoes Universal Credit for Tax-Free Childcare " + "respect of them: the carer condition of the legacy severe disability " + "premium (HB Regs 2006 Sch 3 para 14(2)(a)(iii) and (b)) and of the " + "Pension Credit severe disability addition (SPC Regs 2002 Sch I para " + "1(1) and (4)), through is_cared_for_by_carer_benefit_recipient. " + "Those cannot read the award: its non-dependant deduction spares a " + "non-dependant on Pension Credit, so it reads Pension Credit, which " + "reads the addition. So this reads the award's conditions instead. " + "The family claims Universal Credit (claims_universal_credit), meets " + "the age, pension-route and capital conditions (is_uc_eligible) and " + "has an award before the non-dependant deduction (uc_award_upper_bound " + "positive), and the award includes the carer element in respect of " + "this claimant (uc_carer_element_included_for_claimant). Where the " + "bound is nil, the award is nil and there is no award (Welfare Reform " + "Act 2012 s.5(1)(b) and (2)(b); the minimum is one penny, reg 17), so " + "a carer whose income tapers the award to nil does not count. A carer " + "whose award a non-dependant deduction takes to nil still counts, and " + "so does a family that takes Tax-Free Childcare instead " "(forgoes_universal_credit_for_tax_free_childcare reads the award). " - "receives_uc_carer_element is the same test on a positive award, for " - "readers that can read it." + "Where an input or a scenario stores universal_credit for the period, " + "or a reform neutralizes it, reading it cannot cycle, so this reads " + "the award itself (receives_uc_carer_element)." ) definition_period = YEAR reference = ( @@ -38,9 +51,13 @@ class has_uc_award_with_carer_element(Variable): ) def formula(person, period, parameters): + if award_reads_no_other_variable(person.simulation, period): + return person("receives_uc_carer_element", period) benunit = person.benunit - on_claim = benunit("claims_universal_credit", period) & benunit( - "is_uc_eligible", period + on_claim = ( + benunit("claims_universal_credit", period) + & benunit("is_uc_eligible", period) + & (benunit("uc_award_upper_bound", period) > 0) ) included = person("uc_carer_element_included_for_claimant", period) return on_claim & included diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py new file mode 100644 index 0000000000..16f7cca626 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py @@ -0,0 +1,97 @@ +from policyengine_uk.model_api import * + + +class uc_award_upper_bound(Variable): + value_type = float + entity = BenUnit + label = "Universal Credit award before the non-dependant deduction" + documentation = ( + "An amount the family's award before the benefit cap " + "(universal_credit_pre_benefit_cap) never exceeds, found without the " + "non-dependant deduction. The award is the maximum amount less the " + "income reduction (UC Regs 2013 reg 22). Its housing costs element " + "(uc_housing_costs_element) is the rent a renter may claim less the " + "non-dependant deductions, and its work allowance is the one with " + "housing where that element is positive. A deduction smaller than " + "the rent leaves the award at most what it would be with no " + "deduction; a deduction as large as the rent removes the element and " + "gives the work allowance without housing. This is the larger of " + "those two awards. It repeats the award's steps in the same order, " + "rounding where they store a value, so it is never below the award. " + "Where it is nil the family has no award, since an amount below one " + "penny is no award (Welfare Reform Act 2012 s.5(1)(b) and (2)(b); " + "reg 17). The deduction spares a non-dependant on Pension Credit, so " + "it reads Pension Credit, whose severe disability addition reads who " + "has an award with the carer element. This amount reads neither, so " + "that test can read it (has_uc_award_with_carer_element). It can be " + "positive where the award is nil: a non-dependant deduction can take " + "the award to nil, and a family that takes Tax-Free Childcare instead " + "gets none (forgoes_universal_credit_for_tax_free_childcare)." + ) + definition_period = YEAR + unit = GBP + defined_for = "is_uc_eligible" + reference = ( + "https://www.legislation.gov.uk/uksi/2013/376/regulation/22", + "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/13", + "https://www.legislation.gov.uk/ukpga/2012/5/section/5", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/17", + ) + + def formula(benunit, period, parameters): + p = parameters(period).gov.dwp.universal_credit.means_test + # Housing costs before any non-dependant deduction, as in + # uc_housing_costs_element. + tenure_type = benunit("benunit_tenure_type", period) + tenure_types = tenure_type.possible_values + rent = benunit("benunit_rent", period) + capped_rent_amount = min_(benunit("uc_LHA_cap", period), rent) + max_housing_costs = select( + [ + (tenure_type == tenure_types.RENT_FROM_COUNCIL) + | (tenure_type == tenure_types.RENT_FROM_HA), + tenure_type == tenure_types.RENT_PRIVATELY, + ], + [rent, capped_rent_amount], + default=0, + ) + no_deduction = max_(max_housing_costs, 0).astype(np.float32) + elements = [ + benunit(name, period) + for name in ( + "uc_standard_allowance", + "uc_child_element", + "uc_disability_elements", + "uc_carer_element", + ) + ] + childcare = benunit("uc_childcare_element", period) + earned_income = benunit("uc_earned_income_before_work_allowance", period) + unearned_income = benunit("uc_unearned_income", period) + work_allowance_eligible = benunit("is_uc_work_allowance_eligible", period) + awards = [] + for housing in (no_deduction, np.zeros_like(no_deduction)): + # The steps of uc_maximum_amount (its adds, in order), + # uc_work_allowance, uc_earned_income, uc_income_reduction and + # universal_credit_pre_benefit_cap, each rounded to float32 where + # that variable stores its value, so no rounding puts the bound + # below the award. + maximum = 0 + for element in elements: + maximum = maximum + element + maximum = maximum + housing + childcare + monthly_allowance = where( + housing > 0, + p.work_allowance.with_housing, + p.work_allowance.without_housing, + ) + work_allowance = where( + work_allowance_eligible, + (monthly_allowance * MONTHS_IN_YEAR).astype(np.float32), + 0, + ).astype(np.float32) + earned = max_(0, earned_income - work_allowance) + total_reduction = p.reduction_rate * earned + unearned_income + reduction = min_(maximum, total_reduction).astype(np.float32) + awards.append(maximum - reduction) + return max_(awards[0], awards[1]) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py index 5579a2b6af..dfabb17911 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py @@ -9,28 +9,43 @@ class council_tax_reduction_carer_premium(Variable): documentation = ( "The carer premium in a working-age Council Tax Reduction applicable " "amount. Outside Scotland's working-age scheme it is carer_premium. " - "Scotland's scheme (SSI 2021/249 Sch 1 paras 5 and 6, from 1 April " - "2022) pays it in respect of each applicant or partner who has " - "regular and substantial caring responsibilities for a severely " - "disabled person. Para 6(1) defines those as (a) receiving Carer's " - "Allowance or Carer Support Payment, (b) being entitled to one that " - "the overlapping benefits rules reduce to nil, or (c) having an award " - "of Universal Credit which includes the carer element. Limbs (a) and " - "(b) are carer_premium's test (is_entitled_to_carer_benefit); limb (c) " - "is receives_uc_carer_element, which needs a positive award, since an " - "income that tapers the award to nil leaves no award (Welfare Reform " - "Act 2012 s.5(1)(b), UC Regs 2013 reg 17). A person with both an " - "allowance and a carer element qualifies once: the premium is for each " - "partner who qualifies (para 5(3)), not for each award. Partners " - "caring for the same severely disabled person get one premium (para " - "5(3) and (4); partners_care_for_same_severely_disabled_person), as in " - "carer_premium. Para 5(4) then pays it in respect of the one whose " - "Universal Credit award includes the carer element, then one in " - "receipt of an allowance, then one entitled to an allowance reduced " - "to nil; that choice does not change the amount. Before 1 April 2022 " - "the scheme for persons under pension age, SSI 2012/303 Sch 1 para " - "14, tested entitlement to Carer's Allowance only " - "(carer_premium_counts_uc_carer_element)." + "In Scotland's scheme (SSI 2021/249 Sch 1 paras 5 and 6, from 1 April " + "2022) councils pay it in respect of each applicant or partner who " + "has regular and substantial caring responsibilities for a severely " + "disabled person. The Scottish Ministers define those in para 6(1) " + "as (a) receiving Carer's Allowance or Carer Support Payment, (b) " + "being entitled to one that the overlapping benefits rules reduce to " + "nil, or (c) having an award of Universal Credit which includes the " + "carer element. Limbs (a) and (b) are carer_premium's test " + "(is_entitled_to_carer_benefit). For limb (c), the Scottish Ministers " + "defined an award of Universal Credit to include one with a value of " + "zero from 1 April 2022 (reg 4, as amended by SSI 2022/52 reg 3), and " + "from 1 April 2025 as an award made under Part 1 of the Welfare Reform " + "Act 2012 (SSI 2025/24 reg 8), under which an income that tapers the " + "award to nil leaves no award (s.5(1)(b); UC Regs 2013 reg 17). So " + "while the earlier definition holds " + "(uc_award_includes_zero_value_award), limb (c) reads the award's " + "conditions: the family claims Universal Credit, meets its basic " + "conditions and does not take Tax-Free Childcare instead, and the " + "award includes the carer element for this claimant. Afterwards it " + "reads the positive award (receives_uc_carer_element). From 1 April " + "2025 the Scottish Ministers also count two nil awards as awards (reg " + "10A): one a sanction reduces to nil, and one whose claimant the " + "Secretary of State treats as reclaiming after earnings end it (UC " + "Claims and Payments Regs 2013 reg 32A). The model has neither " + "sanctions nor reclaims, so it pays no premium for those. A person " + "with both an allowance and a carer element qualifies once: councils " + "pay the premium for each partner who qualifies (para 5(3)), not for " + "each award. Partners caring for the same severely disabled person " + "get one premium (para 5(3) and (4); " + "partners_care_for_same_severely_disabled_person), as in " + "carer_premium. Councils give it, under para 5(4), in respect of the " + "one whose Universal Credit award includes the carer element first, " + "then one in receipt of an allowance, then one entitled to an " + "allowance reduced to nil; that choice does not change the amount. " + "Before 1 April 2022 councils paid the premium to persons under " + "pension age on entitlement to Carer's Allowance only (SSI 2012/303 " + "Sch 1 para 14; carer_premium_counts_uc_carer_element)." ) definition_period = YEAR unit = GBP @@ -38,6 +53,9 @@ class council_tax_reduction_carer_premium(Variable): "https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/5", "https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/6", "https://www.legislation.gov.uk/ssi/2021/249/regulation/1", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/4/2024-04-30", + "https://www.legislation.gov.uk/ssi/2025/24/regulation/8/made", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/10A", "https://www.legislation.gov.uk/ssi/2012/303/schedule/1/paragraph/14/2021-04-01", "https://www.legislation.gov.uk/uksi/2013/376/regulation/29", "https://www.legislation.gov.uk/ukpga/2012/5/section/5", @@ -55,11 +73,22 @@ def formula(benunit, period, parameters): ) & ~benunit("council_tax_reduction_pensioner", period) person = benunit.members claimant_or_partner = person("is_claimant_or_partner", period) - # SSI 2021/249 Sch 1 para 6(1)(a) and (b), as in carer_premium, or - # (c) an award of Universal Credit which includes the carer element. - caring = person("is_entitled_to_carer_benefit", period) | person( - "receives_uc_carer_element", period - ) + # SSI 2021/249 Sch 1 para 6(1)(c): an award of Universal Credit which + # includes the carer element. Until 31 March 2025 an award with a + # value of zero counts (reg 4), so the award's conditions decide. + carer_element_award = person("receives_uc_carer_element", period) + if scotland.uc_award_includes_zero_value_award: + family = person.benunit + on_claim = ( + family("claims_universal_credit", period) + & family("is_uc_eligible", period) + & ~family("forgoes_universal_credit_for_tax_free_childcare", period) + ) + carer_element_award = carer_element_award | ( + on_claim & person("uc_carer_element_included_for_claimant", period) + ) + # Para 6(1)(a) and (b), as in carer_premium, or (c). + caring = person("is_entitled_to_carer_benefit", period) | carer_element_award qualifying_carers = benunit.sum(claimant_or_partner & caring) same_person = benunit("partners_care_for_same_severely_disabled_person", period) qualifying_carers = where( diff --git a/policyengine_uk/variables/household/demographic/carer_premium.py b/policyengine_uk/variables/household/demographic/carer_premium.py index bc9adbff80..5a0a9aa9bf 100644 --- a/policyengine_uk/variables/household/demographic/carer_premium.py +++ b/policyengine_uk/variables/household/demographic/carer_premium.py @@ -25,10 +25,10 @@ class carer_premium(Variable): "person caps the benefit unit at one amount. The premium enters the " "Income Support, Housing Benefit and council tax reduction applicable " "amounts; the model takes income-based JSA and income-related ESA " - "from reported awards. Scotland's working-age council tax reduction " - "also pays it for an award of Universal Credit which includes the " - "carer element (council_tax_reduction_carer_premium); no other " - "scheme's carer premium has that limb." + "from reported awards. In Scotland's working-age council tax " + "reduction, councils also pay it for an award of Universal Credit " + "which includes the carer element (council_tax_reduction_carer_premium); " + "no other scheme's carer premium has that limb." ) definition_period = YEAR reference = ( 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 3598aa6d40..f730177fa1 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 @@ -46,13 +46,15 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "whose allowance an overlapping benefit, such as a higher State " "Pension, reduces to nil is entitled but not in receipt, so is not " "attributed for the allowance. A carer element award is " - "has_uc_award_with_carer_element, read from the claim and the award's " - "conditions rather than its amount, because the Universal Credit " - "non-dependant deduction exemption reads Pension Credit, which reads " - "the addition. So a carer whose income tapers the award to nil, who " - "has no award in law (Welfare Reform Act 2012 s.5), is still " - "attributed, and can withhold a premium or addition the law would " - "pay. A carer with both an allowance and a carer element is one " + "has_uc_award_with_carer_element, read from the claim, the award's " + "conditions and the award before any non-dependant deduction, because " + "the Universal Credit non-dependant deduction exemption reads Pension " + "Credit, which reads the addition. A carer whose income tapers the " + "award to nil has no award (Welfare Reform Act 2012 s.5) and is not " + "attributed for it. A carer whose award a non-dependant deduction " + "takes to nil, or whose family takes Tax-Free Childcare instead, is " + "still attributed, and can withhold a premium or addition the law " + "would pay. A carer with both an allowance and a carer element is one " "carer. Partners who care for the same severely disabled person " "(partners_care_for_same_severely_disabled_person) have one relevant " "entitlement between them (s.70(7ZA) and (7A)), so where one of them " diff --git a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py index 4e0fb09af5..161fdfe801 100644 --- a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py @@ -21,8 +21,9 @@ class pension_age_severe_disability_premium(Variable): "claim, as a couple may agree under HB(SPC) reg 63(1) and the CTR " "schemes (England SI 2012/2885 Sch 8 para 4(1)); carers are attributed " "by is_cared_for_by_carer_benefit_recipient, which counts a Universal " - "Credit award with the carer element from the award's conditions " - "rather than its amount. The Pension Credit severe " + "Credit award with the carer element from the award's conditions and " + "the award before any non-dependant deduction. The Pension Credit " + "severe " "disability addition has the same conditions apart from its wider " "residence exceptions, but its own amount parameter, so a Pension " "Credit reform does not move this premium." diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index 0147125bfc..3d2b665905 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -23,9 +23,10 @@ class severe_disability_premium(Variable): "attributed by is_cared_for_by_carer_benefit_recipient, shared with " "the Pension Credit severe disability addition. It counts an award " "of Universal Credit which includes the carer element from the " - "award's conditions rather than its amount " + "award's conditions and the award before any non-dependant deduction " "(has_uc_award_with_carer_element), so a carer whose income tapers " - "the award to nil still bars the premium. Not modelled: a carer's " + "the award to nil does not bar the premium, but one whose award a " + "non-dependant deduction takes to nil does. 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 " From 1f87a159d965b545aca6d308666d6f24f0977640 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 02:51:00 -0500 Subject: [PATCH 04/21] Test that the award bound reads neither the award nor Pension Credit A traced calculation of uc_award_upper_bound for a pensioner and her blind son, who rent privately, reaches none of pension_credit, the housing costs element, the non-dependant deduction or its exemption, the maximum amount, the income reduction, the award or the attribution. The changelog now gives the Scottish nil-award dates. Co-Authored-By: Claude Opus 5.5 --- changelog.d/2120.fixed.md | 2 +- .../test_uc_carer_element_award_properties.py | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/changelog.d/2120.fixed.md b/changelog.d/2120.fixed.md index 3e754bdf83..443c7a80b7 100644 --- a/changelog.d/2120.fixed.md +++ b/changelog.d/2120.fixed.md @@ -1 +1 @@ -The model now withholds the severe disability premium and the Pension Credit severe disability addition when a carer's Universal Credit award includes the carer element for the claimant or partner, and pays the carer premium in Scotland's working-age council tax reduction for such an award from 1 April 2022 (#2120, #2124). +The model now withholds the severe disability premium and the Pension Credit severe disability addition when a carer's Universal Credit award includes the carer element for the claimant or partner, and pays the carer premium in Scotland's working-age council tax reduction for such an award from 1 April 2022, counting an award of nil until 31 March 2025 (#2120, #2124). diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index 96b91f99e3..40202f4e6c 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -626,3 +626,54 @@ def test_a_family_that_takes_tax_free_childcare_instead_has_no_award(): require(forgoes[0] and award[0] == 0 and bound[0] > 0, "the family claims") severe = sim.calculate("severe_disability_premium", YEAR) assert severe[1] == pytest.approx(86.05 * WEEKS) + + +def test_the_bound_reads_neither_the_award_nor_pension_credit(): + # The carer test reads uc_award_upper_bound because the award reads + # Pension Credit (through the non-dependant deduction's exemption), whose + # addition reads the attribution. A pensioner on Attendance Allowance + # shares a privately rented home with her blind son, who claims Universal + # Credit with the carer element; the trace of the bound reaches none of + # those variables. + forbidden = { + "pension_credit", + "uc_housing_costs_element", + "uc_non_dep_deductions", + "uc_non_dep_deduction_exempt", + "uc_maximum_amount", + "uc_income_reduction", + "universal_credit_pre_benefit_cap", + "universal_credit", + "is_cared_for_by_carer_benefit_recipient", + } + sim = Simulation( + situation=situation( + [ + dict( + country="ENGLAND", + tenure="RENT_PRIVATELY", + rent=9_000, + units=[ + dict(adults=[single(75, disability="aa_higher")], children=[]), + dict( + adults=[single(40, blind=True, care_hours=35)], + children=[], + ), + ], + ) + ] + ) + ) + sim.trace = True + sim.calculate("uc_award_upper_bound", YEAR) + reached = set() + + def visit(node): + reached.add(node.name) + for child in node.children: + visit(child) + + for tree in sim.tracer.trees: + visit(tree) + assert "uc_carer_element" in reached + assert not reached & forbidden, reached & forbidden From c477b5d9d1acbd4d99ecb762b5a4c250782ea710 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 03:22:44 -0500 Subject: [PATCH 05/21] Describe the award bound and the carer test in active voice Docstrings only: uc_award_upper_bound names who deducts, awards and sets the minimum, and has_uc_award_with_carer_element states the nil-bound rule plainly. Co-Authored-By: Claude Opus 5.5 --- .../has_uc_award_with_carer_element.py | 8 ++-- .../universal_credit/uc_award_upper_bound.py | 45 ++++++++++--------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py index fe18c62e1d..97cd94c2d3 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py @@ -30,10 +30,10 @@ class has_uc_award_with_carer_element(Variable): "the age, pension-route and capital conditions (is_uc_eligible) and " "has an award before the non-dependant deduction (uc_award_upper_bound " "positive), and the award includes the carer element in respect of " - "this claimant (uc_carer_element_included_for_claimant). Where the " - "bound is nil, the award is nil and there is no award (Welfare Reform " - "Act 2012 s.5(1)(b) and (2)(b); the minimum is one penny, reg 17), so " - "a carer whose income tapers the award to nil does not count. A carer " + "this claimant (uc_carer_element_included_for_claimant). A nil bound " + "leaves a nil award, and so no award (Welfare Reform Act 2012 s.5(1)(b) " + "and (2)(b); DWP sets the minimum at one penny, reg 17), so a carer " + "whose income tapers the award to nil does not count. A carer " "whose award a non-dependant deduction takes to nil still counts, and " "so does a family that takes Tax-Free Childcare instead " "(forgoes_universal_credit_for_tax_free_childcare reads the award). " diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py index 16f7cca626..2514014f1b 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py @@ -6,27 +6,30 @@ class uc_award_upper_bound(Variable): entity = BenUnit label = "Universal Credit award before the non-dependant deduction" documentation = ( - "An amount the family's award before the benefit cap " - "(universal_credit_pre_benefit_cap) never exceeds, found without the " - "non-dependant deduction. The award is the maximum amount less the " - "income reduction (UC Regs 2013 reg 22). Its housing costs element " - "(uc_housing_costs_element) is the rent a renter may claim less the " - "non-dependant deductions, and its work allowance is the one with " - "housing where that element is positive. A deduction smaller than " - "the rent leaves the award at most what it would be with no " - "deduction; a deduction as large as the rent removes the element and " - "gives the work allowance without housing. This is the larger of " - "those two awards. It repeats the award's steps in the same order, " - "rounding where they store a value, so it is never below the award. " - "Where it is nil the family has no award, since an amount below one " - "penny is no award (Welfare Reform Act 2012 s.5(1)(b) and (2)(b); " - "reg 17). The deduction spares a non-dependant on Pension Credit, so " - "it reads Pension Credit, whose severe disability addition reads who " - "has an award with the carer element. This amount reads neither, so " - "that test can read it (has_uc_award_with_carer_element). It can be " - "positive where the award is nil: a non-dependant deduction can take " - "the award to nil, and a family that takes Tax-Free Childcare instead " - "gets none (forgoes_universal_credit_for_tax_free_childcare)." + "The family's award before the benefit cap " + "(universal_credit_pre_benefit_cap) never exceeds this amount, which " + "leaves out the non-dependant deduction. DWP awards the maximum " + "amount less the income reduction (UC Regs 2013 reg 22). The maximum " + "amount includes the housing costs element (uc_housing_costs_element): " + "the rent a renter may claim less DWP's non-dependant deductions. " + "Where that element is positive, DWP applies the work allowance with " + "housing. Where DWP deducts less than the rent, the award stays at or " + "below the award with no deduction; where it deducts at least the " + "rent, the award loses the housing costs element and takes the work " + "allowance without housing. This amount takes the larger of those two " + "awards. It repeats the award's steps in their order, rounding where " + "they store a value, so it never falls below the award. A family " + "whose bound comes to nil has a nil award, and so no award: " + "Parliament makes an amount payable of at least the prescribed " + "minimum a financial condition (Welfare Reform Act 2012 s.5(1)(b) " + "and (2)(b)), and DWP sets that minimum at one penny (reg 17). The " + "deduction spares a non-dependant on Pension Credit, so it reads " + "Pension Credit, whose severe disability addition reads who has an " + "award with the carer element. This amount reads neither, so that " + "test can read it (has_uc_award_with_carer_element). It can exceed a " + "nil award: a non-dependant deduction can take the award to nil, and " + "a family that takes Tax-Free Childcare instead gets none " + "(forgoes_universal_credit_for_tax_free_childcare)." ) definition_period = YEAR unit = GBP From 20edd2e7e7b76ce8a5d5d96330f6538061524ddf Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 03:28:21 -0500 Subject: [PATCH 06/21] Give the carer docstrings their actors and the bound an exact label Docstrings and one label only. uc_award_upper_bound's label names it an upper bound on the award; the attribution, addition, CTR carer premium and CTR premiums docstrings name who reads or pays. Co-Authored-By: Claude Opus 5.5 --- .../severe_disability_minimum_guarantee_addition.py | 5 +++-- .../carer_element/has_uc_award_with_carer_element.py | 4 ++-- .../gov/dwp/universal_credit/uc_award_upper_bound.py | 2 +- .../council_tax_reduction_carer_premium.py | 8 ++++---- .../council_tax_reduction_premiums.py | 2 +- .../is_cared_for_by_carer_benefit_recipient.py | 10 +++++----- 6 files changed, 16 insertions(+), 15 deletions(-) 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 9afa73c9ae..b20611b3b8 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 @@ -29,8 +29,9 @@ class severe_disability_minimum_guarantee_addition(Variable): "is_cared_for_by_carer_benefit_recipient, which the legacy severe " "disability premium shares; a carer benefit there includes an award " "of Universal Credit which includes the carer element (para 1(4)), " - "read from the award's conditions and the award before any " - "non-dependant deduction (has_uc_award_with_carer_element). Blindness " + "which has_uc_award_with_carer_element reads from the award's " + "conditions and the award before any non-dependant deduction. " + "Blindness " "(is_blind) is not in the survey data, so in microsimulation the " "blind-partner route never applies and a blind resident is counted." ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py index 97cd94c2d3..98d426f738 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py @@ -28,8 +28,8 @@ class has_uc_award_with_carer_element(Variable): "reads the addition. So this reads the award's conditions instead. " "The family claims Universal Credit (claims_universal_credit), meets " "the age, pension-route and capital conditions (is_uc_eligible) and " - "has an award before the non-dependant deduction (uc_award_upper_bound " - "positive), and the award includes the carer element in respect of " + "has a positive upper bound on its award (uc_award_upper_bound), and " + "the award includes the carer element in respect of " "this claimant (uc_carer_element_included_for_claimant). A nil bound " "leaves a nil award, and so no award (Welfare Reform Act 2012 s.5(1)(b) " "and (2)(b); DWP sets the minimum at one penny, reg 17), so a carer " diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py index 2514014f1b..c408c27841 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py @@ -4,7 +4,7 @@ class uc_award_upper_bound(Variable): value_type = float entity = BenUnit - label = "Universal Credit award before the non-dependant deduction" + label = "Upper bound on the Universal Credit award" documentation = ( "The family's award before the benefit cap " "(universal_credit_pre_benefit_cap) never exceeds this amount, which " diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py index dfabb17911..0c439c7e16 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py @@ -8,7 +8,7 @@ class council_tax_reduction_carer_premium(Variable): label = "Working-age CTR carer premium" documentation = ( "The carer premium in a working-age Council Tax Reduction applicable " - "amount. Outside Scotland's working-age scheme it is carer_premium. " + "amount. Outside Scotland's working-age scheme it equals carer_premium. " "In Scotland's scheme (SSI 2021/249 Sch 1 paras 5 and 6, from 1 April " "2022) councils pay it in respect of each applicant or partner who " "has regular and substantial caring responsibilities for a severely " @@ -16,7 +16,7 @@ class council_tax_reduction_carer_premium(Variable): "as (a) receiving Carer's Allowance or Carer Support Payment, (b) " "being entitled to one that the overlapping benefits rules reduce to " "nil, or (c) having an award of Universal Credit which includes the " - "carer element. Limbs (a) and (b) are carer_premium's test " + "carer element. carer_premium tests limbs (a) and (b) " "(is_entitled_to_carer_benefit). For limb (c), the Scottish Ministers " "defined an award of Universal Credit to include one with a value of " "zero from 1 April 2022 (reg 4, as amended by SSI 2022/52 reg 3), and " @@ -24,8 +24,8 @@ class council_tax_reduction_carer_premium(Variable): "Act 2012 (SSI 2025/24 reg 8), under which an income that tapers the " "award to nil leaves no award (s.5(1)(b); UC Regs 2013 reg 17). So " "while the earlier definition holds " - "(uc_award_includes_zero_value_award), limb (c) reads the award's " - "conditions: the family claims Universal Credit, meets its basic " + "(uc_award_includes_zero_value_award), this variable reads limb (c) " + "from the award's conditions: the family claims Universal Credit, meets its basic " "conditions and does not take Tax-Free Childcare instead, and the " "award includes the carer element for this claimant. Afterwards it " "reads the positive award (receives_uc_carer_element). From 1 April " diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py index ae8018c0e3..912651908a 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py @@ -22,7 +22,7 @@ class council_tax_reduction_premiums(Variable): "her non-dependant (everyone else in the household, since she has no " "partner or children, unless they receive a qualifying benefit or are " "blind); and the carer premium if she is entitled to a carer benefit. " - "At working age the benefit unit's carer premium is " + "At working age the benefit unit's carer premium comes from " "council_tax_reduction_carer_premium, which in Scotland also counts " "an award of Universal Credit which includes the carer element. " "Whom a carer benefit is paid for caring for comes from " 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 f730177fa1..582cf03c66 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 @@ -45,11 +45,11 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "Support Payment after the overlapping-benefit adjustment. A carer " "whose allowance an overlapping benefit, such as a higher State " "Pension, reduces to nil is entitled but not in receipt, so is not " - "attributed for the allowance. A carer element award is " - "has_uc_award_with_carer_element, read from the claim, the award's " - "conditions and the award before any non-dependant deduction, because " - "the Universal Credit non-dependant deduction exemption reads Pension " - "Credit, which reads the addition. A carer whose income tapers the " + "attributed for the allowance. has_uc_award_with_carer_element gives " + "a carer element award from the claim, the award's conditions and the " + "award before any non-dependant deduction, because the Universal " + "Credit non-dependant deduction exemption reads Pension Credit, which " + "reads the addition. A carer whose income tapers the " "award to nil has no award (Welfare Reform Act 2012 s.5) and is not " "attributed for it. A carer whose award a non-dependant deduction " "takes to nil, or whose family takes Tax-Free Childcare instead, is " From 466519e490f3538abba639c7aba4754252d5ac16 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 05:06:47 -0500 Subject: [PATCH 07/21] Name who acts in the disclosed-limit test comments Comments only: DWP deducts and the council counts, and the disabled person keeps the premium in law. Co-Authored-By: Claude Opus 5.5 --- .../test_uc_carer_element_award_properties.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index 40202f4e6c..80d446d0bb 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -589,13 +589,14 @@ def tax_free_childcare_situation(): ), ) def test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award(): - # The non-dependant's deduction (UC Regs 2013 Sch 4 paras 13 and 16; a - # blind non-dependant has no exemption, and the carer is no exempt renter - # under para 15) takes the carer's award to nil, so she has no award - # (Welfare Reform Act 2012 s.5(1)(b); reg 17). The disabled person is a - # joint tenant, not her non-dependant (para 9(2)(d)), and she is not his - # (HB Regs 2006 reg 3(2)(d)); a blind non-dependant does not bar the - # premium (Sch 3 para 14(4)). So the law pays him the premium. + # DWP's deduction for the non-dependant (UC Regs 2013 Sch 4 paras 13 and + # 16; blindness brings no exemption, and para 15 does not exempt the + # carer as a renter) takes the carer's award to nil, so she has no award + # (Welfare Reform Act 2012 s.5(1)(b); reg 17). DWP does not count the + # disabled person, a joint tenant, as her non-dependant (para 9(2)(d)), + # the council does not count her as his (HB Regs 2006 reg 3(2)(d)), and a + # blind non-dependant does not bar the premium (Sch 3 para 14(4)). So he + # keeps the premium in law. sim = Simulation(situation=non_dependant_situation()) award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) bound = sim.calculate("uc_award_upper_bound", YEAR) @@ -615,10 +616,10 @@ def test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award() ), ) def test_a_family_that_takes_tax_free_childcare_instead_has_no_award(): - # The family is better off with the Tax-Free Childcare top-up, so it - # takes no award (forgoes_universal_credit_for_tax_free_childcare); with - # no award, no one has one in respect of caring for the disabled person, - # and the law pays him the premium. + # The family gets more from the Tax-Free Childcare top-up than from the + # award, so it takes no award (forgoes_universal_credit_for_tax_free_ + # childcare). No one then has an award in respect of caring for the + # disabled person, so he keeps the premium in law. sim = Simulation(situation=tax_free_childcare_situation()) forgoes = sim.calculate("forgoes_universal_credit_for_tax_free_childcare", YEAR) award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) From 2588e563f46a2743543e46ea709566d0c36c2efb Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 10:08:09 -0500 Subject: [PATCH 08/21] Pin the three nil-award routes to #2795 and test the bound against the award MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allowlist the strict expected failures under #2795 so CI's outcome check accepts them, and add the third route: a carer with limited capability for work and a £600 council rent, whose award comes to nil under the lower work allowance while the bound's calculation without housing costs leaves £461.10. The test asserts the law's £4,474.60 premium and raises RuntimeError when a premise fails. test_upper_bound_matches_real_award_endpoints compares the bound exactly with the larger of two real awards, one with non-dependant deductions supplied as nil and one with the housing costs element supplied as nil, before the cap and finally, over every element, both work allowances and rows within a few float32 steps of a nil award. test_the_bound_reads_every_component_of_the_maximum_amount checks that the bound reads each element of uc_maximum_amount in order and every input of the award's steps apart from the deduction, the Tax-Free Childcare choice and the claim. Co-Authored-By: Claude Opus 5.5 --- .github/test_outcome_allowlist.toml | 18 + .../test_uc_carer_element_award_properties.py | 317 +++++++++++++++++- 2 files changed, 326 insertions(+), 9 deletions(-) diff --git a/.github/test_outcome_allowlist.toml b/.github/test_outcome_allowlist.toml index e3b947e134..93f5855ce8 100644 --- a/.github/test_outcome_allowlist.toml +++ b/.github/test_outcome_allowlist.toml @@ -25,6 +25,24 @@ outcome = "xfailed" reason = "#1947" why = "Known bug #1947 (Marriage Allowance booked on the recipient), pinned by a strict xfail until it is fixed." +[[allow]] +nodeid = "policyengine_uk/tests/test_uc_carer_element_award_properties.py::test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award" +outcome = "xfailed" +reason = "#2795" +why = "Known bug #2795 (a carer whose award a non-dependant deduction takes to nil still bars the severe disability premium), pinned by a strict xfail until it is fixed." + +[[allow]] +nodeid = "policyengine_uk/tests/test_uc_carer_element_award_properties.py::test_a_family_that_takes_tax_free_childcare_instead_has_no_award" +outcome = "xfailed" +reason = "#2795" +why = "Known bug #2795 (a carer whose family takes Tax-Free Childcare instead of Universal Credit still bars the severe disability premium), pinned by a strict xfail until it is fixed." + +[[allow]] +nodeid = "policyengine_uk/tests/test_uc_carer_element_award_properties.py::test_small_housing_costs_with_a_work_allowance_leave_no_award" +outcome = "xfailed" +reason = "#2795" +why = "Known bug #2795 (a carer with a work allowance and a small housing costs element whose award comes to nil still bars the severe disability premium), pinned by a strict xfail until it is fixed." + [[allow]] nodeid = "policyengine_uk/tests/test_build_metadata.py::test_runtime_metadata_uses_bundle_contract_when_available" outcome = "skipped" diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index 80d446d0bb..61b1ec50e2 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -13,8 +13,8 @@ (has_uc_award_with_carer_element, uc_award_upper_bound) for the severe disability carer test, since the award itself reads Pension Credit, unless the simulation stores the award. Scotland's carer premium reads the positive award -(receives_uc_carer_element), or from 1 April 2022 to 31 March 2025, when an -award of nil counted, the award's conditions. Each example simulates the +(receives_uc_carer_element), or from 1 April 2022 to 31 March 2025, when the +model counts a nil award as one with a value of zero, the award's conditions. Each example simulates the households as drawn, and again with has_uc_award_with_carer_element supplied as false for everyone, the attribution as it ran before the limb (its carers are then @@ -47,6 +47,11 @@ for than carers (carer benefit recipients and carer element holders). 7. No cycle: every reader calculates on one simulation, whichever is requested first, and also where the simulation stores the award. +8. Endpoint parity: uc_award_upper_bound equals, exactly, the larger of two + awards the model's own formulas calculate, one with non-dependant + deductions supplied as nil and one with the housing costs element supplied + as nil, at the rows test_upper_bound_matches_real_award_endpoints draws, + and it reads every element of uc_maximum_amount in its order. A reform that makes is_uc_eligible read Pension Credit would cycle through the attribution, which reads eligibility; these tests do not cover reforms. @@ -154,9 +159,9 @@ def benefit_unit(draw): @st.composite def households(draw): - # Renters pay the housing costs element, and the household head's family - # bears a deduction for each non-dependant in another benefit unit, which - # the upper bound leaves out. + # DWP includes housing costs for renters and deducts an amount for each + # non-dependant in another benefit unit. The upper bound leaves that + # deduction out. tenure, rent = draw( st.sampled_from( [ @@ -507,7 +512,8 @@ def apply(self): def require(condition, message): - # A broken premise is an error, not the disclosed failure. + # Raise RuntimeError when a premise fails, so xfail accepts only the + # premium assertion. if not condition: raise RuntimeError(message) @@ -584,8 +590,8 @@ def tax_free_childcare_situation(): raises=AssertionError, strict=True, reason=( - "Disclosed limit: the carer test reads the award before the " - "non-dependant deduction, which takes this award to nil." + "#2795: the carer test reads the bound, which leaves out the " + "non-dependant deduction that takes this award to nil." ), ) def test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award(): @@ -611,7 +617,7 @@ def test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award() raises=AssertionError, strict=True, reason=( - "Disclosed limit: the carer test cannot read whether a family takes " + "#2795: the carer test cannot read whether a family takes " "Tax-Free Childcare instead, which reads the award." ), ) @@ -629,6 +635,76 @@ def test_a_family_that_takes_tax_free_childcare_instead_has_no_award(): assert severe[1] == pytest.approx(86.05 * WEEKS) +def small_housing_situation(): + """A blind carer (35) earning £25,000, whom DWP has determined to have + limited capability for work, rents from the council at £600 a year; the + disabled person (60) she cares for lives in another family of the + household.""" + people = { + "carer": { + "age": {YEAR: 35}, + "is_blind": {YEAR: True}, + "care_hours": {YEAR: 35}, + "would_claim_carers_allowance": {YEAR: False}, + "employment_income": {YEAR: 25_000}, + "uc_limited_capability_for_work": {YEAR: True}, + "is_household_head": {YEAR: True}, + }, + "disabled": {"age": {YEAR: 60}, "pip_dl_category": {YEAR: "STANDARD"}}, + } + benunits = { + "carer_unit": {"members": ["carer"]}, + "disabled_unit": {"members": ["disabled"], "would_claim_uc": {YEAR: False}}, + } + household = { + "members": ["carer", "disabled"], + "country": {YEAR: "ENGLAND"}, + "tenure_type": {YEAR: "RENT_FROM_COUNCIL"}, + "rent": {YEAR: 600}, + } + return {"people": people, "benunits": benunits, "households": {"h": household}} + + +@pytest.mark.xfail( + raises=AssertionError, + strict=True, + reason=( + "#2795: the bound's calculation without housing costs takes the " + "higher work allowance and stays positive when this award is nil." + ), +) +def test_small_housing_costs_with_a_work_allowance_leave_no_award(): + # DWP has determined that the carer has limited capability for work, so + # her award has a work allowance (UC Regs 2013 reg 22(1)(b)), and she + # gets the carer element with it: reg 29(4) gives the LCWRA element in + # its place only for limited capability for work and work-related + # activity, and reg 30(1)(a)(i) and (2) ignore the allowance's earnings + # limit and the missing claim. Her award has a housing costs element of + # £600, so DWP applies the lower work allowance, £427 a month (reg + # 22(2)(b)). The taper then exceeds the maximum amount, and the award + # comes to nil (Welfare Reform Act 2012 s.5(1)(b); reg 17). The bound + # also works out the award with no housing costs element and the higher + # work allowance, £710 a month, which leaves £461.10. The council + # ignores the blind carer as a resident (HB Regs 2006 Sch 3 para + # 14(4)(b)), and with no award she does not bar the premium (para + # 14(2)(a)(iii)). So the disabled person keeps the premium in law. + sim = Simulation(situation=small_housing_situation()) + award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) + bound = sim.calculate("uc_award_upper_bound", YEAR) + housing = sim.calculate("uc_housing_costs_element", YEAR) + allowance = sim.calculate("uc_work_allowance", YEAR) + deductions = sim.calculate("uc_non_dep_deductions", YEAR) + forgoes = sim.calculate("forgoes_universal_credit_for_tax_free_childcare", YEAR) + lcwra = sim.calculate("uc_LCWRA_element", YEAR) + carer_element = sim.calculate("uc_carer_element", YEAR) + require(award[0] == 0 and bound[0] > 0, "the carer's award is not nil") + require(housing[0] > 0 and allowance[0] == 427 * 12, "no lower work allowance") + require(deductions[0] == 0 and not forgoes[0], "another route takes it to nil") + require(lcwra[0] == 0 and carer_element[0] > 0, "the award has no carer element") + severe = sim.calculate("severe_disability_premium", YEAR) + assert severe[1] == pytest.approx(86.05 * WEEKS) + + def test_the_bound_reads_neither_the_award_nor_pension_credit(): # The carer test reads uc_award_upper_bound because the award reads # Pension Credit (through the non-dependant deduction's exemption), whose @@ -678,3 +754,226 @@ def visit(node): visit(tree) assert "uc_carer_element" in reached assert not reached & forbidden, reached & forbidden + + +# Families for the endpoint comparison, each in its own English household: +# every element of the maximum amount, both work allowances, and owners, +# private, council and housing association renters. The award reads the +# earned and unearned income each row supplies. +PARITY_FAMILIES = [ + # A single owner, with no work allowance. + dict(adults=[dict(age=30)], children=[], tenure="OWNED_OUTRIGHT", rent=0), + # A lone parent renting privately, with a child (3) in childcare. + dict( + adults=[dict(age=30, employment_income=30_000)], + children=[dict(age=3, childcare_expenses=6_000)], + tenure="RENT_PRIVATELY", + rent=9_000, + ), + # A lone parent of two who owns her home. + dict( + adults=[dict(age=35, employment_income=20_000)], + children=[dict(age=4), dict(age=7)], + tenure="OWNED_OUTRIGHT", + rent=0, + ), + # A council renter with limited capability for work and work-related + # activity. + dict( + adults=[dict(age=45, uc_limited_capability_for_WRA=True)], + children=[], + tenure="RENT_FROM_COUNCIL", + rent=6_000, + ), + # A carer with limited capability for work and a small council rent, + # where the calculation without housing costs gives the larger award. + dict( + adults=[dict(age=35, care_hours=35, uc_limited_capability_for_work=True)], + children=[], + tenure="RENT_FROM_COUNCIL", + rent=600, + ), + # A couple, one caring, with a disabled child, renting from a housing + # association. + dict( + adults=[ + dict(age=40, care_hours=35), + dict(age=38, employment_income=15_000), + ], + children=[dict(age=6, dla_sc_category="MIDDLE")], + tenure="RENT_FROM_HA", + rent=7_200, + ), +] +# Earned income before the work allowance and unearned income, a year. +PARITY_INCOMES = [(0, 0), (6_000, 0), (18_000, 0), (12_000, 1_500)] +BOUNDARY_EARNINGS = 6_000 +# The endpoints: the award with non-dependant deductions supplied as nil, and +# the award with the housing costs element supplied as nil. +ENDPOINTS = ("uc_non_dep_deductions", "uc_housing_costs_element") + + +def parity_situation(rows, endpoint=None): + """One household per (family, earned income, unearned income) row. Each + family claims and takes no Tax-Free Childcare instead, with no benefit cap + reduction or debt deduction, so the final award equals the award before + the cap. endpoint, a variable in ENDPOINTS, is supplied as nil.""" + people, benunits, households = {}, {}, {} + for i, (family, earned, unearned) in enumerate(rows): + members = [] + for role, group in (("a", family["adults"]), ("c", family["children"])): + for j, inputs in enumerate(group): + name = f"r{i}_{role}{j}" + people[name] = {k: {YEAR: v} for k, v in inputs.items()} + people[name]["is_claimant_or_partner"] = {YEAR: role == "a"} + members.append(name) + unit = { + "members": members, + "claims_universal_credit": {YEAR: True}, + "forgoes_universal_credit_for_tax_free_childcare": {YEAR: False}, + "uc_benefit_cap_reduction_after_floor": {YEAR: 0}, + "uc_deductions": {YEAR: 0}, + "uc_earned_income_before_work_allowance": {YEAR: float(earned)}, + "uc_unearned_income": {YEAR: float(unearned)}, + } + if endpoint is not None: + unit[endpoint] = {YEAR: 0} + benunits[f"r{i}"] = unit + households[f"r{i}"] = { + "members": members, + "country": {YEAR: "ENGLAND"}, + "tenure_type": {YEAR: family["tenure"]}, + "rent": {YEAR: family["rent"]}, + } + return {"people": people, "benunits": benunits, "households": households} + + +def endpoint_values(rows, variables): + """Each variable at each endpoint, from its own simulation.""" + results = [] + for endpoint in ENDPOINTS: + sim = Simulation(situation=parity_situation(rows, endpoint)) + results.append({v: np.asarray(sim.calculate(v, YEAR)) for v in variables}) + return results + + +def boundary_rows(): + """Rows whose unearned income brings each endpoint's income reduction to + within a few float32 steps of its maximum amount, where the award + cancels to nil or a fraction of a penny.""" + seeds = [(family, BOUNDARY_EARNINGS, 0) for family in PARITY_FAMILIES] + values = endpoint_values(seeds, ("uc_maximum_amount", "uc_income_reduction")) + rows = [] + for at_endpoint in values: + maximum = at_endpoint["uc_maximum_amount"].astype(np.float32) + reduction = at_endpoint["uc_income_reduction"].astype(np.float32) + assert (reduction < maximum).all(), "a seed's taper reaches its maximum" + for f, family in enumerate(PARITY_FAMILIES): + step = np.spacing(maximum[f]) + for k in range(-3, 4): + unearned = np.float32(maximum[f] - reduction[f] + k * step) + rows.append((family, BOUNDARY_EARNINGS, float(unearned))) + return rows + + +def test_upper_bound_matches_real_award_endpoints(): + # The bound repeats the award's arithmetic at two endpoints, since the + # carer test cannot read the award. At each row the bound must equal, + # exactly, the larger of the two awards the model's own formulas give: + # one with non-dependant deductions supplied as nil, one with the + # housing costs element supplied as nil. Comparing the award before the + # cap and the final award also catches a new amount the award adds + # after the cap stage. Drift either way at these rows fails the test. + rows = [ + (family, earned, unearned) + for family in PARITY_FAMILIES + for earned, unearned in PARITY_INCOMES + ] + n_seed_rows = len(rows) + rows += boundary_rows() + components = ( + "uc_standard_allowance", + "uc_child_element", + "uc_disability_elements", + "uc_carer_element", + "uc_housing_costs_element", + "uc_childcare_element", + ) + variables = components + ( + "uc_work_allowance", + "universal_credit_pre_benefit_cap", + "universal_credit", + ) + with_housing, without_housing = endpoint_values(rows, variables) + base = Simulation(situation=parity_situation(rows)) + bound = np.asarray(base.calculate("uc_award_upper_bound", YEAR)) + + # The rows cover every element, both work allowances, both endpoints + # deciding the bound, and awards that cancel to nil or under a penny. + for component in components: + assert (with_housing[component] > 0).any(), component + assert (with_housing["uc_work_allowance"] == 427 * 12).any() + assert (without_housing["uc_work_allowance"] == 710 * 12).any() + pre_cap = [ + values["universal_credit_pre_benefit_cap"] + for values in (with_housing, without_housing) + ] + assert (pre_cap[0] > pre_cap[1]).any() and (pre_cap[1] > pre_cap[0]).any() + for award in pre_cap: + boundary = award[n_seed_rows:] + assert (boundary == 0).any() and ((boundary > 0) & (boundary < 0.01)).any() + + for stage in ("universal_credit_pre_benefit_cap", "universal_credit"): + larger = np.maximum(with_housing[stage], without_housing[stage]) + mismatched = np.flatnonzero(bound != larger) + assert not mismatched.size, (stage, mismatched, bound[mismatched]) + + +# The award's steps that the bound repeats, and the reads it leaves out: the +# non-dependant deduction and the Tax-Free Childcare choice, which read +# Pension Credit and the award, and the claim, which the carer test reads +# beside the bound. +AWARD_STEPS = ( + "uc_maximum_amount", + "uc_housing_costs_element", + "uc_work_allowance", + "uc_earned_income", + "uc_income_reduction", + "universal_credit_pre_benefit_cap", +) +LEFT_OUT = { + "uc_non_dep_deductions", + "forgoes_universal_credit_for_tax_free_childcare", + "claims_universal_credit", +} + + +def test_the_bound_reads_every_component_of_the_maximum_amount(): + # A new element in uc_maximum_amount, or a new read in any step of the + # award, must reach the bound too. The couple with a disabled child + # rents and has a carer, so every step runs. + sim = Simulation(situation=parity_situation([(PARITY_FAMILIES[5], 12_000, 1_500)])) + sim.trace = True + sim.calculate("universal_credit_pre_benefit_cap", YEAR) + sim.calculate("uc_award_upper_bound", YEAR) + reads = {} + + def visit(node): + if node.children: + # A variable's formula runs once; later reads hit the cache. + reads.setdefault(node.name, [child.name for child in node.children]) + for child in node.children: + visit(child) + + for tree in sim.tracer.trees: + visit(tree) + bound_reads = reads["uc_award_upper_bound"] + adds = sim.tax_benefit_system.get_variable("uc_maximum_amount").adds + # The elements in uc_maximum_amount's order, which float32 addition + # depends on; the bound rebuilds the housing costs element from rent. + assert [name for name in bound_reads if name in adds] == [ + name for name in adds if name != "uc_housing_costs_element" + ] + for step in AWARD_STEPS: + missing = set(reads[step]) - set(AWARD_STEPS) - LEFT_OUT - set(bound_reads) + assert not missing, (step, missing) From ca1e24310ad5f3d06173b808297e46a52b17cfcc Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 10:08:25 -0500 Subject: [PATCH 09/21] Describe the bound's third nil-award route and qualify Scotland's 2022-25 reading The bound's documentation and the readers that cite it now name all three ways it can exceed a nil award (#2795): a non-dependant deduction, Tax-Free Childcare, and a small housing costs element with a work allowance. They limit the upper-bound claim to awards the model's formulas calculate; a stored award can exceed it, and the carer test then reads it. The Scottish premium's documentation, parameters, YAML comments and changelog state the 2022-23 to 2024-25 nil-award rule as the model's reading of reg 4, which UC Claims and Payments Regs reg 32A(1)(a), the SSI 2025/24 policy note and Ipswich BC v TD [2024] UKUT 117 (AAC) leave open. Co-Authored-By: Claude Opus 5.5 --- changelog.d/2120.fixed.md | 2 +- ...carer_premium_counts_uc_carer_element.yaml | 2 +- .../uc_award_includes_zero_value_award.yaml | 2 +- .../council_tax_reduction_carer_premium.yaml | 13 ++++- ...e_disability_minimum_guarantee_addition.py | 4 +- .../has_uc_award_with_carer_element.py | 20 ++++--- .../universal_credit/uc_award_upper_bound.py | 56 +++++++++++-------- .../council_tax_reduction_carer_premium.py | 36 ++++++++---- ...is_cared_for_by_carer_benefit_recipient.py | 18 +++--- .../pension_age_severe_disability_premium.py | 4 +- .../demographic/severe_disability_premium.py | 11 ++-- 11 files changed, 102 insertions(+), 66 deletions(-) diff --git a/changelog.d/2120.fixed.md b/changelog.d/2120.fixed.md index 443c7a80b7..b8d218f489 100644 --- a/changelog.d/2120.fixed.md +++ b/changelog.d/2120.fixed.md @@ -1 +1 @@ -The model now withholds the severe disability premium and the Pension Credit severe disability addition when a carer's Universal Credit award includes the carer element for the claimant or partner, and pays the carer premium in Scotland's working-age council tax reduction for such an award from 1 April 2022, counting an award of nil until 31 March 2025 (#2120, #2124). +The model now counts a carer's Universal Credit award with the carer element when deciding the severe disability premium and the Pension Credit severe disability addition. In Scotland's working-age council tax reduction, it counts such awards for the carer premium from 1 April 2022, and until 31 March 2025 it also counts a nil award that earnings bring about, reading the Scottish definition of an award with a value of zero to include one (#2120, #2124). diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/carer_premium_counts_uc_carer_element.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/carer_premium_counts_uc_carer_element.yaml index ad47b6bfb2..98947fb13a 100644 --- a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/carer_premium_counts_uc_carer_element.yaml +++ b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/carer_premium_counts_uc_carer_element.yaml @@ -1,4 +1,4 @@ -description: Scotland's working-age Council Tax Reduction scheme pays the carer premium to an applicant or partner whose award of Universal Credit includes the carer element if this is true, and only on entitlement to Carer's Allowance or Carer Support Payment otherwise. +description: Whether Scottish councils pay the working-age Council Tax Reduction carer premium to an applicant or partner whose award of Universal Credit includes the carer element, as well as on entitlement to Carer's Allowance or Carer Support Payment. values: 2013-04-01: false 2022-04-01: true diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml index c9037e7852..06ff155820 100644 --- a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml +++ b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml @@ -1,4 +1,4 @@ -description: Scotland's working-age Council Tax Reduction scheme treats an award of Universal Credit with a value of zero as an award of Universal Credit if this is true, so a nil award that includes the carer element meets the carer premium's caring condition. +description: Whether Scottish councils count a Universal Credit award with a value of zero when deciding the working-age Council Tax Reduction carer premium. The model reads that to include a nil award that earnings bring about, a reading the sources leave open. values: 2013-04-01: false 2022-04-01: true diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml index 788cbac06f..4256889796 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml @@ -12,7 +12,12 @@ # a value of zero" (reg 4, as amended by SSI 2022/52 reg 3); from 1 April 2025 # it "means an award of universal credit made under Part 1 of the 2012 Act" # (SSI 2025/24 reg 8), and an income that tapers the award to nil leaves no -# award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17). +# award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17). The model +# reads the earlier definition as reaching a carer whose earnings take the +# award to nil. The sources leave that reading open: UC Claims and Payments +# Regs 2013 reg 32A(1)(a) covers a claim on which DWP makes no award, and +# Ipswich BC v TD [2024] UKUT 117 (AAC) finds no Universal Credit +# entitlement in a nil award without construing the Scottish text. # # Amounts: carer premium £48.15 a week from 1 April 2026 (SSI 2021/249 Sch 1 # para 5(1), substituted by SSI 2026/56), £46.40 in 2025-26, £45.60 in @@ -177,12 +182,14 @@ receives_uc_carer_element: false council_tax_reduction_carer_premium: 0 -- name: In 2024 a Scottish carer's nil award still counts as an award with the carer element +- name: In 2024 the model counts a Scottish carer's nil award as an award with the carer element # The same carer in 2024-25. The maximum amount is (393.45 + 198.31) x 12 # = 7,101.12, and the taper on £20,000 of earnings less income tax and # National Insurance takes more, so the award is nil. Until 31 March 2025 # an "award of universal credit" included one "with a value of zero" (reg - # 4), so para 6(1)(c) applies: carer premium 45.60 x 52 = 2,371.20. + # 4). The model reads that as reaching this nil award, a reading the + # sources leave open (see the header), so para 6(1)(c) applies: carer + # premium 45.60 x 52 = 2,371.20. period: 2024 absolute_error_margin: 0.01 input: 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 b20611b3b8..7b69991bd7 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 @@ -30,8 +30,8 @@ class severe_disability_minimum_guarantee_addition(Variable): "disability premium shares; a carer benefit there includes an award " "of Universal Credit which includes the carer element (para 1(4)), " "which has_uc_award_with_carer_element reads from the award's " - "conditions and the award before any non-dependant deduction. " - "Blindness " + "conditions and the upper bound on the award (uc_award_upper_bound), " + "with that variable's limits. Blindness " "(is_blind) is not in the survey data, so in microsimulation the " "blind-partner route never applies and a blind resident is counted." ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py index 98d426f738..1b26f254e4 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py @@ -23,20 +23,24 @@ class has_uc_award_with_carer_element(Variable): "premium (HB Regs 2006 Sch 3 para 14(2)(a)(iii) and (b)) and of the " "Pension Credit severe disability addition (SPC Regs 2002 Sch I para " "1(1) and (4)), through is_cared_for_by_carer_benefit_recipient. " - "Those cannot read the award: its non-dependant deduction spares a " - "non-dependant on Pension Credit, so it reads Pension Credit, which " - "reads the addition. So this reads the award's conditions instead. " + "The premium and addition cannot read the award: the non-dependant " + "deduction reads Pension Credit, whose addition reads the " + "attribution. has_uc_award_with_carer_element therefore reads the " + "award's conditions and bound. " "The family claims Universal Credit (claims_universal_credit), meets " "the age, pension-route and capital conditions (is_uc_eligible) and " "has a positive upper bound on its award (uc_award_upper_bound), and " "the award includes the carer element in respect of " "this claimant (uc_carer_element_included_for_claimant). A nil bound " "leaves a nil award, and so no award (Welfare Reform Act 2012 s.5(1)(b) " - "and (2)(b); DWP sets the minimum at one penny, reg 17), so a carer " - "whose income tapers the award to nil does not count. A carer " - "whose award a non-dependant deduction takes to nil still counts, and " - "so does a family that takes Tax-Free Childcare instead " - "(forgoes_universal_credit_for_tax_free_childcare reads the award). " + "and (2)(b); DWP sets the minimum at one penny, reg 17). A carer " + "whose income reduces both bound calculations to nil does not count. " + "A carer can still count after income reduces the actual award to " + "nil when the bound's calculation without housing costs remains " + "positive. A carer whose award a non-dependant deduction takes to nil " + "still counts, and so does one whose family takes Tax-Free Childcare " + "instead (forgoes_universal_credit_for_tax_free_childcare reads the " + "award); #2795 tracks all three. " "Where an input or a scenario stores universal_credit for the period, " "or a reform neutralizes it, reading it cannot cycle, so this reads " "the award itself (receives_uc_carer_element)." diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py index c408c27841..6bd0f517d6 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py @@ -6,30 +6,38 @@ class uc_award_upper_bound(Variable): entity = BenUnit label = "Upper bound on the Universal Credit award" documentation = ( - "The family's award before the benefit cap " - "(universal_credit_pre_benefit_cap) never exceeds this amount, which " - "leaves out the non-dependant deduction. DWP awards the maximum " - "amount less the income reduction (UC Regs 2013 reg 22). The maximum " - "amount includes the housing costs element (uc_housing_costs_element): " - "the rent a renter may claim less DWP's non-dependant deductions. " - "Where that element is positive, DWP applies the work allowance with " - "housing. Where DWP deducts less than the rent, the award stays at or " - "below the award with no deduction; where it deducts at least the " - "rent, the award loses the housing costs element and takes the work " - "allowance without housing. This amount takes the larger of those two " - "awards. It repeats the award's steps in their order, rounding where " - "they store a value, so it never falls below the award. A family " - "whose bound comes to nil has a nil award, and so no award: " - "Parliament makes an amount payable of at least the prescribed " - "minimum a financial condition (Welfare Reform Act 2012 s.5(1)(b) " - "and (2)(b)), and DWP sets that minimum at one penny (reg 17). The " - "deduction spares a non-dependant on Pension Credit, so it reads " - "Pension Credit, whose severe disability addition reads who has an " - "award with the carer element. This amount reads neither, so that " - "test can read it (has_uc_award_with_carer_element). It can exceed a " - "nil award: a non-dependant deduction can take the award to nil, and " - "a family that takes Tax-Free Childcare instead gets none " - "(forgoes_universal_credit_for_tax_free_childcare)." + "An amount that the family's award before the benefit cap " + "(universal_credit_pre_benefit_cap), as the model calculates it, " + "never exceeds, worked out without reading the non-dependant " + "deduction. DWP awards the maximum amount less the income reduction " + "(UC Regs 2013 reg 22). The maximum amount includes the housing costs " + "element (uc_housing_costs_element): the rent a renter may claim less " + "DWP's non-dependant deductions. Where that element is positive, DWP " + "applies the lower work allowance (reg 22(2)(b)). Where DWP deducts " + "less than the rent, the award stays at or below the award with no " + "deduction. Where it deducts at least the rent, DWP calculates the " + "award with no housing costs element and the higher work allowance " + "(reg 22(2)(a); Sch 4 para 13). This amount takes the larger of those " + "two awards. It repeats the award's steps in their order, rounding " + "where they store a value, so it never falls below the award the " + "model's formulas calculate. An input or a scenario can store a " + "larger award; the carer test then reads the stored award " + "(has_uc_award_with_carer_element). A family whose bound comes to nil " + "has a nil award, and so no award: Parliament makes an amount " + "payable of at least the prescribed minimum a financial condition " + "(Welfare Reform Act 2012 s.5(1)(b) and (2)(b)), and DWP sets that " + "minimum at one penny (reg 17). The deduction spares a non-dependant " + "on Pension Credit, so it reads Pension Credit, whose severe " + "disability addition reads who has an award with the carer element. " + "This amount reads neither, so that test can read it. It can exceed " + "a nil award (#2795). A non-dependant deduction can take the award " + "to nil. A family that takes Tax-Free Childcare instead gets no award " + "(forgoes_universal_credit_for_tax_free_childcare). And for a family " + "with a work allowance and a small housing costs element, the " + "calculation without housing costs applies the higher work " + "allowance, whose extra earnings disregard can outweigh the housing " + "costs it drops, so it can stay positive when the award comes to nil, " + "even with no non-dependant deduction." ) definition_period = YEAR unit = GBP diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py index 0c439c7e16..c9f6dfd020 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py @@ -14,21 +14,32 @@ class council_tax_reduction_carer_premium(Variable): "has regular and substantial caring responsibilities for a severely " "disabled person. The Scottish Ministers define those in para 6(1) " "as (a) receiving Carer's Allowance or Carer Support Payment, (b) " - "being entitled to one that the overlapping benefits rules reduce to " - "nil, or (c) having an award of Universal Credit which includes the " - "carer element. carer_premium tests limbs (a) and (b) " + "being entitled to an allowance that DWP or Social Security Scotland " + "reduces to nil under the overlapping benefits rules, or (c) having " + "an award of Universal Credit which includes the carer element. " + "carer_premium tests limbs (a) and (b) " "(is_entitled_to_carer_benefit). For limb (c), the Scottish Ministers " "defined an award of Universal Credit to include one with a value of " "zero from 1 April 2022 (reg 4, as amended by SSI 2022/52 reg 3), and " "from 1 April 2025 as an award made under Part 1 of the Welfare Reform " "Act 2012 (SSI 2025/24 reg 8), under which an income that tapers the " - "award to nil leaves no award (s.5(1)(b); UC Regs 2013 reg 17). So " - "while the earlier definition holds " - "(uc_award_includes_zero_value_award), this variable reads limb (c) " - "from the award's conditions: the family claims Universal Credit, meets its basic " - "conditions and does not take Tax-Free Childcare instead, and the " - "award includes the carer element for this claimant. Afterwards it " - "reads the positive award (receives_uc_carer_element). From 1 April " + "award to nil leaves no award (s.5(1)(b); UC Regs 2013 reg 17). The " + "model reads the earlier definition as reaching a carer whose " + "earnings take the award to nil, and so counts that nil award from " + "2022-23 to 2024-25. The sources leave that reading open. UC Claims " + "and Payments Regs 2013 reg 32A(1)(a) says DWP makes no award where " + "income fails that condition. The Scottish Government's policy note " + "to SSI 2025/24 records a 2022 intention to treat such a claimant as " + "having an award, but calls reg 10A an expansion that gives the " + "intention effect. Ipswich BC v TD [2024] UKUT 117 (AAC) holds that " + "a nil award carries no Universal Credit entitlement, without " + "construing the Scottish definition. So while the earlier definition " + "holds (uc_award_includes_zero_value_award), this variable reads limb " + "(c) from the award's conditions: the family claims Universal Credit, " + "meets its basic conditions and does not take Tax-Free Childcare " + "instead, and the award includes the carer element for this " + "claimant. Afterwards it reads the positive award " + "(receives_uc_carer_element). From 1 April " "2025 the Scottish Ministers also count two nil awards as awards (reg " "10A): one a sanction reduces to nil, and one whose claimant the " "Secretary of State treats as reclaiming after earnings end it (UC " @@ -74,8 +85,9 @@ def formula(benunit, period, parameters): person = benunit.members claimant_or_partner = person("is_claimant_or_partner", period) # SSI 2021/249 Sch 1 para 6(1)(c): an award of Universal Credit which - # includes the carer element. Until 31 March 2025 an award with a - # value of zero counts (reg 4), so the award's conditions decide. + # includes the carer element. Until 31 March 2025 the model counts a + # nil award as one "with a value of zero" (reg 4), a reading the + # sources leave open, so the award's conditions decide. carer_element_award = person("receives_uc_carer_element", period) if scotland.uc_award_includes_zero_value_award: family = person.benunit 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 582cf03c66..bd5bf01c36 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 @@ -46,15 +46,17 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "whose allowance an overlapping benefit, such as a higher State " "Pension, reduces to nil is entitled but not in receipt, so is not " "attributed for the allowance. has_uc_award_with_carer_element gives " - "a carer element award from the claim, the award's conditions and the " - "award before any non-dependant deduction, because the Universal " - "Credit non-dependant deduction exemption reads Pension Credit, which " - "reads the addition. A carer whose income tapers the " - "award to nil has no award (Welfare Reform Act 2012 s.5) and is not " - "attributed for it. A carer whose award a non-dependant deduction " + "a carer element award from the claim, the award's conditions and an " + "upper bound on the award (uc_award_upper_bound), because the " + "Universal Credit non-dependant deduction exemption reads Pension " + "Credit, which reads the addition. A carer whose bound comes to nil " + "has no award (Welfare Reform Act 2012 s.5) and is not attributed for " + "it. The bound can stay positive after income reduces the actual " + "award to nil, when its calculation without housing costs gives a " + "higher work allowance. A carer whose award a non-dependant deduction " "takes to nil, or whose family takes Tax-Free Childcare instead, is " - "still attributed, and can withhold a premium or addition the law " - "would pay. A carer with both an allowance and a carer element is one " + "still attributed too, and can withhold a premium or addition the law " + "would pay (#2795). A carer with both an allowance and a carer element is one " "carer. Partners who care for the same severely disabled person " "(partners_care_for_same_severely_disabled_person) have one relevant " "entitlement between them (s.70(7ZA) and (7A)), so where one of them " diff --git a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py index 161fdfe801..54767f5f1d 100644 --- a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py @@ -22,8 +22,8 @@ class pension_age_severe_disability_premium(Variable): "schemes (England SI 2012/2885 Sch 8 para 4(1)); carers are attributed " "by is_cared_for_by_carer_benefit_recipient, which counts a Universal " "Credit award with the carer element from the award's conditions and " - "the award before any non-dependant deduction. The Pension Credit " - "severe " + "the upper bound on the award (uc_award_upper_bound), with that " + "variable's limits. The Pension Credit severe " "disability addition has the same conditions apart from its wider " "residence exceptions, but its own amount parameter, so a Pension " "Credit reform does not move this premium." diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index 3d2b665905..e4f8560906 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -23,10 +23,13 @@ class severe_disability_premium(Variable): "attributed by is_cared_for_by_carer_benefit_recipient, shared with " "the Pension Credit severe disability addition. It counts an award " "of Universal Credit which includes the carer element from the " - "award's conditions and the award before any non-dependant deduction " - "(has_uc_award_with_carer_element), so a carer whose income tapers " - "the award to nil does not bar the premium, but one whose award a " - "non-dependant deduction takes to nil does. Not modelled: a carer's " + "award's conditions and an upper bound on the award " + "(has_uc_award_with_carer_element). A carer whose bound comes to nil " + "does not bar the premium. The model can still withhold it where a " + "non-dependant deduction takes the award to nil, where the carer's " + "family takes Tax-Free Childcare instead, or where a small housing " + "costs element leaves the bound's calculation without housing costs " + "positive (#2795). 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 " From b35b61fb760f3d3278b2ddb2325757254f899a1f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 11:06:04 -0500 Subject: [PATCH 10/21] Split the changelog's Scottish sentence and quote the zero-value words Co-Authored-By: Claude Opus 5.5 --- changelog.d/2120.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/2120.fixed.md b/changelog.d/2120.fixed.md index b8d218f489..e67509c102 100644 --- a/changelog.d/2120.fixed.md +++ b/changelog.d/2120.fixed.md @@ -1 +1 @@ -The model now counts a carer's Universal Credit award with the carer element when deciding the severe disability premium and the Pension Credit severe disability addition. In Scotland's working-age council tax reduction, it counts such awards for the carer premium from 1 April 2022, and until 31 March 2025 it also counts a nil award that earnings bring about, reading the Scottish definition of an award with a value of zero to include one (#2120, #2124). +The model now counts a carer's Universal Credit award with the carer element when deciding the severe disability premium and the Pension Credit severe disability addition. In Scotland's working-age council tax reduction, it counts such awards for the carer premium from 1 April 2022. Until 31 March 2025 it also counts a nil award that earnings bring about, reading Scotland's definition of an award "with a value of zero" to include one (#2120, #2124). From f15ff9be504e6434abda351cf1e54ee657fd89af Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 11:20:02 -0500 Subject: [PATCH 11/21] Put actors in the carer docs, YAML comments and test notes The attribution's documentation, the two YAML files' comments and case names, the property test's docstring and comments, and the allowlist reasons now name who acts and use verbs in place of be-verb complements. No formula or expected value changes. Co-Authored-By: Claude Opus 5.5 --- .github/test_outcome_allowlist.toml | 6 +- .../council_tax_reduction_carer_premium.yaml | 28 ++++---- .../uc_carer_element_carer_test.yaml | 36 +++++----- .../test_uc_carer_element_award_properties.py | 67 ++++++++++--------- .../universal_credit/uc_award_upper_bound.py | 4 +- .../council_tax_reduction_carer_premium.py | 2 +- ...is_cared_for_by_carer_benefit_recipient.py | 28 ++++---- 7 files changed, 89 insertions(+), 82 deletions(-) diff --git a/.github/test_outcome_allowlist.toml b/.github/test_outcome_allowlist.toml index 93f5855ce8..7279d3d3b5 100644 --- a/.github/test_outcome_allowlist.toml +++ b/.github/test_outcome_allowlist.toml @@ -29,19 +29,19 @@ why = "Known bug #1947 (Marriage Allowance booked on the recipient), pinned by a nodeid = "policyengine_uk/tests/test_uc_carer_element_award_properties.py::test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award" outcome = "xfailed" reason = "#2795" -why = "Known bug #2795 (a carer whose award a non-dependant deduction takes to nil still bars the severe disability premium), pinned by a strict xfail until it is fixed." +why = "Known bug #2795: a carer whose award a non-dependant deduction takes to nil still bars the severe disability premium. A strict xfail pins it until the model pays the premium." [[allow]] nodeid = "policyengine_uk/tests/test_uc_carer_element_award_properties.py::test_a_family_that_takes_tax_free_childcare_instead_has_no_award" outcome = "xfailed" reason = "#2795" -why = "Known bug #2795 (a carer whose family takes Tax-Free Childcare instead of Universal Credit still bars the severe disability premium), pinned by a strict xfail until it is fixed." +why = "Known bug #2795: a carer whose family takes Tax-Free Childcare instead of Universal Credit still bars the severe disability premium. A strict xfail pins it until the model pays the premium." [[allow]] nodeid = "policyengine_uk/tests/test_uc_carer_element_award_properties.py::test_small_housing_costs_with_a_work_allowance_leave_no_award" outcome = "xfailed" reason = "#2795" -why = "Known bug #2795 (a carer with a work allowance and a small housing costs element whose award comes to nil still bars the severe disability premium), pinned by a strict xfail until it is fixed." +why = "Known bug #2795: a carer with a work allowance and a small housing costs element whose award comes to nil still bars the severe disability premium. A strict xfail pins it until the model pays the premium." [[allow]] nodeid = "policyengine_uk/tests/test_build_metadata.py::test_runtime_metadata_uses_bundle_contract_when_available" diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml index 4256889796..2546b3a853 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml @@ -30,8 +30,8 @@ - name: The issue's Scottish carer aged 40 with 35 hours and no Carer's Allowance claim gets the carer premium # Para 6(1)(c): the award includes the carer element, 209.34 x 12 = - # 2,512.08, so the applicable amount is (95.55 + 48.15) x 52 = 7,472.40, - # not 95.55 x 52 = 4,968.60. + # 2,512.08, so the applicable amount comes to (95.55 + 48.15) x 52 = + # 7,472.40, not 95.55 x 52 = 4,968.60. period: 2026 absolute_error_margin: 0.01 input: @@ -58,7 +58,7 @@ council_tax_reduction_applicable_amount: (95.55 + 48.15) * 52 - name: The Scottish carer premium for a carer element starts with the 2021 Regulations on 1 April 2022 - # Model year 2022 is 2022-23, read at 30 April 2022. Carer element + # The model reads model year 2022 (2022-23) at 30 April 2022. Carer element # 168.81 x 12 = 2,025.72; carer premium 38.85 x 52 = 2,020.20; applicable # amount (77.00 + 38.85) x 52. period: 2022 @@ -83,8 +83,8 @@ council_tax_reduction_applicable_amount: (77.00 + 38.85) * 52 - name: Before 1 April 2022 the Scottish scheme for persons under pension age tested Carer's Allowance only - # SSI 2012/303 Sch 1 para 14: "entitled to a carer's allowance". Model - # year 2021 is 2021-22, read at 30 April 2021, so the carer element, + # SSI 2012/303 Sch 1 para 14: "entitled to a carer's allowance". The model + # reads model year 2021 (2021-22) at 30 April 2021, so the carer element, # 163.73 x 12 = 1,964.76, gives no premium. period: 2021 absolute_error_margin: 0.01 @@ -155,8 +155,8 @@ council_tax_reduction_applicable_amount: 95.55 * 52 - name: A Scottish carer whose income tapers the award to nil has no award and no carer premium - # With £20,000 of earnings the award is nil, so in 2026 there is no award - # (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17) and para 6(1)(c) + # With £20,000 of earnings the award comes to nil, which in 2026 leaves no + # award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17), and para 6(1)(c) # does not apply, though the maximum amount still includes the carer # element. period: 2026 @@ -185,7 +185,7 @@ - name: In 2024 the model counts a Scottish carer's nil award as an award with the carer element # The same carer in 2024-25. The maximum amount is (393.45 + 198.31) x 12 # = 7,101.12, and the taper on £20,000 of earnings less income tax and - # National Insurance takes more, so the award is nil. Until 31 March 2025 + # National Insurance takes more, so the award comes to nil. Until 31 March 2025 # an "award of universal credit" included one "with a value of zero" (reg # 4). The model reads that as reaching this nil award, a reading the # sources leave open (see the header), so para 6(1)(c) applies: carer @@ -215,11 +215,11 @@ carer_premium: 0 council_tax_reduction_carer_premium: 45.60 * 52 -- name: From 1 April 2025 the same Scottish carer's nil award is no award +- name: From 1 April 2025 the same Scottish carer's nil award leaves no award # The same carer in 2025-26. The maximum amount is (400.14 + 201.68) x 12 = - # 7,221.84, and the taper again takes more, so the award is nil. From 1 - # April 2025 an award of universal credit is one "made under Part 1 of the - # 2012 Act" (SSI 2025/24 reg 8), and an income that tapers it to nil leaves + # 7,221.84, and the taper again takes more, so the award comes to nil. From + # 1 April 2025 reg 4 defines an award of universal credit as one "made under + # Part 1 of the 2012 Act" (SSI 2025/24 reg 8), and an income that tapers it to nil leaves # no award, so para 6(1)(c) does not apply: no carer premium. Neither of # reg 10A's nil awards (a sanction, or a reclaim under the UC Claims and # Payments Regs 2013 reg 32A) arises. @@ -248,8 +248,8 @@ council_tax_reduction_carer_premium: 0 - name: A Scottish carer with both Carer Support Payment and the carer element gets one premium - # The premium is paid in respect of each person who qualifies (para 5(3)), - # not for each award: 48.15 x 52 = 2,503.80, not twice that. + # Councils pay the premium in respect of each person who qualifies (para + # 5(3)), not for each award: 48.15 x 52 = 2,503.80, not twice that. period: 2026 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml index 66edc75834..1cae9f34b9 100644 --- a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml +++ b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml @@ -2,11 +2,12 @@ # carer benefit in the carer condition of the legacy severe disability premium # (HB Regs 2006 Sch 3 para 14(2)(a)(iii) and (b); HB(SPC) Regs 2006 Sch 3 # para 6(2)) and of the Pension Credit severe disability addition (SPC Regs -# 2002 Sch I para 1(1)(a)(iii) and (b), with para 1(4)): no premium or -# addition is paid for a person someone "has an award of universal credit which -# includes the carer element in respect of caring for". The carer element is -# for regular and substantial caring for a severely disabled person, whether or -# not the carer claims Carer's Allowance (UC Regs 2013 regs 29 and 30(2)). +# 2002 Sch I para 1(1)(a)(iii) and (b), with para 1(4)): DWP and councils +# withhold the premium or addition when a carer "has an award of universal +# credit which includes the carer element in respect of caring for" the +# claimant. DWP includes the carer element for regular and substantial caring +# for a severely disabled person, whether or not the carer claims Carer's +# Allowance (UC Regs 2013 regs 29 and 30(2)). # # Amounts for 2026-27 (DWP benefit and pension rates 2026 to 2027; the Social # Security Benefits Up-rating Order 2026, SI 2026/148): severe disability @@ -18,7 +19,8 @@ # The carer is a non-dependant who is blind, so the non-dependant condition # ignores them (para 14(4)(b)); their award bars the premium instead. # Carer element 209.34 x 12 = 2,512.08; award (424.90 + 209.34) x 12 = - # 7,610.88. Without the limb the premium was 86.05 x 52 = 4,474.60. + # 7,610.88. Without the limb the model paid the premium, 86.05 x 52 = + # 4,474.60. period: 2026 input: people: @@ -79,9 +81,9 @@ - name: A son's Universal Credit carer element in another benefit unit bars a pensioner's severe disability addition # SPC Regs 2002 Sch I para 1(1)(a)(iii) and (4). The son is blind, so the - # residence condition ignores him (para 2). Without the limb the addition - # was 86.05 x 52 = 4,474.60, and so was the pension-age Housing Benefit - # premium (HB(SPC) Regs 2006 Sch 3 para 6(2)(a)(iii)). + # residence condition ignores him (para 2). Without the limb the model paid + # the addition, 86.05 x 52 = 4,474.60, and the pension-age Housing Benefit + # premium at the same amount (HB(SPC) Regs 2006 Sch 3 para 6(2)(a)(iii)). period: 2026 input: people: @@ -109,7 +111,7 @@ severe_disability_minimum_guarantee_addition: [0, 0] pension_age_severe_disability_premium: [0, 0] -- name: A carer on both Carer's Allowance and Universal Credit is one carer +- name: A carer on both Carer's Allowance and Universal Credit counts once # The carer receives Carer's Allowance, 86.45 x 52 = 4,495.40, and a # Universal Credit award with the carer element, (424.90 + 209.34) x 12 - # 4,495.40 = 3,115.48. They care for one person (SSCBA 1992 s.70(7) and @@ -147,7 +149,7 @@ is_cared_for_by_carer_benefit_recipient: [true, false, false] severe_disability_minimum_guarantee_addition: [4_474.60, 0] -- name: A partner's carer element is not a second carer where the other partner receives the allowance for the same person +- name: A partner's carer element adds no second carer where the other partner receives the allowance for the same person # Both partners care 35 hours; the younger claims Carer's Allowance and the # joint award includes the carer element for the elder (UC Regs 2013 reg # 29(3); the model takes the elder). Partners caring for the same severely @@ -194,8 +196,8 @@ # exceeds the maximum amount even with no non-dependant deduction # (uc_award_upper_bound; the disabled person's family, which does not # claim, has a bound of its standard allowance, 424.90 x 12 = 5,098.80), so - # the award is nil and there is no award (Welfare Reform Act 2012 s.5(1)(b); - # UC Regs 2013 reg 17). No one has an award in + # the award comes to nil, which leaves no award (Welfare Reform Act 2012 + # s.5(1)(b); UC Regs 2013 reg 17). No one has an award in # respect of caring for the disabled person, who keeps the premium, 86.05 x # 52 = 4,474.60. period: 2026 @@ -310,10 +312,10 @@ severe_disability_minimum_guarantee_addition: [4_474.60, 0] pension_age_severe_disability_premium: [4_474.60, 0] -- name: A stored award is read as the award, even without a claim +- name: The carer test reads a stored award as the award, even without a claim # The carer of the first case earns £20,000, which tapers the award the - # model would work out to nil, and neither family claims. Universal Credit - # is supplied instead: nil for the disabled person's family and £100 for + # model would work out to nil, and neither family claims. The case supplies + # Universal Credit instead: nil for the disabled person's family and £100 for # the carer's. A stored award reads no other variable, so the carer test # reads it (receives_uc_carer_element): the carer's award includes the # carer element and bars the premium. @@ -349,7 +351,7 @@ is_cared_for_by_carer_benefit_recipient: [true, false] severe_disability_premium: [0, 0] -- name: Without a Universal Credit claim there is no carer element award +- name: Without a Universal Credit claim no one has a carer element award # The same carer as in the first case, in a family that does not claim # Universal Credit and does not claim Carer's Allowance: no one has an award # in respect of caring for the disabled person, who keeps the premium, diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index 61b1ec50e2..c603307602 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -14,12 +14,12 @@ disability carer test, since the award itself reads Pension Credit, unless the simulation stores the award. Scotland's carer premium reads the positive award (receives_uc_carer_element), or from 1 April 2022 to 31 March 2025, when the -model counts a nil award as one with a value of zero, the award's conditions. Each example simulates the -households as drawn, and -again with has_uc_award_with_carer_element supplied as false for everyone, the -attribution as it ran before the limb (its carers are then -receives_carer_benefit alone). The Scottish premium's counterpart without the -limb is carer_premium. +model counts a nil award as one with a value of zero, the award's conditions. +Each example simulates the households as drawn, and again with +has_uc_award_with_carer_element supplied as false for everyone, the +attribution as it ran before the limb (its carers then come from +receives_carer_benefit alone). carer_premium gives the Scottish premium's +counterpart without the limb. Invariants, on generated households under baseline law: @@ -31,22 +31,24 @@ 2. Never raises: no carer element raises the severe disability premium (the shared, council tax reduction and pension-age premiums) or the Pension Credit addition. -3. Never lowers, and only in Scotland: council_tax_reduction_carer_premium is - never below carer_premium, and differs from it only for a Scottish +3. Never lowers, and only in Scotland: council_tax_reduction_carer_premium + never falls below carer_premium, and differs from it only for a Scottish working-age family. -4. Bounds: the Scottish premium is a whole number of weekly amounts, at most +4. Bounds: the Scottish premium comes to a whole number of weekly amounts, at most the number of claimants and partners, and at most one where partners care for the same person; a person with both an allowance and a carer element counts once. -5. Where the bound is nil, the award is nil: uc_award_upper_bound is at least - universal_credit_pre_benefit_cap, which is at least universal_credit, so - receives_uc_carer_element implies has_uc_award_with_carer_element. That - implication also holds where the simulation stores the award. -6. Attribution: no one is their own carer, everyone cared for is severely - disabled for Carer's Allowance, and a household has no more people cared - for than carers (carer benefit recipients and carer element holders). -7. No cycle: every reader calculates on one simulation, whichever is requested - first, and also where the simulation stores the award. +5. A nil bound leaves a nil award: uc_award_upper_bound >= + universal_credit_pre_benefit_cap >= universal_credit for awards the + model's formulas calculate, so receives_uc_carer_element implies + has_uc_award_with_carer_element. That implication also holds where the + simulation stores the award, which can exceed the bound. +6. Attribution: the attribution makes no one their own carer, everyone it + counts as cared for meets Carer's Allowance's severe disability test, and + no household has more people cared for than carers (carer benefit + recipients and carer element holders). +7. No cycle: every reader calculates on one simulation, whichever the test + requests first, and also where the simulation stores the award. 8. Endpoint parity: uc_award_upper_bound equals, exactly, the larger of two awards the model's own formulas calculate, one with non-dependant deductions supplied as nil and one with the housing costs element supplied @@ -223,9 +225,9 @@ def situation(population, without_limb=()): def with_and_without_limb(population, variables): """Each variable for the households as drawn, and without the limb. - Without the limb, has_uc_award_with_carer_element is supplied as false - for everyone, in a second simulation: a variable supplied for some people - takes its default, not its formula, for the rest. + Without the limb, a second simulation supplies + has_uc_award_with_carer_element as false for everyone: a variable supplied + for some people takes its default, not its formula, for the rest. """ sim = Simulation(situation=situation(population)) sim_without = Simulation( @@ -315,8 +317,8 @@ def test_issue_households_read_the_carer_element_award(): # #2120: the blind carer's award includes the carer element, 209.34 x 12, # so the disabled person has no severe disability premium (86.05 x 52 # without it). #2124: the Scottish carer's applicable amount includes the - # carer premium, (95.55 + 48.15) x 52, not 95.55 x 52. Only variables - # that predate the fix are read, so the test also runs on the old code. + # carer premium, (95.55 + 48.15) x 52, not 95.55 x 52. The test reads + # only variables that predate the fix, so it also runs on the old code. sim = Simulation(situation=situation(EXAMPLES[:1] + EXAMPLES[2:3])) severe = sim.calculate("severe_disability_premium", YEAR) carer_element = sim.calculate("uc_carer_element", YEAR) @@ -353,8 +355,8 @@ def test_the_carer_element_limb_moves_only_its_readers_and_only_one_way(populati benunit_household = household_index(sim, "benunit") person_household = household_index(sim, "person") - # 5. Where the bound is nil the award is nil, exactly, so the carer test - # misses no award. + # 5. A nil bound leaves a nil award, exactly, so the carer test misses no + # award. bound = drawn["uc_award_upper_bound"] award = drawn["universal_credit_pre_benefit_cap"] assert (bound >= award).all() @@ -411,9 +413,9 @@ def test_the_carer_element_limb_moves_only_its_readers_and_only_one_way(populati same_person = drawn["partners_care_for_same_severely_disabled_person"] assert (np.round(weekly[same_person]) <= 1).all() - # 6. Everyone cared for is severely disabled for Carer's Allowance; no - # household has more people cared for than carers, and a lone carer is - # not cared for. + # 6. Everyone cared for meets Carer's Allowance's severe disability test; no + # household has more people cared for than carers, and the attribution + # never counts a lone carer as cared for. assert not (cared_for & ~drawn["is_severely_disabled_for_carers_allowance"]).any() carer = drawn["receives_carer_benefit"] | has_award for h in np.unique(person_household): @@ -445,8 +447,8 @@ def test_every_reader_calculates_on_one_simulation_in_any_order(population, orde def stored_award_situation(stored=True): """The issue's blind carer on £20,000, which tapers the award the model - works out to nil, with neither family claiming; the award is supplied - instead: nil for the disabled person's family, £100 for the carer's.""" + works out to nil, with neither family claiming; the situation supplies + the award instead: nil for the disabled person's family, £100 for the carer's.""" sim_situation = situation( [ dict( @@ -496,7 +498,7 @@ def test_a_stored_award_is_the_award_and_reads_no_cycle(order): def test_a_reform_that_abolishes_universal_credit_leaves_no_carer_element_award(): - # A neutralized award reads no other variable and is nil, so no one has + # A neutralized award reads no other variable and comes to nil, so no one has # an award and the blind carer of the issue no longer bars the premium. from policyengine_core.reforms import Reform @@ -817,7 +819,8 @@ def parity_situation(rows, endpoint=None): """One household per (family, earned income, unearned income) row. Each family claims and takes no Tax-Free Childcare instead, with no benefit cap reduction or debt deduction, so the final award equals the award before - the cap. endpoint, a variable in ENDPOINTS, is supplied as nil.""" + the cap. The situation supplies endpoint, a variable in ENDPOINTS, as + nil.""" people, benunits, households = {}, {}, {} for i, (family, earned, unearned) in enumerate(rows): members = [] diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py index 6bd0f517d6..f05e1291c0 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py @@ -32,10 +32,10 @@ class uc_award_upper_bound(Variable): "This amount reads neither, so that test can read it. It can exceed " "a nil award (#2795). A non-dependant deduction can take the award " "to nil. A family that takes Tax-Free Childcare instead gets no award " - "(forgoes_universal_credit_for_tax_free_childcare). And for a family " + "(forgoes_universal_credit_for_tax_free_childcare). For a family " "with a work allowance and a small housing costs element, the " "calculation without housing costs applies the higher work " - "allowance, whose extra earnings disregard can outweigh the housing " + "allowance. Its extra earnings disregard can outweigh the housing " "costs it drops, so it can stay positive when the award comes to nil, " "even with no non-dependant deduction." ) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py index c9f6dfd020..3d8f2b2983 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py @@ -33,7 +33,7 @@ class council_tax_reduction_carer_premium(Variable): "having an award, but calls reg 10A an expansion that gives the " "intention effect. Ipswich BC v TD [2024] UKUT 117 (AAC) holds that " "a nil award carries no Universal Credit entitlement, without " - "construing the Scottish definition. So while the earlier definition " + "construing the Scottish definition. While the earlier definition " "holds (uc_award_includes_zero_value_award), this variable reads limb " "(c) from the award's conditions: the family claims Universal Credit, " "meets its basic conditions and does not take Tax-Free Childcare " 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 bd5bf01c36..2b37522e7f 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 @@ -13,10 +13,11 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "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) and (4)). The data say who receives a carer benefit but not whom " - "they care for, so each award is attributed. A carer benefit or carer " - "element is paid for caring for a person who is severely disabled for " - "Carer's Allowance (is_severely_disabled_for_carers_allowance; UC Regs " - "2013 reg 30), a carer has one award (SSCBA 1992 s.70(7)), and only " + "they care for, so the model attributes each award. DWP and Social " + "Security Scotland pay a carer benefit or carer element for caring " + "for a person severely disabled for Carer's Allowance " + "(is_severely_disabled_for_carers_allowance; UC Regs 2013 reg 30), a " + "carer has one award (SSCBA 1992 s.70(7)), and only " "one person has an allowance or a carer element for caring for the " "same severely disabled person (s.70(7ZA) to (7ZC) and (7A); both " "rules were in s.70(7) before 16 November 2023). No one is their own " @@ -44,23 +45,24 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "is receives_carer_benefit: a positive Carer's Allowance or Carer " "Support Payment after the overlapping-benefit adjustment. A carer " "whose allowance an overlapping benefit, such as a higher State " - "Pension, reduces to nil is entitled but not in receipt, so is not " - "attributed for the allowance. has_uc_award_with_carer_element gives " + "Pension, reduces to nil keeps the entitlement but receives nothing, " + "so the model does not attribute the allowance. " + "has_uc_award_with_carer_element gives " "a carer element award from the claim, the award's conditions and an " "upper bound on the award (uc_award_upper_bound), because the " "Universal Credit non-dependant deduction exemption reads Pension " "Credit, which reads the addition. A carer whose bound comes to nil " - "has no award (Welfare Reform Act 2012 s.5) and is not attributed for " - "it. The bound can stay positive after income reduces the actual " + "has no award (Welfare Reform Act 2012 s.5), and the model attributes " + "none. The bound can stay positive after income reduces the actual " "award to nil, when its calculation without housing costs gives a " "higher work allowance. A carer whose award a non-dependant deduction " - "takes to nil, or whose family takes Tax-Free Childcare instead, is " - "still attributed too, and can withhold a premium or addition the law " - "would pay (#2795). A carer with both an allowance and a carer element is one " - "carer. Partners who care for the same severely disabled person " + "takes to nil, or whose family takes Tax-Free Childcare instead, " + "still counts too, and can withhold a premium or addition the law " + "would pay (#2795). A carer with both an allowance and a carer element " + "counts once. Partners who care for the same severely disabled person " "(partners_care_for_same_severely_disabled_person) have one relevant " "entitlement between them (s.70(7ZA) and (7A)), so where one of them " - "receives a carer benefit, the other's carer element is not a second " + "receives a carer benefit, the other's carer element adds no second " "carer." ) definition_period = YEAR From 4613ac60ffcddfead4f7a118d99a26a1d61f2c40 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 1 Nov 2026 11:58:44 -0500 Subject: [PATCH 12/21] Give the bound's documentation a main verb and number every test invariant The bound's documentation opens with what never exceeds it and names has_uc_award_with_carer_element as its reader; the Scottish premium cites the financial condition reg 32A(1)(a) turns on; the attribution comment matches its documentation; and the property test's docstring numbers the trace, parity and component checks 8 to 10, as the pull request does. No formula changes. Co-Authored-By: Claude Opus 5.5 --- .../tests/test_uc_carer_element_award_properties.py | 10 +++++++--- .../gov/dwp/universal_credit/uc_award_upper_bound.py | 9 +++++---- .../council_tax_reduction_carer_premium.py | 5 +++-- .../is_cared_for_by_carer_benefit_recipient.py | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index c603307602..57034831b5 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -49,11 +49,15 @@ recipients and carer element holders). 7. No cycle: every reader calculates on one simulation, whichever the test requests first, and also where the simulation stores the award. -8. Endpoint parity: uc_award_upper_bound equals, exactly, the larger of two +8. The bound reads neither the award nor Pension Credit: its trace reaches + none of the award's steps, the non-dependant deduction or the attribution. +9. Endpoint parity: uc_award_upper_bound equals, exactly, the larger of two awards the model's own formulas calculate, one with non-dependant deductions supplied as nil and one with the housing costs element supplied - as nil, at the rows test_upper_bound_matches_real_award_endpoints draws, - and it reads every element of uc_maximum_amount in its order. + as nil, at the rows test_upper_bound_matches_real_award_endpoints draws. +10. Every component: the bound reads each element of uc_maximum_amount in its + order, and every input of the award's steps apart from the non-dependant + deduction, the Tax-Free Childcare choice and the claim. A reform that makes is_uc_eligible read Pension Credit would cycle through the attribution, which reads eligibility; these tests do not cover reforms. diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py index f05e1291c0..6907e11f96 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py @@ -6,10 +6,10 @@ class uc_award_upper_bound(Variable): entity = BenUnit label = "Upper bound on the Universal Credit award" documentation = ( - "An amount that the family's award before the benefit cap " + "The family's award before the benefit cap " "(universal_credit_pre_benefit_cap), as the model calculates it, " - "never exceeds, worked out without reading the non-dependant " - "deduction. DWP awards the maximum amount less the income reduction " + "never exceeds this amount, which the model works out without reading " + "the non-dependant deduction. DWP awards the maximum amount less the income reduction " "(UC Regs 2013 reg 22). The maximum amount includes the housing costs " "element (uc_housing_costs_element): the rent a renter may claim less " "DWP's non-dependant deductions. Where that element is positive, DWP " @@ -29,7 +29,8 @@ class uc_award_upper_bound(Variable): "minimum at one penny (reg 17). The deduction spares a non-dependant " "on Pension Credit, so it reads Pension Credit, whose severe " "disability addition reads who has an award with the carer element. " - "This amount reads neither, so that test can read it. It can exceed " + "This amount reads neither, so has_uc_award_with_carer_element can " + "read it. It can exceed " "a nil award (#2795). A non-dependant deduction can take the award " "to nil. A family that takes Tax-Free Childcare instead gets no award " "(forgoes_universal_credit_for_tax_free_childcare). For a family " diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py index 3d8f2b2983..9ee13545f3 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py @@ -27,8 +27,9 @@ class council_tax_reduction_carer_premium(Variable): "model reads the earlier definition as reaching a carer whose " "earnings take the award to nil, and so counts that nil award from " "2022-23 to 2024-25. The sources leave that reading open. UC Claims " - "and Payments Regs 2013 reg 32A(1)(a) says DWP makes no award where " - "income fails that condition. The Scottish Government's policy note " + "and Payments Regs 2013 reg 32A(1)(a) covers a claim on which DWP " + "makes no award because income fails the financial condition in " + "s.5(1)(b). The Scottish Government's policy note " "to SSI 2025/24 records a 2022 intention to treat such a claimant as " "having an award, but calls reg 10A an expansion that gives the " "intention effect. Ipswich BC v TD [2024] UKUT 117 (AAC) holds that " 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 2b37522e7f..a3a0fda087 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 @@ -83,7 +83,7 @@ def formula(person, period, parameters): could_be_cared_for = person("is_severely_disabled_for_carers_allowance", period) receives = person("receives_carer_benefit", period) # An award of Universal Credit which includes the carer element counts - # as a carer too. A partner's carer element is not a second carer for + # as a carer too. A partner's carer element adds no second carer for # a couple caring for the same person where one of them receives a # carer benefit: they have one relevant entitlement between them # (SSCBA 1992 s.70(7ZA) and (7A)). From d10b985d2136b2207396a14e4e7a46d0ee4c4860 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 2 Nov 2026 05:10:12 -0500 Subject: [PATCH 13/21] Add a private renter above the LHA rate to the endpoint comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bound's housing costs read uc_LHA_cap, and the comparison's only private renter paid less than the cap, so a copy that used the full rent passed. A lone parent renting privately for £30,000 a year, capped at £18,600, now adds 18 rows (126 in all), and the test asserts that the rows include a housing costs element held to the cap. The Scottish carer-premium YAML cites paras 5(2) and 6(1) for a single applicant with two qualifying limbs. Documentation and comments name their actors; no formula changes. Co-Authored-By: Claude Opus 5.5 --- changelog.d/2120.fixed.md | 2 +- .../council_tax_reduction_carer_premium.yaml | 21 +++++---- .../test_uc_carer_element_award_properties.py | 27 +++++++++-- ...e_disability_minimum_guarantee_addition.py | 11 ++--- .../universal_credit/uc_award_upper_bound.py | 45 ++++++++++--------- .../council_tax_reduction_carer_premium.py | 5 ++- .../council_tax_reduction_premiums.py | 2 +- .../household/demographic/carer_premium.py | 2 +- .../pension_age_severe_disability_premium.py | 11 ++--- .../demographic/severe_disability_premium.py | 3 +- 10 files changed, 78 insertions(+), 51 deletions(-) diff --git a/changelog.d/2120.fixed.md b/changelog.d/2120.fixed.md index e67509c102..b3214856e0 100644 --- a/changelog.d/2120.fixed.md +++ b/changelog.d/2120.fixed.md @@ -1 +1 @@ -The model now counts a carer's Universal Credit award with the carer element when deciding the severe disability premium and the Pension Credit severe disability addition. In Scotland's working-age council tax reduction, it counts such awards for the carer premium from 1 April 2022. Until 31 March 2025 it also counts a nil award that earnings bring about, reading Scotland's definition of an award "with a value of zero" to include one (#2120, #2124). +The model now counts a carer's Universal Credit award with the carer element when deciding the severe disability premium and the Pension Credit severe disability addition. In Scotland's working-age council tax reduction, it counts such awards for the carer premium from 1 April 2022. Until 31 March 2025 it also counts an award that DWP tapers to nil because of the carer's earnings, under its reading of Scotland's definition of an award "with a value of zero" (#2120, #2124). diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml index 2546b3a853..07e321aa83 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml @@ -183,13 +183,14 @@ council_tax_reduction_carer_premium: 0 - name: In 2024 the model counts a Scottish carer's nil award as an award with the carer element - # The same carer in 2024-25. The maximum amount is (393.45 + 198.31) x 12 - # = 7,101.12, and the taper on £20,000 of earnings less income tax and - # National Insurance takes more, so the award comes to nil. Until 31 March 2025 - # an "award of universal credit" included one "with a value of zero" (reg - # 4). The model reads that as reaching this nil award, a reading the - # sources leave open (see the header), so para 6(1)(c) applies: carer - # premium 45.60 x 52 = 2,371.20. + # The same carer in 2024-25. The maximum amount comes to (393.45 + 198.31) + # x 12 = 7,101.12, and the taper on £20,000 of earnings less income tax and + # National Insurance takes more, so the award comes to nil. Until 31 March + # 2025 an "award of universal credit" included one "with a value of zero" + # (reg 4), and the model reads those words as reaching this nil award. + # Under that chosen reading, the model includes the carer premium (para + # 6(1)(c)): 45.60 x 52 = 2,371.20. The sources leave that reading open (see + # the header). period: 2024 absolute_error_margin: 0.01 input: @@ -248,8 +249,10 @@ council_tax_reduction_carer_premium: 0 - name: A Scottish carer with both Carer Support Payment and the carer element gets one premium - # Councils pay the premium in respect of each person who qualifies (para - # 5(3)), not for each award: 48.15 x 52 = 2,503.80, not twice that. + # The applicant has regular and substantial caring responsibilities under + # both para 6(1)(a) and (c), and councils include the premium once for an + # applicant with those responsibilities (para 5(2)): 48.15 x 52 = + # 2,503.80, not twice that. period: 2026 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index 57034831b5..629146f9fc 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -763,9 +763,10 @@ def visit(node): # Families for the endpoint comparison, each in its own English household: -# every element of the maximum amount, both work allowances, and owners, -# private, council and housing association renters. The award reads the -# earned and unearned income each row supplies. +# every element of the maximum amount, both work allowances, owners, council +# and housing association renters, and private renters below and above the +# Local Housing Allowance rate. The award reads the earned and unearned +# income each row supplies. PARITY_FAMILIES = [ # A single owner, with no work allowance. dict(adults=[dict(age=30)], children=[], tenure="OWNED_OUTRIGHT", rent=0), @@ -810,6 +811,16 @@ def visit(node): tenure="RENT_FROM_HA", rent=7_200, ), + # A lone parent earning £6,000, with a child (3) in childcare, who rents + # privately for £30,000 a year. The model places a household with no + # region in Inner South East London, where DWP caps the housing costs + # element at the two-bedroom Local Housing Allowance rate, £1,550 a month. + dict( + adults=[dict(age=30, employment_income=6_000)], + children=[dict(age=3, childcare_expenses=6_000)], + tenure="RENT_PRIVATELY", + rent=30_000, + ), ] # Earned income before the work allowance and unearned income, a year. PARITY_INCOMES = [(0, 0), (6_000, 0), (18_000, 0), (12_000, 1_500)] @@ -907,6 +918,8 @@ def test_upper_bound_matches_real_award_endpoints(): "uc_childcare_element", ) variables = components + ( + "uc_LHA_cap", + "benunit_rent", "uc_work_allowance", "universal_credit_pre_benefit_cap", "universal_credit", @@ -915,10 +928,16 @@ def test_upper_bound_matches_real_award_endpoints(): base = Simulation(situation=parity_situation(rows)) bound = np.asarray(base.calculate("uc_award_upper_bound", YEAR)) - # The rows cover every element, both work allowances, both endpoints + # The rows cover every element, a housing costs element that DWP caps at + # the Local Housing Allowance rate, both work allowances, both endpoints # deciding the bound, and awards that cancel to nil or under a penny. for component in components: assert (with_housing[component] > 0).any(), component + lha_cap = with_housing["uc_LHA_cap"] + assert ( + (lha_cap < with_housing["benunit_rent"]) + & (with_housing["uc_housing_costs_element"] == lha_cap) + ).any() assert (with_housing["uc_work_allowance"] == 427 * 12).any() assert (without_housing["uc_work_allowance"] == 710 * 12).any() pre_cap = [ 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 7b69991bd7..3ca17774c6 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 @@ -27,11 +27,12 @@ class severe_disability_minimum_guarantee_addition(Variable): "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; a carer benefit there includes an award " - "of Universal Credit which includes the carer element (para 1(4)), " - "which has_uc_award_with_carer_element reads from the award's " - "conditions and the upper bound on the award (uc_award_upper_bound), " - "with that variable's limits. Blindness " + "disability premium shares. The attribution counts an award of " + "Universal Credit which includes the carer element (para 1(4)), which " + "has_uc_award_with_carer_element reads from the award's conditions " + "and the upper bound on the award (uc_award_upper_bound). The bound " + "can stay positive when the actual award comes to nil (#2795). " + "Blindness " "(is_blind) is not in the survey data, so in microsimulation the " "blind-partner route never applies and a blind resident is counted." ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py index 6907e11f96..b02c2341a6 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py @@ -6,39 +6,40 @@ class uc_award_upper_bound(Variable): entity = BenUnit label = "Upper bound on the Universal Credit award" documentation = ( - "The family's award before the benefit cap " - "(universal_credit_pre_benefit_cap), as the model calculates it, " - "never exceeds this amount, which the model works out without reading " - "the non-dependant deduction. DWP awards the maximum amount less the income reduction " - "(UC Regs 2013 reg 22). The maximum amount includes the housing costs " - "element (uc_housing_costs_element): the rent a renter may claim less " - "DWP's non-dependant deductions. Where that element is positive, DWP " + "The model calculates this upper bound without reading the " + "non-dependant deduction. Its formula for " + "universal_credit_pre_benefit_cap never returns a larger award. DWP " + "awards the maximum amount less the income reduction (UC Regs 2013 " + "reg 22). The maximum amount includes the housing costs element " + "(uc_housing_costs_element): the rent a renter may claim less DWP's " + "non-dependant deductions. Where that element is positive, DWP " "applies the lower work allowance (reg 22(2)(b)). Where DWP deducts " "less than the rent, the award stays at or below the award with no " "deduction. Where it deducts at least the rent, DWP calculates the " "award with no housing costs element and the higher work allowance " - "(reg 22(2)(a); Sch 4 para 13). This amount takes the larger of those " + "(reg 22(2)(a); Sch 4 para 13). The formula takes the larger of those " "two awards. It repeats the award's steps in their order, rounding " - "where they store a value, so it never falls below the award the " - "model's formulas calculate. An input or a scenario can store a " + "where they store a value, so the bound never falls below the award " + "the model's formulas calculate. An input or a scenario can store a " "larger award; the carer test then reads the stored award " "(has_uc_award_with_carer_element). A family whose bound comes to nil " "has a nil award, and so no award: Parliament makes an amount " "payable of at least the prescribed minimum a financial condition " "(Welfare Reform Act 2012 s.5(1)(b) and (2)(b)), and DWP sets that " - "minimum at one penny (reg 17). The deduction spares a non-dependant " - "on Pension Credit, so it reads Pension Credit, whose severe " - "disability addition reads who has an award with the carer element. " - "This amount reads neither, so has_uc_award_with_carer_element can " - "read it. It can exceed " - "a nil award (#2795). A non-dependant deduction can take the award " - "to nil. A family that takes Tax-Free Childcare instead gets no award " - "(forgoes_universal_credit_for_tax_free_childcare). For a family " + "minimum at one penny (reg 17). DWP makes no deduction for a " + "non-dependant who receives Pension Credit (Sch 4 para 16(2)(b)), so " + "the model's deduction reads Pension Credit, whose severe disability " + "addition reads who has an award with the carer element. The bound " + "reads neither, so has_uc_award_with_carer_element can read it. The " + "bound can exceed a nil award in three ways (#2795). A non-dependant " + "deduction can take the award to nil. A family that takes Tax-Free " + "Childcare instead gets no award " + "(forgoes_universal_credit_for_tax_free_childcare). And for a family " "with a work allowance and a small housing costs element, the " - "calculation without housing costs applies the higher work " - "allowance. Its extra earnings disregard can outweigh the housing " - "costs it drops, so it can stay positive when the award comes to nil, " - "even with no non-dependant deduction." + "calculation without housing costs applies the higher work allowance, " + "whose extra earnings disregard can outweigh the housing costs it " + "drops; that calculation can stay positive when the award comes to " + "nil, even with no non-dependant deduction." ) definition_period = YEAR unit = GBP diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py index 9ee13545f3..283670b440 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py @@ -47,8 +47,9 @@ class council_tax_reduction_carer_premium(Variable): "Claims and Payments Regs 2013 reg 32A). The model has neither " "sanctions nor reclaims, so it pays no premium for those. A person " "with both an allowance and a carer element qualifies once: councils " - "pay the premium for each partner who qualifies (para 5(3)), not for " - "each award. Partners caring for the same severely disabled person " + "include the premium for each applicant or partner who qualifies " + "(paras 5(2) and (3)), not for each award. Partners caring for the " + "same severely disabled person " "get one premium (para 5(3) and (4); " "partners_care_for_same_severely_disabled_person), as in " "carer_premium. Councils give it, under para 5(4), in respect of the " diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py index 912651908a..4369488705 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_premiums.py @@ -97,7 +97,7 @@ def head_has(condition): & (others_counted == 0) ) * disability.severe_single # A head who applies alone holds no Universal Credit award of her own - # (the benefit unit's award is its claimant's and partner's), so + # (the benefit unit's claimant and partner hold its award), so # Scotland's carer element limb (council_tax_reduction_carer_premium) # never reaches her. carer_premium = ( diff --git a/policyengine_uk/variables/household/demographic/carer_premium.py b/policyengine_uk/variables/household/demographic/carer_premium.py index 5a0a9aa9bf..6ba97fc802 100644 --- a/policyengine_uk/variables/household/demographic/carer_premium.py +++ b/policyengine_uk/variables/household/demographic/carer_premium.py @@ -60,7 +60,7 @@ def formula(benunit, period, parameters): # tests caring responsibilities and pays each partner who qualifies. # Para 6(1) defines those as (a) receiving either allowance, (b) being # entitled to one reduced to nil by the overlapping benefits rules, or - # (c) a Universal Credit award with the carer element. This premium + # (c) a Universal Credit award with the carer element. This variable # applies (a) and (b), which every scheme shares; Scottish working-age # council tax reduction reads council_tax_reduction_carer_premium, # which adds (c). diff --git a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py index 54767f5f1d..5dc57d82aa 100644 --- a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py @@ -19,11 +19,12 @@ class pension_age_severe_disability_premium(Variable): "its amounts and its documented assumptions: where only one partner " "qualifies and the other is blind, the qualifying partner is taken to " "claim, as a couple may agree under HB(SPC) reg 63(1) and the CTR " - "schemes (England SI 2012/2885 Sch 8 para 4(1)); carers are attributed " - "by is_cared_for_by_carer_benefit_recipient, which counts a Universal " - "Credit award with the carer element from the award's conditions and " - "the upper bound on the award (uc_award_upper_bound), with that " - "variable's limits. The Pension Credit severe " + "schemes (England SI 2012/2885 Sch 8 para 4(1)). " + "is_cared_for_by_carer_benefit_recipient attributes carers, and " + "counts a Universal Credit award with the carer element from the " + "award's conditions and the upper bound on the award " + "(uc_award_upper_bound). The bound can stay positive when the actual " + "award comes to nil (#2795). The Pension Credit severe " "disability addition has the same conditions apart from its wider " "residence exceptions, but its own amount parameter, so a Pension " "Credit reform does not move this premium." diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index e4f8560906..90a2c3948d 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -21,7 +21,8 @@ class severe_disability_premium(Variable): "when one is paid for only one of them, and nothing when carers are " "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. It counts an award " + "the Pension Credit severe disability addition. The attribution " + "counts an award " "of Universal Credit which includes the carer element from the " "award's conditions and an upper bound on the award " "(has_uc_award_with_carer_element). A carer whose bound comes to nil " From 260f6a77e90bc290348bde6f3699b48b7a964b29 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 2 Nov 2026 05:18:40 -0500 Subject: [PATCH 14/21] Name the actors in the carer-award comments and documentation Comments, docstrings, YAML notes and the zero-value parameter's description give each step to DWP, councils, the Scottish Ministers or the model, and drop sentences about earlier code. No formula or expected value changes. Co-Authored-By: Claude Opus 5.5 --- .../uc_award_includes_zero_value_award.yaml | 2 +- .../council_tax_reduction_carer_premium.yaml | 73 ++++++++++--------- .../uc_carer_element_carer_test.yaml | 45 ++++++------ .../test_uc_carer_element_award_properties.py | 47 ++++++------ .../has_uc_award_with_carer_element.py | 11 +-- .../universal_credit/uc_award_upper_bound.py | 2 +- ...is_cared_for_by_carer_benefit_recipient.py | 11 +-- 7 files changed, 99 insertions(+), 92 deletions(-) diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml index 06ff155820..3c25bfbf63 100644 --- a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml +++ b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml @@ -1,4 +1,4 @@ -description: Whether Scottish councils count a Universal Credit award with a value of zero when deciding the working-age Council Tax Reduction carer premium. The model reads that to include a nil award that earnings bring about, a reading the sources leave open. +description: Whether Scottish councils count a Universal Credit award with a value of zero when deciding the working-age Council Tax Reduction carer premium. The model reads that definition as reaching an award that DWP tapers to nil because of the carer's earnings; the sources leave that reading open. values: 2013-04-01: false 2022-04-01: true diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml index 07e321aa83..eac36da9df 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml @@ -1,13 +1,14 @@ -# Scotland's working-age council tax reduction includes the carer premium -# where the applicant or partner has regular and substantial caring +# Scottish councils include the carer premium in the working-age council tax +# reduction where the applicant or partner has regular and substantial caring # responsibilities for a severely disabled person (SSI 2021/249 Sch 1 para -# 5(2)), which para 6(1)(c) extends to "an award of universal credit which -# includes the carer element described in regulation 29 of the 2013 -# Regulations". No other scheme's carer premium has that limb: the Housing -# Benefit, English and Welsh schedules test entitlement to Carer's Allowance -# or Carer Support Payment (HB Regs 2006 Sch 3 para 17; WSI 2013/3029 Sch 7 -# para 14), as did Scotland's scheme for persons under pension age before -# 1 April 2022 (SSI 2012/303 Sch 1 para 14). From 1 April 2022 to 31 March +# 5(2)), and the Scottish Ministers count among those responsibilities "an +# award of universal credit which includes the carer element described in +# regulation 29 of the 2013 Regulations" (para 6(1)(c)). No other scheme's +# carer premium has that limb: under the Housing Benefit, English and Welsh +# schedules, DWP and councils test entitlement to Carer's Allowance or Carer +# Support Payment (HB Regs 2006 Sch 3 para 17; WSI 2013/3029 Sch 7 para 14), +# as Scottish councils did for persons under pension age before 1 April 2022 +# (SSI 2012/303 Sch 1 para 14). From 1 April 2022 to 31 March # 2025 an "award of universal credit" included "a universal credit award with # a value of zero" (reg 4, as amended by SSI 2022/52 reg 3); from 1 April 2025 # it "means an award of universal credit made under Part 1 of the 2012 Act" @@ -29,9 +30,10 @@ # (2022-23), standard allowance £424.90, £400.14, £393.45 and £334.91. - name: The issue's Scottish carer aged 40 with 35 hours and no Carer's Allowance claim gets the carer premium - # Para 6(1)(c): the award includes the carer element, 209.34 x 12 = - # 2,512.08, so the applicable amount comes to (95.55 + 48.15) x 52 = - # 7,472.40, not 95.55 x 52 = 4,968.60. + # The award includes the carer element, 209.34 x 12 = 2,512.08, so the + # council includes the carer premium (para 6(1)(c)) and the applicable + # amount comes to (95.55 + 48.15) x 52 = 7,472.40, not 95.55 x 52 = + # 4,968.60. period: 2026 absolute_error_margin: 0.01 input: @@ -83,9 +85,10 @@ council_tax_reduction_applicable_amount: (77.00 + 38.85) * 52 - name: Before 1 April 2022 the Scottish scheme for persons under pension age tested Carer's Allowance only - # SSI 2012/303 Sch 1 para 14: "entitled to a carer's allowance". The model - # reads model year 2021 (2021-22) at 30 April 2021, so the carer element, - # 163.73 x 12 = 1,964.76, gives no premium. + # Under SSI 2012/303 Sch 1 para 14, councils included the premium for an + # applicant or partner "entitled to a carer's allowance". The model reads + # model year 2021 (2021-22) at 30 April 2021, so the carer element, 163.73 + # x 12 = 1,964.76, brings no premium. period: 2021 absolute_error_margin: 0.01 input: @@ -107,9 +110,9 @@ council_tax_reduction_carer_premium: 0 - name: The same carer in England gets no carer premium for the carer element - # England: the working-age premiums follow the Housing Benefit schedule, - # whose carer premium needs entitlement to Carer's Allowance (HB Regs 2006 - # Sch 3 para 17(1)). + # In England the model takes the working-age premiums from the Housing + # Benefit schedule, whose carer premium needs entitlement to Carer's + # Allowance (HB Regs 2006 Sch 3 para 17(1)). period: 2026 absolute_error_margin: 0.01 input: @@ -132,7 +135,8 @@ council_tax_reduction_applicable_amount: 95.55 * 52 - name: The same carer in Wales gets no carer premium for the carer element - # WSI 2013/3029 Sch 7 para 14(1): "entitled to a carer's allowance". + # Welsh councils include the premium for an applicant or partner "entitled + # to a carer's allowance" (WSI 2013/3029 Sch 7 para 14(1)). period: 2026 absolute_error_margin: 0.01 input: @@ -217,11 +221,12 @@ council_tax_reduction_carer_premium: 45.60 * 52 - name: From 1 April 2025 the same Scottish carer's nil award leaves no award - # The same carer in 2025-26. The maximum amount is (400.14 + 201.68) x 12 = - # 7,221.84, and the taper again takes more, so the award comes to nil. From - # 1 April 2025 reg 4 defines an award of universal credit as one "made under - # Part 1 of the 2012 Act" (SSI 2025/24 reg 8), and an income that tapers it to nil leaves - # no award, so para 6(1)(c) does not apply: no carer premium. Neither of + # The same carer in 2025-26. The maximum amount comes to (400.14 + 201.68) + # x 12 = 7,221.84, and the taper again takes more, so the award comes to + # nil. From 1 April 2025 reg 4 defines an award of universal credit as one + # "made under Part 1 of the 2012 Act" (SSI 2025/24 reg 8), and an income + # that tapers it to nil leaves no award, so para 6(1)(c) does not apply: no + # carer premium. Neither of # reg 10A's nil awards (a sanction, or a reclaim under the UC Claims and # Payments Regs 2013 reg 32A) arises. period: 2025 @@ -278,8 +283,9 @@ - name: Scottish partners caring for the same person get one premium, whichever holds the carer element # Both care 35 hours. The younger receives Carer Support Payment and the # joint award includes the carer element for the elder (UC Regs 2013 reg - # 29(3)). Para 5(3) and (4) pay one premium, in respect of the one whose - # award includes the carer element: 48.15 x 52 = 2,503.80. + # 29(3)). Councils include one premium under para 5(3) and (4), in respect + # of the one whose award includes the carer element: 48.15 x 52 = + # 2,503.80. period: 2026 absolute_error_margin: 0.01 input: @@ -307,8 +313,9 @@ council_tax_reduction_carer_premium: 48.15 * 52 - name: A Scottish pensioner's schedule has no carer element limb - # SSI 2012/319 Sch 1 para 10(1): entitlement to Carer's Allowance or Carer - # Support Payment. The working-age variable keeps the shared premium. + # Under Scotland's pension-age scheme, councils test entitlement to Carer's + # Allowance or Carer Support Payment (SSI 2012/319 Sch 1 para 10(1)), and + # the working-age variable keeps the shared premium. period: 2026 absolute_error_margin: 0.01 input: @@ -331,11 +338,11 @@ council_tax_reduction_premiums: 0 - name: A dependant's caring does not raise the Scottish carer premium - # Para 5(2) includes the premium where "an applicant or the applicant's - # partner" has caring responsibilities. The son receives Carer Support - # Payment for caring for his grandmother, but he is neither the applicant - # nor a partner, so his parent's family gets no carer premium (48.15 x 52 - # = 2,503.80 if his caring counted). + # Councils include the premium where "an applicant or the applicant's + # partner" has caring responsibilities (para 5(2)). The son receives Carer + # Support Payment for caring for his grandmother, but councils count only + # the applicant and partner, so his parent's family gets no carer premium + # (48.15 x 52 = 2,503.80 if his caring counted). period: 2026 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml index 1cae9f34b9..fd1065b142 100644 --- a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml +++ b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml @@ -1,11 +1,11 @@ -# An award of Universal Credit which includes the carer element counts as a -# carer benefit in the carer condition of the legacy severe disability premium -# (HB Regs 2006 Sch 3 para 14(2)(a)(iii) and (b); HB(SPC) Regs 2006 Sch 3 -# para 6(2)) and of the Pension Credit severe disability addition (SPC Regs -# 2002 Sch I para 1(1)(a)(iii) and (b), with para 1(4)): DWP and councils -# withhold the premium or addition when a carer "has an award of universal -# credit which includes the carer element in respect of caring for" the -# claimant. DWP includes the carer element for regular and substantial caring +# DWP and councils count an award of Universal Credit which includes the +# carer element as a carer benefit in the carer condition of the legacy +# severe disability premium (HB Regs 2006 Sch 3 para 14(2)(a)(iii) and (b); +# HB(SPC) Regs 2006 Sch 3 para 6(2)) and of the Pension Credit severe +# disability addition (SPC Regs 2002 Sch I para 1(1)(a)(iii) and (b), with +# para 1(4)): they withhold the premium or addition when a carer "has an +# award of universal credit which includes the carer element in respect of +# caring for" the claimant. DWP includes the carer element for regular and substantial caring # for a severely disabled person, whether or not the carer claims Carer's # Allowance (UC Regs 2013 regs 29 and 30(2)). # @@ -16,11 +16,11 @@ # over) and £666.97 (joint, either 25 or over), carer element £209.34 a month. - name: A blind carer's Universal Credit carer element bars the premium of the person they care for - # The carer is a non-dependant who is blind, so the non-dependant condition - # ignores them (para 14(4)(b)); their award bars the premium instead. - # Carer element 209.34 x 12 = 2,512.08; award (424.90 + 209.34) x 12 = - # 7,610.88. Without the limb the model paid the premium, 86.05 x 52 = - # 4,474.60. + # The council ignores the carer, a blind non-dependant, under the + # non-dependant condition (para 14(4)(b)), and the carer's award bars the + # premium instead. Carer element 209.34 x 12 = 2,512.08; award (424.90 + + # 209.34) x 12 = 7,610.88. Without the award, the council would include the + # premium, 86.05 x 52 = 4,474.60. period: 2026 input: people: @@ -50,10 +50,11 @@ severe_disability_premium: [0, 0] - name: A partner whose joint award includes the carer element leaves a couple who both qualify the single rate - # Para 14(2)(b)(iii): a carer element in respect of caring for only one of - # the couple gives one single rate, 86.05 x 52 = 4,474.60, where no carer - # gave the double rate, 172.10 x 52 = 8,949.20. Joint award (666.97 + - # 209.34) x 12 = 10,515.72. + # The joint award includes a carer element in respect of caring for only + # one of the couple, so the council includes one single rate, 86.05 x 52 = + # 4,474.60, and not the double rate, 172.10 x 52 = 8,949.20, that it + # includes where no one cares for either (para 14(2)(b)(iii)). Joint award + # (666.97 + 209.34) x 12 = 10,515.72. period: 2026 input: people: @@ -80,9 +81,10 @@ severe_disability_premium: 4_474.60 - name: A son's Universal Credit carer element in another benefit unit bars a pensioner's severe disability addition - # SPC Regs 2002 Sch I para 1(1)(a)(iii) and (4). The son is blind, so the - # residence condition ignores him (para 2). Without the limb the model paid - # the addition, 86.05 x 52 = 4,474.60, and the pension-age Housing Benefit + # DWP ignores the blind son as a resident (SPC Regs 2002 Sch I para + # 2(2)(b)), and his award with the carer element bars the addition (para + # 1(1)(a)(iii) and (4)). Without the award, DWP would include the addition, + # 86.05 x 52 = 4,474.60, and the council the pension-age Housing Benefit # premium at the same amount (HB(SPC) Regs 2006 Sch 3 para 6(2)(a)(iii)). period: 2026 input: @@ -189,7 +191,8 @@ is_cared_for_by_carer_benefit_recipient: [false, false, true, false] - name: A carer whose income tapers the award to nil has no award and does not bar the premium - # The carer's maximum amount is (424.90 + 209.34) x 12 = 7,610.88. Earnings + # The carer's maximum amount comes to (424.90 + 209.34) x 12 = 7,610.88. + # Earnings # of £25,000 less income tax (25,000 - 12,570) x 0.20 = 2,486.00 and # National Insurance (25,000 - 12,570) x 0.08 = 994.40 leave 21,519.60, and # with no work allowance the taper takes 0.55 x 21,519.60 = 11,835.78. That diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index 629146f9fc..6d76b8513d 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -16,10 +16,9 @@ (receives_uc_carer_element), or from 1 April 2022 to 31 March 2025, when the model counts a nil award as one with a value of zero, the award's conditions. Each example simulates the households as drawn, and again with -has_uc_award_with_carer_element supplied as false for everyone, the -attribution as it ran before the limb (its carers then come from -receives_carer_benefit alone). carer_premium gives the Scottish premium's -counterpart without the limb. +has_uc_award_with_carer_element supplied as false for everyone, which leaves +receives_carer_benefit as the attribution's only source of carers. +carer_premium gives the Scottish premium's counterpart without the limb. Invariants, on generated households under baseline law: @@ -61,10 +60,6 @@ A reform that makes is_uc_eligible read Pension Credit would cycle through the attribution, which reads eligibility; these tests do not cover reforms. - -The no-change invariant compares the two code paths in this tree; the -differential against origin/main itself runs on hand-built datasets outside -the test suite (see the pull request). """ import numpy as np @@ -132,9 +127,10 @@ def adult(draw, age): age=draw(age), disability=draw(st.sampled_from(["none", "none", "pip_standard", "aa_higher"])), blind=draw(st.sampled_from([False, False, True])), - # The qualifying hours, with or without a Carer's Allowance claim: the - # Universal Credit carer element needs no claim (UC Regs 2013 reg - # 30(2)), and its test ignores the allowance's earnings limit. + # Draw the qualifying hours with or without a Carer's Allowance claim: + # DWP includes the Universal Credit carer element without a claim and + # above the allowance's earnings limit (UC Regs 2013 reg 30(1)(a)(i) + # and (2)). care_hours=draw(st.sampled_from([0, 35, 35])), would_claim_carers_allowance=draw(st.booleans()), earnings=draw(st.sampled_from([0, 0, 8_000, 25_000])), @@ -268,8 +264,8 @@ def single(age=40, **inputs): EXAMPLES = [ - # The blind carer: a Universal Credit carer element in another benefit - # unit bars the disabled person's premium. + # A blind carer's Universal Credit carer element, in another benefit + # unit, bars the disabled person's premium. dict( country="ENGLAND", units=[ @@ -303,7 +299,7 @@ def single(age=40, **inputs): ) ], ), - # A pensioner cared for by a son on a qualifying benefit. + # A pensioner whose son, on a qualifying benefit, cares for her. dict( country="WALES", units=[ @@ -318,11 +314,10 @@ def single(age=40, **inputs): def test_issue_households_read_the_carer_element_award(): - # #2120: the blind carer's award includes the carer element, 209.34 x 12, - # so the disabled person has no severe disability premium (86.05 x 52 - # without it). #2124: the Scottish carer's applicable amount includes the - # carer premium, (95.55 + 48.15) x 52, not 95.55 x 52. The test reads - # only variables that predate the fix, so it also runs on the old code. + # In #2120 the blind carer's award includes the carer element, 209.34 x + # 12, so the disabled person has no severe disability premium (86.05 x 52 + # without it). In #2124 the Scottish carer's applicable amount includes + # the carer premium, (95.55 + 48.15) x 52, not 95.55 x 52. sim = Simulation(situation=situation(EXAMPLES[:1] + EXAMPLES[2:3])) severe = sim.calculate("severe_disability_premium", YEAR) carer_element = sim.calculate("uc_carer_element", YEAR) @@ -502,8 +497,8 @@ def test_a_stored_award_is_the_award_and_reads_no_cycle(order): def test_a_reform_that_abolishes_universal_credit_leaves_no_carer_element_award(): - # A neutralized award reads no other variable and comes to nil, so no one has - # an award and the blind carer of the issue no longer bars the premium. + # A neutralized award reads no other variable and comes to nil, so no one + # has an award, and the issue's blind carer does not bar the premium. from policyengine_core.reforms import Reform class AbolishUniversalCredit(Reform): @@ -676,16 +671,16 @@ def small_housing_situation(): strict=True, reason=( "#2795: the bound's calculation without housing costs takes the " - "higher work allowance and stays positive when this award is nil." + "higher work allowance and stays positive when this award comes to nil." ), ) def test_small_housing_costs_with_a_work_allowance_leave_no_award(): # DWP has determined that the carer has limited capability for work, so # her award has a work allowance (UC Regs 2013 reg 22(1)(b)), and she - # gets the carer element with it: reg 29(4) gives the LCWRA element in - # its place only for limited capability for work and work-related - # activity, and reg 30(1)(a)(i) and (2) ignore the allowance's earnings - # limit and the missing claim. Her award has a housing costs element of + # gets the carer element with it: DWP gives the LCWRA element in its + # place only for limited capability for work and work-related activity + # (reg 29(4)), and ignores the allowance's earnings limit and the missing + # claim (reg 30(1)(a)(i) and (2)). Her award has a housing costs element of # £600, so DWP applies the lower work allowance, £427 a month (reg # 22(2)(b)). The taper then exceeds the maximum amount, and the award # comes to nil (Welfare Reform Act 2012 s.5(1)(b); reg 17). The bound diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py index 1b26f254e4..64394d7f8d 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py @@ -27,11 +27,12 @@ class has_uc_award_with_carer_element(Variable): "deduction reads Pension Credit, whose addition reads the " "attribution. has_uc_award_with_carer_element therefore reads the " "award's conditions and bound. " - "The family claims Universal Credit (claims_universal_credit), meets " - "the age, pension-route and capital conditions (is_uc_eligible) and " - "has a positive upper bound on its award (uc_award_upper_bound), and " - "the award includes the carer element in respect of " - "this claimant (uc_carer_element_included_for_claimant). A nil bound " + "It counts an award where the family claims Universal Credit " + "(claims_universal_credit), meets the age, pension-route and capital " + "conditions (is_uc_eligible) and has a positive upper bound on its " + "award (uc_award_upper_bound), and where the award includes the carer " + "element in respect of this claimant " + "(uc_carer_element_included_for_claimant). A nil bound " "leaves a nil award, and so no award (Welfare Reform Act 2012 s.5(1)(b) " "and (2)(b); DWP sets the minimum at one penny, reg 17). A carer " "whose income reduces both bound calculations to nil does not count. " diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py index b02c2341a6..c92135e432 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py @@ -12,7 +12,7 @@ class uc_award_upper_bound(Variable): "awards the maximum amount less the income reduction (UC Regs 2013 " "reg 22). The maximum amount includes the housing costs element " "(uc_housing_costs_element): the rent a renter may claim less DWP's " - "non-dependant deductions. Where that element is positive, DWP " + "non-dependant deductions. Where that element exceeds nil, DWP " "applies the lower work allowance (reg 22(2)(b)). Where DWP deducts " "less than the rent, the award stays at or below the award with no " "deduction. Where it deducts at least the rent, DWP calculates the " 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 a3a0fda087..dddddd6928 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 @@ -19,9 +19,9 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "(is_severely_disabled_for_carers_allowance; UC Regs 2013 reg 30), a " "carer has one award (SSCBA 1992 s.70(7)), and only " "one person has an allowance or a carer element for caring for the " - "same severely disabled person (s.70(7ZA) to (7ZC) and (7A); both " - "rules were in s.70(7) before 16 November 2023). No one is their own " - "carer. A " + "same severely disabled person (s.70(7ZA) to (7ZC) and (7A); before " + "16 November 2023, s.70(7) contained both rules). The model makes no " + "one 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 " @@ -41,8 +41,9 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "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)). Receipt " - "is receives_carer_benefit: a positive Carer's Allowance or Carer " + "weeks after the person they cared for has died (s.70(1A)). The " + "model reads receipt from receives_carer_benefit: a positive Carer's " + "Allowance or Carer " "Support Payment after the overlapping-benefit adjustment. A carer " "whose allowance an overlapping benefit, such as a higher State " "Pension, reduces to nil keeps the entitlement but receives nothing, " From fff13eda816c608a8dbe2e064131472363fae9f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 16:02:21 +0000 Subject: [PATCH 15/21] Name the housing costs the bound compares and reflow the carer comments The bound's documentation now compares the deduction with the housing costs DWP allows, capped at the Local Housing Allowance rate for a private renter. Three documentation sentences name their actor, and the YAML comments in the two carer test files wrap without orphaned words. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6KFgPgaVD6zHA1bFq9 --- .../council_tax_reduction_carer_premium.yaml | 11 +++++----- .../uc_carer_element_carer_test.yaml | 20 +++++++++---------- ...e_disability_minimum_guarantee_addition.py | 11 +++++----- .../universal_credit/uc_award_upper_bound.py | 8 +++++--- ...is_cared_for_by_carer_benefit_recipient.py | 6 +++--- .../demographic/severe_disability_premium.py | 9 ++++----- 6 files changed, 31 insertions(+), 34 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml index eac36da9df..80cfdf4ca3 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml @@ -160,9 +160,9 @@ - name: A Scottish carer whose income tapers the award to nil has no award and no carer premium # With £20,000 of earnings the award comes to nil, which in 2026 leaves no - # award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17), and para 6(1)(c) - # does not apply, though the maximum amount still includes the carer - # element. + # award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17), and para + # 6(1)(c) does not apply, though the maximum amount still includes the + # carer element. period: 2026 absolute_error_margin: 0.01 input: @@ -226,9 +226,8 @@ # nil. From 1 April 2025 reg 4 defines an award of universal credit as one # "made under Part 1 of the 2012 Act" (SSI 2025/24 reg 8), and an income # that tapers it to nil leaves no award, so para 6(1)(c) does not apply: no - # carer premium. Neither of - # reg 10A's nil awards (a sanction, or a reclaim under the UC Claims and - # Payments Regs 2013 reg 32A) arises. + # carer premium. Neither of reg 10A's nil awards (a sanction, or a reclaim + # under the UC Claims and Payments Regs 2013 reg 32A) arises. period: 2025 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml index fd1065b142..61976b053c 100644 --- a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml +++ b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml @@ -5,9 +5,9 @@ # disability addition (SPC Regs 2002 Sch I para 1(1)(a)(iii) and (b), with # para 1(4)): they withhold the premium or addition when a carer "has an # award of universal credit which includes the carer element in respect of -# caring for" the claimant. DWP includes the carer element for regular and substantial caring -# for a severely disabled person, whether or not the carer claims Carer's -# Allowance (UC Regs 2013 regs 29 and 30(2)). +# caring for" the claimant. DWP includes the carer element for regular and +# substantial caring for a severely disabled person, whether or not the carer +# claims Carer's Allowance (UC Regs 2013 regs 29 and 30(2)). # # Amounts for 2026-27 (DWP benefit and pension rates 2026 to 2027; the Social # Security Benefits Up-rating Order 2026, SI 2026/148): severe disability @@ -192,17 +192,15 @@ - name: A carer whose income tapers the award to nil has no award and does not bar the premium # The carer's maximum amount comes to (424.90 + 209.34) x 12 = 7,610.88. - # Earnings - # of £25,000 less income tax (25,000 - 12,570) x 0.20 = 2,486.00 and - # National Insurance (25,000 - 12,570) x 0.08 = 994.40 leave 21,519.60, and - # with no work allowance the taper takes 0.55 x 21,519.60 = 11,835.78. That - # exceeds the maximum amount even with no non-dependant deduction + # Earnings of £25,000 less income tax (25,000 - 12,570) x 0.20 = 2,486.00 + # and National Insurance (25,000 - 12,570) x 0.08 = 994.40 leave 21,519.60, + # and with no work allowance the taper takes 0.55 x 21,519.60 = 11,835.78. + # That exceeds the maximum amount even with no non-dependant deduction # (uc_award_upper_bound; the disabled person's family, which does not # claim, has a bound of its standard allowance, 424.90 x 12 = 5,098.80), so # the award comes to nil, which leaves no award (Welfare Reform Act 2012 - # s.5(1)(b); UC Regs 2013 reg 17). No one has an award in - # respect of caring for the disabled person, who keeps the premium, 86.05 x - # 52 = 4,474.60. + # s.5(1)(b); UC Regs 2013 reg 17). No one has an award in respect of caring + # for the disabled person, who keeps the premium, 86.05 x 52 = 4,474.60. period: 2026 absolute_error_margin: 0.01 input: 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 3ca17774c6..537f2538e7 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 @@ -24,16 +24,15 @@ class severe_disability_minimum_guarantee_addition(Variable): "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 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. The attribution counts an award of " + "listed on is_counted_resident_for_severe_disability_addition. " + "is_cared_for_by_carer_benefit_recipient attributes each carer " + "benefit to the person it is paid for caring for, and the legacy " + "severe disability premium shares it. The attribution counts an award of " "Universal Credit which includes the carer element (para 1(4)), which " "has_uc_award_with_carer_element reads from the award's conditions " "and the upper bound on the award (uc_award_upper_bound). The bound " "can stay positive when the actual award comes to nil (#2795). " - "Blindness " - "(is_blind) is not in the survey data, so in microsimulation the " + "Blindness (is_blind) is not in the survey data, so in microsimulation the " "blind-partner route never applies and a blind resident is counted." ) entity = BenUnit diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py index c92135e432..aade3f9656 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py @@ -14,9 +14,11 @@ class uc_award_upper_bound(Variable): "(uc_housing_costs_element): the rent a renter may claim less DWP's " "non-dependant deductions. Where that element exceeds nil, DWP " "applies the lower work allowance (reg 22(2)(b)). Where DWP deducts " - "less than the rent, the award stays at or below the award with no " - "deduction. Where it deducts at least the rent, DWP calculates the " - "award with no housing costs element and the higher work allowance " + "less than the housing costs it allows (the rent, capped at the Local " + "Housing Allowance rate for a private renter), the award stays at or " + "below the award with no deduction. Where it deducts at least those " + "housing costs, DWP calculates the award with no housing costs " + "element and the higher work allowance " "(reg 22(2)(a); Sch 4 para 13). The formula takes the larger of those " "two awards. It repeats the award's steps in their order, rounding " "where they store a value, so the bound never falls below the award " 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 dddddd6928..4ea9c3f8f6 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 @@ -39,9 +39,9 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "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)). The " + "does not depend on that choice. The model does not observe carers " + "outside the household, nor a carer whose allowance runs on for up to " + "eight weeks after the person they cared for has died (s.70(1A)). The " "model reads receipt from receives_carer_benefit: a positive Carer's " "Allowance or Carer " "Support Payment after the overlapping-benefit adjustment. A carer " diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index 90a2c3948d..18f1f60b36 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -19,11 +19,10 @@ 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. 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 attribution " - "counts an award " - "of Universal Credit which includes the carer element from the " + "paid for both. is_cared_for_by_carer_benefit_recipient attributes " + "each carer benefit to the person it is paid for caring for, and the " + "Pension Credit severe disability addition shares it. The attribution " + "counts an award of Universal Credit which includes the carer element from the " "award's conditions and an upper bound on the award " "(has_uc_award_with_carer_element). A carer whose bound comes to nil " "does not bar the premium. The model can still withhold it where a " From b6b1ce7cb3b179870ee6f6b840a1f75aef35b63e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 19:58:21 +0000 Subject: [PATCH 16/21] Read the carer's actual Universal Credit award in the severe disability carer test has_uc_award_with_carer_element now reads the award before the benefit cap (universal_credit_pre_benefit_cap), so a carer whose award a non-dependant deduction, the Tax-Free Childcare choice or the taper with a lower work allowance takes to nil has no award. The non-dependant deduction's Pension Credit branch keeps that read from cycling; the benefit cap, which reads Child Tax Credit and so Pension Credit, would not, so the test reads the award before the cap. Remove uc_award_upper_bound and its tests, the three expected-failure markers and their allowlist entries, add the executed English case and a Northern Ireland case, pin the branch's armed forces independence payment case, and cite Northern Ireland's counterparts. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6FQc4koGSzCUYko19B --- .github/test_outcome_allowlist.toml | 18 - changelog.d/2120.fixed.md | 2 +- .../uc_award_includes_zero_value_award.yaml | 18 - .../uc_carer_element_carer_test.yaml | 144 +++++- .../test_uc_carer_element_award_properties.py | 482 +++++------------- ...e_disability_minimum_guarantee_addition.py | 11 +- .../has_uc_award_with_carer_element.py | 77 ++- .../universal_credit/uc_award_upper_bound.py | 112 ---- ...is_cared_for_by_carer_benefit_recipient.py | 29 +- .../pension_age_severe_disability_premium.py | 7 +- .../demographic/severe_disability_premium.py | 13 +- 11 files changed, 342 insertions(+), 571 deletions(-) delete mode 100644 policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml delete mode 100644 policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py diff --git a/.github/test_outcome_allowlist.toml b/.github/test_outcome_allowlist.toml index 7279d3d3b5..e3b947e134 100644 --- a/.github/test_outcome_allowlist.toml +++ b/.github/test_outcome_allowlist.toml @@ -25,24 +25,6 @@ outcome = "xfailed" reason = "#1947" why = "Known bug #1947 (Marriage Allowance booked on the recipient), pinned by a strict xfail until it is fixed." -[[allow]] -nodeid = "policyengine_uk/tests/test_uc_carer_element_award_properties.py::test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award" -outcome = "xfailed" -reason = "#2795" -why = "Known bug #2795: a carer whose award a non-dependant deduction takes to nil still bars the severe disability premium. A strict xfail pins it until the model pays the premium." - -[[allow]] -nodeid = "policyengine_uk/tests/test_uc_carer_element_award_properties.py::test_a_family_that_takes_tax_free_childcare_instead_has_no_award" -outcome = "xfailed" -reason = "#2795" -why = "Known bug #2795: a carer whose family takes Tax-Free Childcare instead of Universal Credit still bars the severe disability premium. A strict xfail pins it until the model pays the premium." - -[[allow]] -nodeid = "policyengine_uk/tests/test_uc_carer_element_award_properties.py::test_small_housing_costs_with_a_work_allowance_leave_no_award" -outcome = "xfailed" -reason = "#2795" -why = "Known bug #2795: a carer with a work allowance and a small housing costs element whose award comes to nil still bars the severe disability premium. A strict xfail pins it until the model pays the premium." - [[allow]] nodeid = "policyengine_uk/tests/test_build_metadata.py::test_runtime_metadata_uses_bundle_contract_when_available" outcome = "skipped" diff --git a/changelog.d/2120.fixed.md b/changelog.d/2120.fixed.md index b3214856e0..a5b29b8b7d 100644 --- a/changelog.d/2120.fixed.md +++ b/changelog.d/2120.fixed.md @@ -1 +1 @@ -The model now counts a carer's Universal Credit award with the carer element when deciding the severe disability premium and the Pension Credit severe disability addition. In Scotland's working-age council tax reduction, it counts such awards for the carer premium from 1 April 2022. Until 31 March 2025 it also counts an award that DWP tapers to nil because of the carer's earnings, under its reading of Scotland's definition of an award "with a value of zero" (#2120, #2124). +The model now counts a carer's Universal Credit award with the carer element, where the award comes to at least one penny, when deciding the severe disability premium and the Pension Credit severe disability addition, and in Scotland's working-age council tax reduction it counts such an award for the carer premium from 1 April 2022 (#2120, #2124). diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml deleted file mode 100644 index 3c25bfbf63..0000000000 --- a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/working_age/uc_award_includes_zero_value_award.yaml +++ /dev/null @@ -1,18 +0,0 @@ -description: Whether Scottish councils count a Universal Credit award with a value of zero when deciding the working-age Council Tax Reduction carer premium. The model reads that definition as reaching an award that DWP tapers to nil because of the carer's earnings; the sources leave that reading open. -values: - 2013-04-01: false - 2022-04-01: true - 2025-04-01: false -metadata: - unit: bool - period: year - label: Scottish working-age Council Tax Reduction counts a Universal Credit award with a value of zero - reference: - - title: The Council Tax Reduction (Scotland) Regulations 2021 reg. 4 ("award of universal credit" includes a universal credit award with a value of zero), as in force to 31 March 2025 - href: https://www.legislation.gov.uk/ssi/2021/249/regulation/4/2024-04-30 - - title: The Council Tax Reduction (Scotland) Amendment Regulations 2022 (SSI 2022/52) reg. 3 (inserts that definition from 31 March 2022) - href: https://www.legislation.gov.uk/ssi/2022/52/regulation/3/made - - title: The Council Tax Reduction (Miscellaneous Amendment) (Scotland) Regulations 2025 (SSI 2025/24) regs. 1 and 8 (substitute "means an award of universal credit made under Part 1 of the 2012 Act" from 1 April 2025) - href: https://www.legislation.gov.uk/ssi/2025/24/regulation/8/made - - title: The Council Tax Reduction (Scotland) Regulations 2021 reg. 10A (awards reduced to nil by a sanction, and reclaims under the Claims and Payments Regulations 2013 reg. 32A, from 1 April 2025) - href: https://www.legislation.gov.uk/ssi/2021/249/regulation/10A diff --git a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml index 61976b053c..6e62b31464 100644 --- a/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml +++ b/policyengine_uk/tests/policy/baseline/household/demographic/uc_carer_element_carer_test.yaml @@ -195,12 +195,10 @@ # Earnings of £25,000 less income tax (25,000 - 12,570) x 0.20 = 2,486.00 # and National Insurance (25,000 - 12,570) x 0.08 = 994.40 leave 21,519.60, # and with no work allowance the taper takes 0.55 x 21,519.60 = 11,835.78. - # That exceeds the maximum amount even with no non-dependant deduction - # (uc_award_upper_bound; the disabled person's family, which does not - # claim, has a bound of its standard allowance, 424.90 x 12 = 5,098.80), so - # the award comes to nil, which leaves no award (Welfare Reform Act 2012 - # s.5(1)(b); UC Regs 2013 reg 17). No one has an award in respect of caring - # for the disabled person, who keeps the premium, 86.05 x 52 = 4,474.60. + # That exceeds the maximum amount, so the award comes to nil, which leaves + # no award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17). No one + # has an award in respect of caring for the disabled person, who keeps the + # premium, 86.05 x 52 = 4,474.60. period: 2026 absolute_error_margin: 0.01 input: @@ -226,8 +224,8 @@ country: ENGLAND output: uc_earned_income: [0, 21_519.60] + universal_credit_pre_benefit_cap: [0, 0] universal_credit: [0, 0] - uc_award_upper_bound: [5_098.80, 0] receives_uc_carer_element: [false, false] has_uc_award_with_carer_element: [false, false] is_cared_for_by_carer_benefit_recipient: [false, false] @@ -238,9 +236,8 @@ # £6,000 a year. The housing costs element raises the maximum amount to # 7,610.88 + 6,000 = 13,610.88, and the taper of 11,835.78 leaves an award # of 1,775.10. The disabled person, on the PIP daily living component, - # brings no non-dependant deduction (UC Regs 2013 Sch 4 para 16(2)(e)), so - # the award equals its bound. It includes the carer element and bars the - # premium. + # brings no non-dependant deduction (UC Regs 2013 Sch 4 para 16(2)(e)). + # The award includes the carer element and bars the premium. period: 2026 absolute_error_margin: 0.01 input: @@ -270,7 +267,7 @@ output: uc_housing_costs_element: [6_000, 0] universal_credit: [1_775.10, 0] - uc_award_upper_bound: [1_775.10, 5_098.80] + universal_credit_pre_benefit_cap: [1_775.10, 0] has_uc_award_with_carer_element: [true, false] is_cared_for_by_carer_benefit_recipient: [false, true] severe_disability_premium: [0, 0] @@ -307,7 +304,6 @@ output: uc_earned_income: [0, 17_919.60] universal_credit: [0, 0] - uc_award_upper_bound: [0, 0] has_uc_award_with_carer_element: [false, false] is_cared_for_by_carer_benefit_recipient: [false, false] severe_disability_minimum_guarantee_addition: [4_474.60, 0] @@ -383,3 +379,127 @@ has_uc_award_with_carer_element: [false, false] is_cared_for_by_carer_benefit_recipient: [false, false] severe_disability_premium: [4_474.60, 0] + +- name: A blind carer whose award comes to nil with a small housing costs element does not bar a pensioner's addition + # The household head, aged 35 and blind, has a limited capability for work + # determination, cares 35 hours a week for a pensioner aged 75 in another + # benefit unit, claims no Carer's Allowance, earns £25,000 and pays the + # council £600 a year in rent. The pensioner receives Attendance Allowance + # at the higher rate, 114.60 x 52 = 5,959.20, and £10,000 of State Pension. + # + # The carer's Universal Credit. Income tax (25,000 - 12,570) x 0.20 = + # 2,486.00 and National Insurance (25,000 - 12,570) x 0.08 = 994.40 leave + # earned income of 21,519.60. The award includes the carer element: the + # carer has no limited capability for work-related activity (UC Regs 2013 + # reg 29(4)). The pensioner brings no non-dependant deduction (Sch 4 para + # 16(2)(d)). The maximum amount comes to (424.90 + 209.34) x 12 + 600 = + # 8,210.88. With a housing costs element, DWP applies the lower work + # allowance, 427 x 12 = 5,124.00 (reg 22(2)(b)), and the taper takes + # 0.55 x (21,519.60 - 5,124.00) = 9,017.58, more than the maximum amount. + # The model caps the reduction at the maximum amount, so the award comes + # to nil and the carer has no award (Welfare Reform Act 2012 s.5(1)(b); + # reg 17). + # + # The pensioner's addition. She receives Attendance Allowance (SPC Regs + # 2002 Sch I para 1(1)(a)(i)); DWP ignores the blind carer as a resident + # (para 2(2)(b)); and no one has an award including the carer element in + # respect of caring for her (para 1(1)(a)(iii) and (4)). So DWP includes + # the addition, 86.05 x 52 = 4,474.60. + # + # Pension Credit. The minimum guarantee comes to 238.00 x 52 + 4,474.60 = + # 16,850.60. Her income for Pension Credit is the State Pension, 10,000 + # (the model counts no Attendance Allowance), so guarantee credit comes to + # 16,850.60 - 10,000 = 6,850.60. Her qualifying income, 10,000 / 52 = + # 192.31 a week, falls short of the savings credit threshold of 208.07, so + # savings credit comes to nil. + # + # Household net income. Earnings 25,000, plus State Pension 10,000, + # Attendance Allowance 5,959.20, Pension Credit 6,850.60, Winter Fuel + # Payment 200 (aged under 80, lower rate) and the Household Energy Payment + # 100 (England, the model's default band D), less income tax 2,486.00 and + # National Insurance 994.40: 44,629.40. Were the carer counted, the + # addition would fall to nil and Pension Credit to 16,850.60 - 4,474.60 - + # 10,000 = 2,376.00, and household net income to 40,154.80. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 35 + is_blind: true + uc_limited_capability_for_work: true + care_hours: 35 + would_claim_carers_allowance: false + employment_income: 25_000 + is_household_head: true + pensioner: + age: 75 + aa_category: HIGHER + state_pension: 10_000 + benunits: + carer_unit: + members: [carer] + pensioner_unit: + members: [pensioner] + households: + household: + members: [carer, pensioner] + country: ENGLAND + tenure_type: RENT_FROM_COUNCIL + rent: 600 + output: + uc_earned_income_before_work_allowance: [21_519.60, 0] + uc_earned_income: [16_395.60, 0] + uc_maximum_amount: [8_210.88, 0] + uc_work_allowance: [5_124, 0] + uc_income_reduction: [8_210.88, 0] + universal_credit_pre_benefit_cap: [0, 0] + has_uc_award_with_carer_element: [false, false] + is_cared_for_by_carer_benefit_recipient: [false, false] + severe_disability_minimum_guarantee_addition: [0, 4_474.60] + minimum_guarantee: [12_376, 16_850.60] + savings_credit: [0, 0] + pension_credit: [0, 6_850.60] + household_net_income: 44_629.40 + +- name: In Northern Ireland a son's award including the carer element bars a pensioner's addition + # SR 2016/236 reg 31(7) inserted the Universal Credit words into SPC Regs + # (NI) 2003 Sch 1 para 1(1)(a)(iii), with para 1(4) defining the award by + # reference to UC Regs (NI) 2016 reg 30. The model reads the same + # parameters in Northern Ireland: Universal Credit standard allowance + # 424.90 and carer element 209.34 a month (gov.dwp.universal_credit), and + # the addition 86.05 a week + # (gov.dwp.pension_credit.guarantee_credit.severe_disability.addition). + # The pensioner, aged 75, receives Attendance Allowance at the higher rate. + # Her blind son, aged 40, cares 35 hours a week, claims no Carer's + # Allowance and has no income, so his award comes to (424.90 + 209.34) x + # 12 = 7,610.88 and includes the carer element, 209.34 x 12 = 2,512.08. + # DWP ignores him as a resident (para 2(2)(b)), but his award bars the + # addition, which would otherwise come to 86.05 x 52 = 4,474.60. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 75 + aa_category: HIGHER + son: + age: 40 + is_blind: true + care_hours: 35 + would_claim_carers_allowance: false + benunits: + pensioner_unit: + members: [pensioner] + son_unit: + members: [son] + households: + household: + members: [pensioner, son] + country: NORTHERN_IRELAND + output: + uc_carer_element: [0, 2_512.08] + universal_credit_pre_benefit_cap: [0, 7_610.88] + has_uc_award_with_carer_element: [false, true] + is_cared_for_by_carer_benefit_recipient: [true, false] + severe_disability_minimum_guarantee_addition: [0, 0] diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index 6d76b8513d..a6ad8ee35a 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -9,13 +9,11 @@ 2021/249 Sch 1 paras 5 and 6(1)(c), from 1 April 2022) pays the carer premium to an applicant or partner with such an award. -The model reads the award from its conditions and an upper bound on it -(has_uc_award_with_carer_element, uc_award_upper_bound) for the severe -disability carer test, since the award itself reads Pension Credit, unless the -simulation stores the award. Scotland's carer premium reads the positive award -(receives_uc_carer_element), or from 1 April 2022 to 31 March 2025, when the -model counts a nil award as one with a value of zero, the award's conditions. -Each example simulates the households as drawn, and again with +The severe disability carer test (has_uc_award_with_carer_element) reads the +award before the benefit cap, universal_credit_pre_benefit_cap, since the cap +reads Pension Credit, or the award itself where the simulation stores it. +Scotland's carer premium reads the positive award (receives_uc_carer_element) +in every year. Each example simulates the households as drawn, and again with has_uc_award_with_carer_element supplied as false for everyone, which leaves receives_carer_benefit as the attribution's only source of carers. carer_premium gives the Scottish premium's counterpart without the limb. @@ -37,26 +35,23 @@ the number of claimants and partners, and at most one where partners care for the same person; a person with both an allowance and a carer element counts once. -5. A nil bound leaves a nil award: uc_award_upper_bound >= - universal_credit_pre_benefit_cap >= universal_credit for awards the - model's formulas calculate, so receives_uc_carer_element implies - has_uc_award_with_carer_element. That implication also holds where the - simulation stores the award, which can exceed the bound. +5. The carer test is the award: has_uc_award_with_carer_element equals + receives_uc_carer_element for every person, and + universal_credit_pre_benefit_cap comes to more than nil wherever + universal_credit does, and for every family whose award includes the + carer element in respect of someone, exactly where universal_credit does. + The equality also holds where the simulation stores the award. 6. Attribution: the attribution makes no one their own carer, everyone it counts as cared for meets Carer's Allowance's severe disability test, and no household has more people cared for than carers (carer benefit recipients and carer element holders). 7. No cycle: every reader calculates on one simulation, whichever the test requests first, and also where the simulation stores the award. -8. The bound reads neither the award nor Pension Credit: its trace reaches - none of the award's steps, the non-dependant deduction or the attribution. -9. Endpoint parity: uc_award_upper_bound equals, exactly, the larger of two - awards the model's own formulas calculate, one with non-dependant - deductions supplied as nil and one with the housing costs element supplied - as nil, at the rows test_upper_bound_matches_real_award_endpoints draws. -10. Every component: the bound reads each element of uc_maximum_amount in its - order, and every input of the award's steps apart from the non-dependant - deduction, the Tax-Free Childcare choice and the claim. +8. The cut: the non-dependant deduction's Pension Credit exemption reads the + non-dependant family's Pension Credit in a branch with every housing costs + element set to nil, where the carer test reads the award without housing + costs. test_the_non_dependant_branch_reads_the_award_without_housing_costs + pins the one kind of case where that changes a deduction. A reform that makes is_uc_eligible read Pension Credit would cycle through the attribution, which reads eligibility; these tests do not cover reforms. @@ -103,7 +98,7 @@ "is_cared_for_by_carer_benefit_recipient", "has_uc_award_with_carer_element", "receives_uc_carer_element", - "uc_award_upper_bound", + "universal_credit_pre_benefit_cap", "universal_credit", "pension_credit", "housing_benefit", @@ -162,8 +157,7 @@ def benefit_unit(draw): @st.composite def households(draw): # DWP includes housing costs for renters and deducts an amount for each - # non-dependant in another benefit unit. The upper bound leaves that - # deduction out. + # non-dependant in another benefit unit. tenure, rent = draw( st.sampled_from( [ @@ -343,9 +337,9 @@ def test_the_carer_element_limb_moves_only_its_readers_and_only_one_way(populati "receives_uc_carer_element", "receives_carer_benefit", "is_claimant_or_partner", - "uc_award_upper_bound", "universal_credit_pre_benefit_cap", "universal_credit", + "uc_carer_element", ) sim, values = with_and_without_limb(population, variables) drawn = {v: pair[0] for v, pair in values.items()} @@ -354,15 +348,17 @@ def test_the_carer_element_limb_moves_only_its_readers_and_only_one_way(populati benunit_household = household_index(sim, "benunit") person_household = household_index(sim, "person") - # 5. A nil bound leaves a nil award, exactly, so the carer test misses no - # award. - bound = drawn["uc_award_upper_bound"] + # 5. The carer test is the award: the award before the cap is positive + # wherever the final award is, and exactly where it is for a family whose + # award includes the carer element, so the two tests agree. award = drawn["universal_credit_pre_benefit_cap"] - assert (bound >= award).all() - assert (award >= drawn["universal_credit"]).all() + final = drawn["universal_credit"] + assert not ((final > 0) & ~(award > 0)).any() + carer_family = drawn["uc_carer_element"] > 0 + assert ((award > 0) == (final > 0))[carer_family].all() has_award = drawn["has_uc_award_with_carer_element"] positive_award = drawn["receives_uc_carer_element"] - assert not (positive_award & ~has_award).any() + assert (has_award == positive_award).all() # 1. No change without a carer element, household by household. touched = list(set(person_household[has_award | positive_award])) @@ -482,7 +478,7 @@ def test_a_stored_award_is_the_award_and_reads_no_cycle(order): has_award = sim.calculate("has_uc_award_with_carer_element", YEAR) receives = sim.calculate("receives_uc_carer_element", YEAR) assert receives.tolist() == [False, True] - assert has_award.tolist() == [False, True] + assert has_award.tolist() == receives.tolist() assert sim.calculate("is_cared_for_by_carer_benefit_recipient", YEAR).tolist() == [ True, False, @@ -513,8 +509,8 @@ def apply(self): def require(condition, message): - # Raise RuntimeError when a premise fails, so xfail accepts only the - # premium assertion. + # Raise RuntimeError when a premise fails, so a failure names the premise + # apart from the premium assertion. if not condition: raise RuntimeError(message) @@ -587,14 +583,6 @@ def tax_free_childcare_situation(): return {"people": people, "benunits": benunits, "households": {"h": household}} -@pytest.mark.xfail( - raises=AssertionError, - strict=True, - reason=( - "#2795: the carer test reads the bound, which leaves out the " - "non-dependant deduction that takes this award to nil." - ), -) def test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award(): # DWP's deduction for the non-dependant (UC Regs 2013 Sch 4 paras 13 and # 16; blindness brings no exemption, and para 15 does not exempt the @@ -606,22 +594,19 @@ def test_a_non_dependant_deduction_that_takes_the_award_to_nil_leaves_no_award() # keeps the premium in law. sim = Simulation(situation=non_dependant_situation()) award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) - bound = sim.calculate("uc_award_upper_bound", YEAR) deductions = sim.calculate("uc_non_dep_deductions", YEAR) - require(award[0] == 0 and bound[0] > 0, "the carer's award is not nil") - require(deductions[0] > bound[0], "the deduction does not take it to nil") + without_deduction = non_dependant_situation() + without_deduction["benunits"]["carer_unit"]["uc_non_dep_deductions"] = {YEAR: 0} + award_without = Simulation(situation=without_deduction).calculate( + "universal_credit_pre_benefit_cap", YEAR + ) + require(award[0] == 0 and deductions[0] > 0, "the carer's award is not nil") + require(award_without[0] > 0, "the deduction does not take it to nil") + assert not sim.calculate("has_uc_award_with_carer_element", YEAR).any() severe = sim.calculate("severe_disability_premium", YEAR) assert severe[1] == pytest.approx(86.05 * WEEKS) -@pytest.mark.xfail( - raises=AssertionError, - strict=True, - reason=( - "#2795: the carer test cannot read whether a family takes " - "Tax-Free Childcare instead, which reads the award." - ), -) def test_a_family_that_takes_tax_free_childcare_instead_has_no_award(): # The family gets more from the Tax-Free Childcare top-up than from the # award, so it takes no award (forgoes_universal_credit_for_tax_free_ @@ -630,8 +615,11 @@ def test_a_family_that_takes_tax_free_childcare_instead_has_no_award(): sim = Simulation(situation=tax_free_childcare_situation()) forgoes = sim.calculate("forgoes_universal_credit_for_tax_free_childcare", YEAR) award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) - bound = sim.calculate("uc_award_upper_bound", YEAR) - require(forgoes[0] and award[0] == 0 and bound[0] > 0, "the family claims") + maximum = sim.calculate("uc_maximum_amount", YEAR) + reduction = sim.calculate("uc_income_reduction", YEAR) + require(forgoes[0] and award[0] == 0, "the family claims") + require(maximum[0] > reduction[0], "the award would come to nil anyway") + assert not sim.calculate("has_uc_award_with_carer_element", YEAR).any() severe = sim.calculate("severe_disability_premium", YEAR) assert severe[1] == pytest.approx(86.05 * WEEKS) @@ -666,14 +654,6 @@ def small_housing_situation(): return {"people": people, "benunits": benunits, "households": {"h": household}} -@pytest.mark.xfail( - raises=AssertionError, - strict=True, - reason=( - "#2795: the bound's calculation without housing costs takes the " - "higher work allowance and stays positive when this award comes to nil." - ), -) def test_small_housing_costs_with_a_work_allowance_leave_no_award(): # DWP has determined that the carer has limited capability for work, so # her award has a work allowance (UC Regs 2013 reg 22(1)(b)), and she @@ -683,318 +663,108 @@ def test_small_housing_costs_with_a_work_allowance_leave_no_award(): # claim (reg 30(1)(a)(i) and (2)). Her award has a housing costs element of # £600, so DWP applies the lower work allowance, £427 a month (reg # 22(2)(b)). The taper then exceeds the maximum amount, and the award - # comes to nil (Welfare Reform Act 2012 s.5(1)(b); reg 17). The bound - # also works out the award with no housing costs element and the higher - # work allowance, £710 a month, which leaves £461.10. The council + # comes to nil (Welfare Reform Act 2012 s.5(1)(b); reg 17), although an + # award with no housing costs element and the higher work allowance, + # £710 a month, would leave £461.10. The council # ignores the blind carer as a resident (HB Regs 2006 Sch 3 para # 14(4)(b)), and with no award she does not bar the premium (para # 14(2)(a)(iii)). So the disabled person keeps the premium in law. sim = Simulation(situation=small_housing_situation()) award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) - bound = sim.calculate("uc_award_upper_bound", YEAR) housing = sim.calculate("uc_housing_costs_element", YEAR) allowance = sim.calculate("uc_work_allowance", YEAR) deductions = sim.calculate("uc_non_dep_deductions", YEAR) forgoes = sim.calculate("forgoes_universal_credit_for_tax_free_childcare", YEAR) lcwra = sim.calculate("uc_LCWRA_element", YEAR) carer_element = sim.calculate("uc_carer_element", YEAR) - require(award[0] == 0 and bound[0] > 0, "the carer's award is not nil") + require(award[0] == 0, "the carer's award is not nil") require(housing[0] > 0 and allowance[0] == 427 * 12, "no lower work allowance") require(deductions[0] == 0 and not forgoes[0], "another route takes it to nil") require(lcwra[0] == 0 and carer_element[0] > 0, "the award has no carer element") + assert not sim.calculate("has_uc_award_with_carer_element", YEAR).any() severe = sim.calculate("severe_disability_premium", YEAR) assert severe[1] == pytest.approx(86.05 * WEEKS) -def test_the_bound_reads_neither_the_award_nor_pension_credit(): - # The carer test reads uc_award_upper_bound because the award reads - # Pension Credit (through the non-dependant deduction's exemption), whose - # addition reads the attribution. A pensioner on Attendance Allowance - # shares a privately rented home with her blind son, who claims Universal - # Credit with the carer element; the trace of the bound reaches none of - # those variables. - forbidden = { - "pension_credit", - "uc_housing_costs_element", - "uc_non_dep_deductions", - "uc_non_dep_deduction_exempt", - "uc_maximum_amount", - "uc_income_reduction", - "universal_credit_pre_benefit_cap", - "universal_credit", - "is_cared_for_by_carer_benefit_recipient", +def veteran_situation(exempt=None): + """A blind carer (40) earning £20,000 rents from the council at £6,000 a + year as household head and cares 35 hours a week for a veteran (70) in + another family of the household, who receives armed forces independence + payment and £13,000 of State Pension. exempt, if given, supplies the + veteran family's Pension Credit exemption from the non-dependant + deduction.""" + people = { + "carer": { + "age": {YEAR: 40}, + "is_blind": {YEAR: True}, + "care_hours": {YEAR: 35}, + "would_claim_carers_allowance": {YEAR: False}, + "employment_income": {YEAR: 20_000}, + "is_household_head": {YEAR: True}, + }, + "veteran": { + "age": {YEAR: 70}, + "armed_forces_independence_payment": {YEAR: 150 * WEEKS}, + "state_pension": {YEAR: 13_000}, + }, } - sim = Simulation( - situation=situation( - [ - dict( - country="ENGLAND", - tenure="RENT_PRIVATELY", - rent=9_000, - units=[ - dict(adults=[single(75, disability="aa_higher")], children=[]), - dict( - adults=[single(40, blind=True, care_hours=35)], - children=[], - ), - ], - ) - ] - ) - ) - sim.trace = True - sim.calculate("uc_award_upper_bound", YEAR) - reached = set() - - def visit(node): - reached.add(node.name) - for child in node.children: - visit(child) - - for tree in sim.tracer.trees: - visit(tree) - assert "uc_carer_element" in reached - assert not reached & forbidden, reached & forbidden - - -# Families for the endpoint comparison, each in its own English household: -# every element of the maximum amount, both work allowances, owners, council -# and housing association renters, and private renters below and above the -# Local Housing Allowance rate. The award reads the earned and unearned -# income each row supplies. -PARITY_FAMILIES = [ - # A single owner, with no work allowance. - dict(adults=[dict(age=30)], children=[], tenure="OWNED_OUTRIGHT", rent=0), - # A lone parent renting privately, with a child (3) in childcare. - dict( - adults=[dict(age=30, employment_income=30_000)], - children=[dict(age=3, childcare_expenses=6_000)], - tenure="RENT_PRIVATELY", - rent=9_000, - ), - # A lone parent of two who owns her home. - dict( - adults=[dict(age=35, employment_income=20_000)], - children=[dict(age=4), dict(age=7)], - tenure="OWNED_OUTRIGHT", - rent=0, - ), - # A council renter with limited capability for work and work-related - # activity. - dict( - adults=[dict(age=45, uc_limited_capability_for_WRA=True)], - children=[], - tenure="RENT_FROM_COUNCIL", - rent=6_000, - ), - # A carer with limited capability for work and a small council rent, - # where the calculation without housing costs gives the larger award. - dict( - adults=[dict(age=35, care_hours=35, uc_limited_capability_for_work=True)], - children=[], - tenure="RENT_FROM_COUNCIL", - rent=600, - ), - # A couple, one caring, with a disabled child, renting from a housing - # association. - dict( - adults=[ - dict(age=40, care_hours=35), - dict(age=38, employment_income=15_000), - ], - children=[dict(age=6, dla_sc_category="MIDDLE")], - tenure="RENT_FROM_HA", - rent=7_200, - ), - # A lone parent earning £6,000, with a child (3) in childcare, who rents - # privately for £30,000 a year. The model places a household with no - # region in Inner South East London, where DWP caps the housing costs - # element at the two-bedroom Local Housing Allowance rate, £1,550 a month. - dict( - adults=[dict(age=30, employment_income=6_000)], - children=[dict(age=3, childcare_expenses=6_000)], - tenure="RENT_PRIVATELY", - rent=30_000, - ), -] -# Earned income before the work allowance and unearned income, a year. -PARITY_INCOMES = [(0, 0), (6_000, 0), (18_000, 0), (12_000, 1_500)] -BOUNDARY_EARNINGS = 6_000 -# The endpoints: the award with non-dependant deductions supplied as nil, and -# the award with the housing costs element supplied as nil. -ENDPOINTS = ("uc_non_dep_deductions", "uc_housing_costs_element") - - -def parity_situation(rows, endpoint=None): - """One household per (family, earned income, unearned income) row. Each - family claims and takes no Tax-Free Childcare instead, with no benefit cap - reduction or debt deduction, so the final award equals the award before - the cap. The situation supplies endpoint, a variable in ENDPOINTS, as - nil.""" - people, benunits, households = {}, {}, {} - for i, (family, earned, unearned) in enumerate(rows): - members = [] - for role, group in (("a", family["adults"]), ("c", family["children"])): - for j, inputs in enumerate(group): - name = f"r{i}_{role}{j}" - people[name] = {k: {YEAR: v} for k, v in inputs.items()} - people[name]["is_claimant_or_partner"] = {YEAR: role == "a"} - members.append(name) - unit = { - "members": members, - "claims_universal_credit": {YEAR: True}, - "forgoes_universal_credit_for_tax_free_childcare": {YEAR: False}, - "uc_benefit_cap_reduction_after_floor": {YEAR: 0}, - "uc_deductions": {YEAR: 0}, - "uc_earned_income_before_work_allowance": {YEAR: float(earned)}, - "uc_unearned_income": {YEAR: float(unearned)}, - } - if endpoint is not None: - unit[endpoint] = {YEAR: 0} - benunits[f"r{i}"] = unit - households[f"r{i}"] = { - "members": members, - "country": {YEAR: "ENGLAND"}, - "tenure_type": {YEAR: family["tenure"]}, - "rent": {YEAR: family["rent"]}, - } - return {"people": people, "benunits": benunits, "households": households} - - -def endpoint_values(rows, variables): - """Each variable at each endpoint, from its own simulation.""" - results = [] - for endpoint in ENDPOINTS: - sim = Simulation(situation=parity_situation(rows, endpoint)) - results.append({v: np.asarray(sim.calculate(v, YEAR)) for v in variables}) - return results - - -def boundary_rows(): - """Rows whose unearned income brings each endpoint's income reduction to - within a few float32 steps of its maximum amount, where the award - cancels to nil or a fraction of a penny.""" - seeds = [(family, BOUNDARY_EARNINGS, 0) for family in PARITY_FAMILIES] - values = endpoint_values(seeds, ("uc_maximum_amount", "uc_income_reduction")) - rows = [] - for at_endpoint in values: - maximum = at_endpoint["uc_maximum_amount"].astype(np.float32) - reduction = at_endpoint["uc_income_reduction"].astype(np.float32) - assert (reduction < maximum).all(), "a seed's taper reaches its maximum" - for f, family in enumerate(PARITY_FAMILIES): - step = np.spacing(maximum[f]) - for k in range(-3, 4): - unearned = np.float32(maximum[f] - reduction[f] + k * step) - rows.append((family, BOUNDARY_EARNINGS, float(unearned))) - return rows - - -def test_upper_bound_matches_real_award_endpoints(): - # The bound repeats the award's arithmetic at two endpoints, since the - # carer test cannot read the award. At each row the bound must equal, - # exactly, the larger of the two awards the model's own formulas give: - # one with non-dependant deductions supplied as nil, one with the - # housing costs element supplied as nil. Comparing the award before the - # cap and the final award also catches a new amount the award adds - # after the cap stage. Drift either way at these rows fails the test. - rows = [ - (family, earned, unearned) - for family in PARITY_FAMILIES - for earned, unearned in PARITY_INCOMES - ] - n_seed_rows = len(rows) - rows += boundary_rows() - components = ( - "uc_standard_allowance", - "uc_child_element", - "uc_disability_elements", - "uc_carer_element", - "uc_housing_costs_element", - "uc_childcare_element", - ) - variables = components + ( - "uc_LHA_cap", - "benunit_rent", - "uc_work_allowance", - "universal_credit_pre_benefit_cap", - "universal_credit", - ) - with_housing, without_housing = endpoint_values(rows, variables) - base = Simulation(situation=parity_situation(rows)) - bound = np.asarray(base.calculate("uc_award_upper_bound", YEAR)) - - # The rows cover every element, a housing costs element that DWP caps at - # the Local Housing Allowance rate, both work allowances, both endpoints - # deciding the bound, and awards that cancel to nil or under a penny. - for component in components: - assert (with_housing[component] > 0).any(), component - lha_cap = with_housing["uc_LHA_cap"] - assert ( - (lha_cap < with_housing["benunit_rent"]) - & (with_housing["uc_housing_costs_element"] == lha_cap) - ).any() - assert (with_housing["uc_work_allowance"] == 427 * 12).any() - assert (without_housing["uc_work_allowance"] == 710 * 12).any() - pre_cap = [ - values["universal_credit_pre_benefit_cap"] - for values in (with_housing, without_housing) - ] - assert (pre_cap[0] > pre_cap[1]).any() and (pre_cap[1] > pre_cap[0]).any() - for award in pre_cap: - boundary = award[n_seed_rows:] - assert (boundary == 0).any() and ((boundary > 0) & (boundary < 0.01)).any() - - for stage in ("universal_credit_pre_benefit_cap", "universal_credit"): - larger = np.maximum(with_housing[stage], without_housing[stage]) - mismatched = np.flatnonzero(bound != larger) - assert not mismatched.size, (stage, mismatched, bound[mismatched]) - - -# The award's steps that the bound repeats, and the reads it leaves out: the -# non-dependant deduction and the Tax-Free Childcare choice, which read -# Pension Credit and the award, and the claim, which the carer test reads -# beside the bound. -AWARD_STEPS = ( - "uc_maximum_amount", - "uc_housing_costs_element", - "uc_work_allowance", - "uc_earned_income", - "uc_income_reduction", - "universal_credit_pre_benefit_cap", -) -LEFT_OUT = { - "uc_non_dep_deductions", - "forgoes_universal_credit_for_tax_free_childcare", - "claims_universal_credit", -} + benunits = { + "carer_unit": {"members": ["carer"]}, + "veteran_unit": {"members": ["veteran"], "would_claim_uc": {YEAR: False}}, + } + if exempt is not None: + benunits["veteran_unit"]["uc_non_dep_receives_pension_credit"] = {YEAR: exempt} + household = { + "members": ["carer", "veteran"], + "country": {YEAR: "ENGLAND"}, + "tenure_type": {YEAR: "RENT_FROM_COUNCIL"}, + "rent": {YEAR: 6_000}, + } + return {"people": people, "benunits": benunits, "households": {"h": household}} -def test_the_bound_reads_every_component_of_the_maximum_amount(): - # A new element in uc_maximum_amount, or a new read in any step of the - # award, must reach the bound too. The couple with a disabled child - # rents and has a carer, so every step runs. - sim = Simulation(situation=parity_situation([(PARITY_FAMILIES[5], 12_000, 1_500)])) - sim.trace = True - sim.calculate("universal_credit_pre_benefit_cap", YEAR) - sim.calculate("uc_award_upper_bound", YEAR) - reads = {} - - def visit(node): - if node.children: - # A variable's formula runs once; later reads hit the cache. - reads.setdefault(node.name, [child.name for child in node.children]) - for child in node.children: - visit(child) - - for tree in sim.tracer.trees: - visit(tree) - bound_reads = reads["uc_award_upper_bound"] - adds = sim.tax_benefit_system.get_variable("uc_maximum_amount").adds - # The elements in uc_maximum_amount's order, which float32 addition - # depends on; the bound rebuilds the housing costs element from rent. - assert [name for name in bound_reads if name in adds] == [ - name for name in adds if name != "uc_housing_costs_element" +@pytest.mark.parametrize( + "first", ["universal_credit", "pension_credit", "has_uc_award_with_carer_element"] +) +def test_the_non_dependant_branch_reads_the_award_without_housing_costs(first): + # The carer's award: income tax 1,486.00 and National Insurance 594.40 + # leave earned income of 17,919.60, and the taper, 0.55 x 17,919.60 = + # 9,855.78, leaves (424.90 + 209.34) x 12 + 6,000 - 9,855.78 = 3,755.10 + # with no non-dependant deduction. The award includes the carer element, + # so the veteran, whom DWP and the model treat as severely disabled + # through armed forces independence payment, gets no severe disability + # addition, and with £250 a week of State Pension against a standard + # minimum guarantee of £238.00, no Pension Credit. + # + # The deduction's exemption reads the veteran's Pension Credit in a + # branch with every housing costs element set to nil. There the carer's + # award, 7,610.88 - 9,855.78, comes to nil, the veteran gets the addition + # (DWP ignores a blind resident, SPC Regs 2002 Sch I para 2(2)(b)) and + # Pension Credit, (238.00 + 86.05 - 250.00) x 52 = 3,850.60, and the model + # exempts the veteran from the deduction. UC Regs 2013 Sch 4 para 16(2) + # names no armed forces independence payment, so the law, in which the + # veteran gets no Pension Credit, deducts 96.55 x 12 = 1,158.60 and leaves + # an award of 2,596.50, which still includes the carer element. This test + # pins the model's figures, whichever variable the simulation requests + # first. + sim = Simulation(situation=veteran_situation()) + sim.calculate(first, YEAR) + assert sim.calculate("has_uc_award_with_carer_element", YEAR).tolist() == [ + True, + False, ] - for step in AWARD_STEPS: - missing = set(reads[step]) - set(AWARD_STEPS) - LEFT_OUT - set(bound_reads) - assert not missing, (step, missing) + assert sim.calculate("pension_credit", YEAR).tolist() == [0, 0] + assert sim.calculate("uc_non_dep_receives_pension_credit", YEAR).tolist() == [ + False, + True, + ] + assert sim.calculate("uc_non_dep_deductions", YEAR)[0] == 0 + award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) + assert award[0] == pytest.approx(3_755.10, abs=0.01) + # The law's figures, with the exemption supplied as the law reads it. + law = Simulation(situation=veteran_situation(exempt=False)) + assert law.calculate("uc_non_dep_deductions", YEAR)[0] == pytest.approx(96.55 * 12) + law_award = law.calculate("universal_credit_pre_benefit_cap", YEAR) + assert law_award[0] == pytest.approx(2_596.50, abs=0.01) + assert law.calculate("has_uc_award_with_carer_element", YEAR)[0] + assert law.calculate("pension_credit", YEAR)[1] == 0 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 537f2538e7..daa584b612 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 @@ -29,9 +29,11 @@ class severe_disability_minimum_guarantee_addition(Variable): "benefit to the person it is paid for caring for, and the legacy " "severe disability premium shares it. The attribution counts an award of " "Universal Credit which includes the carer element (para 1(4)), which " - "has_uc_award_with_carer_element reads from the award's conditions " - "and the upper bound on the award (uc_award_upper_bound). The bound " - "can stay positive when the actual award comes to nil (#2795). " + "has_uc_award_with_carer_element reads from the carer's award: a " + "carer whose award comes to nil has no award and does not bar the " + "addition. The model applies the same rule in Northern Ireland, " + "where SR 2016/236 reg 31(7) inserted the same words into SPC Regs " + "(NI) 2003 Sch 1 para 1, with the same amount parameter. " "Blindness (is_blind) is not in the survey data, so in microsimulation the " "blind-partner route never applies and a blind resident is counted." ) @@ -44,6 +46,9 @@ class severe_disability_minimum_guarantee_addition(Variable): "https://www.legislation.gov.uk/uksi/2002/1792/schedule/I/paragraph/2", "https://www.legislation.gov.uk/uksi/2002/1792/schedule/I/paragraph/3", "https://www.legislation.gov.uk/uksi/2002/1792/regulation/6", + "https://www.legislation.gov.uk/nisr/2003/28/schedule/1/paragraph/1", + "https://www.legislation.gov.uk/nisr/2016/236/regulation/31", + "https://www.legislation.gov.uk/nisr/2016/216/regulation/30", ) def formula(benunit, period, parameters): diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py index 64394d7f8d..a7faff105e 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py @@ -23,46 +23,67 @@ class has_uc_award_with_carer_element(Variable): "premium (HB Regs 2006 Sch 3 para 14(2)(a)(iii) and (b)) and of the " "Pension Credit severe disability addition (SPC Regs 2002 Sch I para " "1(1) and (4)), through is_cared_for_by_carer_benefit_recipient. " - "The premium and addition cannot read the award: the non-dependant " - "deduction reads Pension Credit, whose addition reads the " - "attribution. has_uc_award_with_carer_element therefore reads the " - "award's conditions and bound. " - "It counts an award where the family claims Universal Credit " - "(claims_universal_credit), meets the age, pension-route and capital " - "conditions (is_uc_eligible) and has a positive upper bound on its " - "award (uc_award_upper_bound), and where the award includes the carer " - "element in respect of this claimant " - "(uc_carer_element_included_for_claimant). A nil bound " - "leaves a nil award, and so no award (Welfare Reform Act 2012 s.5(1)(b) " - "and (2)(b); DWP sets the minimum at one penny, reg 17). A carer " - "whose income reduces both bound calculations to nil does not count. " - "A carer can still count after income reduces the actual award to " - "nil when the bound's calculation without housing costs remains " - "positive. A carer whose award a non-dependant deduction takes to nil " - "still counts, and so does one whose family takes Tax-Free Childcare " - "instead (forgoes_universal_credit_for_tax_free_childcare reads the " - "award); #2795 tracks all three. " - "Where an input or a scenario stores universal_credit for the period, " - "or a reform neutralizes it, reading it cannot cycle, so this reads " - "the award itself (receives_uc_carer_element)." + "An award needs an amount payable of at least one penny (Welfare " + "Reform Act 2012 s.5(1)(b) and (2)(b); UC Regs 2013 reg 17), so the " + "model counts a claimant in respect of whom the award includes the " + "carer element (uc_carer_element_included_for_claimant) only where " + "the family's award before the benefit cap " + "(universal_credit_pre_benefit_cap) comes to more than nil. That " + "award comes to nil where income tapers it to nil, after any " + "non-dependant deduction and with the work allowance that the " + "housing costs element sets, and where the family takes Tax-Free " + "Childcare instead (forgoes_universal_credit_for_tax_free_childcare). " + "The model reads the award before the cap, not universal_credit, " + "because the benefit cap reads Child Tax Credit, whose income reads " + "Pension Credit, whose addition reads this variable. The two agree " + "for an award that includes the carer element: DWP exempts it from " + "the cap (UC Regs 2013 reg 83(1)(j)), and debt deductions leave at " + "least one penny payable. The housing costs element reads the " + "non-dependant deduction. Its exemption for a non-dependant family " + "on Pension Credit (uc_non_dep_receives_pension_credit) calculates " + "that family's Pension Credit in a branch with every housing costs " + "element set to nil, and in that branch this variable reads the " + "award without housing costs. The branch decides only that " + "exemption. A member of the non-dependant family gains or loses it " + "through this variable only where the family gets Pension Credit " + "with the severe disability addition and none without it, and the " + "member has no other exemption: someone who receives armed forces " + "independence payment and no other qualifying benefit, or a blind " + "partner under SPC Sch I para 1(1)(c) who receives none. There the " + "model can exempt a non-dependant whom the law does not, or the " + "reverse. Where an input or a scenario stores universal_credit for " + "the period, or a reform neutralizes it, reading it cannot cycle, so " + "this reads the award itself (receives_uc_carer_element). The model " + "applies the same rule in Northern Ireland, whose legislation uses " + "the same words: Welfare Reform (Northern Ireland) Order 2015 art. " + "10, UC Regs (NI) 2016 regs 17 and 30, SPC Regs (NI) 2003 Sch 1 para " + "1 as SR 2016/236 reg 31(7) amended it, and HB Regs (NI) 2006 Sch 4 " + "para 14(2)(a)(iii)." ) definition_period = YEAR reference = ( "https://www.legislation.gov.uk/uksi/2013/376/regulation/29", "https://www.legislation.gov.uk/ukpga/2012/5/section/5", "https://www.legislation.gov.uk/uksi/2013/376/regulation/17", + "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/16", "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/nisi/2015/2006/article/10", + "https://www.legislation.gov.uk/nisr/2016/216/regulation/17", + "https://www.legislation.gov.uk/nisr/2016/216/regulation/30", + "https://www.legislation.gov.uk/nisr/2016/216/schedule/4/paragraph/15", + "https://www.legislation.gov.uk/nisr/2003/28/schedule/1/paragraph/1", + "https://www.legislation.gov.uk/nisr/2016/236/regulation/31", + "https://www.legislation.gov.uk/nisr/2006/405/schedule/4/paragraph/14", ) def formula(person, period, parameters): if award_reads_no_other_variable(person.simulation, period): return person("receives_uc_carer_element", period) benunit = person.benunit - on_claim = ( - benunit("claims_universal_credit", period) - & benunit("is_uc_eligible", period) - & (benunit("uc_award_upper_bound", period) > 0) - ) + # The award before the cap: DWP exempts a carer-element award from + # the cap, and universal_credit's cap reads Pension Credit. + award = benunit("universal_credit_pre_benefit_cap", period) + includes_carer_element = benunit("uc_carer_element", period) > 0 included = person("uc_carer_element_included_for_claimant", period) - return on_claim & included + return included & includes_carer_element & (award > 0) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py deleted file mode 100644 index aade3f9656..0000000000 --- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_award_upper_bound.py +++ /dev/null @@ -1,112 +0,0 @@ -from policyengine_uk.model_api import * - - -class uc_award_upper_bound(Variable): - value_type = float - entity = BenUnit - label = "Upper bound on the Universal Credit award" - documentation = ( - "The model calculates this upper bound without reading the " - "non-dependant deduction. Its formula for " - "universal_credit_pre_benefit_cap never returns a larger award. DWP " - "awards the maximum amount less the income reduction (UC Regs 2013 " - "reg 22). The maximum amount includes the housing costs element " - "(uc_housing_costs_element): the rent a renter may claim less DWP's " - "non-dependant deductions. Where that element exceeds nil, DWP " - "applies the lower work allowance (reg 22(2)(b)). Where DWP deducts " - "less than the housing costs it allows (the rent, capped at the Local " - "Housing Allowance rate for a private renter), the award stays at or " - "below the award with no deduction. Where it deducts at least those " - "housing costs, DWP calculates the award with no housing costs " - "element and the higher work allowance " - "(reg 22(2)(a); Sch 4 para 13). The formula takes the larger of those " - "two awards. It repeats the award's steps in their order, rounding " - "where they store a value, so the bound never falls below the award " - "the model's formulas calculate. An input or a scenario can store a " - "larger award; the carer test then reads the stored award " - "(has_uc_award_with_carer_element). A family whose bound comes to nil " - "has a nil award, and so no award: Parliament makes an amount " - "payable of at least the prescribed minimum a financial condition " - "(Welfare Reform Act 2012 s.5(1)(b) and (2)(b)), and DWP sets that " - "minimum at one penny (reg 17). DWP makes no deduction for a " - "non-dependant who receives Pension Credit (Sch 4 para 16(2)(b)), so " - "the model's deduction reads Pension Credit, whose severe disability " - "addition reads who has an award with the carer element. The bound " - "reads neither, so has_uc_award_with_carer_element can read it. The " - "bound can exceed a nil award in three ways (#2795). A non-dependant " - "deduction can take the award to nil. A family that takes Tax-Free " - "Childcare instead gets no award " - "(forgoes_universal_credit_for_tax_free_childcare). And for a family " - "with a work allowance and a small housing costs element, the " - "calculation without housing costs applies the higher work allowance, " - "whose extra earnings disregard can outweigh the housing costs it " - "drops; that calculation can stay positive when the award comes to " - "nil, even with no non-dependant deduction." - ) - definition_period = YEAR - unit = GBP - defined_for = "is_uc_eligible" - reference = ( - "https://www.legislation.gov.uk/uksi/2013/376/regulation/22", - "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/13", - "https://www.legislation.gov.uk/ukpga/2012/5/section/5", - "https://www.legislation.gov.uk/uksi/2013/376/regulation/17", - ) - - def formula(benunit, period, parameters): - p = parameters(period).gov.dwp.universal_credit.means_test - # Housing costs before any non-dependant deduction, as in - # uc_housing_costs_element. - tenure_type = benunit("benunit_tenure_type", period) - tenure_types = tenure_type.possible_values - rent = benunit("benunit_rent", period) - capped_rent_amount = min_(benunit("uc_LHA_cap", period), rent) - max_housing_costs = select( - [ - (tenure_type == tenure_types.RENT_FROM_COUNCIL) - | (tenure_type == tenure_types.RENT_FROM_HA), - tenure_type == tenure_types.RENT_PRIVATELY, - ], - [rent, capped_rent_amount], - default=0, - ) - no_deduction = max_(max_housing_costs, 0).astype(np.float32) - elements = [ - benunit(name, period) - for name in ( - "uc_standard_allowance", - "uc_child_element", - "uc_disability_elements", - "uc_carer_element", - ) - ] - childcare = benunit("uc_childcare_element", period) - earned_income = benunit("uc_earned_income_before_work_allowance", period) - unearned_income = benunit("uc_unearned_income", period) - work_allowance_eligible = benunit("is_uc_work_allowance_eligible", period) - awards = [] - for housing in (no_deduction, np.zeros_like(no_deduction)): - # The steps of uc_maximum_amount (its adds, in order), - # uc_work_allowance, uc_earned_income, uc_income_reduction and - # universal_credit_pre_benefit_cap, each rounded to float32 where - # that variable stores its value, so no rounding puts the bound - # below the award. - maximum = 0 - for element in elements: - maximum = maximum + element - maximum = maximum + housing + childcare - monthly_allowance = where( - housing > 0, - p.work_allowance.with_housing, - p.work_allowance.without_housing, - ) - work_allowance = where( - work_allowance_eligible, - (monthly_allowance * MONTHS_IN_YEAR).astype(np.float32), - 0, - ).astype(np.float32) - earned = max_(0, earned_income - work_allowance) - total_reduction = p.reduction_rate * earned + unearned_income - reduction = min_(maximum, total_reduction).astype(np.float32) - awards.append(maximum - reduction) - return max_(awards[0], awards[1]) 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 4ea9c3f8f6..80bc7d9619 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 @@ -49,17 +49,17 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "Pension, reduces to nil keeps the entitlement but receives nothing, " "so the model does not attribute the allowance. " "has_uc_award_with_carer_element gives " - "a carer element award from the claim, the award's conditions and an " - "upper bound on the award (uc_award_upper_bound), because the " - "Universal Credit non-dependant deduction exemption reads Pension " - "Credit, which reads the addition. A carer whose bound comes to nil " - "has no award (Welfare Reform Act 2012 s.5), and the model attributes " - "none. The bound can stay positive after income reduces the actual " - "award to nil, when its calculation without housing costs gives a " - "higher work allowance. A carer whose award a non-dependant deduction " - "takes to nil, or whose family takes Tax-Free Childcare instead, " - "still counts too, and can withhold a premium or addition the law " - "would pay (#2795). A carer with both an allowance and a carer element " + "a carer element award from the family's award before the benefit " + "cap: a carer whose award comes to nil, whether income tapers it to " + "nil or the family takes Tax-Free Childcare instead, has no award " + "(Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17), and the " + "model attributes none. The model applies the same attribution in " + "Northern Ireland (SPC Regs (NI) 2003 Sch 1 para 1(1) and (4), as SR " + "2016/236 reg 31(7) amended it; HB Regs (NI) 2006 Sch 4 para 14(2); " + "SR 2016/236 regs 26, 28, 35 and 36 insert the same words in Northern " + "Ireland's Income Support, Jobseeker's Allowance, pension-age Housing " + "Benefit and ESA schemes). A carer with both an allowance and a " + "carer element " "counts once. Partners who care for the same severely disabled person " "(partners_care_for_same_severely_disabled_person) have one relevant " "entitlement between them (s.70(7ZA) and (7A)), so where one of them " @@ -77,6 +77,13 @@ class is_cared_for_by_carer_benefit_recipient(Variable): "https://www.legislation.gov.uk/uksi/2013/376/regulation/29", "https://www.legislation.gov.uk/uksi/2013/376/regulation/30", "https://www.legislation.gov.uk/ukpga/2012/5/section/5", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/17", + "https://www.legislation.gov.uk/nisr/2003/28/schedule/1/paragraph/1", + "https://www.legislation.gov.uk/nisr/2016/236/regulation/31", + "https://www.legislation.gov.uk/nisr/2006/405/schedule/4/paragraph/14", + "https://www.legislation.gov.uk/nisr/2016/216/regulation/30", + "https://www.legislation.gov.uk/nisi/2015/2006/article/10", + "https://www.legislation.gov.uk/nisr/2016/216/regulation/17", ) def formula(person, period, parameters): diff --git a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py index 5dc57d82aa..65913bcb92 100644 --- a/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/pension_age_severe_disability_premium.py @@ -21,10 +21,9 @@ class pension_age_severe_disability_premium(Variable): "claim, as a couple may agree under HB(SPC) reg 63(1) and the CTR " "schemes (England SI 2012/2885 Sch 8 para 4(1)). " "is_cared_for_by_carer_benefit_recipient attributes carers, and " - "counts a Universal Credit award with the carer element from the " - "award's conditions and the upper bound on the award " - "(uc_award_upper_bound). The bound can stay positive when the actual " - "award comes to nil (#2795). The Pension Credit severe " + "counts a Universal Credit award with the carer element only where " + "the carer's award comes to more than nil " + "(has_uc_award_with_carer_element). The Pension Credit severe " "disability addition has the same conditions apart from its wider " "residence exceptions, but its own amount parameter, so a Pension " "Credit reform does not move this premium." diff --git a/policyengine_uk/variables/household/demographic/severe_disability_premium.py b/policyengine_uk/variables/household/demographic/severe_disability_premium.py index 18f1f60b36..658c8eeb5f 100644 --- a/policyengine_uk/variables/household/demographic/severe_disability_premium.py +++ b/policyengine_uk/variables/household/demographic/severe_disability_premium.py @@ -22,14 +22,11 @@ class severe_disability_premium(Variable): "paid for both. is_cared_for_by_carer_benefit_recipient attributes " "each carer benefit to the person it is paid for caring for, and the " "Pension Credit severe disability addition shares it. The attribution " - "counts an award of Universal Credit which includes the carer element from the " - "award's conditions and an upper bound on the award " - "(has_uc_award_with_carer_element). A carer whose bound comes to nil " - "does not bar the premium. The model can still withhold it where a " - "non-dependant deduction takes the award to nil, where the carer's " - "family takes Tax-Free Childcare instead, or where a small housing " - "costs element leaves the bound's calculation without housing costs " - "positive (#2795). Not modelled: a carer's " + "counts an award of Universal Credit which includes the carer " + "element (has_uc_award_with_carer_element). A carer whose award " + "comes to nil, after any non-dependant deduction or because the " + "family takes Tax-Free Childcare instead, does not bar the premium. " + "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 " From 31139e822c91a1c052eaccd335e1ece0f856797b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 19:58:21 +0000 Subject: [PATCH 17/21] Need a positive Universal Credit award for Scotland's carer premium in every year An earnings nil case leaves no award to have a value of zero under SSI 2021/249 reg 4 as it stood from 2022 to 2025, so limb (c) of Sch 1 para 6(1) reads the positive award from 2022-23. Remove uc_award_includes_zero_value_award and update the 2024 case. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6FQc4koGSzCUYko19B --- .../council_tax_reduction_carer_premium.yaml | 34 +++++----- .../council_tax_reduction_carer_premium.py | 65 ++++++++----------- 2 files changed, 47 insertions(+), 52 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml index 80cfdf4ca3..2d5b93dc14 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml @@ -13,12 +13,13 @@ # a value of zero" (reg 4, as amended by SSI 2022/52 reg 3); from 1 April 2025 # it "means an award of universal credit made under Part 1 of the 2012 Act" # (SSI 2025/24 reg 8), and an income that tapers the award to nil leaves no -# award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17). The model -# reads the earlier definition as reaching a carer whose earnings take the -# award to nil. The sources leave that reading open: UC Claims and Payments -# Regs 2013 reg 32A(1)(a) covers a claim on which DWP makes no award, and -# Ipswich BC v TD [2024] UKUT 117 (AAC) finds no Universal Credit -# entitlement in a nil award without construing the Scottish text. +# award (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17). An income +# that tapers the amount payable below one penny leaves no award to have a +# value of zero in any year: DWP makes no award or the award ends (UC Claims +# and Payments Regs 2013 reg 32A(1)), and the Scottish Government's policy +# note to SSI 2025/24 confines the earlier words to an award a sanction +# reduces to zero. So the model needs an award of at least one penny in every +# year from 2022-23. # # Amounts: carer premium £48.15 a week from 1 April 2026 (SSI 2021/249 Sch 1 # para 5(1), substituted by SSI 2026/56), £46.40 in 2025-26, £45.60 in @@ -186,15 +187,16 @@ receives_uc_carer_element: false council_tax_reduction_carer_premium: 0 -- name: In 2024 the model counts a Scottish carer's nil award as an award with the carer element +- name: In 2024 a Scottish carer whose earnings taper the award to nil has no award and no carer premium # The same carer in 2024-25. The maximum amount comes to (393.45 + 198.31) - # x 12 = 7,101.12, and the taper on £20,000 of earnings less income tax and - # National Insurance takes more, so the award comes to nil. Until 31 March - # 2025 an "award of universal credit" included one "with a value of zero" - # (reg 4), and the model reads those words as reaching this nil award. - # Under that chosen reading, the model includes the carer premium (para - # 6(1)(c)): 45.60 x 52 = 2,371.20. The sources leave that reading open (see - # the header). + # x 12 = 7,101.12, and the taper, 0.55 x 17,942.66 = 9,868.46 on £20,000 + # of earnings less income tax and National Insurance, takes more, so the + # award comes to nil. Until 31 March 2025 an "award of universal credit" + # included "a universal credit award with a value of zero" (reg 4), but an + # income that fails the financial condition leaves no award to have that + # value (Welfare Reform Act 2012 s.5(1)(b); UC Regs 2013 reg 17), so para + # 6(1)(c) does not apply: no carer premium, where the model would + # otherwise include 45.60 x 52 = 2,371.20. period: 2024 absolute_error_margin: 0.01 input: @@ -214,11 +216,13 @@ region: SCOTLAND output: uc_carer_element: 198.31 * 12 + uc_maximum_amount: 7_101.12 + uc_earned_income: 17_942.66 universal_credit: 0 receives_uc_carer_element: false is_entitled_to_carer_benefit: false carer_premium: 0 - council_tax_reduction_carer_premium: 45.60 * 52 + council_tax_reduction_carer_premium: 0 - name: From 1 April 2025 the same Scottish carer's nil award leaves no award # The same carer in 2025-26. The maximum amount comes to (400.14 + 201.68) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py index 283670b440..cda349ac2e 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py @@ -19,33 +19,31 @@ class council_tax_reduction_carer_premium(Variable): "an award of Universal Credit which includes the carer element. " "carer_premium tests limbs (a) and (b) " "(is_entitled_to_carer_benefit). For limb (c), the Scottish Ministers " - "defined an award of Universal Credit to include one with a value of " - "zero from 1 April 2022 (reg 4, as amended by SSI 2022/52 reg 3), and " - "from 1 April 2025 as an award made under Part 1 of the Welfare Reform " - "Act 2012 (SSI 2025/24 reg 8), under which an income that tapers the " - "award to nil leaves no award (s.5(1)(b); UC Regs 2013 reg 17). The " - "model reads the earlier definition as reaching a carer whose " - "earnings take the award to nil, and so counts that nil award from " - "2022-23 to 2024-25. The sources leave that reading open. UC Claims " - "and Payments Regs 2013 reg 32A(1)(a) covers a claim on which DWP " - "makes no award because income fails the financial condition in " - "s.5(1)(b). The Scottish Government's policy note " - "to SSI 2025/24 records a 2022 intention to treat such a claimant as " - "having an award, but calls reg 10A an expansion that gives the " - "intention effect. Ipswich BC v TD [2024] UKUT 117 (AAC) holds that " - "a nil award carries no Universal Credit entitlement, without " - "construing the Scottish definition. While the earlier definition " - "holds (uc_award_includes_zero_value_award), this variable reads limb " - "(c) from the award's conditions: the family claims Universal Credit, " - "meets its basic conditions and does not take Tax-Free Childcare " - "instead, and the award includes the carer element for this " - "claimant. Afterwards it reads the positive award " - "(receives_uc_carer_element). From 1 April " + "defined an award of Universal Credit from 1 April 2022 to include " + '"a universal credit award with a value of zero" (reg 4, as SSI ' + '2022/52 reg 3 amended it), and from 1 April 2025 as "an award of ' + 'universal credit made under Part 1 of the 2012 Act" (SSI 2025/24 ' + "reg 8). An income that tapers the amount payable below one penny " + "fails a financial condition of entitlement (Welfare Reform Act 2012 " + "s.5(1)(b); UC Regs 2013 reg 17), so DWP makes no award or the award " + "ends (UC Claims and Payments Regs 2013 reg 32A(1)), and there is no " + "award to have a value of zero. The Scottish Government's policy " + "note to SSI 2025/24 confines the earlier words to an award a " + "sanction reduces to zero, and the Scottish Ministers inserted reg " + "10A to bring part of the earnings case in from 1 April 2025. So in " + "every year from 2022-23 the model reads limb (c) from the award " + "itself (receives_uc_carer_element): an award of at least one penny " + "that includes the carer element in respect of this claimant. For " + "an award that earnings end, its last assessment period and any " + "reclaim months from 2022-23 to 2024-25 remain arguable; the model " + "cannot tell that award from a first claim, on which DWP makes no " + "award. From 1 April " "2025 the Scottish Ministers also count two nil awards as awards (reg " "10A): one a sanction reduces to nil, and one whose claimant the " "Secretary of State treats as reclaiming after earnings end it (UC " "Claims and Payments Regs 2013 reg 32A). The model has neither " - "sanctions nor reclaims, so it pays no premium for those. A person " + "sanctions nor reclaims, so it pays no premium for those, nor for " + "a sanctioned nil award from 2022-23 to 2024-25. A person " "with both an allowance and a carer element qualifies once: councils " "include the premium for each applicant or partner who qualifies " "(paras 5(2) and (3)), not for each award. Partners caring for the " @@ -66,12 +64,16 @@ class council_tax_reduction_carer_premium(Variable): "https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/5", "https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/6", "https://www.legislation.gov.uk/ssi/2021/249/regulation/1", - "https://www.legislation.gov.uk/ssi/2021/249/regulation/4/2024-04-30", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/4/2022-04-01", + "https://www.legislation.gov.uk/ssi/2022/52/regulation/3/made", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/4/2025-04-01", "https://www.legislation.gov.uk/ssi/2025/24/regulation/8/made", "https://www.legislation.gov.uk/ssi/2021/249/regulation/10A", "https://www.legislation.gov.uk/ssi/2012/303/schedule/1/paragraph/14/2021-04-01", "https://www.legislation.gov.uk/uksi/2013/376/regulation/29", "https://www.legislation.gov.uk/ukpga/2012/5/section/5", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/17", + "https://www.legislation.gov.uk/uksi/2013/380/regulation/32A", ) def formula(benunit, period, parameters): @@ -87,20 +89,9 @@ def formula(benunit, period, parameters): person = benunit.members claimant_or_partner = person("is_claimant_or_partner", period) # SSI 2021/249 Sch 1 para 6(1)(c): an award of Universal Credit which - # includes the carer element. Until 31 March 2025 the model counts a - # nil award as one "with a value of zero" (reg 4), a reading the - # sources leave open, so the award's conditions decide. + # includes the carer element. An earnings nil case has no award in + # any year (Welfare Reform Act 2012 s.5(1)(b); reg 4 and reg 10A). carer_element_award = person("receives_uc_carer_element", period) - if scotland.uc_award_includes_zero_value_award: - family = person.benunit - on_claim = ( - family("claims_universal_credit", period) - & family("is_uc_eligible", period) - & ~family("forgoes_universal_credit_for_tax_free_childcare", period) - ) - carer_element_award = carer_element_award | ( - on_claim & person("uc_carer_element_included_for_claimant", period) - ) # Para 6(1)(a) and (b), as in carer_premium, or (c). caring = person("is_entitled_to_carer_benefit", period) | carer_element_award qualifying_carers = benunit.sum(claimant_or_partner & caring) From f75f41abc6637ad2d43912e3c0bdc191c6aff625 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 17:56:48 +0000 Subject: [PATCH 18/21] Compare the veteran test with the law at the merge with main After the merge with main, the model exempts the veteran from the non-dependant deduction through armed forces independence payment, which UC Regs 2013 reg 2 counts as attendance allowance for Sch 4 para 16(2)(d). The veteran test now asserts the model's figures with the branch's Pension Credit result supplied as false, labels the forced-deduction run a counterfactual, and records that Sch 4 para 15 exempts the blind single renter from every deduction. A new test pins a blind partner whose deduction the branch's result decides. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6HcTFPvy9Mo5Upj4Rk --- .../test_uc_carer_element_award_properties.py | 159 +++++++++++++++--- .../has_uc_award_with_carer_element.py | 13 +- 2 files changed, 143 insertions(+), 29 deletions(-) diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index a6ad8ee35a..5e62f7c1db 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -51,7 +51,10 @@ non-dependant family's Pension Credit in a branch with every housing costs element set to nil, where the carer test reads the award without housing costs. test_the_non_dependant_branch_reads_the_award_without_housing_costs - pins the one kind of case where that changes a deduction. + pins that reading in three request orders, for a household where the + branch's result changes no deduction, and + test_the_branch_decides_a_blind_partners_deduction pins a household where + the branch's result decides one. A reform that makes is_uc_eligible read Pension Credit would cycle through the attribution, which reads eligibility; these tests do not cover reforms. @@ -686,13 +689,19 @@ def test_small_housing_costs_with_a_work_allowance_leave_no_award(): assert severe[1] == pytest.approx(86.05 * WEEKS) -def veteran_situation(exempt=None): +def veteran_situation(pension_credit=None, veteran_exempt=None, blind_partner=False): """A blind carer (40) earning £20,000 rents from the council at £6,000 a year as household head and cares 35 hours a week for a veteran (70) in another family of the household, who receives armed forces independence - payment and £13,000 of State Pension. exempt, if given, supplies the - veteran family's Pension Credit exemption from the non-dependant - deduction.""" + payment and £13,000 of State Pension. The carer claims alone, so UC Regs + 2013 Sch 4 para 15(1)(a) and (2)(a) exempt the carer, a blind single + renter, from every non-dependant deduction; the model holds no such + exemption. pension_credit, if given, supplies the veteran family's + uc_non_dep_receives_pension_credit, which the model otherwise reads from + a branch. veteran_exempt, if given, supplies the veteran's + uc_non_dep_deduction_exempt. blind_partner adds the veteran's blind + partner (68), who receives no disability benefit, and raises the + veteran's State Pension to £21,000.""" people = { "carer": { "age": {YEAR: 40}, @@ -712,14 +721,23 @@ def veteran_situation(exempt=None): "carer_unit": {"members": ["carer"]}, "veteran_unit": {"members": ["veteran"], "would_claim_uc": {YEAR: False}}, } - if exempt is not None: - benunits["veteran_unit"]["uc_non_dep_receives_pension_credit"] = {YEAR: exempt} + if pension_credit is not None: + benunits["veteran_unit"]["uc_non_dep_receives_pension_credit"] = { + YEAR: pension_credit + } + if veteran_exempt is not None: + people["veteran"]["uc_non_dep_deduction_exempt"] = {YEAR: veteran_exempt} household = { "members": ["carer", "veteran"], "country": {YEAR: "ENGLAND"}, "tenure_type": {YEAR: "RENT_FROM_COUNCIL"}, "rent": {YEAR: 6_000}, } + if blind_partner: + people["veteran"]["state_pension"] = {YEAR: 21_000} + people["partner"] = {"age": {YEAR: 68}, "is_blind": {YEAR: True}} + benunits["veteran_unit"]["members"].append("partner") + household["members"].append("partner") return {"people": people, "benunits": benunits, "households": {"h": household}} @@ -733,20 +751,26 @@ def test_the_non_dependant_branch_reads_the_award_without_housing_costs(first): # with no non-dependant deduction. The award includes the carer element, # so the veteran, whom DWP and the model treat as severely disabled # through armed forces independence payment, gets no severe disability - # addition, and with £250 a week of State Pension against a standard - # minimum guarantee of £238.00, no Pension Credit. + # addition. The veteran pays income tax of 86.00 and keeps 12,914.00 of + # State Pension, which exceeds the standard minimum guarantee of 238.00 + # x 52 = 12,376.00, so the veteran gets no Pension Credit. # - # The deduction's exemption reads the veteran's Pension Credit in a - # branch with every housing costs element set to nil. There the carer's - # award, 7,610.88 - 9,855.78, comes to nil, the veteran gets the addition - # (DWP ignores a blind resident, SPC Regs 2002 Sch I para 2(2)(b)) and - # Pension Credit, (238.00 + 86.05 - 250.00) x 52 = 3,850.60, and the model - # exempts the veteran from the deduction. UC Regs 2013 Sch 4 para 16(2) - # names no armed forces independence payment, so the law, in which the - # veteran gets no Pension Credit, deducts 96.55 x 12 = 1,158.60 and leaves - # an award of 2,596.50, which still includes the carer element. This test - # pins the model's figures, whichever variable the simulation requests - # first. + # uc_non_dep_receives_pension_credit reads the veteran's Pension Credit + # in a branch with every housing costs element set to nil. There the + # carer's award, 7,610.88 - 9,855.78, comes to nil, the veteran gets the + # addition (DWP ignores a blind resident, SPC Regs 2002 Sch I para + # 2(2)(b)) and Pension Credit of (238.00 + 86.05) x 52 - 12,914.00 = + # 3,936.60, so the branch returns true. + # + # In law DWP deducts nothing for this household, on two grounds. UC Regs + # 2013 Sch 4 para 15(1)(a) and (2)(a) exempt the carer, a blind single + # renter, from every deduction. Para 16(2)(d) exempts the veteran, who + # receives "attendance allowance" under head (f) of reg 2's definition, + # armed forces independence payment. The model holds the second ground + # (uc_receives_attendance_allowance) but not the first, so the model's + # award and the law's agree at 3,755.10, and the branch's result changes + # neither. This test pins the model's figures, whichever variable the + # simulation requests first. sim = Simulation(situation=veteran_situation()) sim.calculate(first, YEAR) assert sim.calculate("has_uc_award_with_carer_element", YEAR).tolist() == [ @@ -761,10 +785,97 @@ def test_the_non_dependant_branch_reads_the_award_without_housing_costs(first): assert sim.calculate("uc_non_dep_deductions", YEAR)[0] == 0 award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) assert award[0] == pytest.approx(3_755.10, abs=0.01) - # The law's figures, with the exemption supplied as the law reads it. - law = Simulation(situation=veteran_situation(exempt=False)) - assert law.calculate("uc_non_dep_deductions", YEAR)[0] == pytest.approx(96.55 * 12) + # The branch's result set to its value in law, false, since the veteran + # gets no Pension Credit: the model still exempts the veteran through + # armed forces independence payment and deducts nothing. + law = Simulation(situation=veteran_situation(pension_credit=False)) + assert law.calculate("uc_non_dep_deduction_exempt", YEAR).tolist() == [ + False, + True, + ] + assert law.calculate("uc_non_dep_deductions", YEAR)[0] == 0 law_award = law.calculate("universal_credit_pre_benefit_cap", YEAR) - assert law_award[0] == pytest.approx(2_596.50, abs=0.01) + assert law_award[0] == pytest.approx(3_755.10, abs=0.01) assert law.calculate("has_uc_award_with_carer_element", YEAR)[0] assert law.calculate("pension_credit", YEAR)[1] == 0 + # A counterfactual that neither the law nor the model gives this + # household: with the veteran's exemption supplied as false, the model + # deducts 96.55 x 12 = 1,158.60 and leaves an award of 3,755.10 - + # 1,158.60 = 2,596.50. The award still includes the carer element, so + # even a deduction would leave the carer test and the veteran's nil + # Pension Credit unchanged. + counterfactual = Simulation(situation=veteran_situation(veteran_exempt=False)) + assert counterfactual.calculate("uc_non_dep_deductions", YEAR)[0] == pytest.approx( + 96.55 * 12 + ) + counterfactual_award = counterfactual.calculate( + "universal_credit_pre_benefit_cap", YEAR + ) + assert counterfactual_award[0] == pytest.approx(2_596.50, abs=0.01) + assert counterfactual.calculate("has_uc_award_with_carer_element", YEAR)[0] + assert counterfactual.calculate("pension_credit", YEAR)[1] == 0 + + +def test_the_branch_decides_a_blind_partners_deduction(): + # The veteran of the test above, now with £21,000 of State Pension and a + # blind partner (68) who receives no disability benefit. Para 16(2) + # exempts the veteran through armed forces independence payment but + # lists no blindness, so only the family's Pension Credit can exempt the + # partner, and the branch's result decides the partner's deduction. + # + # The veteran pays income tax of (21,000 - 12,570) x 0.20 - 1,260 x 0.20 + # = 1,434.00, with the partner's marriage allowance, and the couple keep + # 19,566.00, which exceeds the couple's standard minimum guarantee of + # 363.25 x 52 = 18,889.00, so the couple get no Pension Credit. In the + # branch, the carer's award comes to nil, and SPC Regs 2002 Sch I para + # 1(1)(c) gives the couple the addition (the veteran receives armed + # forces independence payment, the partner is blind, and DWP ignores the + # blind carer as a resident under para 2(2)(b)): Pension Credit comes to + # (363.25 + 86.05) x 52 - 19,566.00 = 3,797.60, the branch returns true, + # and the model exempts the partner and deducts nothing. + # + # In law DWP deducts nothing either, because para 15(1)(a) and (2)(a) + # exempt the carer, a blind single renter, from every deduction; the + # carer's award then comes to 3,755.10, includes the carer element and + # leaves the couple without the addition or Pension Credit. The model + # holds no para 15 exemption, so with the branch's result supplied at + # its value in law, false, the model deducts 96.55 x 12 = 1,158.60 for + # the partner and leaves an award of 2,596.50. + sim = Simulation(situation=veteran_situation(blind_partner=True)) + assert sim.calculate("has_uc_award_with_carer_element", YEAR).tolist() == [ + True, + False, + False, + ] + assert sim.calculate("pension_credit", YEAR).tolist() == [0, 0] + assert sim.calculate("uc_non_dep_receives_pension_credit", YEAR).tolist() == [ + False, + True, + ] + assert sim.calculate("uc_non_dep_deduction_exempt", YEAR).tolist() == [ + False, + True, + True, + ] + assert sim.calculate("uc_non_dep_deductions", YEAR)[0] == 0 + award = sim.calculate("universal_credit_pre_benefit_cap", YEAR) + assert award[0] == pytest.approx(3_755.10, abs=0.01) + branch = Simulation(situation=veteran_situation(blind_partner=True)) + branch.set_input("uc_housing_costs_element", YEAR, [0, 0]) + assert branch.calculate("pension_credit", YEAR)[1] == pytest.approx( + 3_797.60, abs=0.01 + ) + flag_false = Simulation( + situation=veteran_situation(pension_credit=False, blind_partner=True) + ) + assert flag_false.calculate("uc_non_dep_deduction_exempt", YEAR).tolist() == [ + False, + True, + False, + ] + assert flag_false.calculate("uc_non_dep_deductions", YEAR)[0] == pytest.approx( + 96.55 * 12 + ) + flag_false_award = flag_false.calculate("universal_credit_pre_benefit_cap", YEAR) + assert flag_false_award[0] == pytest.approx(2_596.50, abs=0.01) + assert flag_false.calculate("has_uc_award_with_carer_element", YEAR)[0] diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py index a7faff105e..4bb7d495a5 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/carer_element/has_uc_award_with_carer_element.py @@ -47,11 +47,14 @@ class has_uc_award_with_carer_element(Variable): "exemption. A member of the non-dependant family gains or loses it " "through this variable only where the family gets Pension Credit " "with the severe disability addition and none without it, and the " - "member has no other exemption: someone who receives armed forces " - "independence payment and no other qualifying benefit, or a blind " - "partner under SPC Sch I para 1(1)(c) who receives none. There the " - "model can exempt a non-dependant whom the law does not, or the " - "reverse. Where an input or a scenario stores universal_credit for " + "member has no other exemption: a blind partner under SPC Sch I para " + "1(1)(c) who receives no qualifying benefit. There the model reads " + "that member's exemption from the award without housing costs. For " + "the family to get the addition, DWP must ignore the renter as a " + "resident (SPC Sch I para 2(2)); a single renter whom DWP ignores as " + "blind or for a qualifying benefit gets UC Regs 2013 Sch 4 para 15's " + "exemption from every deduction, which the model does not hold. " + "Where an input or a scenario stores universal_credit for " "the period, or a reform neutralizes it, reading it cannot cycle, so " "this reads the award itself (receives_uc_carer_element). The model " "applies the same rule in Northern Ireland, whose legislation uses " From f8cba9d544a2c493e6388b374b4739d86e43b54f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 4 Nov 2026 14:01:25 -0500 Subject: [PATCH 19/21] Say when the Pension Credit branch's result can differ from the family's own MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uc_non_dep_receives_pension_credit calculates a non-dependant family's Pension Credit in a branch with every family's housing costs element set to nil. Now that the carer test reads the carer's award, a family whose severe disability addition reads a renter's carer element sees that award without housing costs in the branch, so the branch's Pension Credit can differ from the family's own (the veteran test: £3,936.60 in the branch, nil outside it). The documentation and the formula's comment said the branch preserved the family's entitlement; they now say when it doesn't. Co-Authored-By: Claude Opus 5.5 --- .../uc_non_dep_receives_pension_credit.py | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_receives_pension_credit.py b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_receives_pension_credit.py index d04efdbf02..1a1c9cbc52 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_receives_pension_credit.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_receives_pension_credit.py @@ -10,13 +10,17 @@ class uc_non_dep_receives_pension_credit(Variable): documentation = ( "Whether a family not liable for rent receives Pension Credit, for the " "Universal Credit non-dependant housing contribution exemption. Such " - "a family's own Universal Credit housing element is zero. Calculating " - "its Pension Credit in a branch with that element fixed at zero " - "therefore preserves its entitlement, including legacy-closure " - "routing, take-up, explicit inputs and frozen Pension Credit reforms. " - "It avoids evaluating a renter's own Pension Credit while calculating " - "their Universal Credit housing element. Rent-liable families' " - "branch results are disregarded because they are not non-dependants." + "a family's own Universal Credit housing element is zero. The formula " + "calculates its Pension Credit in a branch with every family's " + "housing costs element set to nil, which keeps the family's " + "legacy-closure routing, take-up, explicit inputs and frozen Pension " + "Credit reforms. Where the family's severe disability addition reads " + "a renter's Universal Credit award that includes the carer element, " + "the branch reads that award without housing costs, so the branch's " + "Pension Credit can differ from the family's own. The branch avoids " + "evaluating a renter's own Pension Credit while calculating their " + "Universal Credit housing element. The formula ignores the branch " + "result for a rent-liable family, which is not a non-dependant." ) definition_period = YEAR reference = ( @@ -26,9 +30,11 @@ class uc_non_dep_receives_pension_credit(Variable): def formula(benunit, period, parameters): # A non-dependant is not liable for the rent (Sch 4 para 9(2)(d)), - # so their own housing element is already zero. This branch keeps - # their actual Pension Credit calculation while breaking the cycle - # through the renter's UC entitlement and pension-age route. + # so their own housing element is already zero. The branch keeps + # their Pension Credit calculation and breaks the cycle through the + # renter's UC entitlement and pension-age route. Where their severe + # disability addition reads a renter's carer element, the branch + # reads the renter's award without housing costs. simulation = benunit.simulation # Income tax is independent of the UC housing element. Calculate # the earnings counterfactuals on the parent so this branch and From f43bdd292fa5929ae8a649432d7ee159d0faa970 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 9 Nov 2026 07:33:49 -0500 Subject: [PATCH 20/21] Expect no deduction where para 15 exempts the carer in the veteran tests Co-Authored-By: Claude Opus 5.5 --- .../test_uc_carer_element_award_properties.py | 67 +++++++++---------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index 5e62f7c1db..76f713844f 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -52,8 +52,8 @@ element set to nil, where the carer test reads the award without housing costs. test_the_non_dependant_branch_reads_the_award_without_housing_costs pins that reading in three request orders, for a household where the - branch's result changes no deduction, and - test_the_branch_decides_a_blind_partners_deduction pins a household where + branch's result changes no exemption, and + test_the_branch_decides_a_blind_partners_exemption pins a household where the branch's result decides one. A reform that makes is_uc_eligible read Pension Credit would cycle through the @@ -695,12 +695,12 @@ def veteran_situation(pension_credit=None, veteran_exempt=None, blind_partner=Fa another family of the household, who receives armed forces independence payment and £13,000 of State Pension. The carer claims alone, so UC Regs 2013 Sch 4 para 15(1)(a) and (2)(a) exempt the carer, a blind single - renter, from every non-dependant deduction; the model holds no such - exemption. pension_credit, if given, supplies the veteran family's - uc_non_dep_receives_pension_credit, which the model otherwise reads from - a branch. veteran_exempt, if given, supplies the veteran's - uc_non_dep_deduction_exempt. blind_partner adds the veteran's blind - partner (68), who receives no disability benefit, and raises the + renter, from every non-dependant deduction + (uc_non_dep_deductions_renter_exempt). pension_credit, if given, supplies + the veteran family's uc_non_dep_receives_pension_credit, which the model + otherwise reads from a branch. veteran_exempt, if given, supplies the + veteran's uc_non_dep_deduction_exempt. blind_partner adds the veteran's + blind partner (68), who receives no disability benefit, and raises the veteran's State Pension to £21,000.""" people = { "carer": { @@ -766,11 +766,12 @@ def test_the_non_dependant_branch_reads_the_award_without_housing_costs(first): # 2013 Sch 4 para 15(1)(a) and (2)(a) exempt the carer, a blind single # renter, from every deduction. Para 16(2)(d) exempts the veteran, who # receives "attendance allowance" under head (f) of reg 2's definition, - # armed forces independence payment. The model holds the second ground - # (uc_receives_attendance_allowance) but not the first, so the model's - # award and the law's agree at 3,755.10, and the branch's result changes - # neither. This test pins the model's figures, whichever variable the - # simulation requests first. + # armed forces independence payment. The model holds both grounds + # (uc_non_dep_deductions_renter_exempt and + # uc_receives_attendance_allowance), so the model's award and the law's + # agree at 3,755.10, and the branch's result changes neither. This test + # pins the model's figures, whichever variable the simulation requests + # first. sim = Simulation(situation=veteran_situation()) sim.calculate(first, YEAR) assert sim.calculate("has_uc_award_with_carer_element", YEAR).tolist() == [ @@ -798,30 +799,26 @@ def test_the_non_dependant_branch_reads_the_award_without_housing_costs(first): assert law_award[0] == pytest.approx(3_755.10, abs=0.01) assert law.calculate("has_uc_award_with_carer_element", YEAR)[0] assert law.calculate("pension_credit", YEAR)[1] == 0 - # A counterfactual that neither the law nor the model gives this - # household: with the veteran's exemption supplied as false, the model - # deducts 96.55 x 12 = 1,158.60 and leaves an award of 3,755.10 - - # 1,158.60 = 2,596.50. The award still includes the carer element, so - # even a deduction would leave the carer test and the veteran's nil - # Pension Credit unchanged. + # With the veteran's exemption supplied as false, the carer's own para 15 + # exemption still leaves no deduction and an award of 3,755.10 that + # includes the carer element, so the carer test and the veteran's nil + # Pension Credit do not change. counterfactual = Simulation(situation=veteran_situation(veteran_exempt=False)) - assert counterfactual.calculate("uc_non_dep_deductions", YEAR)[0] == pytest.approx( - 96.55 * 12 - ) + assert counterfactual.calculate("uc_non_dep_deductions", YEAR)[0] == 0 counterfactual_award = counterfactual.calculate( "universal_credit_pre_benefit_cap", YEAR ) - assert counterfactual_award[0] == pytest.approx(2_596.50, abs=0.01) + assert counterfactual_award[0] == pytest.approx(3_755.10, abs=0.01) assert counterfactual.calculate("has_uc_award_with_carer_element", YEAR)[0] assert counterfactual.calculate("pension_credit", YEAR)[1] == 0 -def test_the_branch_decides_a_blind_partners_deduction(): +def test_the_branch_decides_a_blind_partners_exemption(): # The veteran of the test above, now with £21,000 of State Pension and a # blind partner (68) who receives no disability benefit. Para 16(2) # exempts the veteran through armed forces independence payment but # lists no blindness, so only the family's Pension Credit can exempt the - # partner, and the branch's result decides the partner's deduction. + # partner, and the branch's result decides the partner's exemption. # # The veteran pays income tax of (21,000 - 12,570) x 0.20 - 1,260 x 0.20 # = 1,434.00, with the partner's marriage allowance, and the couple keep @@ -832,15 +829,15 @@ def test_the_branch_decides_a_blind_partners_deduction(): # forces independence payment, the partner is blind, and DWP ignores the # blind carer as a resident under para 2(2)(b)): Pension Credit comes to # (363.25 + 86.05) x 52 - 19,566.00 = 3,797.60, the branch returns true, - # and the model exempts the partner and deducts nothing. + # and the model exempts the partner. # - # In law DWP deducts nothing either, because para 15(1)(a) and (2)(a) + # In law the couple get no Pension Credit, so para 16 does not exempt the + # partner, and DWP still deducts nothing, because para 15(1)(a) and (2)(a) # exempt the carer, a blind single renter, from every deduction; the - # carer's award then comes to 3,755.10, includes the carer element and - # leaves the couple without the addition or Pension Credit. The model - # holds no para 15 exemption, so with the branch's result supplied at - # its value in law, false, the model deducts 96.55 x 12 = 1,158.60 for - # the partner and leaves an award of 2,596.50. + # carer's award comes to 3,755.10, includes the carer element and leaves + # the couple without the addition. The model applies para 15 too, so with + # the branch's result supplied at its value in law, false, the partner + # loses the exemption and the model still deducts nothing. sim = Simulation(situation=veteran_situation(blind_partner=True)) assert sim.calculate("has_uc_award_with_carer_element", YEAR).tolist() == [ True, @@ -873,9 +870,7 @@ def test_the_branch_decides_a_blind_partners_deduction(): True, False, ] - assert flag_false.calculate("uc_non_dep_deductions", YEAR)[0] == pytest.approx( - 96.55 * 12 - ) + assert flag_false.calculate("uc_non_dep_deductions", YEAR)[0] == 0 flag_false_award = flag_false.calculate("universal_credit_pre_benefit_cap", YEAR) - assert flag_false_award[0] == pytest.approx(2_596.50, abs=0.01) + assert flag_false_award[0] == pytest.approx(3_755.10, abs=0.01) assert flag_false.calculate("has_uc_award_with_carer_element", YEAR)[0] From 649d3ad8fd7aa71b165d4b50032f2bb4fb815d88 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 9 Nov 2026 07:33:49 -0500 Subject: [PATCH 21/21] Count the carer element per person in Scotland's working-age award, from 1 April 2022 Co-Authored-By: Claude Opus 5.5 --- .../council_tax_reduction_carer_premium.yaml | 47 +++++++++++++++++++ .../test_uc_carer_element_award_properties.py | 13 ++++- .../council_tax_reduction_carer_premium.py | 4 +- ...ouncil_tax_reduction_working_age_carers.py | 34 ++++++++------ 4 files changed, 82 insertions(+), 16 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml index 2d5b93dc14..4f7b91f1c5 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.yaml @@ -59,6 +59,8 @@ carer_premium: 0 council_tax_reduction_carer_premium: 48.15 * 52 council_tax_reduction_applicable_amount: (95.55 + 48.15) * 52 + council_tax_reduction_working_age_carers: 1 + council_tax_reduction_working_age_adult_premiums: 48.15 * 52 - name: The Scottish carer premium for a carer element starts with the 2021 Regulations on 1 April 2022 # The model reads model year 2022 (2022-23) at 30 April 2022. Carer element @@ -84,6 +86,8 @@ uc_carer_element: 168.81 * 12 council_tax_reduction_carer_premium: 38.85 * 52 council_tax_reduction_applicable_amount: (77.00 + 38.85) * 52 + council_tax_reduction_working_age_carers: 1 + council_tax_reduction_working_age_adult_premiums: 38.85 * 52 - name: Before 1 April 2022 the Scottish scheme for persons under pension age tested Carer's Allowance only # Under SSI 2012/303 Sch 1 para 14, councils included the premium for an @@ -109,6 +113,8 @@ output: uc_carer_element: 163.73 * 12 council_tax_reduction_carer_premium: 0 + council_tax_reduction_working_age_carers: 0 + council_tax_reduction_working_age_adult_premiums: 0 - name: The same carer in England gets no carer premium for the carer element # In England the model takes the working-age premiums from the Housing @@ -134,6 +140,7 @@ receives_uc_carer_element: true council_tax_reduction_carer_premium: 0 council_tax_reduction_applicable_amount: 95.55 * 52 + council_tax_reduction_working_age_carers: 0 - name: The same carer in Wales gets no carer premium for the carer element # Welsh councils include the premium for an applicant or partner "entitled @@ -158,6 +165,7 @@ receives_uc_carer_element: true council_tax_reduction_carer_premium: 0 council_tax_reduction_applicable_amount: 95.55 * 52 + council_tax_reduction_working_age_carers: 0 - name: A Scottish carer whose income tapers the award to nil has no award and no carer premium # With £20,000 of earnings the award comes to nil, which in 2026 leaves no @@ -186,6 +194,7 @@ universal_credit: 0 receives_uc_carer_element: false council_tax_reduction_carer_premium: 0 + council_tax_reduction_working_age_carers: 0 - name: In 2024 a Scottish carer whose earnings taper the award to nil has no award and no carer premium # The same carer in 2024-25. The maximum amount comes to (393.45 + 198.31) @@ -223,6 +232,7 @@ is_entitled_to_carer_benefit: false carer_premium: 0 council_tax_reduction_carer_premium: 0 + council_tax_reduction_working_age_carers: 0 - name: From 1 April 2025 the same Scottish carer's nil award leaves no award # The same carer in 2025-26. The maximum amount comes to (400.14 + 201.68) @@ -255,6 +265,7 @@ receives_uc_carer_element: false is_entitled_to_carer_benefit: false council_tax_reduction_carer_premium: 0 + council_tax_reduction_working_age_carers: 0 - name: A Scottish carer with both Carer Support Payment and the carer element gets one premium # The applicant has regular and substantial caring responsibilities under @@ -282,6 +293,7 @@ receives_uc_carer_element: true carer_premium: 48.15 * 52 council_tax_reduction_carer_premium: 48.15 * 52 + council_tax_reduction_working_age_carers: 1 - name: Scottish partners caring for the same person get one premium, whichever holds the carer element # Both care 35 hours. The younger receives Carer Support Payment and the @@ -314,6 +326,40 @@ receives_carer_benefit: [false, true] receives_uc_carer_element: [true, false] council_tax_reduction_carer_premium: 48.15 * 52 + council_tax_reduction_working_age_carers: 1 + +- name: Scottish partners caring for different people get a premium each, one on Carer Support Payment and one with the carer element + # Both care 35 hours, for different people. The younger receives Carer + # Support Payment, and the award includes a carer element for each (UC Regs + # 2013 reg 29(2)). Councils include the premium for each (para 5(2)): the + # elder under para 6(1)(c), the younger under para 6(1)(a), so 2 x 48.15 x + # 52 = 5,007.60. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + elder: + age: 45 + care_hours: 35 + would_claim_carers_allowance: false + younger: + age: 40 + care_hours: 35 + would_claim_carers_allowance: true + benunits: + couple: + members: [elder, younger] + partners_care_for_same_severely_disabled_person: false + households: + household: + members: [elder, younger] + country: SCOTLAND + region: SCOTLAND + output: + is_entitled_to_carer_benefit: [false, true] + receives_uc_carer_element: [true, true] + council_tax_reduction_working_age_carers: 2 + council_tax_reduction_working_age_adult_premiums: 2 * 48.15 * 52 - name: A Scottish pensioner's schedule has no carer element limb # Under Scotland's pension-age scheme, councils test entitlement to Carer's @@ -374,3 +420,4 @@ receives_carer_benefit: [false, true, false] carer_premium: [0, 0] council_tax_reduction_carer_premium: [0, 0] + council_tax_reduction_working_age_carers: [0, 0] diff --git a/policyengine_uk/tests/test_uc_carer_element_award_properties.py b/policyengine_uk/tests/test_uc_carer_element_award_properties.py index 76f713844f..2528e93b13 100644 --- a/policyengine_uk/tests/test_uc_carer_element_award_properties.py +++ b/policyengine_uk/tests/test_uc_carer_element_award_properties.py @@ -34,7 +34,9 @@ 4. Bounds: the Scottish premium comes to a whole number of weekly amounts, at most the number of claimants and partners, and at most one where partners care for the same person; a person with both an allowance and a carer element - counts once. + counts once. council_tax_reduction_working_age_carers, which Scotland's + working-age award reads, gives the same count unless the household head + applies alone. 5. The carer test is the award: has_uc_award_with_carer_element equals receives_uc_carer_element for every person, and universal_credit_pre_benefit_cap comes to more than nil wherever @@ -107,6 +109,7 @@ "housing_benefit", "income_support", "council_tax_reduction", + "council_tax_reduction_working_age_carers", "household_net_income", ) @@ -332,6 +335,8 @@ def test_the_carer_element_limb_moves_only_its_readers_and_only_one_way(populati variables = SEVERE_READERS + ( "carer_premium", "council_tax_reduction_carer_premium", + "council_tax_reduction_working_age_carers", + "council_tax_reduction_head_applies_alone", "council_tax_reduction_pensioner", "partners_care_for_same_severely_disabled_person", "is_cared_for_by_carer_benefit_recipient", @@ -410,6 +415,12 @@ def test_the_carer_element_limb_moves_only_its_readers_and_only_one_way(populati assert (np.round(weekly) <= claimants).all() same_person = drawn["partners_care_for_same_severely_disabled_person"] assert (np.round(weekly[same_person]) <= 1).all() + # Scotland's working-age award counts carers with its own variable. It + # assesses the applicant and partner, who are the claimant and partner + # unless the household head applies alone. + counted = scottish_working_age & ~drawn["council_tax_reduction_head_applies_alone"] + carers = drawn["council_tax_reduction_working_age_carers"] + assert (carers[counted] == np.round(weekly[counted])).all() # 6. Everyone cared for meets Carer's Allowance's severe disability test; no # household has more people cared for than carers, and the attribution diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py index cda349ac2e..5ff87710cc 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_premium.py @@ -56,7 +56,9 @@ class council_tax_reduction_carer_premium(Variable): "allowance reduced to nil; that choice does not change the amount. " "Before 1 April 2022 councils paid the premium to persons under " "pension age on entitlement to Carer's Allowance only (SSI 2012/303 " - "Sch 1 para 14; carer_premium_counts_uc_carer_element)." + "Sch 1 para 14; carer_premium_counts_uc_carer_element). Scotland's " + "working-age award reads council_tax_reduction_working_age_carers, " + "which applies the same test to the applicant and partner." ) definition_period = YEAR unit = GBP diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/working_age/council_tax_reduction_working_age_carers.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/working_age/council_tax_reduction_working_age_carers.py index 4ae02293f5..64fc812874 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/working_age/council_tax_reduction_working_age_carers.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/working_age/council_tax_reduction_working_age_carers.py @@ -16,12 +16,14 @@ class council_tax_reduction_working_age_carers(Variable): "carer premium: entitled to Carer's Allowance or, in Scotland, Carer " "Support Payment, whether or not an overlapping benefit reduces the " "payment to nil; partners caring for the same severely disabled " - "person count once. In Scotland an award of Universal Credit that " - "includes the carer element also qualifies; when no one in the family " - "is entitled to a carer benefit, it counts as one carer." + "person count once. In Scotland from 1 April 2022 an applicant or " + "partner whose Universal Credit award includes the carer element in " + "respect of them also qualifies (receives_uc_carer_element), once " + "even with a carer benefit as well." ) definition_period = YEAR reference = ( + "https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/5", "https://www.legislation.gov.uk/ssi/2021/249/schedule/1/paragraph/6", "https://www.legislation.gov.uk/wsi/2013/3029/schedule/7/paragraph/14", ) @@ -29,18 +31,22 @@ class council_tax_reduction_working_age_carers(Variable): def formula(benunit, period, parameters): person = benunit.members claimant_or_partner = working_age_applicant_or_partner(person, period) - entitled = person("is_entitled_to_carer_benefit", period) - carers = benunit.sum(claimant_or_partner & entitled) + caring = person("is_entitled_to_carer_benefit", period) + scottish_scheme = parameters( + period + ).gov.local_authorities.scotland.council_tax_reduction.working_age + if scottish_scheme.carer_premium_counts_uc_carer_element: + # An award of Universal Credit that includes the carer element + # (SSI 2021/249 Sch 1 para 6(1)(c)) brings in the claimant it is + # included for; uc_carer_element alone is the potential element. + # A person with a carer benefit as well counts once (para 5(2)). + scotland = is_scotland_scheme(benunit.household("country", period)) + caring = caring | ( + benunit.project(scotland) & person("receives_uc_carer_element", period) + ) + carers = benunit.sum(claimant_or_partner & caring) # Partners caring for the same severely disabled person get one # premium (SSI 2021/249 Sch 1 para 5(3)-(4); SSCBA s.70(7ZA) allows # one Carer's Allowance per cared-for person), as in carer_premium. same_person = benunit("partners_care_for_same_severely_disabled_person", period) - carers = where(same_person, min_(carers, 1), carers) - scotland = is_scotland_scheme(benunit.household("country", period)) - # An award of Universal Credit that includes the carer element - # (SSI 2021/249 Sch 1 para 6(1)(c)); uc_carer_element alone is the - # potential element. - uc_carer_element = (benunit("uc_carer_element", period) > 0) & benunit( - "council_tax_reduction_working_age_has_universal_credit", period - ) - return carers + (scotland & uc_carer_element & (carers == 0)) + return where(same_person, min_(carers, 1), carers)