From 378a248ebb6ab2ec0216255cd656572a61880d3f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 1 Oct 2026 09:54:43 -0400 Subject: [PATCH 1/5] Reduce Carer's Allowance and Carer Support Payment by overlapping benefits Carer's Allowance is reduced by any overlapping personal benefit (Social Security (Overlapping Benefits) Regulations 1979 reg 12): State Pension (all of a Pensions Act 2014 Part 1 pension; an old pension without its additional pension), incapacity benefit, SDA, maternity allowance and contributory JSA and ESA. Carer Support Payment is reduced the same way (CSP Regs 2023 reg 16(2)-(3)), and the Scottish Carer Supplement is paid only while CSP is payable (reg 14A). The unreduced amounts stay in carers_allowance_pre_overlap and carer_support_payment_pre_overlap, and is_entitled_to_carer_benefit keeps underlying entitlement for the carer premiums, the Pension Credit carer addition, the benefit cap exemption and the childcare work condition. Fixes #1953 Co-Authored-By: Claude Opus 5.5 --- .../carers-overlapping-benefits.added.md | 1 + .../carers-overlapping-benefits.fixed.md | 1 + .../disability_criteria.yaml | 2 +- .../overlapping_benefits.yaml | 35 ++ .../overlapping_benefits.yaml | 25 ++ ...carers_allowance_overlapping_benefits.yaml | 347 ++++++++++++++++ ..._support_payment_overlapping_benefits.yaml | 127 ++++++ ...t_carer_overlapping_benefits_properties.py | 376 ++++++++++++++++++ .../variables/gov/dwp/carers_allowance.py | 28 +- .../carers_allowance_overlapping_benefits.py | 18 + .../gov/dwp/carers_allowance_pre_overlap.py | 33 ++ .../gov/dwp/is_benefit_cap_exempt_earnings.py | 6 +- ...is_benefit_cap_exempt_health_disability.py | 7 +- .../gov/dwp/is_benefit_cap_exempt_other.py | 6 +- .../gov/dwp/overlapping_state_pension.py | 41 ++ .../carer_support_payment.py | 29 +- ...er_support_payment_overlapping_benefits.py | 16 + .../carer_support_payment_pre_overlap.py | 34 ++ .../scottish_carer_supplement.py | 9 +- .../demographic/is_carer_for_benefits.py | 18 +- policyengine_uk/variables/input/care.py | 27 ++ 21 files changed, 1144 insertions(+), 42 deletions(-) create mode 100644 changelog.d/carers-overlapping-benefits.added.md create mode 100644 changelog.d/carers-overlapping-benefits.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/carers_allowance/overlapping_benefits.yaml create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/overlapping_benefits.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_overlapping_benefits.yaml create mode 100644 policyengine_uk/tests/test_carer_overlapping_benefits_properties.py create mode 100644 policyengine_uk/variables/gov/dwp/carers_allowance_overlapping_benefits.py create mode 100644 policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py create mode 100644 policyengine_uk/variables/gov/dwp/overlapping_state_pension.py create mode 100644 policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_overlapping_benefits.py create mode 100644 policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_pre_overlap.py diff --git a/changelog.d/carers-overlapping-benefits.added.md b/changelog.d/carers-overlapping-benefits.added.md new file mode 100644 index 0000000000..93ee3e247f --- /dev/null +++ b/changelog.d/carers-overlapping-benefits.added.md @@ -0,0 +1 @@ +Add `carers_allowance_pre_overlap`, `carer_support_payment_pre_overlap`, their `*_overlapping_benefits` totals, `overlapping_state_pension` and `is_entitled_to_carer_benefit`. `carers_allowance` and `carer_support_payment` are now the amounts payable after the overlapping-benefit reduction. diff --git a/changelog.d/carers-overlapping-benefits.fixed.md b/changelog.d/carers-overlapping-benefits.fixed.md new file mode 100644 index 0000000000..390bc9e6e3 --- /dev/null +++ b/changelog.d/carers-overlapping-benefits.fixed.md @@ -0,0 +1 @@ +Reduce Carer's Allowance and Carer Support Payment by overlapping benefits such as State Pension and contributory ESA (Social Security (Overlapping Benefits) Regulations 1979 reg 12; Carer Support Payment Regulations 2023 reg 16(2)), while carer premiums, the Pension Credit carer addition, the benefit cap exemption and the childcare work condition keep the underlying entitlement. diff --git a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/disability_criteria.yaml b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/disability_criteria.yaml index ad83f0f8ca..40cedba10c 100644 --- a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/disability_criteria.yaml +++ b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/disability_criteria.yaml @@ -8,7 +8,7 @@ metadata: label: Extended childcare entitlement disability criteria values: 2015-09-01: - - carers_allowance # 11A(1)(a): Carer's allowance under Section 70 of Social Security Contributions and Benefits Act 1992 + - is_entitled_to_carer_benefit # 11A(1)(a)-(b): Carer's allowance under Section 70 of Social Security Contributions and Benefits Act 1992, or Scottish carer's assistance; regs 14(4) and 15 ask whether a person is entitled to it, so an allowance reduced to nil by an overlapping benefit still counts - uc_carer_element # 11A(1)(c): Universal Credit carer element under Regulation 29 of the Universal Credit Regulations 2013 - esa_contrib # 11A(1)(e): Employment and support allowance under Welfare Reform Act 2007 - incapacity_benefit # 11A(1)(g): Long-term or short-term incapacity benefit under Social Security Contributions and Benefits Act diff --git a/policyengine_uk/parameters/gov/dwp/carers_allowance/overlapping_benefits.yaml b/policyengine_uk/parameters/gov/dwp/carers_allowance/overlapping_benefits.yaml new file mode 100644 index 0000000000..0b126edb4f --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/carers_allowance/overlapping_benefits.yaml @@ -0,0 +1,35 @@ +description: The Department for Work and Pensions reduces Carer's Allowance by these overlapping personal benefits. +metadata: + economy: false + label: Carer's Allowance overlapping benefits + period: year + unit: list + propagate_metadata_to_children: true + reference: + - title: The Social Security (Overlapping Benefits) Regulations 1979 reg. 4 + href: https://www.legislation.gov.uk/uksi/1979/597/regulation/4 + - title: The Social Security (Overlapping Benefits) Regulations 1979 reg. 12 + href: https://www.legislation.gov.uk/uksi/1979/597/regulation/12 + - title: The Employment and Support Allowance (Consequential Provisions) (No. 2) Regulations 2008 reg. 51(3) + href: https://www.legislation.gov.uk/uksi/2008/1554/regulation/51 + - title: The Pensions Act 2014 (Consequential, Supplementary and Incidental Amendments) Order 2015 art. 4(3) + href: https://www.legislation.gov.uk/uksi/2015/1985/article/4/made +values: + # Reg. 4(1)(a) covers personal benefits under SSCBA 1992 Parts II and III, + # the Welfare Reform Act 2007 Part 1 (from 27 October 2008), the Pensions + # Act 2014 Part 1 (from 6 April 2016) and the Jobseekers Act 1995. Reg. 12 + # reduces Carer's Allowance by any of them except additional pension and + # graduated retirement benefit. Industrial injuries benefits are outside + # reg. 4(1)(a), and Schedule 1 adjusts Carer's Allowance only by + # unemployability supplement, death benefits paid to a surviving spouse and + # training allowance, so war disablement pensions and AFCS payments do not + # reduce it. Benefits the model does not hold (widow's benefits, + # bereavement allowance, unemployability supplement, training allowance) + # are not listed. + 2008-10-27: + - overlapping_state_pension + - incapacity_benefit + - sda + - maternity_allowance + - jsa_contrib + - esa_contrib diff --git a/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/overlapping_benefits.yaml b/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/overlapping_benefits.yaml new file mode 100644 index 0000000000..d695aa181e --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/overlapping_benefits.yaml @@ -0,0 +1,25 @@ +description: Social Security Scotland reduces Carer Support Payment by these overlapping benefits. +metadata: + economy: false + label: Carer Support Payment overlapping benefits + period: year + unit: list + propagate_metadata_to_children: true + reference: + - title: The Carer's Assistance (Carer Support Payment) (Scotland) Regulations 2023 reg. 16(2), (3) and (6) + href: https://www.legislation.gov.uk/ssi/2023/302/regulation/16 +values: + # Reg. 16(3) lists State Pension (all of a Pensions Act 2014 Part 1 + # pension) and Retirement Pension (excluding additional pension and + # graduated retirement benefit), incapacity benefit, severe disablement + # allowance, maternity allowance, contribution-based JSA and contributory + # ESA. Its other overlapping benefits (unemployability supplement, widow's + # and bereavement benefits, war widow's pension, industrial death benefit, + # training allowance) are not held by the model. + 2023-11-19: + - overlapping_state_pension + - incapacity_benefit + - sda + - maternity_allowance + - jsa_contrib + - esa_contrib diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml new file mode 100644 index 0000000000..1c3b9408a9 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml @@ -0,0 +1,347 @@ +# Carer's Allowance after the overlapping-benefit adjustment. +# Social Security (Overlapping Benefits) Regulations 1979 (SI 1979/597): +# reg 4(1)(a) makes Carer's Allowance overlap with other personal benefits +# under SSCBA 1992 Parts II and III, the Welfare Reform Act 2007 Part 1, the +# Pensions Act 2014 Part 1 and the Jobseekers Act 1995. Reg 12 reduces Carer's +# Allowance by the other benefit, except additional pension and graduated +# retirement benefit, so that the two together are never less than the +# unadjusted allowance. A new State Pension (Pensions Act 2014 Part 1) +# overlaps in full, including any protected payment above the full rate. +# 2026-27 weekly rates: Carer's Allowance 86.45 (SI 2026/148), full basic +# State Pension 184.90, full new State Pension 241.30, Pension Credit +# standard minimum guarantee 238.00 and carer addition 48.15. + +- name: Case 1, no overlapping benefit leaves Carer's Allowance in full. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + state_pension: 0 + care_hours: 35 + country: ENGLAND + output: + carers_allowance_pre_overlap: 86.45 * 52 + carers_allowance_overlapping_benefits: 0 + carers_allowance: 86.45 * 52 + +- name: Case 2, State Pension below the allowance reduces it pound for pound. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + state_pension: 2_000 + care_hours: 35 + country: ENGLAND + output: + overlapping_state_pension: 2_000 + carers_allowance_pre_overlap: 86.45 * 52 + carers_allowance: 86.45 * 52 - 2_000 + receives_carers_allowance: true + +- name: Case 3, State Pension equal to the allowance reduces it to nil. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + state_pension: 86.45 * 52 + care_hours: 35 + country: ENGLAND + output: + carers_allowance: 0 + receives_carers_allowance: false + is_entitled_to_carer_benefit: true + +- name: Case 4, State Pension above the allowance leaves underlying entitlement and the carer addition. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 70 + state_pension: 6_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + households: + household: + members: [carer] + country: ENGLAND + output: + carers_allowance_pre_overlap: 86.45 * 52 + carers_allowance: 0 + receives_carer_benefit: false + is_entitled_to_carer_benefit: true + is_carer_for_benefits: true + # SPC Regs 2002 Sch I para 4(2): the claimant is entitled to Carer's + # Allowance, whether or not it is paid. + carer_minimum_guarantee_addition: 48.15 * 52 + +- name: Case 5, the additional pension in an old State Pension does not overlap. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 80 + basic_state_pension: 2_000 + additional_state_pension: 5_000 + care_hours: 35 + country: ENGLAND + output: + state_pension_type: BASIC + state_pension: 7_000 + overlapping_state_pension: 2_000 + carers_allowance: 86.45 * 52 - 2_000 + +- name: Case 6, a new State Pension overlaps in full, protected payment included. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 68 + new_state_pension: 2_000 + additional_state_pension: 5_000 + care_hours: 35 + country: ENGLAND + output: + state_pension_type: NEW + overlapping_state_pension: 7_000 + carers_allowance: 0 + +- name: Case 7, a reported old State Pension above the full basic rate overlaps up to that rate. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 80 + state_pension_reported: 13_000 + care_hours: 35 + country: ENGLAND + output: + basic_state_pension: 184.90 * 52 + additional_state_pension: 13_000 - 184.90 * 52 + overlapping_state_pension: 184.90 * 52 + carers_allowance: 0 + +- name: Case 8, a reported old State Pension below the allowance. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 80 + state_pension_reported: 3_000 + care_hours: 35 + country: ENGLAND + output: + overlapping_state_pension: 3_000 + carers_allowance: 86.45 * 52 - 3_000 + +- name: Case 9, a reported new State Pension below the allowance. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 68 + state_pension_reported: 3_000 + care_hours: 35 + country: ENGLAND + output: + new_state_pension: 3_000 + overlapping_state_pension: 3_000 + carers_allowance: 86.45 * 52 - 3_000 + +- name: Case 10, contributory ESA reduces the allowance. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + esa_contrib_reported: 3_000 + care_hours: 35 + country: ENGLAND + output: + carers_allowance_overlapping_benefits: 3_000 + carers_allowance: 86.45 * 52 - 3_000 + +- name: Case 11, contribution-based JSA and Maternity Allowance together reduce the allowance. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 30 + jsa_contrib_reported: 1_000 + maternity_allowance_reported: 1_000 + care_hours: 35 + country: ENGLAND + output: + carers_allowance_overlapping_benefits: 2_000 + carers_allowance: 86.45 * 52 - 2_000 + +- name: Case 12, incapacity benefit above the allowance keeps the benefit cap exemption. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 50 + incapacity_benefit_reported: 5_000 + care_hours: 35 + country: ENGLAND + output: + carers_allowance: 0 + is_entitled_to_carer_benefit: true + # HB Regs 2006 reg 75F(1)(h) and UC Regs 2013 reg 83(1)(i) exempt a + # person "entitled to" Carer's Allowance. + is_benefit_cap_exempt_health_disability: true + +- name: Case 13, Severe Disablement Allowance at its maximum rate exceeds the allowance. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 50 + sda_reported: 1 + care_hours: 35 + country: ENGLAND + output: + # gov.dwp.sda.maximum is 119.35 a week in 2026-27. + sda: 119.35 * 52 + carers_allowance: 0 + +- name: Case 14, industrial injuries and armed forces compensation do not overlap. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 50 + iidb_reported: 5_000 + afcs_reported: 10_000 + care_hours: 35 + country: ENGLAND + output: + carers_allowance_overlapping_benefits: 0 + carers_allowance: 86.45 * 52 + +- name: Case 15, a carer who would not claim has no entitlement to keep. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + state_pension: 6_000 + care_hours: 35 + would_claim_carers_allowance: false + country: ENGLAND + output: + carers_allowance_pre_overlap: 0 + carers_allowance: 0 + is_entitled_to_carer_benefit: false + # Caring 35 hours a week still makes the person a carer for benefits. + is_carer_for_benefits: true + +- name: Case 16, Carer's Allowance supplied as an input is treated as an entitlement. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + carers_allowance: 1_000 + care_hours: 0 + country: ENGLAND + output: + carers_allowance_pre_overlap: 0 + receives_carers_allowance: true + is_entitled_to_carer_benefit: true + is_benefit_cap_exempt_health_disability: true + +- name: Case 17, Pension Credit counts the allowance after the overlap. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 70 + state_pension: 2_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + households: + household: + members: [carer] + country: ENGLAND + output: + carers_allowance: 86.45 * 52 - 2_000 + # SPC Regs 2002 reg 15(3)-(4)(a): the adjusted amount counts, so State + # Pension and Carer's Allowance together count as the allowance alone. + pension_credit_income: 86.45 * 52 + minimum_guarantee: (238 + 48.15) * 52 + guarantee_credit: (238 + 48.15 - 86.45) * 52 + +- name: Case 18, Pension Credit counts no allowance when State Pension exceeds it. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 70 + state_pension: 6_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + households: + household: + members: [carer] + country: ENGLAND + output: + carers_allowance: 0 + pension_credit_income: 6_000 + minimum_guarantee: (238 + 48.15) * 52 + guarantee_credit: (238 + 48.15) * 52 - 6_000 + +- name: Case 19, an allowance reduced to nil does not withhold the severe disability addition. + period: 2021 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + attendance_allowance: 100 + partner: + age: 70 + state_pension: 7_000 + care_hours: 35 + benunits: + benunit: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + country: ENGLAND + output: + # SPC Regs 2002 Sch I para 1(1): the addition is withheld only where a + # person is "entitled to and in receipt of" Carer's Allowance for caring. + carers_allowance: [0, 0] + receives_carer_benefit: [false, false] + is_entitled_to_carer_benefit: [false, true] + is_carer_for_benefits: [false, true] + severe_disability_minimum_guarantee_addition: 67.3 * 52 + +- name: Case 20, a parent entitled to an allowance reduced to nil still meets the childcare work condition. + period: 2025 + input: + people: + parent1: + age: 40 + in_work: true + is_parent: true + parent2: + age: 38 + in_work: false + is_parent: true + care_hours: 35 + maternity_allowance_reported: 5_000 + child: + age: 4 + is_parent: false + benunits: + benunit: + members: [parent1, parent2, child] + family_type: COUPLE_WITH_CHILDREN + households: + household: + members: [parent1, parent2, child] + country: ENGLAND + output: + carers_allowance: [0, 0, 0] + # Childcare (Free of Charge for Working Parents) (England) Regulations + # 2022 regs 14-15 and 11A(1)(a): "entitled to a specified benefit". + extended_childcare_entitlement_work_condition: [1, 1, 0] diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_overlapping_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_overlapping_benefits.yaml new file mode 100644 index 0000000000..3fbe44c4d9 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_overlapping_benefits.yaml @@ -0,0 +1,127 @@ +# Carer Support Payment after the overlapping-benefit reduction. +# Carer's Assistance (Carer Support Payment) (Scotland) Regulations 2023 +# (SSI 2023/302) reg 16(2): for any award week in which the carer is entitled +# to an overlapping benefit, Carer Support Payment is reduced by the amount of +# that benefit, and is £0 where the benefit is at least as much. Reg 16(3) and +# (6) list State Pension (any pension under the Pensions Act 2014 Part 1) and +# Retirement Pension excluding additional pension and graduated retirement +# benefit. Reg 14A pays the Scottish Carer Supplement for each award week in +# which Carer Support Payment is payable. +# 2026-27 weekly rates: Carer Support Payment 86.45 and Scottish Carer +# Supplement 11.70 (SSI 2026/170 reg 12(3)); 2025-26 Carer Support Payment +# 83.30. Pension Credit standard minimum guarantee 238.00, carer addition +# 48.15. + +- name: Case 1, State Pension below Carer Support Payment reduces it and the supplement is paid in full. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 70 + state_pension: 2_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + output: + carers_allowance: 0 + carer_support_payment_pre_overlap: 86.45 * 52 + carer_support_payment: 86.45 * 52 - 2_000 + scottish_carer_supplement: 11.70 * 52 + # SPC Regs 2002 reg 15(4)(g): the reduced amount counts; the supplement + # does not count (reg 15(1)(ri)). + pension_credit_income: 86.45 * 52 + guarantee_credit: (238 + 48.15 - 86.45) * 52 + +- name: Case 2, State Pension above Carer Support Payment reduces it to £0 with no supplement. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 70 + state_pension: 6_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + output: + carer_support_payment_pre_overlap: 86.45 * 52 + carer_support_payment: 0 + scottish_carer_supplement: 0 + is_entitled_to_carer_benefit: true + is_carer_for_benefits: true + carer_minimum_guarantee_addition: 48.15 * 52 + pension_credit_income: 6_000 + +- name: Case 3, State Pension equal to Carer Support Payment gives £0 (reg 16(2)(b)). + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + state_pension: 86.45 * 52 + care_hours: 35 + country: SCOTLAND + output: + carer_support_payment: 0 + scottish_carer_supplement: 0 + +- name: Case 4, the 2025-26 rate. + period: 2025 + absolute_error_margin: 0.01 + input: + age: 70 + state_pension: 2_000 + care_hours: 35 + country: SCOTLAND + output: + carer_support_payment: 83.30 * 52 - 2_000 + +- name: Case 5, contributory ESA reduces Carer Support Payment. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + esa_contrib_reported: 3_000 + care_hours: 35 + country: SCOTLAND + output: + carer_support_payment_overlapping_benefits: 3_000 + carer_support_payment: 86.45 * 52 - 3_000 + scottish_carer_supplement: 11.70 * 52 + +- name: Case 6, the additional pension in an old State Pension does not overlap. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 80 + basic_state_pension: 2_000 + additional_state_pension: 5_000 + care_hours: 35 + country: SCOTLAND + output: + overlapping_state_pension: 2_000 + carer_support_payment: 86.45 * 52 - 2_000 + +- name: Case 7, a new State Pension overlaps in full, protected payment included. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 68 + new_state_pension: 2_000 + additional_state_pension: 5_000 + care_hours: 35 + country: SCOTLAND + output: + overlapping_state_pension: 7_000 + carer_support_payment: 0 + scottish_carer_supplement: 0 diff --git a/policyengine_uk/tests/test_carer_overlapping_benefits_properties.py b/policyengine_uk/tests/test_carer_overlapping_benefits_properties.py new file mode 100644 index 0000000000..32397dd38d --- /dev/null +++ b/policyengine_uk/tests/test_carer_overlapping_benefits_properties.py @@ -0,0 +1,376 @@ +"""Overlapping-benefit reduction of Carer's Allowance and Carer Support Payment. + +Carer's Allowance is a personal benefit that overlaps with State Pension and +with the contributory earnings-replacement benefits. The Social Security +(Overlapping Benefits) Regulations 1979 reg 12 reduce it by the other benefit, +"so however that" the allowance and the other benefit together are never less +than the unadjusted allowance. Additional pension and graduated retirement +benefit paid with an old State Pension are left out (regs 4(2)(f) and 12). The +Carer Support Payment Regulations (SSI 2023/302) reg 16(2)-(3) reduce Carer +Support Payment in the same way, to £0 where the overlapping benefit is at +least as much. + +Invariants, for carers of any age in England, Wales and Scotland in 2026, who +qualify by hours or by a reported award, with State Pension supplied directly, +by component or as a reported amount, and with other overlapping benefits: + +1. Bounds: 0 <= payable <= unadjusted, for both benefits, and a person never + has both. +2. Floor (reg 12): payable + overlapping benefits >= unadjusted. +3. Differential: payable equals max(0, unadjusted - overlapping), with both + terms rebuilt here from the drawn inputs and the statutory rates, not read + from the model's own intermediate variables. +4. Monotonic: raising State Pension never raises the payable carer benefit, + and never lowers the carer benefit and State Pension taken together. +5. Entitlement survives: raising State Pension leaves the carer's entitlement, + carer status and Pension Credit carer addition unchanged (SPC Regs Sch I + para 4 turns on entitlement, not payment). +6. The Scottish Carer Supplement is paid exactly when Carer Support Payment is + payable (CSP Regs reg 14A), at its full rate. +7. Take-up: where a carer's overlapping State Pension is at least the carer + benefit, claiming it changes nothing, so household net income is the same + whether or not they would claim. +8. Pension Credit counts the payable amount (SPC Regs reg 15(3)-(4)(a), (g)): + for a pensioner below the personal allowance, Pension Credit income is + State Pension plus private pension plus the payable carer benefit. + +The reduction compares annual amounts. The law works week by week, so for an +overlapping benefit paid for part of a year the annual comparison is a lower +bound on the carer benefit payable; the draws here hold each benefit constant. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEAR = 2026 +WEEKS = 52 +TOLERANCE = 0.01 +PROPERTY_SETTINGS = settings( + max_examples=12, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +COUNTRIES = ["ENGLAND", "WALES", "SCOTLAND"] +STATE_PENSION_MODES = ["none", "direct", "components", "reported"] +OTHER_OVERLAPPING = [ + "esa_contrib_reported", + "jsa_contrib_reported", + "incapacity_benefit_reported", + "maternity_allowance_reported", +] + + +def money(high): + return st.floats(0, high, allow_nan=False, allow_infinity=False).map( + lambda x: round(x, 2) + ) + + +@st.composite +def carers(draw, min_size=1, max_size=6): + """A list of single-adult households, each with one potential carer.""" + n = draw(st.integers(min_size, max_size)) + people = [] + for _ in range(n): + mode = draw(st.sampled_from(STATE_PENSION_MODES)) + person = { + "age": draw(st.integers(25, 95)), + "country": draw(st.sampled_from(COUNTRIES)), + "care_hours": draw(st.sampled_from([0, 20, 35, 60])), + "carers_allowance_reported": draw(st.sampled_from([0, 1, 4_000])), + "would_claim_carers_allowance": draw(st.booleans()), + "mode": mode, + # Amounts straddle the carer benefit (about £4,495 a year) and the + # full basic and new State Pension rates. + "pension": draw(money(16_000)), + "additional": draw(money(6_000)), + "extra": draw(money(8_000)), + "other": { + variable: draw(st.sampled_from([0, 0, 0, 1_500, 4_495.4, 9_000])) + for variable in OTHER_OVERLAPPING + }, + } + people.append(person) + return people + + +def state_pension_inputs(person, extra=0.0): + """Inputs for the person's State Pension, with ``extra`` added to it.""" + mode, pension = person["mode"], person["pension"] + extra + if mode == "direct": + return {"state_pension": pension} + if mode == "reported": + return {"state_pension_reported": pension} + if mode == "components": + # The type decides which flat-rate component the person can hold; + # set both and let the additional pension follow the type. + return { + "basic_state_pension": pension if person["age"] >= 77 else 0, + "new_state_pension": 0 if person["age"] >= 77 else pension, + "additional_state_pension": person["additional"], + } + return {} + + +def build(people, extra=None, **overrides): + """One simulation holding every drawn person in their own household.""" + extra = extra or [0.0] * len(people) + situation = {"people": {}, "benunits": {}, "households": {}} + for i, person in enumerate(people): + inputs = { + "age": person["age"], + "care_hours": person["care_hours"], + "carers_allowance_reported": person["carers_allowance_reported"], + "would_claim_carers_allowance": person["would_claim_carers_allowance"], + **person["other"], + **state_pension_inputs(person, extra[i]), + **person.get("person_inputs", {}), + } + inputs.update(overrides) + household = {"country": person["country"], **person.get("household_inputs", {})} + situation["people"][f"p{i}"] = {k: {YEAR: v} for k, v in inputs.items()} + situation["benunits"][f"b{i}"] = {"members": [f"p{i}"]} + situation["households"][f"h{i}"] = { + "members": [f"p{i}"], + **{k: {YEAR: v} for k, v in household.items()}, + } + return Simulation(situation=situation) + + +def calc(sim, variable): + return np.asarray(sim.calculate(variable, YEAR), dtype=float) + + +def rates(sim): + parameters = sim.tax_benefit_system.parameters(f"{YEAR}-06-01") + return { + "ca": float(parameters.gov.dwp.carers_allowance.rate) * WEEKS, + "csp": float(parameters.gov.social_security_scotland.carer_support_payment.rate) + * WEEKS, + "supplement": float( + parameters.gov.social_security_scotland.carer_support_payment.supplement + ) + * WEEKS, + "basic": float(parameters.gov.dwp.state_pension.basic_state_pension.amount) + * WEEKS, + "min_hours": float(parameters.gov.dwp.carers_allowance.min_hours), + } + + +def reference(people, sim, extra=None): + """Unadjusted and overlapping amounts rebuilt from the drawn inputs.""" + extra = extra or [0.0] * len(people) + r = rates(sim) + pension_type = sim.calculate("state_pension_type", YEAR) + if hasattr(pension_type, "decode_to_str"): + pension_type = pension_type.decode_to_str() + pension_type = np.asarray(pension_type).astype(str) + unadjusted_ca, unadjusted_csp, overlapping = [], [], [] + for i, person in enumerate(people): + qualifies = ( + person["care_hours"] >= r["min_hours"] + or person["carers_allowance_reported"] > 0 + ) and person["would_claim_carers_allowance"] + scottish = person["country"] == "SCOTLAND" + unadjusted_ca.append(r["ca"] * (qualifies and not scottish)) + unadjusted_csp.append(r["csp"] * (qualifies and scottish)) + old_system = pension_type[i] == "BASIC" + pension = person["pension"] + extra[i] + mode = person["mode"] + if mode == "direct": + state_pension = pension + elif mode == "reported": + # An old State Pension above the full basic rate is additional + # pension, which does not overlap. A new State Pension overlaps + # in full, protected payment included. + state_pension = min(pension, r["basic"]) if old_system else pension + if pension_type[i] == "NONE": + state_pension = 0 + elif mode == "components": + state_pension = pension + (0 if old_system else person["additional"]) + else: + state_pension = 0 + # sda is not drawn, so the other overlapping benefits are the four + # reported contributory benefits. + overlapping.append(state_pension + sum(person["other"].values())) + return ( + np.array(unadjusted_ca), + np.array(unadjusted_csp), + np.array(overlapping), + ) + + +@PROPERTY_SETTINGS +@given(people=carers()) +def test_overlap_bounds_floor_and_reference(people): + sim = build(people) + ca, csp = calc(sim, "carers_allowance"), calc(sim, "carer_support_payment") + ca_pre = calc(sim, "carers_allowance_pre_overlap") + csp_pre = calc(sim, "carer_support_payment_pre_overlap") + ref_ca_pre, ref_csp_pre, ref_overlap = reference(people, sim) + + # 1. Bounds, and no one has both benefits. + assert (ca >= 0).all() and (csp >= 0).all() + assert (ca <= ca_pre + TOLERANCE).all() + assert (csp <= csp_pre + TOLERANCE).all() + assert not ((ca > 0) & (csp > 0)).any() + assert not ((ca_pre > 0) & (csp_pre > 0)).any() + + # 2. Reg 12 floor, against the model's own overlapping amounts. + ca_overlap = calc(sim, "carers_allowance_overlapping_benefits") + csp_overlap = calc(sim, "carer_support_payment_overlapping_benefits") + assert (ca + ca_overlap >= ca_pre - TOLERANCE).all() + assert (csp + csp_overlap >= csp_pre - TOLERANCE).all() + + # 3. Differential against amounts rebuilt from the inputs. + np.testing.assert_allclose(ca_pre, ref_ca_pre, atol=TOLERANCE) + np.testing.assert_allclose(csp_pre, ref_csp_pre, atol=TOLERANCE) + np.testing.assert_allclose( + ca, np.maximum(ref_ca_pre - ref_overlap, 0), atol=TOLERANCE + ) + np.testing.assert_allclose( + csp, np.maximum(ref_csp_pre - ref_overlap, 0), atol=TOLERANCE + ) + + # 6. Scottish Carer Supplement only, and in full, with a payable CSP. + supplement = calc(sim, "scottish_carer_supplement") + np.testing.assert_allclose( + supplement, (csp > 0) * rates(sim)["supplement"], atol=TOLERANCE + ) + + +@PROPERTY_SETTINGS +@given(people=carers()) +def test_carer_benefit_is_monotonic_in_state_pension(people): + drawn = [p for p in people if p["mode"] != "none"] + if not drawn: + return + extra = [p["extra"] for p in drawn] + low, high = build(drawn), build(drawn, extra=extra) + + for benefit in ["carers_allowance", "carer_support_payment"]: + before, after = calc(low, benefit), calc(high, benefit) + # 4. More State Pension never raises the carer benefit... + assert (after <= before + TOLERANCE).all() + # ...and never lowers the two together. + together_before = before + calc(low, "state_pension") + together_after = after + calc(high, "state_pension") + assert (together_after >= together_before - TOLERANCE).all() + + # 5. Entitlement and carer status do not depend on State Pension. + for variable in [ + "carers_allowance_pre_overlap", + "carer_support_payment_pre_overlap", + "is_entitled_to_carer_benefit", + "is_carer_for_benefits", + "carer_minimum_guarantee_addition", + ]: + np.testing.assert_allclose(calc(low, variable), calc(high, variable)) + + +@st.composite +def overlapped_pensioners(draw): + """Pension-age carers whose State Pension is at least the carer benefit.""" + n = draw(st.integers(1, 4)) + return [ + { + "age": draw(st.integers(67, 90)), + "country": draw(st.sampled_from(COUNTRIES)), + "care_hours": draw(st.sampled_from([35, 60])), + "carers_allowance_reported": 0, + "would_claim_carers_allowance": True, + "mode": "direct", + # At least the 2026-27 carer benefit of £86.45 a week. + "pension": draw( + st.floats(4_495.4, 16_000, allow_nan=False).map(lambda x: round(x, 2)) + ), + "additional": 0, + "extra": 0, + "other": {variable: 0 for variable in OTHER_OVERLAPPING}, + "person_inputs": {"private_pension_income": draw(money(15_000))}, + "household_inputs": { + "rent": draw(st.sampled_from([0, 5_000])), + "tenure_type": "RENT_FROM_COUNCIL", + }, + } + for _ in range(n) + ] + + +@settings( + max_examples=8, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(people=overlapped_pensioners()) +def test_claiming_changes_nothing_when_state_pension_covers_the_benefit(people): + claims = build(people, would_claim_carers_allowance=True) + does_not = build(people, would_claim_carers_allowance=False) + # 7. The benefit is not payable either way, so nothing else moves. + assert (calc(claims, "carers_allowance") == 0).all() + assert (calc(claims, "carer_support_payment") == 0).all() + assert (calc(claims, "is_entitled_to_carer_benefit") == 1).all() + assert (calc(does_not, "is_entitled_to_carer_benefit") == 0).all() + for variable in [ + "household_net_income", + "pension_credit", + "housing_benefit", + "income_tax", + "carer_minimum_guarantee_addition", + ]: + np.testing.assert_allclose( + calc(claims, variable), calc(does_not, variable), atol=TOLERANCE + ) + + +@st.composite +def low_income_pensioners(draw): + """Pension-age carers whose income stays below the personal allowance.""" + n = draw(st.integers(1, 6)) + return [ + { + "age": draw(st.integers(67, 90)), + "country": draw(st.sampled_from(COUNTRIES)), + "care_hours": 35, + "carers_allowance_reported": 0, + "would_claim_carers_allowance": True, + "mode": "direct", + "pension": draw(money(7_000)), + "additional": 0, + "extra": 0, + "other": {variable: 0 for variable in OTHER_OVERLAPPING}, + "person_inputs": {"private_pension_income": draw(money(1_000))}, + } + for _ in range(n) + ] + + +@PROPERTY_SETTINGS +@given(people=low_income_pensioners()) +def test_pension_credit_counts_the_payable_carer_benefit(people): + sim = build(people) + private = np.array([p["person_inputs"]["private_pension_income"] for p in people]) + state_pension = np.array([p["pension"] for p in people]) + payable = calc(sim, "carers_allowance") + calc(sim, "carer_support_payment") + unadjusted = rates(sim)["ca"] + + # 8. Income is at most £7,000 + £1,000 + £4,495.40, below the £12,570 + # personal allowance, so Pension Credit income has no tax deducted. + assert (calc(sim, "income_tax") == 0).all() + np.testing.assert_allclose( + calc(sim, "pension_credit_income"), + state_pension + private + payable, + atol=TOLERANCE, + ) + # The carer's State Pension and carer benefit together are the larger of + # the two, never their sum. + np.testing.assert_allclose( + state_pension + payable, + np.maximum(state_pension, unadjusted), + atol=TOLERANCE, + ) diff --git a/policyengine_uk/variables/gov/dwp/carers_allowance.py b/policyengine_uk/variables/gov/dwp/carers_allowance.py index 1a65e817b5..392026fe7a 100644 --- a/policyengine_uk/variables/gov/dwp/carers_allowance.py +++ b/policyengine_uk/variables/gov/dwp/carers_allowance.py @@ -5,20 +5,22 @@ class carers_allowance(Variable): value_type = float entity = Person label = "Carer's Allowance" + documentation = ( + "Carer's Allowance payable after the overlapping-benefit adjustment: " + "the allowance is reduced by any other personal benefit that overlaps " + "with it, such as State Pension, and only the balance is paid. The " + "comparison is made on annual amounts, which equals the weekly rule " + "when the overlapping benefit is paid at a constant rate all year." + ) definition_period = YEAR unit = GBP + reference = ( + "https://www.legislation.gov.uk/ukpga/1992/4/section/70", + "https://www.legislation.gov.uk/uksi/1979/597/regulation/4", + "https://www.legislation.gov.uk/uksi/1979/597/regulation/12", + ) def formula(person, period, parameters): - in_scotland = person.household("country", period).decode_to_str() == "SCOTLAND" - csp_replaces_ca = period.start.year >= 2025 - receives_ca = person("carers_allowance_reported", period) > 0 - ca = parameters(period).gov.dwp.carers_allowance - weekly_care_hours = person("care_hours", period) - meets_work_condition = weekly_care_hours >= ca.min_hours - would_claim = person("would_claim_carers_allowance", period) - eligible = ( - ~(in_scotland & csp_replaces_ca) - & (meets_work_condition | receives_ca) - & would_claim - ) - return eligible * ca.rate * WEEKS_IN_YEAR + pre_overlap = person("carers_allowance_pre_overlap", period) + overlapping = person("carers_allowance_overlapping_benefits", period) + return max_(pre_overlap - overlapping, 0) diff --git a/policyengine_uk/variables/gov/dwp/carers_allowance_overlapping_benefits.py b/policyengine_uk/variables/gov/dwp/carers_allowance_overlapping_benefits.py new file mode 100644 index 0000000000..649e0f4a4a --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/carers_allowance_overlapping_benefits.py @@ -0,0 +1,18 @@ +from policyengine_uk.model_api import * + + +class carers_allowance_overlapping_benefits(Variable): + value_type = float + entity = Person + label = "Personal benefits that overlap with Carer's Allowance" + documentation = ( + "Other personal benefits by which regulation 12 of the Social Security " + "(Overlapping Benefits) Regulations 1979 reduces Carer's Allowance." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/1979/597/regulation/4", + "https://www.legislation.gov.uk/uksi/1979/597/regulation/12", + ) + adds = "gov.dwp.carers_allowance.overlapping_benefits" diff --git a/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py b/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py new file mode 100644 index 0000000000..fc34465dfe --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py @@ -0,0 +1,33 @@ +from policyengine_uk.model_api import * + + +class carers_allowance_pre_overlap(Variable): + value_type = float + entity = Person + label = "Carer's Allowance before the overlapping-benefit adjustment" + documentation = ( + "The Carer's Allowance a person is entitled to before the Social " + "Security (Overlapping Benefits) Regulations 1979 reduce it by another " + "personal benefit. A person whose allowance is reduced to nil keeps " + "this underlying entitlement, which is what carer premiums, the " + "Pension Credit carer addition and the benefit cap exemption look at. " + "carers_allowance is the amount payable." + ) + definition_period = YEAR + unit = GBP + reference = "https://www.legislation.gov.uk/ukpga/1992/4/section/70" + + def formula(person, period, parameters): + in_scotland = person.household("country", period).decode_to_str() == "SCOTLAND" + csp_replaces_ca = period.start.year >= 2025 + receives_ca = person("carers_allowance_reported", period) > 0 + ca = parameters(period).gov.dwp.carers_allowance + weekly_care_hours = person("care_hours", period) + meets_work_condition = weekly_care_hours >= ca.min_hours + would_claim = person("would_claim_carers_allowance", period) + eligible = ( + ~(in_scotland & csp_replaces_ca) + & (meets_work_condition | receives_ca) + & would_claim + ) + return eligible * ca.rate * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py index c27e13c39d..b7bd17ba85 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py @@ -35,7 +35,11 @@ def formula(benunit, period, parameters): # Disability and carer benefits that exempt from cap QUAL_PERSONAL_BENEFITS = [ "attendance_allowance", - "carers_allowance", + # Entitlement, not payment: HB Regs 2006 reg 75F(1)(h)-(ha) and UC + # Regs 2013 reg 83(1)(i)-(ia) exempt a person "entitled to" Carer's + # Allowance or Carer Support Payment, even if an overlapping benefit + # reduces it to nil. + "is_entitled_to_carer_benefit", "dla", # Disability Living Allowance (includes components) "pip_dl", # PIP daily living component "pip_m", # PIP mobility component diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py index 69d9205c9a..ee5bbbba9b 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py @@ -35,8 +35,11 @@ def formula(benunit, period, parameters): # Disability and carer benefits that exempt from cap QUAL_PERSONAL_BENEFITS = [ "attendance_allowance", - "carers_allowance", - "carer_support_payment", + # Entitlement, not payment: HB Regs 2006 reg 75F(1)(h)-(ha) and UC + # Regs 2013 reg 83(1)(i)-(ia) exempt a person "entitled to" Carer's + # Allowance or Carer Support Payment, even if an overlapping benefit + # reduces it to nil. + "is_entitled_to_carer_benefit", "dla", # Disability Living Allowance (includes components) "pip_dl", # PIP daily living component "pip_m", # PIP mobility component diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py index 0fb1547506..857df4e9e6 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py @@ -35,7 +35,11 @@ def formula(benunit, period, parameters): # Disability and carer benefits that exempt from cap QUAL_PERSONAL_BENEFITS = [ "attendance_allowance", - "carers_allowance", + # Entitlement, not payment: HB Regs 2006 reg 75F(1)(h)-(ha) and UC + # Regs 2013 reg 83(1)(i)-(ia) exempt a person "entitled to" Carer's + # Allowance or Carer Support Payment, even if an overlapping benefit + # reduces it to nil. + "is_entitled_to_carer_benefit", "dla", # Disability Living Allowance (includes components) "pip_dl", # PIP daily living component "pip_m", # PIP mobility component diff --git a/policyengine_uk/variables/gov/dwp/overlapping_state_pension.py b/policyengine_uk/variables/gov/dwp/overlapping_state_pension.py new file mode 100644 index 0000000000..8fad12e854 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/overlapping_state_pension.py @@ -0,0 +1,41 @@ +from policyengine_uk.model_api import * + + +class overlapping_state_pension(Variable): + value_type = float + entity = Person + label = "State Pension that overlaps with carer benefits" + documentation = ( + "The State Pension by which Carer's Allowance and Carer Support Payment " + "are reduced. A new State Pension under Part 1 of the Pensions Act 2014 " + "overlaps in full, including any protected payment above the full rate. " + "An old State Pension overlaps without its additional pension. A State " + "Pension supplied directly, with no components, overlaps in full." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/1979/597/regulation/4", + "https://www.legislation.gov.uk/uksi/1979/597/regulation/12", + "https://www.legislation.gov.uk/ssi/2023/302/regulation/16", + ) + + def formula(person, period, parameters): + state_pension = person("state_pension", period) + additional = person("additional_state_pension", period) + components = add( + person, + period, + ["basic_state_pension", "new_state_pension", "additional_state_pension"], + ) + pension_type = person("state_pension_type", period) + old_system = pension_type == pension_type.possible_values.BASIC + # state_pension can scale its components (a contributed reform does), + # so take the additional pension as a share of them. + additional_share = np.divide( + additional, + components, + out=np.zeros_like(components, dtype=float), + where=components > 0, + ) + return state_pension * (1 - old_system * additional_share) diff --git a/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment.py b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment.py index 71bb385aac..fdd0efde06 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment.py +++ b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment.py @@ -7,27 +7,24 @@ class carer_support_payment(Variable): label = "Carer Support Payment" documentation = ( "The Carer Support Payment component of Carer Support, which replaces " - "Carer's Allowance for eligible carers living in Scotland. The " - "Scottish Carer Supplement paid with it from 15 March 2026 is a " - "separate component: see scottish_carer_supplement." + "Carer's Allowance for eligible carers living in Scotland, after the " + "overlapping-benefit reduction: it is reduced by any overlapping " + "benefit, such as State Pension, and is £0 where that benefit is at " + "least as much. The Scottish Carer Supplement paid with it from 15 " + "March 2026 is a separate component: see scottish_carer_supplement. " + "The comparison is made on annual amounts, which equals the weekly " + "rule when the overlapping benefit is paid at a constant rate all year." ) definition_period = YEAR unit = GBP - reference = [ + reference = ( "https://www.mygov.scot/carer-support-payment", "https://www.legislation.gov.uk/asp/2018/9/part/4", "https://www.legislation.gov.uk/ssi/2023/302/regulation/3", - ] + "https://www.legislation.gov.uk/ssi/2023/302/regulation/16", + ) def formula(person, period, parameters): - in_scotland = person.household("country", period).decode_to_str() == "SCOTLAND" - csp_in_effect = period.start.year >= 2025 - csp = parameters(period).gov.social_security_scotland.carer_support_payment - weekly_care_hours = person("care_hours", period) - meets_hours = weekly_care_hours >= csp.min_hours - receives_ca = person("carers_allowance_reported", period) > 0 - would_claim = person("would_claim_carers_allowance", period) - eligible = ( - in_scotland & csp_in_effect & (meets_hours | receives_ca) & would_claim - ) - return eligible * csp.rate * WEEKS_IN_YEAR + pre_overlap = person("carer_support_payment_pre_overlap", period) + overlapping = person("carer_support_payment_overlapping_benefits", period) + return max_(pre_overlap - overlapping, 0) diff --git a/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_overlapping_benefits.py b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_overlapping_benefits.py new file mode 100644 index 0000000000..32220a1928 --- /dev/null +++ b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_overlapping_benefits.py @@ -0,0 +1,16 @@ +from policyengine_uk.model_api import * + + +class carer_support_payment_overlapping_benefits(Variable): + value_type = float + entity = Person + label = "Benefits that overlap with Carer Support Payment" + documentation = ( + "Overlapping benefits by which regulation 16(2) of the Carer's " + "Assistance (Carer Support Payment) (Scotland) Regulations 2023 " + "reduces Carer Support Payment." + ) + definition_period = YEAR + unit = GBP + reference = "https://www.legislation.gov.uk/ssi/2023/302/regulation/16" + adds = "gov.social_security_scotland.carer_support_payment.overlapping_benefits" diff --git a/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_pre_overlap.py b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_pre_overlap.py new file mode 100644 index 0000000000..8826fc1a5b --- /dev/null +++ b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_pre_overlap.py @@ -0,0 +1,34 @@ +from policyengine_uk.model_api import * + + +class carer_support_payment_pre_overlap(Variable): + value_type = float + entity = Person + label = "Carer Support Payment before the overlapping-benefit reduction" + documentation = ( + "The Carer Support Payment component a carer in Scotland is entitled " + "to before regulation 16(2) of the Carer Support Payment Regulations " + "reduces it by an overlapping benefit. A carer whose payment is " + "reduced to £0 keeps this underlying entitlement. " + "carer_support_payment is the amount given." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/asp/2018/9/part/4", + "https://www.legislation.gov.uk/ssi/2023/302/regulation/3", + "https://www.legislation.gov.uk/ssi/2023/302/regulation/16", + ) + + def formula(person, period, parameters): + in_scotland = person.household("country", period).decode_to_str() == "SCOTLAND" + csp_in_effect = period.start.year >= 2025 + csp = parameters(period).gov.social_security_scotland.carer_support_payment + weekly_care_hours = person("care_hours", period) + meets_hours = weekly_care_hours >= csp.min_hours + receives_ca = person("carers_allowance_reported", period) > 0 + would_claim = person("would_claim_carers_allowance", period) + eligible = ( + in_scotland & csp_in_effect & (meets_hours | receives_ca) & would_claim + ) + return eligible * csp.rate * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/social_security_scotland/scottish_carer_supplement.py b/policyengine_uk/variables/gov/social_security_scotland/scottish_carer_supplement.py index d5a83e8a4f..de294f633b 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/scottish_carer_supplement.py +++ b/policyengine_uk/variables/gov/social_security_scotland/scottish_carer_supplement.py @@ -7,8 +7,12 @@ class scottish_carer_supplement(Variable): label = "Scottish Carer Supplement" documentation = ( "Paid for each week in which Carer Support Payment is payable, from 15 " - "March 2026. It is a component of Carer Support separate from the " - "Carer Support Payment component. Pension Credit and Housing Benefit, " + "March 2026. Where an overlapping benefit such as State Pension " + "reduces Carer Support Payment to £0, nothing is payable and no " + "supplement is paid; where it reduces the payment but leaves some, " + "the supplement is paid in full. It is a component of Carer Support " + "separate from the Carer Support Payment component. Pension Credit " + "and Housing Benefit, " "at pension age and working age, do not count it as income; it is " "taxable." ) @@ -16,6 +20,7 @@ class scottish_carer_supplement(Variable): unit = GBP reference = [ "https://www.legislation.gov.uk/ssi/2023/302/regulation/14A", + "https://www.legislation.gov.uk/ssi/2023/302/regulation/16", "https://www.legislation.gov.uk/uksi/2026/246/article/17/made", "https://www.legislation.gov.uk/uksi/2026/246/article/20/made", "https://www.legislation.gov.uk/uksi/2026/246/article/21/made", diff --git a/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py b/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py index 3e45dad5b2..d1e8816804 100644 --- a/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py +++ b/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py @@ -8,11 +8,13 @@ class is_carer_for_benefits(Variable): definition_period = YEAR documentation = ( - "Receives a carer benefit, or provides at least the Carer's Allowance " - "qualifying hours of care a week: the underlying entitlement that the " - "Universal Credit carer element, the legacy carer premiums, the Pension " - "Credit carer addition and the non-dependant deduction exemption " - "recognise without a Carer's Allowance award. The hours limb tests " + "Receives or is entitled to a carer benefit, or provides at least the " + "Carer's Allowance qualifying hours of care a week: the underlying " + "entitlement that the Universal Credit carer element, the legacy carer " + "premiums, the Pension Credit carer addition and the non-dependant " + "deduction exemption recognise without a Carer's Allowance award. A " + "carer whose allowance is reduced to nil by an overlapping benefit, " + "such as State Pension, stays entitled. The hours limb tests " "hours alone, as carers_allowance does: it does not check that the " "person cared for receives a qualifying disability benefit (the second " "limb of regulation 30), so a survey hours field counts caring for " @@ -23,4 +25,8 @@ class is_carer_for_benefits(Variable): def formula(person, period, parameters): min_hours = parameters(period).gov.dwp.carers_allowance.min_hours meets_hours = person("care_hours", period) >= min_hours - return person("receives_carer_benefit", period) | meets_hours + return ( + person("receives_carer_benefit", period) + | person("is_entitled_to_carer_benefit", period) + | meets_hours + ) diff --git a/policyengine_uk/variables/input/care.py b/policyengine_uk/variables/input/care.py index 51ebb4c854..9ee3fb3c61 100644 --- a/policyengine_uk/variables/input/care.py +++ b/policyengine_uk/variables/input/care.py @@ -39,3 +39,30 @@ def formula(person, period, parameters): return person("receives_carers_allowance", period) | person( "receives_carer_support_payment", period ) + + +class is_entitled_to_carer_benefit(Variable): + value_type = bool + entity = Person + label = "entitled to a carer benefit" + documentation = ( + "Whether this person is entitled to Carer's Allowance or the Scottish " + "Carer Support Payment, whether or not it is paid. An overlapping " + "benefit such as State Pension can reduce the payment to nil and leave " + "this underlying entitlement in place." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/ukpga/1992/4/section/70", + "https://www.legislation.gov.uk/uksi/1979/597/regulation/12", + "https://www.legislation.gov.uk/ssi/2023/302/regulation/16", + ) + + def formula(person, period, parameters): + entitlements = add( + person, + period, + ["carers_allowance_pre_overlap", "carer_support_payment_pre_overlap"], + ) + # A carer benefit supplied directly as an input is also an entitlement. + return (entitlements > 0) | person("receives_carer_benefit", period) From 3ea77f13e4cea1c099ab9a11b409aa622a728039 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 1 Oct 2026 10:03:08 -0400 Subject: [PATCH 2/5] Note the overlapping-benefit reduction in the Carer's Allowance program entry Co-Authored-By: Claude Opus 5.5 --- policyengine_uk/programs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/policyengine_uk/programs.yaml b/policyengine_uk/programs.yaml index f83e4acdd9..3027c7ba56 100644 --- a/policyengine_uk/programs.yaml +++ b/policyengine_uk/programs.yaml @@ -285,6 +285,7 @@ programs: variable: carers_allowance parameter_prefix: gov.dwp.carers_allowance verified_start_year: 2022 + notes: Reduced by overlapping benefits such as State Pension and contributory ESA - id: dla name: DLA From 607a815ced29160cc83fcd89160543a2b3a4f745 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 17:44:25 -0400 Subject: [PATCH 3/5] Fix entitlement and receipt handling in carer overlap rules Require claimant or partner entitlement for carer additions and premiums, and payment for the UC non-dependant exemption. Preserve the UC hours route, repair overlap properties and policy fixtures, and clarify annual and reported-pension assumptions. Co-Authored-By: Claude Opus 5.5 --- .../carers-overlapping-benefits.fixed.md | 2 +- .../uc_non_dep_deduction_exempt.yaml | 10 +- ...carers_allowance_overlapping_benefits.yaml | 142 +++++++++- .../claimant_partner_income_and_carers.yaml | 8 +- ...carer_and_severe_disability_additions.yaml | 7 +- ..._support_payment_overlapping_benefits.yaml | 48 +++- ...t_carer_overlapping_benefits_properties.py | 243 +++++++++++++----- ..._pension_credit_carer_income_properties.py | 44 +++- .../gov/dwp/carers_allowance_pre_overlap.py | 6 +- .../gov/dwp/is_benefit_cap_exempt_earnings.py | 6 +- .../gov/dwp/is_benefit_cap_exempt_other.py | 6 +- .../gov/dwp/overlapping_state_pension.py | 9 +- .../carer_minimum_guarantee_addition.py | 10 +- .../uc_non_dep_deduction_exempt.py | 7 +- .../meets_uc_minimum_age_condition.py | 5 +- .../household/demographic/carer_premium.py | 14 +- .../demographic/is_carer_for_benefits.py | 16 +- 17 files changed, 449 insertions(+), 134 deletions(-) diff --git a/changelog.d/carers-overlapping-benefits.fixed.md b/changelog.d/carers-overlapping-benefits.fixed.md index 390bc9e6e3..7f1a85bd55 100644 --- a/changelog.d/carers-overlapping-benefits.fixed.md +++ b/changelog.d/carers-overlapping-benefits.fixed.md @@ -1 +1 @@ -Reduce Carer's Allowance and Carer Support Payment by overlapping benefits such as State Pension and contributory ESA (Social Security (Overlapping Benefits) Regulations 1979 reg 12; Carer Support Payment Regulations 2023 reg 16(2)), while carer premiums, the Pension Credit carer addition, the benefit cap exemption and the childcare work condition keep the underlying entitlement. +- Reduced Carer's Allowance and Carer Support Payment by overlapping benefits, required claimant or partner entitlement for Pension Credit carer additions and carer premiums, and required payment for the Universal Credit non-dependant carer exemption. diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.yaml index b3b46c66a1..13d4369453 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.yaml @@ -24,9 +24,9 @@ - name: Exempt - carers period: 2025 - absolute_error_margin: 1 + absolute_error_margin: 0 input: - pension_credit: 1 + pension_credit: 0 dla_sc_middle_plus: false pip_dl: 0 attendance_allowance: 0 @@ -34,7 +34,7 @@ output: uc_non_dep_deduction_exempt: true -- name: Exempt - carer by hours with underlying entitlement, no award +- name: Caring hours without a claim do not give the carer receipt exemption period: 2025 absolute_error_margin: 0 input: @@ -48,4 +48,6 @@ would_claim_carers_allowance: false output: receives_carers_allowance: false - uc_non_dep_deduction_exempt: true + is_entitled_to_carer_benefit: false + is_carer_for_benefits: true + uc_non_dep_deduction_exempt: false diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml index 1c3b9408a9..3ba85424d0 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml @@ -9,7 +9,11 @@ # overlaps in full, including any protected payment above the full rate. # 2026-27 weekly rates: Carer's Allowance 86.45 (SI 2026/148), full basic # State Pension 184.90, full new State Pension 241.30, Pension Credit -# standard minimum guarantee 238.00 and carer addition 48.15. +# standard minimum guarantee 238.00, carer addition 48.15 and severe +# disability addition 86.05 (172.10 doubled, SPC Regs 2002 reg 6(5)). +# The regulations work week by week. Cases using reported annual benefits +# verify the model's annual-netting approximation; part-year spells are not +# resolved. This agrees with the weekly rule for constant year-round amounts. - name: Case 1, no overlapping benefit leaves Carer's Allowance in full. period: 2026 @@ -106,7 +110,7 @@ overlapping_state_pension: 7_000 carers_allowance: 0 -- name: Case 7, a reported old State Pension above the full basic rate overlaps up to that rate. +- name: Case 7, the model splits a reported old State Pension at the full basic rate and only the basic part overlaps. period: 2026 absolute_error_margin: 0.01 input: @@ -145,7 +149,7 @@ overlapping_state_pension: 3_000 carers_allowance: 86.45 * 52 - 3_000 -- name: Case 10, contributory ESA reduces the allowance. +- name: Case 10, annual netting of contributory ESA reduces the allowance. period: 2026 absolute_error_margin: 0.01 input: @@ -154,10 +158,11 @@ care_hours: 35 country: ENGLAND output: + # Contributory ESA of 3,000 paid evenly over the year. carers_allowance_overlapping_benefits: 3_000 carers_allowance: 86.45 * 52 - 3_000 -- name: Case 11, contribution-based JSA and Maternity Allowance together reduce the allowance. +- name: Case 11, annual netting sums contribution-based JSA and Maternity Allowance. period: 2026 absolute_error_margin: 0.01 input: @@ -167,6 +172,8 @@ care_hours: 35 country: ENGLAND output: + # This checks annual netting, not payment for individual award weeks. + # The annual inputs contain no start/end dates for either benefit. carers_allowance_overlapping_benefits: 2_000 carers_allowance: 86.45 * 52 - 2_000 @@ -178,6 +185,10 @@ incapacity_benefit_reported: 5_000 care_hours: 35 country: ENGLAND + # No Universal Credit carer element, as for a Housing Benefit claimant. + # The model otherwise gives every benefit unit with a carer that + # element, which exempts it from the cap by itself. + uc_carer_element: 0 output: carers_allowance: 0 is_entitled_to_carer_benefit: true @@ -194,7 +205,8 @@ care_hours: 35 country: ENGLAND output: - # gov.dwp.sda.maximum is 119.35 a week in 2026-27. + # The model pays a reported SDA at its parameter gov.dwp.sda.maximum, + # 119.35 a week in 2026-27. sda: 119.35 * 52 carers_allowance: 0 @@ -224,8 +236,11 @@ carers_allowance_pre_overlap: 0 carers_allowance: 0 is_entitled_to_carer_benefit: false - # Caring 35 hours a week still makes the person a carer for benefits. + # UC Regs 2013 reg 30(2) does not require a Carer's Allowance claim. + # SPC Sch I para 4(2) and HB Sch 3 para 9 require entitlement. is_carer_for_benefits: true + carer_minimum_guarantee_addition: 0 + carer_premium: 0 - name: Case 16, Carer's Allowance supplied as an input is treated as an entitlement. period: 2026 @@ -235,6 +250,7 @@ carers_allowance: 1_000 care_hours: 0 country: ENGLAND + uc_carer_element: 0 output: carers_allowance_pre_overlap: 0 receives_carers_allowance: true @@ -287,8 +303,8 @@ minimum_guarantee: (238 + 48.15) * 52 guarantee_credit: (238 + 48.15) * 52 - 6_000 -- name: Case 19, an allowance reduced to nil does not withhold the severe disability addition. - period: 2021 +- name: Case 19, an allowance reduced to nil does not withhold the double severe disability addition. + period: 2026 absolute_error_margin: 0.01 input: people: @@ -297,6 +313,7 @@ attendance_allowance: 100 partner: age: 70 + attendance_allowance: 100 state_pension: 7_000 care_hours: 35 benunits: @@ -307,15 +324,45 @@ members: [claimant, partner] country: ENGLAND output: - # SPC Regs 2002 Sch I para 1(1): the addition is withheld only where a - # person is "entitled to and in receipt of" Carer's Allowance for caring. carers_allowance: [0, 0] receives_carer_benefit: [false, false] is_entitled_to_carer_benefit: [false, true] - is_carer_for_benefits: [false, true] - severe_disability_minimum_guarantee_addition: 67.3 * 52 + # SPC Regs 2002 Sch I para 1(1)(b): both partners receive Attendance + # Allowance. Reg 6(5)(b) doubles the addition where no one is "entitled + # to and in receipt of" Carer's Allowance for caring for either partner: + # the partner is entitled, but nothing is paid. + severe_disability_minimum_guarantee_addition: 172.10 * 52 + +- name: Case 20, an allowance still in payment after the overlap withholds the double amount. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + attendance_allowance: 100 + partner: + age: 70 + attendance_allowance: 100 + state_pension: 2_000 + care_hours: 35 + benunits: + benunit: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + country: ENGLAND + output: + # Numeric list entries: the YAML runner evaluates arithmetic only for + # scalar outputs. The partial payment is 86.45 * 52 - 2_000. + carers_allowance: [0, 2_495.40] + receives_carer_benefit: [false, true] + # Reg 6(5)(a): a carer's allowance paid for caring for one partner + # leaves the single amount. + severe_disability_minimum_guarantee_addition: 86.05 * 52 -- name: Case 20, a parent entitled to an allowance reduced to nil still meets the childcare work condition. +- name: Case 21, a parent entitled to an allowance reduced to nil still meets the childcare work condition. period: 2025 input: people: @@ -328,6 +375,7 @@ in_work: false is_parent: true care_hours: 35 + # Annual-netting approximation; the input has no payment dates. maternity_allowance_reported: 5_000 child: age: 4 @@ -336,6 +384,9 @@ benunit: members: [parent1, parent2, child] family_type: COUPLE_WITH_CHILDREN + # No Universal Credit carer element, which would meet reg 11A(1)(c) + # by itself, so the entitlement is what counts. + uc_carer_element: 0 households: household: members: [parent1, parent2, child] @@ -345,3 +396,68 @@ # Childcare (Free of Charge for Working Parents) (England) Regulations # 2022 regs 14-15 and 11A(1)(a): "entitled to a specified benefit". extended_childcare_entitlement_work_condition: [1, 1, 0] + +- name: Case 22, a nil allowance gives a carer premium but no UC non-dependant carer exemption. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + care_hours: 35 + incapacity_benefit_reported: 5_000 + country: ENGLAND + output: + carers_allowance: 0 + is_entitled_to_carer_benefit: true + receives_carer_benefit: false + # HB (SPC) Sch 3 para 9 and working-age HB Sch 3 para 17 use entitlement. + carer_premium: 48.15 * 52 + # UC Sch 4 para 16(2)(g) requires receipt. + uc_non_dep_deduction_exempt: false + +- name: Case 23, a partially paid allowance gives the UC non-dependant carer exemption. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + care_hours: 35 + incapacity_benefit_reported: 1_000 + country: ENGLAND + output: + carers_allowance: 86.45 * 52 - 1_000 + receives_carer_benefit: true + uc_non_dep_deduction_exempt: true + +- name: Case 24, caring hours without a claim keep UC carer status but give no premium or receipt exemption. + period: 2026 + input: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + country: ENGLAND + output: + carers_allowance: 0 + is_entitled_to_carer_benefit: false + is_carer_for_benefits: true + benunit_has_carer: true + carer_premium: 0 + uc_non_dep_deduction_exempt: false + +- name: Case 25, a dependent young person's entitlement gives neither the claimant's premium nor Pension Credit carer addition. + period: 2026 + input: + people: + claimant: + age: 75 + is_parent: true + is_entitled_to_carer_benefit: false + young_person: + age: 18 + is_in_non_advanced_education: true + is_entitled_to_carer_benefit: true + benunits: + benunit: + members: [claimant, young_person] + output: + is_claimant_or_partner: [true, false] + carer_premium: 0 + carer_minimum_guarantee_addition: 0 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/claimant_partner_income_and_carers.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/claimant_partner_income_and_carers.yaml index dbf4bb5dbf..462cdd1fca 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/claimant_partner_income_and_carers.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/claimant_partner_income_and_carers.yaml @@ -142,7 +142,7 @@ income_tax: 100 national_insurance: 0 pension_contributions: 200 - is_carer_for_benefits: true + is_entitled_to_carer_benefit: true partner: age: 75 private_pension_income: 2_000 @@ -151,7 +151,7 @@ income_tax: 50 national_insurance: 25 pension_contributions: 100 - is_carer_for_benefits: true + is_entitled_to_carer_benefit: true benunits: benunit: members: [claimant, partner] @@ -174,11 +174,11 @@ claimant: age: 75 is_parent: true - is_carer_for_benefits: false + is_entitled_to_carer_benefit: false young_person: age: 18 is_in_non_advanced_education: true - is_carer_for_benefits: true + is_entitled_to_carer_benefit: true benunits: benunit: members: [claimant, young_person] diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/carer_and_severe_disability_additions.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/carer_and_severe_disability_additions.yaml index 4c8758bcbb..6a174b6d6c 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/carer_and_severe_disability_additions.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/carer_and_severe_disability_additions.yaml @@ -1,4 +1,4 @@ -- name: Carer addition for a claimant with underlying entitlement and no Carer's Allowance award +- name: No carer addition without a claim even when the caring hours condition is met period: 2025 absolute_error_margin: 0.01 input: @@ -18,7 +18,8 @@ output: carers_allowance: 0 is_carer_for_benefits: true - carer_minimum_guarantee_addition: 46.37 * 52 + is_entitled_to_carer_benefit: false + carer_minimum_guarantee_addition: 0 - name: Carer addition for a claimant receiving Carer's Allowance period: 2025 @@ -59,7 +60,7 @@ output: carer_minimum_guarantee_addition: 0 -- name: Severe disability addition is withheld only by a carer benefit in payment, not by underlying entitlement +- name: Severe disability addition is not withheld by a non-claiming carer period: 2021 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_overlapping_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_overlapping_benefits.yaml index 3fbe44c4d9..dae6cb22bc 100644 --- a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_overlapping_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_overlapping_benefits.yaml @@ -11,6 +11,9 @@ # Supplement 11.70 (SSI 2026/170 reg 12(3)); 2025-26 Carer Support Payment # 83.30. Pension Credit standard minimum guarantee 238.00, carer addition # 48.15. +# Reg 16(2) works award week by award week. Cases using reported annual +# benefits verify the model's annual-netting approximation; part-year spells +# are not resolved. Constant year-round amounts agree with the weekly rule. - name: Case 1, State Pension below Carer Support Payment reduces it and the supplement is paid in full. period: 2026 @@ -86,7 +89,7 @@ output: carer_support_payment: 83.30 * 52 - 2_000 -- name: Case 5, contributory ESA reduces Carer Support Payment. +- name: Case 5, annual netting of contributory ESA reduces Carer Support Payment. period: 2026 absolute_error_margin: 0.01 input: @@ -125,3 +128,46 @@ overlapping_state_pension: 7_000 carer_support_payment: 0 scottish_carer_supplement: 0 + +- name: Case 8, nil CSP keeps the premium but gives no UC non-dependant carer exemption. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + care_hours: 35 + incapacity_benefit_reported: 5_000 + country: SCOTLAND + output: + carer_support_payment: 0 + is_entitled_to_carer_benefit: true + carer_premium: 48.15 * 52 + uc_non_dep_deduction_exempt: false + +- name: Case 9, partial CSP gives the UC non-dependant carer exemption. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + care_hours: 35 + incapacity_benefit_reported: 1_000 + country: SCOTLAND + output: + carer_support_payment: 86.45 * 52 - 1_000 + receives_carer_benefit: true + uc_non_dep_deduction_exempt: true + +- name: Case 10, a Scottish carer without a claim has no addition premium or receipt exemption. + period: 2026 + input: + age: 40 + care_hours: 35 + would_claim_carers_allowance: false + country: SCOTLAND + output: + carer_support_payment: 0 + is_entitled_to_carer_benefit: false + is_carer_for_benefits: true + benunit_has_carer: true + carer_minimum_guarantee_addition: 0 + carer_premium: 0 + uc_non_dep_deduction_exempt: false diff --git a/policyengine_uk/tests/test_carer_overlapping_benefits_properties.py b/policyengine_uk/tests/test_carer_overlapping_benefits_properties.py index 32397dd38d..3c17abe12a 100644 --- a/policyengine_uk/tests/test_carer_overlapping_benefits_properties.py +++ b/policyengine_uk/tests/test_carer_overlapping_benefits_properties.py @@ -10,37 +10,52 @@ Support Payment in the same way, to £0 where the overlapping benefit is at least as much. -Invariants, for carers of any age in England, Wales and Scotland in 2026, who -qualify by hours or by a reported award, with State Pension supplied directly, -by component or as a reported amount, and with other overlapping benefits: +Invariants, for carers in England, Wales and Scotland in 2026, who qualify by +hours or by a reported award, with State Pension supplied directly, by +component or as a reported amount, and with other overlapping benefits: 1. Bounds: 0 <= payable <= unadjusted, for both benefits, and a person never has both. -2. Floor (reg 12): payable + overlapping benefits >= unadjusted. -3. Differential: payable equals max(0, unadjusted - overlapping), with both - terms rebuilt here from the drawn inputs and the statutory rates, not read - from the model's own intermediate variables. -4. Monotonic: raising State Pension never raises the payable carer benefit, +2. Differential: payable equals max(0, unadjusted - overlapping), with both + terms rebuilt here from the drawn inputs, the statutory rates and the State + Pension type worked out below from age and gender. The reported-pension + split follows the model's convention, as described below. +3. Monotonic: raising State Pension never raises the payable carer benefit, and never lowers the carer benefit and State Pension taken together. -5. Entitlement survives: raising State Pension leaves the carer's entitlement, +4. Entitlement survives: raising State Pension leaves the carer's entitlement, carer status and Pension Credit carer addition unchanged (SPC Regs Sch I - para 4 turns on entitlement, not payment). -6. The Scottish Carer Supplement is paid exactly when Carer Support Payment is + para 4(2) turns on entitlement, not payment). +5. The Scottish Carer Supplement is paid exactly when Carer Support Payment is payable (CSP Regs reg 14A), at its full rate. -7. Take-up: where a carer's overlapping State Pension is at least the carer - benefit, claiming it changes nothing, so household net income is the same - whether or not they would claim. -8. Pension Credit counts the payable amount (SPC Regs reg 15(3)-(4)(a), (g)): +6. Claiming a benefit the overlap reduces to nil creates entitlement: where a + pension-age carer's State Pension is at least the carer benefit, the carer + benefit, Pension Credit income and income tax are the same whether or not + they would claim. Claiming unlocks the Pension Credit carer addition + (SPC Regs Sch I para 4(2)). Below the minimum guarantee, Guarantee Credit + and Pension Credit increase by exactly that addition. +7. Pension Credit counts the payable amount (SPC Regs reg 15(3)-(4)(a), (g)): for a pensioner below the personal allowance, Pension Credit income is State Pension plus private pension plus the payable carer benefit. -The reduction compares annual amounts. The law works week by week, so for an -overlapping benefit paid for part of a year the annual comparison is a lower -bound on the carer benefit payable; the draws here hold each benefit constant. +The test takes these conventions from the model, not from the law: +- State Pension age in 2026 is 66 for both genders. The model's annual + approximation places the old/new cutoff at 75 for men and 74 for women: + the 2016 pension ages (65 and 64) plus the ten elapsed years. The reference + computes the type from these thresholds without reading the model's + calculated state_pension_type. +- A State Pension supplied directly, with no components, overlaps in full. +- A reported old State Pension is split at the full basic rate, and the + excess is treated as additional pension, which does not overlap. Basic + pension increments above that rate, or additional pension within a partial + pension below it, would be split differently in law. +- The reduction compares annual amounts. With the same underlying year-round + carer entitlement, annual netting is a lower bound on the weekly payable + total. These assertions check annual netting and do not infer the dates or + duration of overlapping awards. """ import numpy as np -from hypothesis import HealthCheck, given, settings +from hypothesis import HealthCheck, example, given, settings from hypothesis import strategies as st from policyengine_uk import Simulation @@ -62,6 +77,26 @@ "incapacity_benefit_reported", "maternity_allowance_reported", ] +# From state_pension/age/{male,female}.yaml and the 2016 activation in +# state_pension/new_state_pension/active.yaml; annual model conventions. +STATE_PENSION_AGE = 66 +OLD_STATE_PENSION_AGE = {"MALE": 65 + YEAR - 2016, "FEMALE": 64 + YEAR - 2016} + + +class CarerOverlapSimulation(Simulation): + """Label the synthetic dataset with the same year as its pension inputs.""" + + default_input_period = YEAR + default_calculation_period = YEAR + + +def pension_type_for(person): + age = person["age"] + if age < STATE_PENSION_AGE: + return "NONE" + if age >= OLD_STATE_PENSION_AGE[person.get("gender", "MALE")]: + return "BASIC" + return "NEW" def money(high): @@ -74,23 +109,27 @@ def money(high): def carers(draw, min_size=1, max_size=6): """A list of single-adult households, each with one potential carer.""" n = draw(st.integers(min_size, max_size)) + mode = draw(st.sampled_from(STATE_PENSION_MODES)) people = [] for _ in range(n): - mode = draw(st.sampled_from(STATE_PENSION_MODES)) person = { "age": draw(st.integers(25, 95)), + "gender": draw(st.sampled_from(["MALE", "FEMALE"])), "country": draw(st.sampled_from(COUNTRIES)), "care_hours": draw(st.sampled_from([0, 20, 35, 60])), "carers_allowance_reported": draw(st.sampled_from([0, 1, 4_000])), "would_claim_carers_allowance": draw(st.booleans()), "mode": mode, # Amounts straddle the carer benefit (about £4,495 a year) and the - # full basic and new State Pension rates. - "pension": draw(money(16_000)), + # full basic and new State Pension rates; half the draws fall + # below the carer benefit, where the overlap leaves some payable. + "pension": draw(st.one_of(money(4_400), money(16_000))), "additional": draw(money(6_000)), "extra": draw(money(8_000)), + # Other overlapping benefits are rare, so they do not mask the + # State Pension overlap in most draws. "other": { - variable: draw(st.sampled_from([0, 0, 0, 1_500, 4_495.4, 9_000])) + variable: draw(st.sampled_from([0] * 9 + [1_500, 4_495.4, 9_000])) for variable in OTHER_OVERLAPPING }, } @@ -101,28 +140,41 @@ def carers(draw, min_size=1, max_size=6): def state_pension_inputs(person, extra=0.0): """Inputs for the person's State Pension, with ``extra`` added to it.""" mode, pension = person["mode"], person["pension"] + extra + pension_type = pension_type_for(person) if mode == "direct": return {"state_pension": pension} if mode == "reported": return {"state_pension_reported": pension} if mode == "components": - # The type decides which flat-rate component the person can hold; - # set both and let the additional pension follow the type. + # The flat-rate component follows the type. With an old State + # Pension the additional amount is additional pension; with a new + # one it is a protected payment. No one gets either before reaching + # pensionable age. return { - "basic_state_pension": pension if person["age"] >= 77 else 0, - "new_state_pension": 0 if person["age"] >= 77 else pension, - "additional_state_pension": person["additional"], + "basic_state_pension": pension if pension_type == "BASIC" else 0, + "new_state_pension": pension if pension_type == "NEW" else 0, + "additional_state_pension": ( + person["additional"] if pension_type != "NONE" else 0 + ), } return {} def build(people, extra=None, **overrides): - """One simulation holding every drawn person in their own household.""" + """One simulation holding every drawn person in their own household. + + A supplied input overrides its variable's formula for the whole + population; unspecified people receive that variable's default value. + Use one State Pension input mode per batch so a direct pension or an + explicit component cannot replace another person's calculated pension. + """ + assert len({person["mode"] for person in people}) == 1 extra = extra or [0.0] * len(people) situation = {"people": {}, "benunits": {}, "households": {}} for i, person in enumerate(people): inputs = { "age": person["age"], + "gender": person.get("gender", "MALE"), "care_hours": person["care_hours"], "carers_allowance_reported": person["carers_allowance_reported"], "would_claim_carers_allowance": person["would_claim_carers_allowance"], @@ -138,7 +190,7 @@ def build(people, extra=None, **overrides): "members": [f"p{i}"], **{k: {YEAR: v} for k, v in household.items()}, } - return Simulation(situation=situation) + return CarerOverlapSimulation(situation=situation) def calc(sim, variable): @@ -158,6 +210,10 @@ def rates(sim): "basic": float(parameters.gov.dwp.state_pension.basic_state_pension.amount) * WEEKS, "min_hours": float(parameters.gov.dwp.carers_allowance.min_hours), + "carer_addition": float( + parameters.gov.dwp.pension_credit.guarantee_credit.carer.addition + ) + * WEEKS, } @@ -165,10 +221,6 @@ def reference(people, sim, extra=None): """Unadjusted and overlapping amounts rebuilt from the drawn inputs.""" extra = extra or [0.0] * len(people) r = rates(sim) - pension_type = sim.calculate("state_pension_type", YEAR) - if hasattr(pension_type, "decode_to_str"): - pension_type = pension_type.decode_to_str() - pension_type = np.asarray(pension_type).astype(str) unadjusted_ca, unadjusted_csp, overlapping = [], [], [] for i, person in enumerate(people): qualifies = ( @@ -178,20 +230,28 @@ def reference(people, sim, extra=None): scottish = person["country"] == "SCOTLAND" unadjusted_ca.append(r["ca"] * (qualifies and not scottish)) unadjusted_csp.append(r["csp"] * (qualifies and scottish)) - old_system = pension_type[i] == "BASIC" + pension_type = pension_type_for(person) pension = person["pension"] + extra[i] mode = person["mode"] if mode == "direct": + # The model's convention: with no components, all of it overlaps. state_pension = pension elif mode == "reported": - # An old State Pension above the full basic rate is additional - # pension, which does not overlap. A new State Pension overlaps - # in full, protected payment included. - state_pension = min(pension, r["basic"]) if old_system else pension - if pension_type[i] == "NONE": - state_pension = 0 + # The model's split of a reported amount: an old State Pension + # above the full basic rate is additional pension, which does not + # overlap. A new State Pension overlaps in full, protected payment + # included, and there is none before pensionable age. + state_pension = { + "BASIC": min(pension, r["basic"]), + "NEW": pension, + "NONE": 0, + }[pension_type] elif mode == "components": - state_pension = pension + (0 if old_system else person["additional"]) + state_pension = { + "BASIC": pension, + "NEW": pension + person["additional"], + "NONE": 0, + }[pension_type] else: state_pension = 0 # sda is not drawn, so the other overlapping benefits are the four @@ -204,15 +264,57 @@ def reference(people, sim, extra=None): ) +def pinned(age, country, mode, pension, additional=0.0, gender="MALE"): + """A qualifying carer with no other overlapping benefit.""" + return { + "age": age, + "gender": gender, + "country": country, + "care_hours": 35, + "carers_allowance_reported": 0, + "would_claim_carers_allowance": True, + "mode": mode, + "pension": pension, + "additional": additional, + "extra": 0.0, + "other": {variable: 0 for variable in OTHER_OVERLAPPING}, + } + + @PROPERTY_SETTINGS @given(people=carers()) -def test_overlap_bounds_floor_and_reference(people): +# An old State Pension below the carer benefit, with additional pension that +# must not overlap, and a new State Pension whose protected payment must. +@example( + people=[ + pinned(80, "ENGLAND", "components", 2_000, 5_000), + pinned(80, "SCOTLAND", "components", 2_000, 5_000, "FEMALE"), + pinned(68, "ENGLAND", "components", 2_000, 1_000, "FEMALE"), + pinned(68, "SCOTLAND", "components", 2_000, 1_000), + # The same age belongs to different systems for men and women in + # the model's annual pension-age approximation. + pinned(74, "ENGLAND", "components", 2_000, 5_000), + pinned(74, "SCOTLAND", "components", 2_000, 5_000, "FEMALE"), + pinned(66, "WALES", "components", 2_000, 1_000), + ] +) +@example(people=[pinned(80, "WALES", "reported", 3_000)]) +@example(people=[pinned(70, "ENGLAND", "direct", 4_495.4)]) +def test_overlap_bounds_and_reference(people): sim = build(people) ca, csp = calc(sim, "carers_allowance"), calc(sim, "carer_support_payment") ca_pre = calc(sim, "carers_allowance_pre_overlap") csp_pre = calc(sim, "carer_support_payment_pre_overlap") ref_ca_pre, ref_csp_pre, ref_overlap = reference(people, sim) + # Components are assigned using age and gender, independently of the + # model's calculated type. Check the model's annual thresholds as well. + pension_type = sim.calculate("state_pension_type", YEAR) + if hasattr(pension_type, "decode_to_str"): + pension_type = pension_type.decode_to_str() + pension_type = np.asarray(pension_type).astype(str) + assert list(pension_type) == [pension_type_for(p) for p in people] + # 1. Bounds, and no one has both benefits. assert (ca >= 0).all() and (csp >= 0).all() assert (ca <= ca_pre + TOLERANCE).all() @@ -220,15 +322,14 @@ def test_overlap_bounds_floor_and_reference(people): assert not ((ca > 0) & (csp > 0)).any() assert not ((ca_pre > 0) & (csp_pre > 0)).any() - # 2. Reg 12 floor, against the model's own overlapping amounts. ca_overlap = calc(sim, "carers_allowance_overlapping_benefits") csp_overlap = calc(sim, "carer_support_payment_overlapping_benefits") - assert (ca + ca_overlap >= ca_pre - TOLERANCE).all() - assert (csp + csp_overlap >= csp_pre - TOLERANCE).all() - # 3. Differential against amounts rebuilt from the inputs. + # 2. Differential against amounts rebuilt from the inputs. np.testing.assert_allclose(ca_pre, ref_ca_pre, atol=TOLERANCE) np.testing.assert_allclose(csp_pre, ref_csp_pre, atol=TOLERANCE) + np.testing.assert_allclose(ca_overlap, ref_overlap, atol=TOLERANCE) + np.testing.assert_allclose(csp_overlap, ref_overlap, atol=TOLERANCE) np.testing.assert_allclose( ca, np.maximum(ref_ca_pre - ref_overlap, 0), atol=TOLERANCE ) @@ -236,7 +337,7 @@ def test_overlap_bounds_floor_and_reference(people): csp, np.maximum(ref_csp_pre - ref_overlap, 0), atol=TOLERANCE ) - # 6. Scottish Carer Supplement only, and in full, with a payable CSP. + # 5. Scottish Carer Supplement only, and in full, with a payable CSP. supplement = calc(sim, "scottish_carer_supplement") np.testing.assert_allclose( supplement, (csp > 0) * rates(sim)["supplement"], atol=TOLERANCE @@ -254,14 +355,14 @@ def test_carer_benefit_is_monotonic_in_state_pension(people): for benefit in ["carers_allowance", "carer_support_payment"]: before, after = calc(low, benefit), calc(high, benefit) - # 4. More State Pension never raises the carer benefit... + # 3. More State Pension never raises the carer benefit... assert (after <= before + TOLERANCE).all() # ...and never lowers the two together. together_before = before + calc(low, "state_pension") together_after = after + calc(high, "state_pension") assert (together_after >= together_before - TOLERANCE).all() - # 5. Entitlement and carer status do not depend on State Pension. + # 4. Entitlement and carer status do not depend on State Pension. for variable in [ "carers_allowance_pre_overlap", "carer_support_payment_pre_overlap", @@ -274,7 +375,7 @@ def test_carer_benefit_is_monotonic_in_state_pension(people): @st.composite def overlapped_pensioners(draw): - """Pension-age carers whose State Pension is at least the carer benefit.""" + """Fully overlapped carers with income below the minimum guarantee.""" n = draw(st.integers(1, 4)) return [ { @@ -286,12 +387,12 @@ def overlapped_pensioners(draw): "mode": "direct", # At least the 2026-27 carer benefit of £86.45 a week. "pension": draw( - st.floats(4_495.4, 16_000, allow_nan=False).map(lambda x: round(x, 2)) + st.floats(4_495.4, 10_000, allow_nan=False).map(lambda x: round(x, 2)) ), "additional": 0, "extra": 0, "other": {variable: 0 for variable in OTHER_OVERLAPPING}, - "person_inputs": {"private_pension_income": draw(money(15_000))}, + "person_inputs": {"private_pension_income": draw(money(1_000))}, "household_inputs": { "rent": draw(st.sampled_from([0, 5_000])), "tenure_type": "RENT_FROM_COUNCIL", @@ -308,24 +409,36 @@ def overlapped_pensioners(draw): suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], ) @given(people=overlapped_pensioners()) -def test_claiming_changes_nothing_when_state_pension_covers_the_benefit(people): +def test_claiming_a_carer_benefit_reduced_to_nil_increases_pension_credit(people): claims = build(people, would_claim_carers_allowance=True) does_not = build(people, would_claim_carers_allowance=False) - # 7. The benefit is not payable either way, so nothing else moves. - assert (calc(claims, "carers_allowance") == 0).all() - assert (calc(claims, "carer_support_payment") == 0).all() + # 6. The benefit is not payable either way; claiming establishes the + # entitlement needed for the carer addition. + for sim in [claims, does_not]: + assert (calc(sim, "carers_allowance") == 0).all() + assert (calc(sim, "carer_support_payment") == 0).all() + assert (calc(sim, "scottish_carer_supplement") == 0).all() assert (calc(claims, "is_entitled_to_carer_benefit") == 1).all() assert (calc(does_not, "is_entitled_to_carer_benefit") == 0).all() - for variable in [ - "household_net_income", - "pension_credit", - "housing_benefit", - "income_tax", - "carer_minimum_guarantee_addition", - ]: + for variable in ["pension_credit_income", "income_tax"]: np.testing.assert_allclose( calc(claims, variable), calc(does_not, variable), atol=TOLERANCE ) + addition = rates(claims)["carer_addition"] + np.testing.assert_allclose( + calc(claims, "carer_minimum_guarantee_addition"), addition, atol=TOLERANCE + ) + assert (calc(does_not, "carer_minimum_guarantee_addition") == 0).all() + np.testing.assert_allclose( + calc(claims, "minimum_guarantee") - calc(does_not, "minimum_guarantee"), + addition, + atol=TOLERANCE, + ) + assert (calc(does_not, "guarantee_credit") > 0).all() + for variable in ["guarantee_credit", "pension_credit"]: + np.testing.assert_allclose( + calc(claims, variable) - calc(does_not, variable), addition, atol=TOLERANCE + ) @st.composite @@ -359,7 +472,7 @@ def test_pension_credit_counts_the_payable_carer_benefit(people): payable = calc(sim, "carers_allowance") + calc(sim, "carer_support_payment") unadjusted = rates(sim)["ca"] - # 8. Income is at most £7,000 + £1,000 + £4,495.40, below the £12,570 + # 7. Income is at most £7,000 + £1,000 + £4,495.40, below the £12,570 # personal allowance, so Pension Credit income has no tax deducted. assert (calc(sim, "income_tax") == 0).all() np.testing.assert_allclose( diff --git a/policyengine_uk/tests/test_pension_credit_carer_income_properties.py b/policyengine_uk/tests/test_pension_credit_carer_income_properties.py index 6c5b82f21c..b23e3456e6 100644 --- a/policyengine_uk/tests/test_pension_credit_carer_income_properties.py +++ b/policyengine_uk/tests/test_pension_credit_carer_income_properties.py @@ -34,21 +34,23 @@ income tax: each plus income tax is unchanged. These compare PolicyEngine's own income measures, so they hold whatever -carer's benefit the model pays. The model does not yet apply the -overlapping-benefit reduction of Carer's Allowance and Carer Support Payment -by State Pension (SPC Regs reg 15(4)(a) and (g)). +carer's benefit the model pays. State Pension overlaps with Carer's Allowance +and Carer Support Payment and reduces them (Social Security (Overlapping +Benefits) Regulations 1979 reg 12; Carer Support Payment Regulations 2023 reg +16(2)), to nil where it is at least as much. So the carer's own State Pension +is drawn below the carer benefit, which leaves some of it paid; the other +partner's State Pension is drawn up to £15,000. Only one member of a couple is drawn as a carer. Two carers in a couple get two Pension Credit carer additions (SPC Regs reg 6(8)) but, until the couple carer premium is set to twice the single rate, one Housing Benefit carer premium, so invariant 2 does not yet hold for them. Disability benefits are -not drawn: with a carer benefit in payment in the benefit unit, Pension Credit -withholds its severe disability addition, but the Housing Benefit severe -disability premium keys on a different disability test. +not drawn: Pension Credit and Housing Benefit use different severe-disability +tests and retain different approximations for carers and household composition. """ import numpy as np -from hypothesis import HealthCheck, given, settings +from hypothesis import HealthCheck, example, given, settings from hypothesis import strategies as st from policyengine_uk import Simulation @@ -66,6 +68,22 @@ "WALES": "WALES", "SCOTLAND": "SCOTLAND", } +# Below the 2026-27 Carer's Allowance and Carer Support Payment of £86.45 a +# week (£4,495.40 a year), so the overlapping-benefit reduction leaves some of +# the carer's benefit payable. +CARER_STATE_PENSION_MAX = 4_400 +# Keep a near-complete overlap in every run: a reported carer award still +# leaves £95.40 of CSP paid, so the income and supplement properties apply. +PARTIALLY_OVERLAPPED_SCOTTISH_CARER = { + "adults": [{"age": 80, "state_pension": 15_000}], + "carer": 0, + "carer_state_pension": CARER_STATE_PENSION_MAX, + "by_hours": False, + "country": "SCOTLAND", + "rent": 6_000, + "council_tax": 1_500, + "savings": 0, +} NO_SCOTTISH_CARER_SUPPLEMENT = { "gov.social_security_scotland.carer_support_payment.supplement": { "2026-01-01.2100-12-31": 0 @@ -91,6 +109,7 @@ def family(draw, countries=tuple(REGIONS)): return dict( adults=adults, carer=draw(st.integers(0, len(adults) - 1)), + carer_state_pension=draw(money(CARER_STATE_PENSION_MAX)), # The carer qualifies either by caring hours or by a reported award. by_hours=draw(st.booleans()), country=draw(st.sampled_from(countries)), @@ -108,13 +127,17 @@ def situation(families): names = [] for j, a in enumerate(fam["adults"]): name = f"p{i}_{k}_{j}" + is_carer = j == fam["carer"] + state_pension = ( + fam["carer_state_pension"] if is_carer else a["state_pension"] + ) person = { "age": {YEAR: a["age"]}, - "state_pension": {YEAR: a["state_pension"]}, + "state_pension": {YEAR: state_pension}, # All private pension goes to the first adult. "private_pension_income": {YEAR: float(pension) * (j == 0)}, } - if j == fam["carer"]: + if is_carer: if fam["by_hours"]: person["care_hours"] = {YEAR: 35} else: @@ -176,6 +199,7 @@ def assert_carer_benefit_paid(g, i, fam): @PROPERTY_SETTINGS @given(st.lists(family(), min_size=1, max_size=4)) +@example([PARTIALLY_OVERLAPPED_SCOTTISH_CARER]) def test_pension_credit_and_housing_benefit_assess_the_same_carer_income(families): g = grid(families) for i, fam in enumerate(families): @@ -199,6 +223,7 @@ def test_pension_credit_and_housing_benefit_assess_the_same_carer_income(familie @PROPERTY_SETTINGS @given(st.lists(family(), min_size=1, max_size=4)) +@example([PARTIALLY_OVERLAPPED_SCOTTISH_CARER]) def test_net_income_does_not_fall_where_guarantee_credit_ends_for_carers(families): g = grid(families) net = g["household_net_income"] + g["tv_licence"] @@ -221,6 +246,7 @@ def test_net_income_does_not_fall_where_guarantee_credit_ends_for_carers(familie @PROPERTY_SETTINGS @given(st.lists(family(countries=("SCOTLAND",)), min_size=1, max_size=4)) +@example([PARTIALLY_OVERLAPPED_SCOTTISH_CARER]) def test_scottish_carer_supplement_is_not_means_tested_income(families): g = grid(families) without = grid(families, reform=NO_SCOTTISH_CARER_SUPPLEMENT) diff --git a/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py b/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py index fc34465dfe..ca06fafbb2 100644 --- a/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py +++ b/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py @@ -9,9 +9,9 @@ class carers_allowance_pre_overlap(Variable): "The Carer's Allowance a person is entitled to before the Social " "Security (Overlapping Benefits) Regulations 1979 reduce it by another " "personal benefit. A person whose allowance is reduced to nil keeps " - "this underlying entitlement, which is what carer premiums, the " - "Pension Credit carer addition and the benefit cap exemption look at. " - "carers_allowance is the amount payable." + "this underlying entitlement. is_entitled_to_carer_benefit combines " + "the pre-overlap amounts and any benefit in payment for rules that " + "test entitlement. carers_allowance is the amount payable." ) definition_period = YEAR unit = GBP diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py index b7bd17ba85..c27e13c39d 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py @@ -35,11 +35,7 @@ def formula(benunit, period, parameters): # Disability and carer benefits that exempt from cap QUAL_PERSONAL_BENEFITS = [ "attendance_allowance", - # Entitlement, not payment: HB Regs 2006 reg 75F(1)(h)-(ha) and UC - # Regs 2013 reg 83(1)(i)-(ia) exempt a person "entitled to" Carer's - # Allowance or Carer Support Payment, even if an overlapping benefit - # reduces it to nil. - "is_entitled_to_carer_benefit", + "carers_allowance", "dla", # Disability Living Allowance (includes components) "pip_dl", # PIP daily living component "pip_m", # PIP mobility component diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py index 857df4e9e6..0fb1547506 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py @@ -35,11 +35,7 @@ def formula(benunit, period, parameters): # Disability and carer benefits that exempt from cap QUAL_PERSONAL_BENEFITS = [ "attendance_allowance", - # Entitlement, not payment: HB Regs 2006 reg 75F(1)(h)-(ha) and UC - # Regs 2013 reg 83(1)(i)-(ia) exempt a person "entitled to" Carer's - # Allowance or Carer Support Payment, even if an overlapping benefit - # reduces it to nil. - "is_entitled_to_carer_benefit", + "carers_allowance", "dla", # Disability Living Allowance (includes components) "pip_dl", # PIP daily living component "pip_m", # PIP mobility component diff --git a/policyengine_uk/variables/gov/dwp/overlapping_state_pension.py b/policyengine_uk/variables/gov/dwp/overlapping_state_pension.py index 8fad12e854..f48729090d 100644 --- a/policyengine_uk/variables/gov/dwp/overlapping_state_pension.py +++ b/policyengine_uk/variables/gov/dwp/overlapping_state_pension.py @@ -9,8 +9,13 @@ class overlapping_state_pension(Variable): "The State Pension by which Carer's Allowance and Carer Support Payment " "are reduced. A new State Pension under Part 1 of the Pensions Act 2014 " "overlaps in full, including any protected payment above the full rate. " - "An old State Pension overlaps without its additional pension. A State " - "Pension supplied directly, with no components, overlaps in full." + "An old State Pension overlaps without its additional pension. When " + "only a reported total is available, the model estimates its basic " + "component up to the full basic rate and treats the excess as " + "additional pension. This split cannot identify basic-pension " + "increments above that rate or additional pension within a partial " + "pension below it. A State Pension supplied directly, with no " + "components, overlaps in full as an input convention." ) definition_period = YEAR unit = GBP diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/carer_minimum_guarantee_addition.py b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/carer_minimum_guarantee_addition.py index b7c4b625d5..d988d368a3 100644 --- a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/carer_minimum_guarantee_addition.py +++ b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/carer_minimum_guarantee_addition.py @@ -10,14 +10,14 @@ class carer_minimum_guarantee_addition(Variable): reference = "https://www.legislation.gov.uk/uksi/2002/1792/schedule/I/paragraph/4" documentation = ( - "Paid for each claimant or partner entitled to Carer's Allowance, which includes " - "underlying entitlement where the allowance is not paid (for example " - "under the overlapping-benefit rule), so the test is is_carer_for_benefits " - "rather than receipt." + "Paid for each claimant or partner entitled to Carer's Allowance or " + "Carer Support Payment, including an entitlement reduced to nil by " + "overlapping benefits. Caring hours alone, without entitlement, do " + "not qualify." ) def formula(benunit, period, parameters): - is_carer = benunit.members("is_carer_for_benefits", period) + is_carer = benunit.members("is_entitled_to_carer_benefit", period) is_claimant_or_partner = benunit.members("is_claimant_or_partner", period) num_carers = benunit.sum(is_carer & is_claimant_or_partner) gc = parameters(period).gov.dwp.pension_credit.guarantee_credit diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.py b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.py index 432cfb0936..ea5db9b881 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.py @@ -10,8 +10,9 @@ class uc_non_dep_deduction_exempt(Variable): "No housing costs contribution is deducted for a non-dependant who is " "receiving Pension Credit, the middle or higher rate of the DLA care " "component, the PIP daily living component or Attendance Allowance, or " - "who is entitled to Carer's Allowance (underlying entitlement included, " - "so the carer test is is_carer_for_benefits rather than receipt)." + "who receives Carer's Allowance or Carer Support Payment. Underlying " + "entitlement reduced to nil by overlapping benefits does not qualify " + "for the carer exemption." ) reference = "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/16" @@ -21,5 +22,5 @@ def formula(person, period, parameters): | person("dla_sc_middle_plus", period) | (person("pip_dl", period) > 0) | (person("attendance_allowance", period) > 0) - | person("is_carer_for_benefits", period) + | person("receives_carer_benefit", period) ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/meets_uc_minimum_age_condition.py b/policyengine_uk/variables/gov/dwp/universal_credit/meets_uc_minimum_age_condition.py index cd1ad10c7f..01670b64a7 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/meets_uc_minimum_age_condition.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/meets_uc_minimum_age_condition.py @@ -19,8 +19,9 @@ class meets_uc_minimum_age_condition(Variable): to is_disabled_for_benefits), not an observation of LCW or a pending assessment supported by medical evidence under regulation 8(1)(a)-(b). is_carer_for_benefits means receipt of Carer's Allowance or Scottish Carer - Support Payment, or at least the Carer's Allowance qualifying hours of - care (currently 35 weekly). Its hours limb does not verify the recipient's + Support Payment, underlying entitlement to either benefit, or at least + the Carer's Allowance qualifying hours of care (currently 35 weekly). + Its hours limb does not verify the recipient's qualifying disability benefit, and it does not exclude paid care. For regulation 8(1)(d), responsibility is proxied by being a claimant in a diff --git a/policyengine_uk/variables/household/demographic/carer_premium.py b/policyengine_uk/variables/household/demographic/carer_premium.py index b1f93386eb..0b995804f4 100644 --- a/policyengine_uk/variables/household/demographic/carer_premium.py +++ b/policyengine_uk/variables/household/demographic/carer_premium.py @@ -6,11 +6,21 @@ class carer_premium(Variable): entity = BenUnit label = "Carer premium" definition_period = YEAR - reference = "The Social Security Amendment (Carer Premium) Regulations 2002" + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/schedule/3/paragraph/17", + "https://www.legislation.gov.uk/uksi/2006/214/schedule/3/paragraph/9", + ) + documentation = ( + "Carer premium for claimants and partners entitled to Carer's " + "Allowance or Carer Support Payment, including entitlement reduced " + "to nil by overlapping benefits. Caring hours alone do not qualify." + ) unit = GBP def formula(benunit, period, parameters): - carers = benunit("num_carers", period.this_year) + entitled = benunit.members("is_entitled_to_carer_benefit", period) + claimant_or_partner = benunit.members("is_claimant_or_partner", period) + carers = benunit.sum(entitled & claimant_or_partner) CP = parameters(period).gov.dwp.carer_premium weekly_premium = select( [carers >= 2, carers == 1], diff --git a/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py b/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py index d1e8816804..4c5e80aa56 100644 --- a/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py +++ b/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py @@ -9,15 +9,17 @@ class is_carer_for_benefits(Variable): documentation = ( "Receives or is entitled to a carer benefit, or provides at least the " - "Carer's Allowance qualifying hours of care a week: the underlying " - "entitlement that the Universal Credit carer element, the legacy carer " - "premiums, the Pension Credit carer addition and the non-dependant " - "deduction exemption recognise without a Carer's Allowance award. A " + "Carer's Allowance qualifying hours of care a week. The Universal " + "Credit carer element does not require a Carer's Allowance claim. " + "Entitlement-based additions and premiums use " + "is_entitled_to_carer_benefit; receipt-based rules use " + "receives_carer_benefit. A " "carer whose allowance is reduced to nil by an overlapping benefit, " "such as State Pension, stays entitled. The hours limb tests " - "hours alone, as carers_allowance does: it does not check that the " - "person cared for receives a qualifying disability benefit (the second " - "limb of regulation 30), so a survey hours field counts caring for " + "hours alone, as carers_allowance_pre_overlap does: it does not check that the " + "person cared for receives a qualifying disability benefit (part of " + "the Carer's Allowance conditions referenced by regulation 30), so a " + "survey hours field counts caring for " "anyone and a dataset's take-up input carries that simplification." ) reference = "https://www.legislation.gov.uk/uksi/2013/376/regulation/30" From f35f25b2985f1c6c77e5c96d4046dc9946791f09 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 23:20:34 -0400 Subject: [PATCH 4/5] Say which way annual netting errs for a part-year overlapping benefit Co-Authored-By: Claude Opus 5.5 --- policyengine_uk/variables/gov/dwp/carers_allowance.py | 6 +++++- .../gov/social_security_scotland/carer_support_payment.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/policyengine_uk/variables/gov/dwp/carers_allowance.py b/policyengine_uk/variables/gov/dwp/carers_allowance.py index 392026fe7a..b01082dbc8 100644 --- a/policyengine_uk/variables/gov/dwp/carers_allowance.py +++ b/policyengine_uk/variables/gov/dwp/carers_allowance.py @@ -10,7 +10,11 @@ class carers_allowance(Variable): "the allowance is reduced by any other personal benefit that overlaps " "with it, such as State Pension, and only the balance is paid. The " "comparison is made on annual amounts, which equals the weekly rule " - "when the overlapping benefit is paid at a constant rate all year." + "when the overlapping benefit is paid at a constant rate all year. " + "Where the overlapping benefit is above the carer rate in some weeks " + "and lower or absent in others, for example when it is paid for part " + "of the year, the annual comparison pays less than the weekly rule " + "would; annual inputs carry no dates to do better." ) definition_period = YEAR unit = GBP diff --git a/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment.py b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment.py index fdd0efde06..1db9e5eb3b 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment.py +++ b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment.py @@ -13,7 +13,11 @@ class carer_support_payment(Variable): "least as much. The Scottish Carer Supplement paid with it from 15 " "March 2026 is a separate component: see scottish_carer_supplement. " "The comparison is made on annual amounts, which equals the weekly " - "rule when the overlapping benefit is paid at a constant rate all year." + "rule when the overlapping benefit is paid at a constant rate all year. " + "Where the overlapping benefit is above the carer rate in some weeks " + "and lower or absent in others, for example when it is paid for part " + "of the year, the annual comparison pays less than the weekly rule " + "would; annual inputs carry no dates to do better." ) definition_period = YEAR unit = GBP From d5abad8327195385df65ce106415e1746f85d550 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 07:33:29 -0400 Subject: [PATCH 5/5] Give the carer premium couple cases person-level entitlement inputs The premium now counts claimants and partners entitled to a carer benefit rather than reading num_carers, so the two cases that set num_carers set entitlement on each person instead. Co-Authored-By: Claude Opus 5.5 --- .../policy/baseline/finance/benefit/care.yaml | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/care.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/care.yaml index 2b42e68c77..abe8b606de 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/care.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/care.yaml @@ -9,18 +9,54 @@ carer_premium: 37.5 * 52 - name: Two carers in a benefit unit receive the couple rate + # The couple rate equals the single rate here because the couple parameter + # holds the single rate; #2001 pays the premium per qualifying person. period: 2020 absolute_error_margin: 0 input: - num_carers: 2 + people: + claimant: + age: 40 + is_claimant_or_partner: true + is_entitled_to_carer_benefit: true + partner: + age: 40 + is_claimant_or_partner: true + is_entitled_to_carer_benefit: true + benunits: + benunit: + members: [claimant, partner] + households: + household: + members: [claimant, partner] output: carer_premium: 37.5 * 52 - name: Three carers in a benefit unit stay at the couple rate (regression for #420) + # Only the claimant and partner count, so a third carer in the unit adds + # nothing. period: 2020 absolute_error_margin: 0 input: - num_carers: 3 + people: + claimant: + age: 40 + is_claimant_or_partner: true + is_entitled_to_carer_benefit: true + partner: + age: 40 + is_claimant_or_partner: true + is_entitled_to_carer_benefit: true + adult_child: + age: 20 + is_claimant_or_partner: false + is_entitled_to_carer_benefit: true + benunits: + benunit: + members: [claimant, partner, adult_child] + households: + household: + members: [claimant, partner, adult_child] output: carer_premium: 37.5 * 52