From aad18681987c92d42adf6e6e207a0500b326cd4d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 11:25:28 +0000 Subject: [PATCH 1/4] Pay the Scottish Carer Supplement for each Carer Support award week MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scottish_carer_supplement paid 52 weekly rates for any positive carer_support_payment. It now pays the weekly supplement rate for min(52, max(carer_support_payment_pre_overlap, carer_support_payment) / weekly Carer Support Payment rate) award weeks where carer_support_payment is positive (SSI 2023/302 reg 14A), so 13 weeks of Carer Support Payment after a move to England (reg 42) bring 13 weekly rates of supplement, £152.10 in 2026-27, rather than £608.40. The formula holds 52 weekly rates in the amount's float32 dtype, so where the model computes both amounts the supplement equals main's bit for bit, and it guards the division against a zero rate. The documentation names the four cases where the count differs from the law. YAML cases worked from reg 16(1) and (1A) and Hypothesis properties cover the bounds, payment, differential, monotonicity, whole weeks, full year, zero rate and the disable_simulated_benefits path. Co-Authored-By: Claude Opus 5.5 --- changelog.d/2791.fixed.md | 1 + .../scottish_carer_supplement.yaml | 140 ++++++ ...carer_supplement_award_weeks_properties.py | 454 ++++++++++++++++++ .../scottish_carer_supplement.py | 65 ++- 4 files changed, 650 insertions(+), 10 deletions(-) create mode 100644 changelog.d/2791.fixed.md create mode 100644 policyengine_uk/tests/test_scottish_carer_supplement_award_weeks_properties.py diff --git a/changelog.d/2791.fixed.md b/changelog.d/2791.fixed.md new file mode 100644 index 0000000000..2b44e7be4a --- /dev/null +++ b/changelog.d/2791.fixed.md @@ -0,0 +1 @@ +Pay the Scottish Carer Supplement for each award week in which Carer Support Payment is payable, up to 52 a year, counting the weeks from the larger of the payment and the entitlement before the overlapping-benefit reduction, so a carer paid 13 weeks of Carer Support Payment after moving to England gets £152.10 of supplement in 2026-27 rather than £608.40. diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml index 967e7ef79c..af6556f492 100644 --- a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml @@ -56,3 +56,143 @@ output: scottish_carer_supplement: 0 carer_support_payment: 0 + +# Award weeks. SSI 2023/302 reg 14A: "An individual is entitled to Scottish +# Carer Supplement for each award week in which Carer Support Payment is +# payable to the individual." In reg 2(2), "award week" means "a period of 7 +# days beginning on a Sunday and ending on a Saturday". The Scottish Ministers +# set the weekly rates in reg 16(1) and (1A): Carer Support Payment 86.45 and +# Scottish Carer Supplement 11.70. Under reg 42(1) and (5), a carer who moves +# to another part of the United Kingdom keeps Carer Support for 13 weeks; +# under reg 42(1A), where the cared-for person dies in that time, for 12 award +# weeks after the first day of the award week following the one in which they +# died, up to 25 weeks in all. The formula counts the weeks as the larger of +# carer_support_payment_pre_overlap and carer_support_payment over the weekly +# Carer Support Payment rate, up to 52, and pays the weekly supplement rate for +# each where carer_support_payment is positive, and nothing where it is £0. + +- name: Carer in England with 13 weeks of Carer Support Payment gets 13 weeks of supplement + period: 2026 + absolute_error_margin: 0.01 + input: + age: 35 + country: ENGLAND + # Reg 42(1) and (5): 13 weeks after the move, 13 x 86.45 = 1,123.85. + carer_support_payment: 1123.85 + output: + carer_support_payment_pre_overlap: 0 + # 1,123.85 / 86.45 = 13 award weeks; 13 x 11.70 = 152.10. + scottish_carer_supplement: 152.10 + +- name: Carer in England whose cared-for person dies in week 13 after the move gets 25 weeks of supplement + period: 2026 + absolute_error_margin: 0.01 + input: + age: 35 + country: ENGLAND + # Reg 42(1A): the cared-for person dies in week 13, and the carer keeps + # Carer Support for the 12 award weeks from the start of week 14, 25 weeks + # in all. 25 x 86.45 = 2,161.25. + carer_support_payment: 2161.25 + output: + carer_support_payment_pre_overlap: 0 + # 2,161.25 / 86.45 = 25 award weeks; 25 x 11.70 = 292.50. + scottish_carer_supplement: 292.50 + +- name: One week of Carer Support Payment gives one week of supplement + period: 2026 + absolute_error_margin: 0.01 + input: + age: 35 + country: ENGLAND + carer_support_payment: 86.45 + output: + # 86.45 / 86.45 = 1 award week; 1 x 11.70 = 11.70. + scottish_carer_supplement: 11.70 + +- name: Carer Support Payment worth more than 52 weeks gives 52 weeks of supplement + period: 2026 + absolute_error_margin: 0.01 + input: + age: 35 + country: ENGLAND + # 60 x 86.45 = 5,187. + carer_support_payment: 5187 + output: + # The model counts 52 award weeks in a year: 52 x 11.70 = 608.40. + scottish_carer_supplement: 608.40 + +- name: Carer in Scotland with no caring inputs and 13 weeks of Carer Support Payment gets 13 weeks of supplement + period: 2026 + absolute_error_margin: 0.01 + input: + age: 35 + country: SCOTLAND + # No caring hours and no reported Carer's Allowance, so the inputs give + # no entitlement before the overlapping-benefit reduction. + carer_support_payment: 1123.85 + output: + carer_support_payment_pre_overlap: 0 + # 13 x 11.70 = 152.10. + scottish_carer_supplement: 152.10 + +- name: Carer in Scotland the model finds eligible keeps 52 weeks of supplement with a supplied part-year payment + period: 2026 + absolute_error_margin: 0.01 + input: + age: 35 + care_hours: 40 + country: SCOTLAND + carer_support_payment: 1123.85 + output: + # Age 35 and 40 hours of care a week describe a carer entitled all year: + # 52 x 86.45 = 4,495.40 before the overlapping-benefit reduction. + carer_support_payment_pre_overlap: 4495.40 + # The formula counts 52 award weeks from the larger amount: 52 x 11.70 = + # 608.40. For a carer with 13 weeks of Carer Support Payment, Social + # Security Scotland would pay 13 x 11.70 = 152.10; see the variable's + # documentation. + scottish_carer_supplement: 608.40 + +- name: Carer whose State Pension reduces Carer Support Payment and leaves some keeps 52 weeks of supplement + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + state_pension: 2_000 + care_hours: 35 + country: SCOTLAND + output: + carer_support_payment_pre_overlap: 4495.40 + # Reg 16(2)(a): 2,000 a year, 38.46 a week, falls short of the 86.45 + # weekly rate, so Carer Support Payment stays payable in every award + # week: 4,495.40 - 2,000 = 2,495.40. + carer_support_payment: 2495.40 + # Reg 14A: 52 x 11.70 = 608.40. + scottish_carer_supplement: 608.40 + +- name: Carer with a supplied payment that State Pension reduced keeps 52 weeks of supplement with the amount before the reduction + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + country: SCOTLAND + # A carer entitled all year, 52 x 86.45 = 4,495.40, whose State Pension + # of 2,000 a year leaves 2,495.40, with both amounts supplied. + carer_support_payment_pre_overlap: 4495.40 + carer_support_payment: 2495.40 + output: + # Reg 16(2)(a) leaves a payment in every award week: 52 x 11.70 = 608.40. + scottish_carer_supplement: 608.40 + +- name: Carer in England with 13 weeks of Carer Support Payment gets 13 weeks of supplement in 2027 + period: 2027 + absolute_error_margin: 0.01 + input: + age: 35 + country: ENGLAND + # 13 x 89.50 = 1,163.50, at the 2027 rate above. + carer_support_payment: 1163.50 + output: + # 1,163.50 / 89.50 = 13 award weeks; 13 x 12.10 = 157.30. + scottish_carer_supplement: 157.30 diff --git a/policyengine_uk/tests/test_scottish_carer_supplement_award_weeks_properties.py b/policyengine_uk/tests/test_scottish_carer_supplement_award_weeks_properties.py new file mode 100644 index 0000000000..f00a7ba080 --- /dev/null +++ b/policyengine_uk/tests/test_scottish_carer_supplement_award_weeks_properties.py @@ -0,0 +1,454 @@ +"""The Scottish Carer Supplement for each award week in which Carer Support +Payment is payable. + +Regulation 14A of the Carer's Assistance (Carer Support Payment) (Scotland) +Regulations 2023 (SSI 2023/302): "An individual is entitled to Scottish Carer +Supplement for each award week in which Carer Support Payment is payable to +the individual." The Scottish Ministers set the weekly rates of the two +components in regulation 16(1) and (1A). The model holds annual amounts, so +scottish_carer_supplement counts the award weeks as + + min(52, max(carer_support_payment_pre_overlap, carer_support_payment) / rate) + +where carer_support_payment is positive, and pays the weekly supplement rate +for each of them. It measures the amount against 52 weekly rates as the engine +stores them, so a full year's entitlement counts exactly 52 weeks. + +Invariants, for single people aged 14 to 90 in each UK country, in each year +from 2024 to 2030: + +1. Bounds: the supplement lies between 0 and 52 weekly supplement rates, as + the engine stores that amount. +2. The supplement is positive exactly where carer_support_payment is + positive, in years whose supplement rate is positive. +3. Differential: the supplement equals the rule rebuilt here from the drawn + inputs, with the rates read from the parameters. +4. Monotonic: raising a supplied carer_support_payment never lowers the + supplement. +5. Whole weeks: a supplied payment of k weekly rates, for k from 1 to 52, + gives k weekly supplements, and a payment of 52 weekly rates or more + gives 52. +6. Full year where the model computes the payment and the amount before the + overlapping-benefit reduction: with neither carer_support_payment nor + carer_support_payment_pre_overlap supplied, the supplement equals 52 + weekly supplement rates, as the engine stores that amount, where + carer_support_payment is positive, and nothing elsewhere. +7. A reform that sets the weekly Carer Support Payment rate to zero leaves no + supplement and raises no floating-point error. + +Each example puts every drawn person in one simulation, with the same inputs +in each year, and checks every year. The reference rebuilds the entitlement +before the overlapping-benefit reduction as 52 weekly rates for a carer in +Scotland, from 2025, who reaches the minimum age, cares for the minimum hours +or reports Carer's Allowance, and would claim; the drawn people have no +earnings. A State Pension supplied directly overlaps in full. The engine +stores amounts in float32, so the reference rounds the amounts it compares to +float32. The differential and whole-week comparisons allow a tenth of a penny +for the rounding, which comes to under £0.0001 at these sizes; the other +invariants hold exactly. + +A last test checks gov.contrib.policyengine.disable_simulated_benefits, which +makes a Scottish carer's reported Carer's Allowance their +carer_support_payment and leaves the amount before the reduction to the model. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, example, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.system import system +from policyengine_uk.utils.scenario import Scenario + +CSP = "gov.social_security_scotland.carer_support_payment" +WEEKS = 52 +# carer_support_payment_pre_couple_limit pays Carer Support Payment from 2025. +CSP_FROM = 2025 +YEARS = [2024, 2025, 2026, 2027, 2028, 2029, 2030] +# The years with a positive supplement rate. +SUPPLEMENT_YEARS = [2026, 2027, 2028, 2029, 2030] +COUNTRIES = ["ENGLAND", "WALES", "NORTHERN_IRELAND", "SCOTLAND"] +TOLERANCE = 0.001 +# A State Pension equal to a year of Carer Support Payment in each year: under +# reg 16(2)(b), the payment given is then £0. +YEAR_OF_CSP = "a year of Carer Support Payment" +PROPERTY_SETTINGS = settings( + max_examples=12, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +def money(high): + return st.floats(0, high, allow_nan=False, allow_infinity=False).map( + lambda x: round(x, 2) + ) + + +# Up to about 72 weeks at the 2030 rate, small amounts of a week or two, and +# none. +PAYMENT = st.one_of(st.just(0.0), money(200), money(7_000)) +# Below the 2025 to 2030 annual rates (4,331.60 to 5,018), above them, and +# equal to them. +STATE_PENSION = st.one_of( + st.just(0.0), money(4_300), money(12_000), st.just(YEAR_OF_CSP) +) + + +def parameters(year): + return system.parameters(year).gov.social_security_scotland.carer_support_payment + + +def stored(amount): + """The amount as the engine stores it.""" + return float(np.float32(amount)) + + +@st.composite +def batches(draw, supplied): + """Single people aged 14 to 90, each in their own household, about half in + Scotland. + + An input overrides its variable's formula for the whole population, so a + batch supplies carer_support_payment for everyone (supplied) or for no + one, and supplies carer_support_payment_pre_overlap for everyone or for no + one. + """ + give_pre_overlap = draw(st.sampled_from([False, False, True])) + people = [] + for _ in range(draw(st.integers(2, 16))): + person = { + "country": draw(st.sampled_from(["SCOTLAND"] * 3 + COUNTRIES[:3])), + "age": draw(st.integers(14, 90)), + "care_hours": draw(st.sampled_from([0, 20, 34, 35, 40, 60])), + "carers_allowance_reported": draw(st.sampled_from([0, 0, 0, 3_000])), + "would_claim_carers_allowance": draw(st.sampled_from([True, True, False])), + } + if supplied: + person["carer_support_payment"] = draw(PAYMENT) + else: + person["state_pension"] = draw(STATE_PENSION) + if give_pre_overlap: + person["carer_support_payment_pre_overlap"] = draw(PAYMENT) + people.append(person) + return people + + +def inputs(person, year): + """The person's inputs in the year, with the sentinels replaced.""" + values = {key: value for key, value in person.items() if key != "country"} + if values.get("state_pension") == YEAR_OF_CSP: + values["state_pension"] = WEEKS * parameters(year).rate + if "weeks_paid" in values: + weeks_paid = values.pop("weeks_paid") + values["carer_support_payment"] = weeks_paid * parameters(year).rate + return values + + +def build(people, years=YEARS, reform=None): + """One simulation holding every person, with inputs in each year.""" + situation = {"people": {}, "benunits": {}, "households": {}} + for i, person in enumerate(people): + by_year = {year: inputs(person, year) for year in years} + situation["people"][f"p{i}"] = { + name: {year: by_year[year][name] for year in years} + for name in by_year[years[0]] + } + situation["benunits"][f"b{i}"] = {"members": [f"p{i}"]} + situation["households"][f"h{i}"] = { + "members": [f"p{i}"], + "country": {year: person["country"] for year in years}, + } + return Simulation(situation=situation, reform=reform) + + +def calc(sim, variable, year): + return np.asarray(sim.calculate(variable, year), dtype=float) + + +def scheme(sim, year): + parameters = sim.tax_benefit_system.parameters(year) + return parameters.gov.social_security_scotland.carer_support_payment + + +def rates(sim, year): + csp = scheme(sim, year) + return float(csp.rate), float(csp.supplement) + + +def reference(sim, year, people): + """The supplement rebuilt from the drawn inputs and the parameters.""" + csp = scheme(sim, year) + rate, supplement = rates(sim, year) + expected = [] + for person in people: + values = inputs(person, year) + if "carer_support_payment_pre_overlap" in values: + pre_overlap = stored(values["carer_support_payment_pre_overlap"]) + else: + eligible = ( + person["country"] == "SCOTLAND" + and year >= CSP_FROM + and person["age"] >= csp.min_age + and ( + person["care_hours"] >= csp.min_hours + or person["carers_allowance_reported"] > 0 + ) + and person["would_claim_carers_allowance"] + ) + pre_overlap = stored(WEEKS * rate) if eligible else 0.0 + if "carer_support_payment" in values: + payable = stored(values["carer_support_payment"]) + else: + # Reg 16(2): the overlapping benefit reduces the payment, to £0 + # where the benefit equals or exceeds the payment. + payable = max(pre_overlap - stored(values["state_pension"]), 0.0) + weeks = min(WEEKS, max(pre_overlap, payable) / rate) if rate > 0 else 0.0 + expected.append(supplement * weeks if payable > 0 else 0.0) + return np.array(expected) + + +def check_bounds_and_payment(sim, year): + """Invariants 1 and 2.""" + supplement = calc(sim, "scottish_carer_supplement", year) + payable = calc(sim, "carer_support_payment", year) + rate, weekly_supplement = rates(sim, year) + assert (supplement >= 0).all(), year + assert (supplement <= np.float32(WEEKS * weekly_supplement)).all(), year + if weekly_supplement > 0 and rate > 0: + np.testing.assert_array_equal(supplement > 0, payable > 0, err_msg=str(year)) + else: + np.testing.assert_array_equal(supplement, 0, err_msg=str(year)) + return supplement + + +def carer(country="ENGLAND", **values): + """A 35-year-old with no caring inputs, who would claim.""" + return { + "country": country, + "age": 35, + "care_hours": 0, + "carers_allowance_reported": 0, + "would_claim_carers_allowance": True, + **values, + } + + +def england(payment): + return carer(carer_support_payment=payment) + + +def scottish_carer(payment, care_hours=40): + return carer("SCOTLAND", care_hours=care_hours, carer_support_payment=payment) + + +def pensioner(state_pension, country="SCOTLAND"): + return { + **carer(country, care_hours=35, state_pension=state_pension), + "age": 70, + } + + +@PROPERTY_SETTINGS +@given(people=batches(supplied=True)) +# 13 and 25 weeks of the 2026 rate after a move to England (reg 42), one week, +# 60 weeks, a carer in Scotland with no caring inputs, and one the model finds +# eligible. +@example( + people=[ + england(1_123.85), + england(2_161.25), + england(86.45), + england(5_187), + scottish_carer(1_123.85, care_hours=0), + scottish_carer(1_123.85), + scottish_carer(0), + ] +) +# Both amounts supplied: a payment that State Pension reduced, none, and one +# week with no entitlement before the reduction. +@example( + people=[ + carer( + "SCOTLAND", + carer_support_payment_pre_overlap=4_495.40, + carer_support_payment=2_495.40, + ), + carer(carer_support_payment_pre_overlap=1_123.85, carer_support_payment=0), + carer(carer_support_payment_pre_overlap=0, carer_support_payment=86.45), + ] +) +def test_supplied_payment_bounds_and_reference(people): + sim = build(people) + for year in YEARS: + supplement = check_bounds_and_payment(sim, year) + # 3. Differential. + np.testing.assert_allclose( + supplement, + reference(sim, year, people), + atol=TOLERANCE, + err_msg=str(year), + ) + + +@st.composite +def raised_batches(draw): + people = draw(batches(supplied=True)) + raised = [ + {**person, "carer_support_payment": person["carer_support_payment"] + extra} + for person, extra in zip(people, [draw(PAYMENT) for _ in people]) + ] + return people, raised + + +@PROPERTY_SETTINGS +@given(batch=raised_batches()) +def test_supplement_is_monotonic_in_a_supplied_payment(batch): + people, raised = batch + # One simulation: the drawn people, then the same people paid more. + sim = build(people + raised) + for year in YEARS: + supplement = calc(sim, "scottish_carer_supplement", year) + low, high = supplement[: len(people)], supplement[len(people) :] + # 4. Exactly, since the formula rounds monotonically at each step. + assert (high >= low).all(), year + + +def test_whole_weeks(): + """5. Each payment is a number of the year's weekly rates.""" + weeks = list(range(1, WEEKS + 1)) + beyond = [52, 52.001, 53, 60, 104, 1_000] + # Carers in Scotland with no caring inputs have no entitlement before the + # overlapping-benefit reduction either. + people = [ + carer(COUNTRIES[i % len(COUNTRIES)], weeks_paid=k) + for i, k in enumerate(weeks + beyond) + ] + sim = build(people, SUPPLEMENT_YEARS) + for year in SUPPLEMENT_YEARS: + _, weekly_supplement = rates(sim, year) + expected = [k * weekly_supplement for k in weeks] + expected += [WEEKS * weekly_supplement] * len(beyond) + np.testing.assert_allclose( + calc(sim, "scottish_carer_supplement", year), + expected, + atol=TOLERANCE, + err_msg=str(year), + ) + + +@PROPERTY_SETTINGS +@given(people=batches(supplied=False)) +# State Pension that reduces the payment and leaves some, that takes it to +# £0, and that equals it (reg 16(2)(b)); a carer outside Scotland. +@example( + people=[ + pensioner(2_000), + pensioner(6_000), + pensioner(YEAR_OF_CSP), + pensioner(0), + pensioner(0, country="ENGLAND"), + ] +) +def test_computed_payment_bounds_and_reference(people): + sim = build(people) + for year in YEARS: + supplement = check_bounds_and_payment(sim, year) + # 3. Differential. + np.testing.assert_allclose( + supplement, + reference(sim, year, people), + atol=TOLERANCE, + err_msg=str(year), + ) + if "carer_support_payment_pre_overlap" in people[0]: + continue + # 6. 52 weekly supplements, as the engine stores them, wherever the + # model pays Carer Support Payment. + payable = calc(sim, "carer_support_payment", year) + _, weekly_supplement = rates(sim, year) + full_year = (payable > 0) * weekly_supplement * WEEKS + np.testing.assert_array_equal( + supplement, full_year.astype(np.float32), err_msg=str(year) + ) + + +@pytest.mark.parametrize("supplied", [True, False]) +def test_zero_rate_gives_no_supplement(supplied): + """7. The formula guards the division by the weekly rate.""" + zero_rate = {f"{CSP}.rate": {"2024-01-01.2100-12-31": 0}} + if supplied: + people = [england(1_123.85), scottish_carer(1_123.85), england(0)] + else: + people = [pensioner(0), pensioner(2_000), pensioner(0, country="ENGLAND")] + sim = build(people, SUPPLEMENT_YEARS, reform=zero_rate) + for year in SUPPLEMENT_YEARS: + assert rates(sim, year)[0] == 0 + # Calculate the inputs to the formula first, so that only the formula + # runs with floating-point errors raised. + calc(sim, "carer_support_payment", year) + calc(sim, "carer_support_payment_pre_overlap", year) + with np.errstate(all="raise"): + supplement = calc(sim, "scottish_carer_supplement", year) + np.testing.assert_array_equal(supplement, 0, err_msg=str(year)) + + +REFORM_ON = Scenario( + applied_before_data_load=True, + parameter_changes={"gov.contrib.policyengine.disable_simulated_benefits": True}, +) + + +def test_disable_simulated_benefits_pays_the_weeks_the_report_implies(): + """Two carers in Scotland, aged 40 and caring 40 hours a week, each report + £4,000 of Carer's Allowance in 2025. The reform makes each report their + carer_support_payment in every year, without uprating it. The model finds + the carer who would claim eligible, so they keep 52 weeks; it finds the + one who would not claim ineligible, so they get the weeks the report + implies at each year's weekly rate.""" + years = range(2025, 2028) + people = { + name: { + "age": {year: 40 for year in years}, + "care_hours": {year: 40 for year in years}, + "would_claim_carers_allowance": {year: claims for year in years}, + "carers_allowance_reported": {2025: 4_000}, + } + for name, claims in [("claims", True), ("would_not_claim", False)] + } + situation = { + "people": people, + "benunits": {f"{name}_unit": {"members": [name]} for name in people}, + "households": { + f"{name}_home": { + "members": [name], + "region": {year: "SCOTLAND" for year in years}, + } + for name in people + }, + } + sim = Simulation(situation=situation, scenario=REFORM_ON) + expected = { + # 52 x 86.45 = 4,495.40 before the reduction. 52 x 11.70 = 608.40; + # 4,000 / 86.45 = 46.27 weeks, x 11.70 = 541.35. + 2026: ([4_495.40, 0], [608.40, 4_000 / 86.45 * 11.70]), + # 52 x 89.50 = 4,654 before the reduction. 52 x 12.10 = 629.20; + # 4,000 / 89.50 = 44.69 weeks, x 12.10 = 540.78. + 2027: ([4_654, 0], [629.20, 4_000 / 89.50 * 12.10]), + } + for year, (pre_overlap, supplement) in expected.items(): + np.testing.assert_allclose(calc(sim, "carer_support_payment", year), 4_000) + np.testing.assert_allclose( + calc(sim, "carer_support_payment_pre_overlap", year), + pre_overlap, + atol=TOLERANCE, + err_msg=str(year), + ) + np.testing.assert_allclose( + calc(sim, "scottish_carer_supplement", year), + supplement, + atol=TOLERANCE, + err_msg=str(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 7dae35df45..ce9f7bdd34 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 @@ -6,20 +6,53 @@ class scottish_carer_supplement(Variable): entity = Person label = "Scottish Carer Supplement" documentation = ( - "Paid for each week in which Carer Support Payment is payable, from 15 " - "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, " - "Housing Benefit and Scottish Council Tax Reduction, at pension age " - "and working age, do not count it as income; it is taxable." + "From 15 March 2026, Social Security Scotland pays the Scottish Carer " + "Supplement for each award week in which Carer Support Payment is " + "payable. The formula counts those weeks from annual amounts: it " + "divides the larger of carer_support_payment_pre_overlap and " + "carer_support_payment by the weekly Carer Support Payment rate, keeps" + " any part week and caps the count at the model's 52 weeks a year. It " + "pays the weekly supplement rate for each of those weeks, and nothing " + "where carer_support_payment is £0, as where an overlapping benefit " + "such as State Pension takes the payment to £0. Where the model " + "computes the amount before the overlapping-benefit reduction, that " + "amount comes to 52 weekly rates or nothing, so a carer whose " + "overlapping benefit reduces the payment but leaves some keeps all 52 " + "weeks. Where a user supplies the payment and the inputs give no " + "amount before the reduction, the formula divides the payment by the " + "weekly rate to count the weeks; a carer who moves to England, for " + "example, keeps Carer Support for up to 13 weeks, or up to 25 in all " + "where the cared-for person dies in that time. The count differs from " + "the law in four cases. Where an overlapping benefit or earnings vary " + "within the year, the formula counts 52 weeks or none from the annual " + "amounts, while Social Security Scotland pays the supplement only for " + "the award weeks in which the payment stays payable. Where a user " + "supplies a part-year payment and also sets inputs that make the " + "person eligible in Scotland, the formula counts 52 weeks, since the " + "model reads those inputs as a whole year. Where a user supplies a " + "payment that an overlapping benefit has reduced, the formula counts " + "fewer weeks than the award weeks in which that payment is payable, " + "unless the inputs also give the amount before the reduction. Under " + "gov.contrib.policyengine.disable_simulated_benefits, a Scottish " + "carer's reported Carer's Allowance becomes carer_support_payment " + "while the amount before the reduction stays the model's, so the " + "formula counts 52 weeks for a reported carer the model finds " + "eligible, even where the report covers only part of the year, and the" + " weeks the report implies at each year's weekly rate for any other " + "reported carer; the reform doesn't uprate the report, so a report of " + "a full year counts fewer weeks in later years. The supplement is a " + "component of Carer Support separate from the Carer Support Payment " + "component. Pension Credit, Housing Benefit and Scottish Council Tax " + "Reduction, at pension age and working age, do not count it as income;" + " it is taxable." ) definition_period = YEAR unit = GBP reference = [ + "https://www.legislation.gov.uk/ssi/2023/302/regulation/2", "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/ssi/2023/302/regulation/42", "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", @@ -29,6 +62,18 @@ class scottish_carer_supplement(Variable): ] def formula(person, period, parameters): - receives_csp = person("carer_support_payment", period) > 0 csp = parameters(period).gov.social_security_scotland.carer_support_payment - return receives_csp * csp.supplement * WEEKS_IN_YEAR + payable = person("carer_support_payment", period) + # The model computes the entitlement before the overlapping-benefit + # reduction as 52 weekly rates or nothing; a supplied payment with no + # such entitlement gives its own weeks. + amount = max_(person("carer_support_payment_pre_overlap", period), payable) + # 52 weekly rates in the amount's dtype, the value the model stores for + # a full year's entitlement, so that a full year counts exactly 52 + # weeks. The share of the year is the award weeks over 52. + full_year = np.asarray(csp.rate * WEEKS_IN_YEAR, dtype=amount.dtype) + has_rate = full_year > 0 + share = where(has_rate, amount / where(has_rate, full_year, 1), 0) + # The model counts 52 award weeks in a year. + year_of_supplement = csp.supplement * WEEKS_IN_YEAR + return where(payable > 0, year_of_supplement * min_(share, 1), 0) From c79f7e6092102043b8fd3838cc3bd5f7ac57d3ab Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 3 Nov 2026 08:40:18 -0500 Subject: [PATCH 2/4] Cite reg 2 for the award week in the supplement tests SSI 2023/302 defines "award week" in regulation 2, which has no numbered paragraphs. Co-Authored-By: Claude Opus 5.5 --- .../gov/social_security_scotland/scottish_carer_supplement.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml index af6556f492..e460f3d35c 100644 --- a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml @@ -59,7 +59,7 @@ # Award weeks. SSI 2023/302 reg 14A: "An individual is entitled to Scottish # Carer Supplement for each award week in which Carer Support Payment is -# payable to the individual." In reg 2(2), "award week" means "a period of 7 +# payable to the individual." In reg 2, "award week" means "a period of 7 # days beginning on a Sunday and ending on a Saturday". The Scottish Ministers # set the weekly rates in reg 16(1) and (1A): Carer Support Payment 86.45 and # Scottish Carer Supplement 11.70. Under reg 42(1) and (5), a carer who moves From a759e4fa6f058973f8ab5edf174c40dafd2b21d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 13:39:34 +0000 Subject: [PATCH 3/4] Name six departures and the award-week limits for the supplement Name two more departures from the weekly rule in the documentation and the YAML header: a supplied part-year amount before the reduction netted against an overlapping benefit paid all year, and part weeks. Mark the list as non-exhaustive. State the 13-week and 25-week-6-day limits after a move, and that the move cases count complete award weeks from a Sunday. Name the 2027 rates the 2027 case uses, and label the partition head "Scottish Carer Supplement". Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6Ja7tnGtKGtfAaux53 --- policyengine_uk/data/fiscal_partition.yaml | 2 +- .../scottish_carer_supplement.yaml | 41 +++++++++++++++---- ...carer_supplement_award_weeks_properties.py | 6 +-- .../scottish_carer_supplement.py | 27 ++++++++---- 4 files changed, 56 insertions(+), 20 deletions(-) diff --git a/policyengine_uk/data/fiscal_partition.yaml b/policyengine_uk/data/fiscal_partition.yaml index b5f39a095c..3649bbeaa3 100644 --- a/policyengine_uk/data/fiscal_partition.yaml +++ b/policyengine_uk/data/fiscal_partition.yaml @@ -1038,7 +1038,7 @@ spending_heads: totals: [gov_spending, household_benefits] sign: nonnegative - id: scottish_carer_supplement - label: Carer's Allowance Supplement (Scotland) + label: Scottish Carer Supplement variable: scottish_carer_supplement entity: person report_group: disability_and_carers_benefits diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml index e460f3d35c..4ba3ad42b4 100644 --- a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml @@ -63,13 +63,32 @@ # days beginning on a Sunday and ending on a Saturday". The Scottish Ministers # set the weekly rates in reg 16(1) and (1A): Carer Support Payment 86.45 and # Scottish Carer Supplement 11.70. Under reg 42(1) and (5), a carer who moves -# to another part of the United Kingdom keeps Carer Support for 13 weeks; -# under reg 42(1A), where the cared-for person dies in that time, for 12 award -# weeks after the first day of the award week following the one in which they -# died, up to 25 weeks in all. The formula counts the weeks as the larger of +# to another part of the United Kingdom keeps Carer Support for 13 weeks from +# the day they stop being ordinarily resident in Scotland (reg 42(4)). Under +# reg 42(1A), where the cared-for person dies in that time, the carer keeps it +# for 12 award weeks after the first day of the award week following the one +# in which they died, up to 25 weeks and 6 days in all. The move cases below +# put the move on a Sunday, so they count 13 and 25 complete award weeks, each +# from a Sunday. A move on any other day spreads the 13 weeks over 14 award +# weeks. The formula counts the weeks as the larger of # carer_support_payment_pre_overlap and carer_support_payment over the weekly # Carer Support Payment rate, up to 52, and pays the weekly supplement rate for # each where carer_support_payment is positive, and nothing where it is £0. +# +# The count differs from the law in these cases, among others; the variable's +# documentation names each: +# - an overlapping benefit or earnings that vary within the year; +# - a supplied part-year payment for a person the model finds eligible in +# Scotland; +# - a supplied payment that an overlapping benefit has reduced, with no amount +# before the reduction; +# - the disable_simulated_benefits reform; +# - a supplied part-year amount before the reduction, which the model nets +# against an overlapping benefit paid all year: 1,123.85 for 13 award weeks +# and 2,000 a year of State Pension net to 0, where the weekly rule leaves +# 623.85 of Carer Support Payment and 152.10 of supplement; +# - part weeks, where Social Security Scotland pays a whole weekly supplement +# for each award week in which Carer Support Payment is payable. - name: Carer in England with 13 weeks of Carer Support Payment gets 13 weeks of supplement period: 2026 @@ -77,7 +96,8 @@ input: age: 35 country: ENGLAND - # Reg 42(1) and (5): 13 weeks after the move, 13 x 86.45 = 1,123.85. + # Reg 42(1), (4) and (5): a move on a Sunday gives 13 complete award + # weeks, each from a Sunday, 13 x 86.45 = 1,123.85. carer_support_payment: 1123.85 output: carer_support_payment_pre_overlap: 0 @@ -90,9 +110,10 @@ input: age: 35 country: ENGLAND - # Reg 42(1A): the cared-for person dies in week 13, and the carer keeps - # Carer Support for the 12 award weeks from the start of week 14, 25 weeks - # in all. 25 x 86.45 = 2,161.25. + # Reg 42(1A): the move falls on a Sunday and the cared-for person dies in + # award week 13. The carer keeps Carer Support for the 12 award weeks from + # the start of award week 14, 25 complete award weeks in all, each from a + # Sunday. 25 x 86.45 = 2,161.25. carer_support_payment: 2161.25 output: carer_support_payment_pre_overlap: 0 @@ -191,7 +212,9 @@ input: age: 35 country: ENGLAND - # 13 x 89.50 = 1,163.50, at the 2027 rate above. + # 13 complete award weeks from a Sunday move. 13 x 89.50 = 1,163.50, at + # the model's projected 2027 rates: Carer Support Payment 89.50 and + # Scottish Carer Supplement 12.10. carer_support_payment: 1163.50 output: # 1,163.50 / 89.50 = 13 award weeks; 13 x 12.10 = 157.30. diff --git a/policyengine_uk/tests/test_scottish_carer_supplement_award_weeks_properties.py b/policyengine_uk/tests/test_scottish_carer_supplement_award_weeks_properties.py index f00a7ba080..b03cfc4caf 100644 --- a/policyengine_uk/tests/test_scottish_carer_supplement_award_weeks_properties.py +++ b/policyengine_uk/tests/test_scottish_carer_supplement_award_weeks_properties.py @@ -253,9 +253,9 @@ def pensioner(state_pension, country="SCOTLAND"): @PROPERTY_SETTINGS @given(people=batches(supplied=True)) -# 13 and 25 weeks of the 2026 rate after a move to England (reg 42), one week, -# 60 weeks, a carer in Scotland with no caring inputs, and one the model finds -# eligible. +# 13 and 25 complete award weeks of the 2026 rate after a Sunday move to +# England (reg 42), one week, 60 weeks, a carer in Scotland with no caring +# inputs, and one the model finds eligible. @example( people=[ england(1_123.85), 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 ce9f7bdd34..026f3429f6 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 @@ -20,12 +20,15 @@ class scottish_carer_supplement(Variable): "overlapping benefit reduces the payment but leaves some keeps all 52 " "weeks. Where a user supplies the payment and the inputs give no " "amount before the reduction, the formula divides the payment by the " - "weekly rate to count the weeks; a carer who moves to England, for " - "example, keeps Carer Support for up to 13 weeks, or up to 25 in all " - "where the cared-for person dies in that time. The count differs from " - "the law in four cases. Where an overlapping benefit or earnings vary " - "within the year, the formula counts 52 weeks or none from the annual " - "amounts, while Social Security Scotland pays the supplement only for " + "weekly rate to count the weeks. A carer who moves to England, for " + "example, keeps Carer Support for 13 weeks after the move. Where the " + "cared-for person dies in that time, the carer keeps it for up to 25 " + "weeks and 6 days in all. A move on a Sunday gives 13 complete award " + "weeks, or up to 25 after such a death, each counted from a Sunday. A " + "move on any other day spreads the 13 weeks over 14 award weeks. The " + "count differs from the law in these cases, among others. Where an " + "overlapping benefit or earnings vary within the year, the formula " + "counts 52 weeks or none from the annual amounts, while Social Security Scotland pays the supplement only for " "the award weeks in which the payment stays payable. Where a user " "supplies a part-year payment and also sets inputs that make the " "person eligible in Scotland, the formula counts 52 weeks, since the " @@ -40,7 +43,17 @@ class scottish_carer_supplement(Variable): "eligible, even where the report covers only part of the year, and the" " weeks the report implies at each year's weekly rate for any other " "reported carer; the reform doesn't uprate the report, so a report of " - "a full year counts fewer weeks in later years. The supplement is a " + "a full year counts fewer weeks in later years. Where a user supplies " + "a part-year amount before the overlapping-benefit reduction and the " + "person has an overlapping benefit paid all year, the model nets the " + "annual amounts. The formula can then count no weeks, while Social " + "Security Scotland compares the weekly amounts in each award " + "week of the award. The formula also counts part weeks where a " + "payment falls between whole numbers of weekly rates. Such a payment " + "arises for an award that starts or ends during an award week or " + "spans an April uprating. Social Security Scotland instead pays a " + "whole weekly supplement for each award week in which Carer Support " + "Payment is payable. The supplement is a " "component of Carer Support separate from the Carer Support Payment " "component. Pension Credit, Housing Benefit and Scottish Council Tax " "Reduction, at pension age and working age, do not count it as income;" From 7f2ac120bba1175a5457a1739709387ce8d52cfc Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 4 Nov 2026 09:31:29 -0500 Subject: [PATCH 4/4] Wrap the supplement's documentation and name who nets week by week Split the 119-column chunk of scottish_carer_supplement's documentation, drop the repeated "of the award", and say in the YAML header's fifth departure that Social Security Scotland nets the amounts week by week. No code or expected value changes. Co-Authored-By: Claude Opus 5.5 --- .../scottish_carer_supplement.yaml | 5 ++- .../scottish_carer_supplement.py | 38 +++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml index 4ba3ad42b4..27fbacac25 100644 --- a/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/social_security_scotland/scottish_carer_supplement.yaml @@ -85,8 +85,9 @@ # - the disable_simulated_benefits reform; # - a supplied part-year amount before the reduction, which the model nets # against an overlapping benefit paid all year: 1,123.85 for 13 award weeks -# and 2,000 a year of State Pension net to 0, where the weekly rule leaves -# 623.85 of Carer Support Payment and 152.10 of supplement; +# and 2,000 a year of State Pension net to 0, where Social Security +# Scotland nets them week by week and pays 623.85 of Carer Support +# Payment and 152.10 of supplement; # - part weeks, where Social Security Scotland pays a whole weekly supplement # for each award week in which Carer Support Payment is payable. 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 026f3429f6..64beeee5fd 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 @@ -28,14 +28,15 @@ class scottish_carer_supplement(Variable): "move on any other day spreads the 13 weeks over 14 award weeks. The " "count differs from the law in these cases, among others. Where an " "overlapping benefit or earnings vary within the year, the formula " - "counts 52 weeks or none from the annual amounts, while Social Security Scotland pays the supplement only for " - "the award weeks in which the payment stays payable. Where a user " - "supplies a part-year payment and also sets inputs that make the " - "person eligible in Scotland, the formula counts 52 weeks, since the " - "model reads those inputs as a whole year. Where a user supplies a " - "payment that an overlapping benefit has reduced, the formula counts " - "fewer weeks than the award weeks in which that payment is payable, " - "unless the inputs also give the amount before the reduction. Under " + "counts 52 weeks or none from the annual amounts, while Social " + "Security Scotland pays the supplement only for the award weeks in " + "which the payment stays payable. Where a user supplies a part-year " + "payment and also sets inputs that make the person eligible in " + "Scotland, the formula counts 52 weeks, since the model reads those " + "inputs as a whole year. Where a user supplies a payment that an " + "overlapping benefit has reduced, the formula counts fewer weeks than " + "the award weeks in which that payment is payable, unless the inputs " + "also give the amount before the reduction. Under " "gov.contrib.policyengine.disable_simulated_benefits, a Scottish " "carer's reported Carer's Allowance becomes carer_support_payment " "while the amount before the reduction stays the model's, so the " @@ -47,17 +48,16 @@ class scottish_carer_supplement(Variable): "a part-year amount before the overlapping-benefit reduction and the " "person has an overlapping benefit paid all year, the model nets the " "annual amounts. The formula can then count no weeks, while Social " - "Security Scotland compares the weekly amounts in each award " - "week of the award. The formula also counts part weeks where a " - "payment falls between whole numbers of weekly rates. Such a payment " - "arises for an award that starts or ends during an award week or " - "spans an April uprating. Social Security Scotland instead pays a " - "whole weekly supplement for each award week in which Carer Support " - "Payment is payable. The supplement is a " - "component of Carer Support separate from the Carer Support Payment " - "component. Pension Credit, Housing Benefit and Scottish Council Tax " - "Reduction, at pension age and working age, do not count it as income;" - " it is taxable." + "Security Scotland compares the weekly amounts in each award week. " + "The formula also counts part weeks where a payment falls between " + "whole numbers of weekly rates. Such a payment arises for an award " + "that starts or ends during an award week or spans an April uprating. " + "Social Security Scotland instead pays a whole weekly supplement for " + "each award week in which Carer Support Payment is payable. The " + "supplement is a component of Carer Support separate from the Carer " + "Support Payment component. Pension Credit, Housing Benefit and " + "Scottish Council Tax Reduction, at pension age and working age, do " + "not count it as income; it is taxable." ) definition_period = YEAR unit = GBP