From f9f43d5bfa5930ee23926ec416918fb3c8446b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Sat, 17 Oct 2026 07:36:39 +0100 Subject: [PATCH] Apply Carer Support Payment earnings limit --- changelog.d/2345.fixed.md | 1 + .../carer-support-payment-earnings.md | 62 +++ .../carer_support_payment/earnings_limit.yaml | 30 ++ .../earnings_rounding_precision.yaml | 11 + .../carer_support_payment_earnings_limit.yaml | 390 ++++++++++++++++++ .../tests/test_carer_support_payment.py | 143 +++++++ .../carer_support_payment_pre_overlap.py | 27 +- 7 files changed, 662 insertions(+), 2 deletions(-) create mode 100644 changelog.d/2345.fixed.md create mode 100644 docs/book/assumptions/carer-support-payment-earnings.md create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/earnings_limit.yaml create mode 100644 policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/earnings_rounding_precision.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_earnings_limit.yaml diff --git a/changelog.d/2345.fixed.md b/changelog.d/2345.fixed.md new file mode 100644 index 0000000000..2644dc96ca --- /dev/null +++ b/changelog.d/2345.fixed.md @@ -0,0 +1 @@ +- Applied the Carer Support Payment earnings limit, including its own dated thresholds and favorable fractional-penny rule. diff --git a/docs/book/assumptions/carer-support-payment-earnings.md b/docs/book/assumptions/carer-support-payment-earnings.md new file mode 100644 index 0000000000..c2c87df5b1 --- /dev/null +++ b/docs/book/assumptions/carer-support-payment-earnings.md @@ -0,0 +1,62 @@ +# Carer Support Payment earnings + +Carer Support Payment (CSP) has an earnings limit. Regulation 14 of the +[Carer's Assistance Regulations 2023](https://www.legislation.gov.uk/ssi/2023/302/regulation/14) +excludes a carer when the earnings counted for the relevant award week exceed +the limit. A reported Carer's Allowance award does not exempt them. + +## Limits and penny fractions + +CSP has its own Sunday effective dates, separate from Carer's Allowance: + +| Effective date | Weekly limit | +| --- | ---: | +| 19 November 2023 | £139 | +| 7 April 2024 | £151 | +| 6 April 2025 | £196 | +| 5 April 2026 | £204 | + +The limit is inclusive. [Schedule 2 paragraph 4](https://www.legislation.gov.uk/ssi/2023/302/schedule/2/paragraph/4) +handles a fractional penny in the individual's favour. At the final earnings +test, £204.005 a week counts as £204.00 and passes; £204.01 exceeds the £204 +limit. The model compares annual earnings with the first excluded weekly +penny amount multiplied by 52, in the earnings array's numeric precision. +This applies the penny rule without an extra tolerance or rounding annual +earnings before converting them to a weekly amount. + +## Annual earnings approximation + +The model reuses `carers_allowance_earnings`, a Person-level annual amount in +pounds. It deducts modelled employment tax and primary Class 1 contributions, +notional tax and contributions on self-employment profits, and half the +individual's pension contributions. Partner and cared-for-person earnings do +not enter the carer's test. Employer pension contributions are not deducted. + +Annual net earnings are spread evenly over 52 weeks. Constant-through-year +inputs match the weekly comparison; varying earnings may not. Originally, +regulation 14 assessed that award week's earnings. From 16 November 2025 it +assesses the immediately preceding award week. Annual inputs cannot distinguish +those weeks or reproduce all pay-period and averaging cases in schedule 2. + +The shared reader approximates employment tax using PAYE on pay alone with a +standard code, rather than actual withholding. It does not represent all +statutory care-charge deductions and disregards. Reusing it does not certify +that every schedule 2 calculation is implemented. + +## Coverage and other conditions + +Raw parameter dates retain the statutory dates above. The existing annual +parameter conversion samples 30 April for a named fiscal year; the model's +2026 value therefore represents the £204 limit, rather than a claim that it +legally began on 1 January. CSP remains modelled from 2025, with the previous +Carer's Allowance route retained for Scotland in earlier model years. This +change does not extend coverage back to the 2023 pilot. + +The existing caring-hours, reported-receipt and take-up conditions remain. +Age, education, the cared-for person's qualifying-benefit conditions and the +full claim-status rules are separate gaps. Earnings above the limit remove +underlying CSP entitlement before the overlapping-benefit calculation. This +differs from an overlapping benefit reducing an otherwise entitled person's +payment to zero. The Scottish Carer Supplement is paid only when CSP is +payable. Caring hours and reported caring can still qualify a person for the +Universal Credit carer element, which does not apply this earnings limit. diff --git a/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/earnings_limit.yaml b/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/earnings_limit.yaml new file mode 100644 index 0000000000..7feed18873 --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/earnings_limit.yaml @@ -0,0 +1,30 @@ +description: Weekly net earnings limit for Carer Support Payment, after the favorable fractional-penny rule. +metadata: + economy: false + period: week + unit: currency-GBP + label: Carer Support Payment earnings limit + reference: + - title: The Carer's Assistance (Carer Support Payment) (Scotland) Regulations 2023, regulation 14 + href: https://www.legislation.gov.uk/ssi/2023/302/regulation/14 +values: + 2023-11-19: + value: 139 + reference: + - title: The Carer's Assistance (Carer Support Payment) (Scotland) Regulations 2023, regulations 1 and 14 as made + href: https://www.legislation.gov.uk/ssi/2023/302/made + 2024-04-07: + value: 151 + reference: + - title: Regulation 14 as amended from 7 April 2024 by SSI 2024/105, regulation 7(2) + href: https://www.legislation.gov.uk/ssi/2023/302/regulation/14/2024-04-07 + 2025-04-06: + value: 196 + reference: + - title: Regulation 14 as amended from 6 April 2025 by SSI 2025/100, regulation 13(2) + href: https://www.legislation.gov.uk/ssi/2023/302/regulation/14/2025-04-06 + 2026-04-05: + value: 204 + reference: + - title: The Social Security (Up-rating) (Miscellaneous Amendments) (Scotland) Regulations 2026, regulation 12(2) + href: https://www.legislation.gov.uk/ssi/2026/170/regulation/12/made diff --git a/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/earnings_rounding_precision.yaml b/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/earnings_rounding_precision.yaml new file mode 100644 index 0000000000..9b95aa9e31 --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/earnings_rounding_precision.yaml @@ -0,0 +1,11 @@ +description: Penny precision for the favorable fractional-penny calculation of Carer Support Payment weekly earnings. +values: + 2023-11-19: 0.01 +metadata: + economy: false + period: week + unit: currency-GBP + label: Carer Support Payment earnings rounding precision + reference: + - title: The Carer's Assistance (Carer Support Payment) (Scotland) Regulations 2023, schedule 2 paragraph 4 + href: https://www.legislation.gov.uk/ssi/2023/302/schedule/2/paragraph/4 diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_earnings_limit.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_earnings_limit.yaml new file mode 100644 index 0000000000..8444e2fae7 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/carer_support_payment_earnings_limit.yaml @@ -0,0 +1,390 @@ +# CSP earnings condition: SSI2023/302reg14 andSch2para4. +# Constant-through-year actual pay; no earnings/eligibility/award overrides. +- name: Scottish carer with forty thousand pounds of pay gets no CSP + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 40000 + care_hours: 40 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 0 + - 0 + carer_support_payment_pre_overlap: + - 0 + - 0 + scottish_carer_supplement: + - 0 + - 0 + is_carer_for_benefits: + - true + - false +- name: Exactly two hundred and four pounds a week passes the earnings condition + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 10608 + care_hours: 40 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 4495.4 + - 0 + carers_allowance_earnings: + - 10608 + - 0 + scottish_carer_supplement: + - 608.4 + - 0 +- name: A half penny weekly residual is disregarded in the carers favour + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 10608.26 + care_hours: 40 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 4495.4 + - 0 +- name: A whole penny over the weekly limit excludes CSP + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 10608.52 + care_hours: 40 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 0 + - 0 +- name: Half of the individuals pension contribution brings earnings below the limit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 11000 + care_hours: 40 + employee_pension_contributions: 1000 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 4495.4 + - 0 + carers_allowance_earnings: + - 10500 + - 0 +- name: The same pay without the pension deduction exceeds the limit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 11000 + care_hours: 40 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 0 + - 0 +- name: Reported Carers Allowance does not bypass the CSP earnings bar + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 40000 + care_hours: 0 + carers_allowance_reported: 4000 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 0 + - 0 + is_carer_for_benefits: + - true + - false +- name: A carer who would not claim receives no CSP even below the limit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 8000 + care_hours: 40 + would_claim_carers_allowance: false + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 0 + - 0 +- name: The two hundred and four pound limit is not applied to fiscal year twenty twenty five + period: 2025 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 10192.52 + care_hours: 40 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 0 + - 0 +- name: Exactly the prior years one hundred and ninety six pound limit passes + period: 2025 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 10192 + care_hours: 40 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 4331.6 + - 0 +- name: England keeps Carers Allowance instead of CSP + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 5000 + care_hours: 40 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: ENGLAND + output: + carer_support_payment: + - 0 + - 0 + carers_allowance: + - 4495.4 + - 0 +- name: Below limit earnings do not replace the existing care hours condition + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + employment_income: 5000 + care_hours: 10 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 0 + - 0 +- name: The earnings bar removes underlying CSP before any pension overlap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 70 + employment_income: 40000 + care_hours: 40 + state_pension: 2000 + caree: + age: 50 + pip_dl_category: STANDARD + benunits: + family: + members: + - carer + - caree + households: + home: + members: + - carer + - caree + country: SCOTLAND + output: + carer_support_payment: + - 0 + - 0 + carer_support_payment_pre_overlap: + - 0 + - 0 + scottish_carer_supplement: + - 0 + - 0 + is_entitled_to_carer_benefit: + - false + - false diff --git a/policyengine_uk/tests/test_carer_support_payment.py b/policyengine_uk/tests/test_carer_support_payment.py index c833bfae38..f75a148796 100644 --- a/policyengine_uk/tests/test_carer_support_payment.py +++ b/policyengine_uk/tests/test_carer_support_payment.py @@ -140,3 +140,146 @@ def test_csp_counts_for_uc_non_dep_exemption_and_housing_benefit_income(): assert sim.calculate("uc_non_dep_deduction_exempt", YEAR_2025)[0] assert hb_income == max(0, csp_amount - hb_disregard) + + +def test_csp_earnings_limit_is_independent_of_carers_allowance_limit(): + year = 2026 + inputs = _situation(year, employment_income={year: 11_000}) + baseline = Simulation(situation=inputs) + csp_reform = Simulation( + situation=inputs, + reform={ + "gov.social_security_scotland.carer_support_payment.earnings_limit": 1_000 + }, + ) + ca_reform = Simulation( + situation=inputs, + reform={"gov.dwp.carers_allowance.earnings_limit": 1_000}, + ) + assert baseline.calculate("carer_support_payment", year)[0] == 0 + assert csp_reform.calculate("carer_support_payment", year)[0] == pytest.approx( + 4_495.40, abs=0.01 + ) + assert ca_reform.calculate("carer_support_payment", year)[0] == 0 + + # A CSP parameter reform cannot change the existing England CA route. + inputs["households"]["household"]["country"] = {year: "ENGLAND"} + england = Simulation( + situation=inputs, + reform={ + "gov.social_security_scotland.carer_support_payment.earnings_limit": 1_000 + }, + ) + assert england.calculate("carers_allowance", year)[0] == 0 + + +def test_csp_statutory_dates_and_annual_model_coverage(): + from pathlib import Path + + from policyengine_core.parameters.helpers import load_parameter_file + from policyengine_uk import CountryTaxBenefitSystem + + parameter_file = ( + Path(__file__).resolve().parents[1] + / "parameters/gov/social_security_scotland/carer_support_payment/earnings_limit.yaml" + ) + raw = load_parameter_file(str(parameter_file), name="earnings_limit") + for date, expected in [ + ("2023-11-19", 139), + ("2024-04-06", 139), + ("2024-04-07", 151), + ("2025-04-05", 151), + ("2025-04-06", 196), + ("2026-04-04", 196), + ("2026-04-05", 204), + ]: + assert raw(date) == expected + system = CountryTaxBenefitSystem() + for year, expected in [(2024, 151), (2025, 196), (2026, 204)]: + assert ( + system.parameters( + str(year) + ).gov.social_security_scotland.carer_support_payment.earnings_limit + == expected + ) + # The statutory pilot start does not expand the existing model coverage. + for year in [2023, 2024]: + sim = Simulation(situation=_situation(year)) + assert sim.calculate("carer_support_payment", year)[0] == 0 + + +def test_csp_first_excluded_penny_in_the_actual_holder_dtype(record_property): + import json + + import numpy as np + + year = 2026 + critical = Simulation( + situation=_situation(year, employment_income={year: 10_608.52}) + ) + earnings = critical.calculate("carers_allowance_earnings", year) + encoded_boundary = np.asarray(10_608.52, dtype=earnings.dtype) + assert critical.calculate("carer_support_payment", year)[0] == 0 + observations = [] + # These neighbours check the model's encoded legal penny boundary, rather + # than claim that arbitrary binary fractions are a separate statutory rule. + for pay, expected in [ + (np.nextafter(encoded_boundary, -np.inf, dtype=earnings.dtype), 4_495.40), + (encoded_boundary, 0), + (np.nextafter(encoded_boundary, np.inf, dtype=earnings.dtype), 0), + ]: + sim = Simulation( + situation=_situation(year, employment_income={year: float(pay)}) + ) + counted = sim.calculate("carers_allowance_earnings", year) + actual = sim.calculate("carer_support_payment", year)[0] + assert actual == pytest.approx(expected, abs=0.01) + observations.append( + { + "pay": float(pay), + "counted_annual": float(counted[0]), + "dtype": str(counted.dtype), + "CSP": float(actual), + "expected": expected, + } + ) + record_property("CSP_holder_boundary", json.dumps(observations, sort_keys=True)) + + +@pytest.mark.parametrize("reverse", [False, True]) +def test_csp_earnings_are_personal_and_request_order_independent(reverse): + year = 2026 + people, benunits, households = {}, {}, {} + for family, pay in [("low", 8_000), ("high", 40_000)]: + names = [family + suffix for suffix in ["_carer", "_partner", "_caree"]] + people[names[0]] = { + "age": {year: 45}, + "care_hours": {year: 40}, + "employment_income": {year: pay}, + "is_parent": {year: True}, + } + people[names[1]] = { + "age": {year: 43}, + "employment_income": {year: 40_000 if family == "low" else 0}, + "is_parent": {year: True}, + } + people[names[2]] = {"age": {year: 18}, "pip_dl_category": {year: "STANDARD"}} + benunits[family] = {"members": list(reversed(names)) if reverse else names} + households[family] = { + "members": list(reversed(names)) if reverse else names, + "country": {year: "SCOTLAND"}, + } + if reverse: + people = dict(reversed(list(people.items()))) + sim = Simulation( + situation={"people": people, "benunits": benunits, "households": households} + ) + if reverse: + sim.calculate("is_entitled_to_carer_benefit", year) + values = sim.calculate("carer_support_payment", year) + by_name = dict(zip(sim.populations["person"].ids, values)) + assert by_name["low_carer"] == pytest.approx(4_495.40, abs=0.01) + assert by_name["high_carer"] == 0 + assert all( + value == 0 for name, value in by_name.items() if not name.endswith("_carer") + ) 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 index 8826fc1a5b..a2306a9965 100644 --- 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 @@ -10,7 +10,12 @@ class carer_support_payment_pre_overlap(Variable): "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." + "carer_support_payment is the amount given. The earnings condition uses " + "the existing annual Carer's Allowance net-earnings reader, spread " + "evenly over 52 weeks. This approximates constant earnings; it cannot " + "resolve the award-week or, from 16 November 2025, preceding-award-week " + "assessment for varying earnings. The reader's standard-code PAYE and " + "unrepresented care-charge/disregard assumptions also apply." ) definition_period = YEAR unit = GBP @@ -18,17 +23,35 @@ class carer_support_payment_pre_overlap(Variable): "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", + "https://www.legislation.gov.uk/ssi/2023/302/regulation/14", + "https://www.legislation.gov.uk/ssi/2023/302/schedule/2/paragraph/4", ) def formula(person, period, parameters): in_scotland = person.household("country", period).decode_to_str() == "SCOTLAND" csp_in_effect = period.start.year >= 2025 + if not csp_in_effect: + return person.empty_array() 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) + earnings = person("carers_allowance_earnings", period) + # Schedule 2 paragraph 4 disregards an adverse fraction of a penny. + # The first excluded weekly amount is the limit plus one penny. + # Compare annual amounts in the reader's dtype so the encoded exact + # penny boundary is not admitted through scalar/array rounding. + first_excluded_annual_earnings = np.asarray( + (csp.earnings_limit + csp.earnings_rounding_precision) * WEEKS_IN_YEAR, + dtype=earnings.dtype, + ) + meets_earnings = earnings < first_excluded_annual_earnings eligible = ( - in_scotland & csp_in_effect & (meets_hours | receives_ca) & would_claim + in_scotland + & csp_in_effect + & (meets_hours | receives_ca) + & meets_earnings + & would_claim ) return eligible * csp.rate * WEEKS_IN_YEAR