From 79a570227dd3ce07493b32e7e0d3a16bbe97a863 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 1 Oct 2026 09:43:28 -0400 Subject: [PATCH 1/6] Count Carer Support Payment as Council Tax Reduction income Scottish carers receive Carer Support Payment in place of Carer's Allowance, but council_tax_reduction_applicable_income listed only carers_allowance, so a Scottish carer's reduction ignored the carer's benefit. Scottish CTR counts the Carer Support Payment component in full: SSI 2021/249 reg 57(1)(b)(iva) at working age and SSI 2012/319 reg 27(1)(j) at pension age. Neither counts the Scottish Carer Supplement (reg 57(1) is a closed list that omits it; reg 27(1)(j)(xxib) excepts it), so scottish_carer_supplement, split out in #1952, stays off the list. Adds YAML cases from statutory rates and Hypothesis properties: CTR income rises by exactly the CSP component when the carer claims, is invariant to the supplement except through income tax, and matches Housing Benefit income for carers in all three nations. Fixes #1955 Co-Authored-By: Claude Opus 5.5 --- changelog.d/1955.fixed.md | 1 + ...l_tax_reduction_carer_support_payment.yaml | 329 ++++++++++++++++++ ...l_tax_reduction_carer_income_properties.py | 308 ++++++++++++++++ ...council_tax_reduction_applicable_income.py | 7 + 4 files changed, 645 insertions(+) create mode 100644 changelog.d/1955.fixed.md create mode 100644 policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml create mode 100644 policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py diff --git a/changelog.d/1955.fixed.md b/changelog.d/1955.fixed.md new file mode 100644 index 0000000000..056db824ef --- /dev/null +++ b/changelog.d/1955.fixed.md @@ -0,0 +1 @@ +Council Tax Reduction income now counts Carer Support Payment, which Scottish carers receive in place of Carer's Allowance (Council Tax Reduction (Scotland) Regulations 2021 reg 57(1)(b)(iva); Council Tax Reduction (State Pension Credit) (Scotland) Regulations 2012 reg 27(1)(j)). The Scottish Carer Supplement stays out. diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml new file mode 100644 index 0000000000..175ca561de --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml @@ -0,0 +1,329 @@ +# Carer Support Payment in Council Tax Reduction income. +# Scottish carers receive Carer Support Payment (CSP) in place of Carer's +# Allowance (CA); the model moves them in 2025. Scottish Council Tax Reduction +# counts the CSP component in full: +# - working age: Council Tax Reduction (Scotland) Regulations 2021 (SSI +# 2021/249) reg 57(1)(b)(iva), a closed list of unearned income; +# - pension age: Council Tax Reduction (State Pension Credit) (Scotland) +# Regulations 2012 (SSI 2012/319) reg 27(1)(j), social security benefits +# other than those it lists. +# Neither counts the Scottish Carer Supplement, paid with CSP from 15 March +# 2026: reg 57(1) does not list it and reg 27(1)(j)(xxib) excepts it. It is +# taxable, so it reaches Council Tax Reduction income only through income tax. +# Neither instrument disregards CSP above the CA amount. +# +# Weekly rates. 2026: CSP and CA 86.45, Scottish Carer Supplement 11.70, +# carer premium 48.15, personal allowance 95.55 (single, 25 or over), 256.00 +# (single, pension age) and 383.35 (couple, pension age). 2025: CSP 83.30, no +# supplement, carer premium 46.38, personal allowance 92.04. 2024: CA 81.90, +# carer premium 45.60, personal allowance 90.50. Reduction is withdrawn at 20% +# of income above the applicable amount. Council tax is 2,000 throughout. +# The working-age cases take no Universal Credit, so the award does not turn +# on how Universal Credit treats CSP. + +- name: Carer Support Payment counts as working-age Council Tax Reduction income in Scotland + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + private_pension_income: 7_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + would_claim_uc: false + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + carers_allowance: 0 + carer_support_payment: 86.45 * 52 + scottish_carer_supplement: 11.70 * 52 + # 7,000 + 4,495.40 + 608.40 is below the 12,570 personal allowance. + income_tax: 0 + # The CSP component only: 7,000 + 4,495.40. Counting the supplement too + # would give 12,103.80. + council_tax_reduction_applicable_income: 7_000 + 86.45 * 52 + council_tax_reduction_applicable_amount: (95.55 + 48.15) * 52 + # 2,000 - 0.2 x (11,495.40 - 7,472.40) + council_tax_benefit: 2_000 - 0.2 * (7_000 + 86.45 * 52 - (95.55 + 48.15) * 52) + council_tax_reduction: 1_195.40 + +- name: A reported carer's award in Scotland counts as Council Tax Reduction income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + private_pension_income: 7_000 + carers_allowance_reported: 1 + care_hours: 0 + benunits: + benunit: + members: [carer] + would_claim_uc: false + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + carer_support_payment: 86.45 * 52 + council_tax_reduction_applicable_income: 7_000 + 86.45 * 52 + council_tax_reduction: 1_195.40 + +- name: A Welsh carer on Carer's Allowance gets the same Council Tax Reduction as the Scottish carer + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + private_pension_income: 7_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + would_claim_uc: false + households: + household: + members: [carer] + country: WALES + region: WALES + council_tax: 2_000 + savings: 0 + output: + carers_allowance: 86.45 * 52 + carer_support_payment: 0 + scottish_carer_supplement: 0 + council_tax_reduction_applicable_income: 7_000 + 86.45 * 52 + council_tax_reduction: 1_195.40 + +- name: A Scottish carer who does not claim Carer Support Payment has none counted + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + private_pension_income: 7_000 + care_hours: 35 + would_claim_carers_allowance: false + benunits: + benunit: + members: [carer] + would_claim_uc: false + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + carer_support_payment: 0 + scottish_carer_supplement: 0 + council_tax_reduction_applicable_income: 7_000 + # The caring hours still give the carer premium, and 7,000 is below the + # applicable amount of 7,472.40. + council_tax_reduction: 2_000 + +- name: Carer Support Payment counts in 2025, before the Scottish Carer Supplement + period: 2025 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + private_pension_income: 7_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + would_claim_uc: false + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + carers_allowance: 0 + carer_support_payment: 83.30 * 52 + scottish_carer_supplement: 0 + council_tax_reduction_applicable_income: 7_000 + 83.30 * 52 + council_tax_reduction_applicable_amount: (92.04 + 46.38) * 52 + # 2,000 - 0.2 x (11,331.60 - 7,197.84) = 1,173.25 + council_tax_reduction: 2_000 - 0.2 * (7_000 + 83.30 * 52 - (92.04 + 46.38) * 52) + +- name: In 2024 the Scottish carer is still on Carer's Allowance, which was already counted + period: 2024 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + private_pension_income: 7_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + would_claim_uc: false + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + carers_allowance: 81.90 * 52 + carer_support_payment: 0 + council_tax_reduction_applicable_income: 7_000 + 81.90 * 52 + # 2,000 - 0.2 x (11,258.80 - 7,077.20) = 1,163.68. The award no longer + # jumps to 2,000 when the carer moves to Carer Support Payment in 2025. + council_tax_reduction: 2_000 - 0.2 * (7_000 + 81.90 * 52 - (90.50 + 45.60) * 52) + +- name: Pension-age Council Tax Reduction counts Carer Support Payment, and the supplement only through tax + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 70 + state_pension: 0 + private_pension_income: 14_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + carer_support_payment: 86.45 * 52 + scottish_carer_supplement: 11.70 * 52 + # Taxable income is 14,000 + 4,495.40 + 608.40 - 12,570 = 6,533.80: the + # first 3,967 at the Scottish starter rate of 19% and 2,566.80 at 20%. + # The supplement adds 608.40 x 20% = 121.68 of that tax. + income_tax: 3_967 * 0.19 + 2_566.80 * 0.20 + # 14,000 + 4,495.40 - 1,267.09: the supplement is not added. + council_tax_reduction_applicable_income: 14_000 + 86.45 * 52 - 1_267.09 + council_tax_reduction_applicable_amount: (256 + 48.15) * 52 + # Income is above the minimum guarantee of (238 + 48.15) x 52 = 14,879.80. + guarantee_credit: 0 + # 2,000 - 0.2 x (17,228.31 - 15,815.80) + council_tax_reduction: 1_717.50 + +- name: A pension-age couple's Council Tax Reduction counts the partner's Carer Support Payment + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + state_pension: 0 + private_pension_income: 12_000 + partner: + age: 70 + state_pension: 0 + private_pension_income: 6_500 + care_hours: 35 + benunits: + benunit: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + # 86.45 x 52 and 11.70 x 52, for the partner. + carer_support_payment: [0, 4_495.40] + scottish_carer_supplement: [0, 608.40] + # Each is below the personal allowance: 12,000, and 6,500 + 4,495.40 + + # 608.40 = 11,603.80. + income_tax: [0, 0] + council_tax_reduction_applicable_income: 12_000 + 6_500 + 86.45 * 52 + council_tax_reduction_applicable_amount: (383.35 + 48.15) * 52 + # Income is above the minimum guarantee of (363.25 + 48.15) x 52 = + # 21,392.80. + guarantee_credit: 0 + # 2,000 - 0.2 x (22,995.40 - 22,438.00) + council_tax_reduction: 1_888.52 + +- name: A pension-age couple below the applicable amount keeps full Council Tax Reduction + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + state_pension: 0 + private_pension_income: 9_000 + partner: + age: 70 + state_pension: 0 + private_pension_income: 6_500 + care_hours: 35 + benunits: + benunit: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_applicable_income: 9_000 + 6_500 + 86.45 * 52 + # Guarantee Credit is paid: 21,392.80 - 19,995.40. + guarantee_credit: (363.25 + 48.15) * 52 - (9_000 + 6_500 + 86.45 * 52) + council_tax_reduction: 2_000 + +- name: An English pensioner carer's Council Tax Reduction is unchanged + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 70 + state_pension: 0 + private_pension_income: 14_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + households: + household: + members: [carer] + country: ENGLAND + region: NORTH_WEST + council_tax: 2_000 + savings: 0 + output: + carers_allowance: 86.45 * 52 + carer_support_payment: 0 + scottish_carer_supplement: 0 + # (14,000 + 4,495.40 - 12,570) x 20% + income_tax: 1_185.08 + council_tax_reduction_applicable_income: 14_000 + 86.45 * 52 - 1_185.08 + # 2,000 - 0.2 x (17,310.32 - 15,815.80) + council_tax_reduction: 1_701.10 diff --git a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py new file mode 100644 index 0000000000..826bca7549 --- /dev/null +++ b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py @@ -0,0 +1,308 @@ +"""Carer Support Payment in Council Tax Reduction income. + +Scottish carers receive Carer Support Payment in place of Carer's Allowance. +Council Tax Reduction income counted Carer's Allowance but left Carer Support +Payment out, so a Scottish carer's reduction was assessed as if the carer's +benefit were not paid. Scottish Council Tax Reduction counts the Carer Support +Payment component in full: the Council Tax Reduction (Scotland) Regulations +2021 (SSI 2021/249) reg 57(1)(b)(iva) at working age, and the Council Tax +Reduction (State Pension Credit) (Scotland) Regulations 2012 (SSI 2012/319) +reg 27(1)(j) at pension age. + +Neither counts the Scottish Carer Supplement, paid with Carer Support Payment +from 15 March 2026: reg 57(1) is a closed list that leaves it out, and reg +27(1)(j)(xxib) excepts it. The supplement is taxable (SI 2026/93), and the +model's Council Tax Reduction income is net of income tax, so the supplement +reaches it only through the tax charged on it. + +Invariants, for single people and couples in Scotland in 2026 in which one +member is a carer, all of working age and taking no Universal Credit or all +over State Pension age: + +1. Counts the component: claiming Carer Support Payment raises Council Tax + Reduction income, before income tax and National Insurance and apart from + the other benefits it counts, by exactly the Carer Support Payment + component (86.45 a week), not by the component plus the supplement. The + reduction itself never rises, and where both awards are partial it falls + by 20% of the rise in income. +2. Invariant to the supplement: setting the Scottish Carer Supplement to zero + leaves Carer Support Payment and every benefit counted in Council Tax + Reduction income unchanged, and changes that income only through income + tax: income plus income tax is unchanged. The supplement never lowers the + reduction, and where both awards are partial it raises it by 20% of the + tax on the supplement. +3. Differential: Council Tax Reduction and Housing Benefit assess the same + carer income. Where Guarantee Credit does not passport Housing Benefit, + Housing Benefit applicable income, before its disregard, childcare element + and tariff income, equals Council Tax Reduction income. This holds in + Scotland, where the carer's benefit is Carer Support Payment, and in + England and Wales, where it is Carer's Allowance. + +These compare the model's own income measures, so they hold whatever carer's +benefit the model pays. The model does not yet reduce Carer Support Payment +by State Pension (the Carer's Assistance (Carer Support Payment) (Scotland) +Regulations 2023 reg 16(2)), and it applies the Housing Benefit applicable +amounts to every nation's Council Tax Reduction. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEAR = 2026 +PENSIONS = np.arange(0, 40_001, 2_000) +WEEKS = 52 +CARER_SUPPORT_PAYMENT = 86.45 * WEEKS +SCOTTISH_CARER_SUPPLEMENT = 11.70 * WEEKS +WITHDRAWAL_RATE = 0.2 +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +REGIONS = { + "ENGLAND": "NORTH_WEST", + "WALES": "WALES", + "SCOTLAND": "SCOTLAND", +} +NO_SCOTTISH_CARER_SUPPLEMENT = { + "gov.social_security_scotland.carer_support_payment.supplement": { + "2026-01-01.2100-12-31": 0 + } +} +# The benefits Council Tax Reduction income counts besides the personal ones. +OTHER_COUNTED_BENEFITS = [ + "child_benefit", + "income_support", + "jsa_income", + "esa_income", + "universal_credit", + "tax_credits", +] +BENUNIT_VARIABLES = [ + "council_tax_reduction_applicable_income", + "council_tax_reduction_applicable_amount", + "council_tax_benefit", + "guarantee_credit", + "housing_benefit_applicable_income", + "housing_benefit_applicable_income_disregard", + "housing_benefit_applicable_income_childcare_element", + "housing_benefit_tariff_income", + *OTHER_COUNTED_BENEFITS, +] +PERSON_VARIABLES = [ + "carers_allowance", + "carer_support_payment", + "scottish_carer_supplement", + "income_tax", + "national_insurance", +] +CLAIMS = (True, False) + + +def money(high): + return st.floats(0, high, allow_nan=False, allow_infinity=False) + + +@st.composite +def family(draw, countries=("SCOTLAND",)): + pension_age = draw(st.booleans()) + adults = [] + for _ in range(draw(st.integers(1, 2))): + if pension_age: + adult = dict( + age=draw(st.integers(67, 95)), + state_pension=draw(money(15_000)), + ) + else: + adult = dict( + age=draw(st.integers(25, 60)), + employment_income=draw(st.one_of(st.just(0.0), money(30_000))), + ) + adults.append(adult) + return dict( + pension_age=pension_age, + adults=adults, + carer=draw(st.integers(0, len(adults) - 1)), + # The carer qualifies either by caring hours or by a reported award. + by_hours=draw(st.booleans()), + country=draw(st.sampled_from(countries)), + council_tax=draw(st.floats(500, 3_000, allow_nan=False)), + ) + + +def situation(families): + """Each family at each private pension, claiming and not claiming.""" + people, benunits, households = {}, {}, {} + for i, fam in enumerate(families): + for k, pension in enumerate(PENSIONS): + for c, claims in enumerate(CLAIMS): + names = [] + for j, a in enumerate(fam["adults"]): + name = f"p{i}_{k}_{c}_{j}" + person = { + "age": {YEAR: a["age"]}, + "state_pension": {YEAR: a.get("state_pension", 0.0)}, + "employment_income": {YEAR: a.get("employment_income", 0.0)}, + # All private pension goes to the first adult. + "private_pension_income": {YEAR: float(pension) * (j == 0)}, + } + if j == fam["carer"]: + if fam["by_hours"]: + person["care_hours"] = {YEAR: 35} + else: + person["carers_allowance_reported"] = {YEAR: 1} + person["would_claim_carers_allowance"] = {YEAR: claims} + people[name] = person + names.append(name) + benunits[f"b{i}_{k}_{c}"] = { + "members": names, + "would_claim_uc": {YEAR: False}, + } + households[f"h{i}_{k}_{c}"] = { + "members": names, + "country": {YEAR: fam["country"]}, + "region": {YEAR: REGIONS[fam["country"]]}, + "council_tax": {YEAR: fam["council_tax"]}, + "savings": {YEAR: 0.0}, + } + return {"people": people, "benunits": benunits, "households": households} + + +def grid(families, reform=None): + """Arrays indexed by family, private pension and whether the carer claims + (index 0 claims, index 1 does not).""" + simulation = Simulation(situation=situation(families), reform=reform) + shape = (len(families), len(PENSIONS), len(CLAIMS)) + g = { + variable: np.asarray(simulation.calculate(variable, YEAR)).reshape(shape) + for variable in BENUNIT_VARIABLES + } + for variable in PERSON_VARIABLES: + values = simulation.calculate(variable, YEAR, map_to="benunit") + g[variable] = np.asarray(values).reshape(shape) + g["other_counted_benefits"] = sum(g[v] for v in OTHER_COUNTED_BENEFITS) + # Council Tax Reduction income before the taxes deducted from it and apart + # from the other benefits it counts. + g["income_before_tax"] = ( + g["council_tax_reduction_applicable_income"] + + g["income_tax"] + + g["national_insurance"] + - g["other_counted_benefits"] + ) + return g + + +def partial(g, i): + """Where the award is neither nil nor the whole council tax bill.""" + award = g["council_tax_benefit"][i] + return (award > 0.01) & (award < np.max(award) - 0.01) + + +@PROPERTY_SETTINGS +@given(st.lists(family(), min_size=1, max_size=3)) +def test_council_tax_reduction_income_counts_the_carer_support_payment_component( + families, +): + g = grid(families) + income = g["council_tax_reduction_applicable_income"] + award = g["council_tax_benefit"] + for i, fam in enumerate(families): + claimed, unclaimed = g["carer_support_payment"][i].T + assert np.allclose(claimed, CARER_SUPPORT_PAYMENT, atol=0.01), fam + assert np.all(unclaimed == 0), fam + assert np.allclose( + g["scottish_carer_supplement"][i, :, 0], + SCOTTISH_CARER_SUPPLEMENT, + atol=0.01, + ), fam + assert np.all(g["carers_allowance"][i] == 0), fam + # Only where the zero floor on income does not bind in either run. + compared = (income[i, :, 0] > 0) & (income[i, :, 1] > 0) + rise = g["income_before_tax"][i, :, 0] - g["income_before_tax"][i, :, 1] + assert np.allclose(rise[compared], CARER_SUPPORT_PAYMENT, atol=0.05), ( + fam, + rise[compared], + ) + # Claiming never raises the reduction. A carer who qualifies by hours + # has the carer premium either way; one who qualifies by the award + # gains the premium with it, so only the hours case is compared. + if fam["by_hours"]: + assert np.all(award[i, :, 0] <= award[i, :, 1] + 0.01), fam + both_partial = partial(g, i)[:, 0] & partial(g, i)[:, 1] + fall = award[i, :, 1] - award[i, :, 0] + income_rise = income[i, :, 0] - income[i, :, 1] + assert np.allclose( + fall[both_partial], + WITHDRAWAL_RATE * income_rise[both_partial], + atol=0.05, + ), (fam, fall[both_partial], income_rise[both_partial]) + + +@PROPERTY_SETTINGS +@given(st.lists(family(), min_size=1, max_size=3)) +def test_council_tax_reduction_income_ignores_the_scottish_carer_supplement( + families, +): + g = grid(families) + without = grid(families, reform=NO_SCOTTISH_CARER_SUPPLEMENT) + for i, fam in enumerate(families): + # The carer claims in column 0. + assert np.all(g["scottish_carer_supplement"][i, :, 0] > 0), fam + assert np.all(without["scottish_carer_supplement"][i] == 0), fam + for variable in ["carer_support_payment", "other_counted_benefits"]: + assert np.allclose(g[variable][i], without[variable][i], atol=0.01), ( + fam, + variable, + ) + income = g["council_tax_reduction_applicable_income"][i] + income_without = without["council_tax_reduction_applicable_income"][i] + # Only where the zero floor on income does not bind in either run. + compared = (income > 0) & (income_without > 0) + assert np.allclose( + (income + g["income_tax"][i])[compared], + (income_without + without["income_tax"][i])[compared], + atol=0.05, + ), fam + award = g["council_tax_benefit"][i] + award_without = without["council_tax_benefit"][i] + # The supplement can only add tax, which lowers income. + assert np.all(award >= award_without - 0.01), fam + both_partial = partial(g, i) & partial(without, i) + tax_on_supplement = g["income_tax"][i] - without["income_tax"][i] + assert np.allclose( + (award - award_without)[both_partial], + WITHDRAWAL_RATE * tax_on_supplement[both_partial], + atol=0.05, + ), fam + # Without a claim there is no supplement, so nothing moves. + assert np.allclose(income[:, 1], income_without[:, 1], atol=0.01), fam + + +@PROPERTY_SETTINGS +@given(st.lists(family(countries=tuple(REGIONS)), min_size=1, max_size=3)) +def test_council_tax_reduction_and_housing_benefit_assess_the_same_carer_income( + families, +): + g = grid(families) + for i, fam in enumerate(families): + carer_benefit = g["carers_allowance"][i] + g["carer_support_payment"][i] + assert np.all(carer_benefit[:, 0] > 0), fam + ctr_income = g["council_tax_reduction_applicable_income"][i] + hb_income = g["housing_benefit_applicable_income"][i] + # Guarantee Credit passports pension-age Housing Benefit to nil + # income; the zero floors must not bind. + passported = fam["pension_age"] & (g["guarantee_credit"][i] > 0) + compared = ~passported & (hb_income > 0) & (ctr_income > 0) + hb_before_adjustments = ( + hb_income + + g["housing_benefit_applicable_income_disregard"][i] + + g["housing_benefit_applicable_income_childcare_element"][i] + - g["housing_benefit_tariff_income"][i] + ) + assert np.allclose( + hb_before_adjustments[compared], ctr_income[compared], atol=0.05 + ), (fam, hb_before_adjustments[compared], ctr_income[compared]) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py index 8cf5f764b8..9ca5cbe362 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py @@ -18,6 +18,13 @@ def formula(benunit, period, parameters): ] personal_benefits = [ "carers_allowance", + # Scottish carers receive Carer Support Payment in place of Carer's + # Allowance. Scottish Council Tax Reduction counts it in full: + # SSI 2021/249 reg 57(1)(b)(iva) at working age and SSI 2012/319 + # reg 27(1)(j) at pension age. Neither counts the Scottish Carer + # Supplement (reg 57(1) does not list it; reg 27(1)(j)(xxib) + # excepts it), so scottish_carer_supplement is not listed. + "carer_support_payment", "esa_contrib", "jsa_contrib", "state_pension", From 1f361a38cf97323d24b95b2b550bbe40bdad3649 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 1 Oct 2026 09:56:42 -0400 Subject: [PATCH 2/6] Say the supplement reaches CTR income only through the model's tax deduction Both Scottish CTR instruments deduct income tax only from earnings (SSI 2021/249 Part 6 Chapter 3; SSI 2012/319 regs 33 and 36), so in law the Scottish Carer Supplement does not touch CTR. The model deducts all income tax, so the property and YAML comments now say the dependence on the supplement through income tax is that existing approximation. Co-Authored-By: Claude Opus 5.5 --- .../council_tax_reduction_carer_support_payment.yaml | 6 ++++-- ...st_council_tax_reduction_carer_income_properties.py | 10 +++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml index 175ca561de..e08c1ca883 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml @@ -8,8 +8,10 @@ # Regulations 2012 (SSI 2012/319) reg 27(1)(j), social security benefits # other than those it lists. # Neither counts the Scottish Carer Supplement, paid with CSP from 15 March -# 2026: reg 57(1) does not list it and reg 27(1)(j)(xxib) excepts it. It is -# taxable, so it reaches Council Tax Reduction income only through income tax. +# 2026: reg 57(1) does not list it and reg 27(1)(j)(xxib) excepts it. Both +# instruments deduct income tax only from earnings, but the model deducts all +# income tax, so the taxable supplement reaches the model's Council Tax +# Reduction income through income tax and in no other way. # Neither instrument disregards CSP above the CA amount. # # Weekly rates. 2026: CSP and CA 86.45, Scottish Carer Supplement 11.70, diff --git a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py index 826bca7549..4eb4c81d4a 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py @@ -11,9 +11,13 @@ Neither counts the Scottish Carer Supplement, paid with Carer Support Payment from 15 March 2026: reg 57(1) is a closed list that leaves it out, and reg -27(1)(j)(xxib) excepts it. The supplement is taxable (SI 2026/93), and the -model's Council Tax Reduction income is net of income tax, so the supplement -reaches it only through the tax charged on it. +27(1)(j)(xxib) excepts it. Both instruments deduct income tax only from +earnings (SSI 2021/249 Part 6 Chapter 3; SSI 2012/319 regs 33 and 36), so in +law the supplement does not touch Council Tax Reduction at all. The model +deducts all of a family's income tax from its Council Tax Reduction income, an +existing approximation, so there the supplement, which is taxable (SI +2026/93), reaches that income through the tax charged on it and in no other +way. Invariants, for single people and couples in Scotland in 2026 in which one member is a carer, all of working age and taking no Universal Credit or all From e6f29a7669b5fadc2f4bf203b37ab8c90c116f8a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 08:55:04 -0400 Subject: [PATCH 3/6] Follow the Guarantee Credit passport in the pension-age CTR case; qualify the 20% invariant After merging main, a pension-age couple on Guarantee Credit has its whole income disregarded for Council Tax Reduction (SSI 2012/319 reg 24, modelled since #1909), so the case now expects applicable income of 0 and checks the Carer Support Payment itself. The property docstring says the 20% fall holds where claiming leaves the applicable amount unchanged, as the review of 1f361a38 asked. Co-Authored-By: Claude Opus 5.5 --- .../council_tax_reduction_carer_support_payment.yaml | 8 ++++++-- .../test_council_tax_reduction_carer_income_properties.py | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml index e08c1ca883..b2c16eeea1 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml @@ -295,9 +295,13 @@ council_tax: 2_000 savings: 0 output: - council_tax_reduction_applicable_income: 9_000 + 6_500 + 86.45 * 52 - # Guarantee Credit is paid: 21,392.80 - 19,995.40. + carer_support_payment: [0, 4_495.4] + # Guarantee Credit is paid: 21,392.80 - 19,995.40. Pension Credit income + # counts the Carer Support Payment component. guarantee_credit: (363.25 + 48.15) * 52 - (9_000 + 6_500 + 86.45 * 52) + # A guarantee credit recipient's whole income is disregarded (SSI 2012/319 + # reg 24), so the Carer Support Payment does not reach the means test. + council_tax_reduction_applicable_income: 0 council_tax_reduction: 2_000 - name: An English pensioner carer's Council Tax Reduction is unchanged diff --git a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py index 4eb4c81d4a..1da46f5ddc 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py @@ -27,8 +27,9 @@ Reduction income, before income tax and National Insurance and apart from the other benefits it counts, by exactly the Carer Support Payment component (86.45 a week), not by the component plus the supplement. The - reduction itself never rises, and where both awards are partial it falls - by 20% of the rise in income. + reduction itself never rises. Where both awards are partial and claiming + leaves the applicable amount unchanged (a carer by hours, who has the + carer premium either way), it falls by 20% of the rise in income. 2. Invariant to the supplement: setting the Scottish Carer Supplement to zero leaves Carer Support Payment and every benefit counted in Council Tax Reduction income unchanged, and changes that income only through income From 33f25232c714f0883ae014a002d3b2cd161852c9 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 15:30:42 -0400 Subject: [PATCH 4/6] Check CTR carer income on the Pension Credit routes separately Since #1909, a pension-age family on Guarantee Credit has its whole income disregarded for Council Tax Reduction, and one on savings credit only is assessed on Pension Credit income plus the Pension Credit paid (SSI 2012/319 regs 24 and 25). The general-income identities now apply only where neither route applies, and a new property checks both routes. A Scottish pensioner carer with no State Pension is pinned as an example: it crosses into savings credit only on the private pension grid and failed the old assertions. Addresses the review of e6f29a76 (finding 1). Co-Authored-By: Claude Opus 5.5 --- ...l_tax_reduction_carer_income_properties.py | 75 +++++++++++++++++-- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py index 1da46f5ddc..d25171a0e7 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py @@ -43,6 +43,16 @@ Scotland, where the carer's benefit is Carer Support Payment, and in England and Wales, where it is Carer's Allowance. +Invariants 1 to 3 concern Council Tax Reduction income under the general rules. +A pension-age family in receipt of a guarantee credit has its whole income +disregarded (SSI 2012/319 reg 24), and one in receipt of savings credit only is +assessed on the Secretary of State's Pension Credit income plus the savings +credit (reg 25), so those cells are left out of 1 to 3 and checked instead by: + +4. Pension Credit routes: where the family is in receipt of a guarantee credit, + Council Tax Reduction income is nil; where it is in receipt of savings credit + only, it equals Pension Credit income plus the Pension Credit paid. + These compare the model's own income measures, so they hold whatever carer's benefit the model pays. The model does not yet reduce Carer Support Payment by State Pension (the Carer's Assistance (Carer Support Payment) (Scotland) @@ -51,7 +61,7 @@ """ 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 @@ -96,6 +106,10 @@ "housing_benefit_applicable_income_disregard", "housing_benefit_applicable_income_childcare_element", "housing_benefit_tariff_income", + "in_receipt_of_guarantee_credit", + "in_receipt_of_savings_credit_only", + "pension_credit_income", + "pension_credit", *OTHER_COUNTED_BENEFITS, ] PERSON_VARIABLES = [ @@ -106,6 +120,16 @@ "national_insurance", ] CLAIMS = (True, False) +# A Scottish pensioner carer with no State Pension: across the private pension +# grid this family moves between the general rules and savings credit only. +SAVINGS_CREDIT_CARER = dict( + pension_age=True, + adults=[dict(age=80, state_pension=0.0)], + carer=0, + by_hours=True, + country="SCOTLAND", + council_tax=2_000.0, +) def money(high): @@ -198,6 +222,12 @@ def grid(families, reform=None): + g["national_insurance"] - g["other_counted_benefits"] ) + # Cells whose Council Tax Reduction income follows the general rules, not + # the Pension Credit routes of SSI 2012/319 regs 24 and 25. + g["general_rules"] = ~( + g["in_receipt_of_guarantee_credit"].astype(bool) + | g["in_receipt_of_savings_credit_only"].astype(bool) + ) return g @@ -209,6 +239,7 @@ def partial(g, i): @PROPERTY_SETTINGS @given(st.lists(family(), min_size=1, max_size=3)) +@example([SAVINGS_CREDIT_CARER]) def test_council_tax_reduction_income_counts_the_carer_support_payment_component( families, ): @@ -225,8 +256,10 @@ def test_council_tax_reduction_income_counts_the_carer_support_payment_component atol=0.01, ), fam assert np.all(g["carers_allowance"][i] == 0), fam - # Only where the zero floor on income does not bind in either run. - compared = (income[i, :, 0] > 0) & (income[i, :, 1] > 0) + # Only where the zero floor on income does not bind in either run, and + # both runs follow the general rules. + general = g["general_rules"][i, :, 0] & g["general_rules"][i, :, 1] + compared = (income[i, :, 0] > 0) & (income[i, :, 1] > 0) & general rise = g["income_before_tax"][i, :, 0] - g["income_before_tax"][i, :, 1] assert np.allclose(rise[compared], CARER_SUPPORT_PAYMENT, atol=0.05), ( fam, @@ -237,7 +270,7 @@ def test_council_tax_reduction_income_counts_the_carer_support_payment_component # gains the premium with it, so only the hours case is compared. if fam["by_hours"]: assert np.all(award[i, :, 0] <= award[i, :, 1] + 0.01), fam - both_partial = partial(g, i)[:, 0] & partial(g, i)[:, 1] + both_partial = partial(g, i)[:, 0] & partial(g, i)[:, 1] & general fall = award[i, :, 1] - award[i, :, 0] income_rise = income[i, :, 0] - income[i, :, 1] assert np.allclose( @@ -249,6 +282,7 @@ def test_council_tax_reduction_income_counts_the_carer_support_payment_component @PROPERTY_SETTINGS @given(st.lists(family(), min_size=1, max_size=3)) +@example([SAVINGS_CREDIT_CARER]) def test_council_tax_reduction_income_ignores_the_scottish_carer_supplement( families, ): @@ -265,8 +299,10 @@ def test_council_tax_reduction_income_ignores_the_scottish_carer_supplement( ) income = g["council_tax_reduction_applicable_income"][i] income_without = without["council_tax_reduction_applicable_income"][i] - # Only where the zero floor on income does not bind in either run. - compared = (income > 0) & (income_without > 0) + # Only where the zero floor on income does not bind in either run, and + # both runs follow the general rules. + general = g["general_rules"][i] & without["general_rules"][i] + compared = (income > 0) & (income_without > 0) & general assert np.allclose( (income + g["income_tax"][i])[compared], (income_without + without["income_tax"][i])[compared], @@ -276,7 +312,7 @@ def test_council_tax_reduction_income_ignores_the_scottish_carer_supplement( award_without = without["council_tax_benefit"][i] # The supplement can only add tax, which lowers income. assert np.all(award >= award_without - 0.01), fam - both_partial = partial(g, i) & partial(without, i) + both_partial = partial(g, i) & partial(without, i) & general tax_on_supplement = g["income_tax"][i] - without["income_tax"][i] assert np.allclose( (award - award_without)[both_partial], @@ -285,6 +321,8 @@ def test_council_tax_reduction_income_ignores_the_scottish_carer_supplement( ), fam # Without a claim there is no supplement, so nothing moves. assert np.allclose(income[:, 1], income_without[:, 1], atol=0.01), fam + # The supplement never lowers the award, even on the Pension Credit + # routes; on them income follows invariant 4. @PROPERTY_SETTINGS @@ -301,7 +339,9 @@ def test_council_tax_reduction_and_housing_benefit_assess_the_same_carer_income( # Guarantee Credit passports pension-age Housing Benefit to nil # income; the zero floors must not bind. passported = fam["pension_age"] & (g["guarantee_credit"][i] > 0) - compared = ~passported & (hb_income > 0) & (ctr_income > 0) + compared = ( + ~passported & g["general_rules"][i] & (hb_income > 0) & (ctr_income > 0) + ) hb_before_adjustments = ( hb_income + g["housing_benefit_applicable_income_disregard"][i] @@ -311,3 +351,22 @@ def test_council_tax_reduction_and_housing_benefit_assess_the_same_carer_income( assert np.allclose( hb_before_adjustments[compared], ctr_income[compared], atol=0.05 ), (fam, hb_before_adjustments[compared], ctr_income[compared]) + + +@PROPERTY_SETTINGS +@given(st.lists(family(), min_size=1, max_size=3)) +@example([SAVINGS_CREDIT_CARER]) +def test_council_tax_reduction_income_on_the_pension_credit_routes(families): + g = grid(families) + for i, fam in enumerate(families): + income = g["council_tax_reduction_applicable_income"][i] + guarantee = g["in_receipt_of_guarantee_credit"][i].astype(bool) + savings_only = g["in_receipt_of_savings_credit_only"][i].astype(bool) + assert not np.any(guarantee & savings_only), fam + assert np.all(income[guarantee] == 0), fam + expected = g["pension_credit_income"][i] + g["pension_credit"][i] + assert np.allclose(income[savings_only], expected[savings_only], atol=0.01), ( + fam, + income[savings_only], + expected[savings_only], + ) From 40cc558e9ba5ff95aefe57f1720d2776b8ea0e70 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 15:56:55 -0400 Subject: [PATCH 5/6] Correct the pension-age tax rule and add overlap tripwires for Scottish CTR Pension-age Scottish CTR does not deduct tax from earnings only: SSI 2012/319 reg 31(12) disregards the tax payable on any income taken into account. Working-age CTR (SSI 2021/249) deducts it from earnings only. Either way the Scottish Carer Supplement, which neither scheme counts, reaches CTR in law neither directly nor through its tax. Say so in the YAML header and the property docstring. Add two cases in which the carer's overlapping benefit (State Pension at pension age, contributory ESA at working age) exceeds the Carer Support Payment rate, so CSP Regs reg 16(2) would reduce the CSP given to nil. The model does not apply reg 16(2) yet. Whether CTR counts CSP before or after that reduction is open (SSI 2012/319 reg 27(3) and (5); SSI 2021/249 reg 57(1)(b)), so the cases fail if CTR income silently drops the CSP, and whoever applies reg 16(2) must settle it. Cite SSI 2021/249 reg 57 and SSI 2012/319 reg 27 on the CTR income variable, and note in scottish_carer_supplement that Scottish CTR does not count it. Co-Authored-By: Claude Opus 5.5 --- ...l_tax_reduction_carer_support_payment.yaml | 102 +++++++++++++++++- ...l_tax_reduction_carer_income_properties.py | 10 +- ...council_tax_reduction_applicable_income.py | 2 + .../scottish_carer_supplement.py | 8 +- 4 files changed, 111 insertions(+), 11 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml index b2c16eeea1..d1390f0f49 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml @@ -8,10 +8,13 @@ # Regulations 2012 (SSI 2012/319) reg 27(1)(j), social security benefits # other than those it lists. # Neither counts the Scottish Carer Supplement, paid with CSP from 15 March -# 2026: reg 57(1) does not list it and reg 27(1)(j)(xxib) excepts it. Both -# instruments deduct income tax only from earnings, but the model deducts all -# income tax, so the taxable supplement reaches the model's Council Tax -# Reduction income through income tax and in no other way. +# 2026: reg 57(1) does not list it and reg 27(1)(j)(xxib) excepts it. In law +# the supplement's tax does not reach Council Tax Reduction either: at working +# age SSI 2021/249 deducts tax only from earnings, and at pension age SSI +# 2012/319 reg 31(12) disregards the tax payable on income taken into +# account, which the supplement is not. The model deducts all income tax, so +# the taxable supplement reaches the model's Council Tax Reduction income +# through income tax and in no other way. # Neither instrument disregards CSP above the CA amount. # # Weekly rates. 2026: CSP and CA 86.45, Scottish Carer Supplement 11.70, @@ -139,6 +142,47 @@ # applicable amount of 7,472.40. council_tax_reduction: 2_000 +- name: A working-age carer on contributory ESA above the Carer Support Payment rate has it counted in Council Tax Reduction + # Contributory ESA (96.15 a week here) is an overlapping benefit, so CSP + # Regs (SSI 2023/302) reg 16(2)(b) would reduce the CSP given to nil. The + # model does not yet apply reg 16(2), so it pays and counts 86.45 x 52. + # SSI 2021/249 reg 57(1)(b) counts CSP "to which the applicant is entitled, + # subject to any adjustment ... in accordance with Regulations under + # section 73 of the Social Security Administration Act 1992"; reg 16(2) is + # made under the Social Security (Scotland) Act 2018, not section 73. + # Whoever applies reg 16(2) must settle which amount CTR counts and update + # this case; it fails if CTR income silently drops the CSP. Taxable income, + # 5,000 + 4,495.40 + 608.40 = 10,103.80, is below the personal allowance + # either way. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 40 + esa_contrib_reported: 5_000 + care_hours: 35 + benunits: + benunit: + members: [carer] + would_claim_uc: false + households: + household: + members: [carer] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + esa_contrib: 5_000 + income_tax: 0 + # 5,000 + 4,495.40. Leaving the CSP out would give 5,000, below the + # applicable amount, and a full award of 2,000. + council_tax_reduction_applicable_income: 5_000 + 86.45 * 52 + council_tax_reduction_applicable_amount: (95.55 + 48.15) * 52 + # 2,000 - 0.2 x (9,495.40 - 7,472.40) + council_tax_reduction: 1_595.40 + - name: Carer Support Payment counts in 2025, before the Scottish Carer Supplement period: 2025 absolute_error_margin: 0.01 @@ -270,6 +314,56 @@ # 2,000 - 0.2 x (22,995.40 - 22,438.00) council_tax_reduction: 1_888.52 +- name: A pension-age carer whose State Pension exceeds the Carer Support Payment rate has it counted in Council Tax Reduction + # The partner's State Pension (142.31 a week) exceeds the CSP rate, so CSP + # Regs (SSI 2023/302) reg 16(2)(b) would reduce the CSP given to them to + # nil. The model does not yet apply reg 16(2), so it pays and counts + # 86.45 x 52. Whether pension-age CTR should count CSP before or after that + # reduction is open. SSI 2012/319 reg 27(3) counts a benefit before any + # deduction other than the reg 27(5) adjustments, and reg 27(5) lists the + # Overlapping Benefits Regulations 1979 but not reg 16(2). Whoever applies + # reg 16(2) must settle which amount CTR counts and update this case; it + # fails if CTR income silently drops the CSP. The figures isolate that + # choice. The couple is unmarried, so no marriage allowance links the + # claimant's tax to the partner's income. The partner's taxable income, + # 7,400 + 4,495.40 + 608.40 = 12,503.80, is below the personal allowance, + # so the tax is the same whether or not the CSP is paid. Guarantee Credit + # is nil either way. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + state_pension: 15_000 + partner: + age: 70 + state_pension: 7_400 + care_hours: 35 + benunits: + benunit: + members: [claimant, partner] + is_married: false + households: + household: + members: [claimant, partner] + country: SCOTLAND + region: SCOTLAND + council_tax: 2_000 + savings: 0 + output: + # (15,000 - 12,570) x 19%, all in the Scottish starter band. + income_tax: [461.70, 0] + # Income is above the minimum guarantee of (363.25 + 48.15) x 52 = + # 21,392.80 with or without the CSP: 15,000 + 7,400 - 461.70 = 21,938.30. + guarantee_credit: 0 + # 15,000 + 7,400 + 86.45 x 52 - 461.70. Leaving the CSP out would give + # 21,938.30, below the applicable amount, and a full award of 2,000. + council_tax_reduction_applicable_income: 15_000 + 7_400 + 86.45 * 52 - 461.70 + council_tax_reduction_applicable_amount: (383.35 + 48.15) * 52 + # 2,000 - 0.2 x (26,433.70 - 22,438.00) + council_tax_reduction: 1_200.86 + - name: A pension-age couple below the applicable amount keeps full Council Tax Reduction period: 2026 absolute_error_margin: 0.01 diff --git a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py index d25171a0e7..aacaf52fb4 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py @@ -11,10 +11,12 @@ Neither counts the Scottish Carer Supplement, paid with Carer Support Payment from 15 March 2026: reg 57(1) is a closed list that leaves it out, and reg -27(1)(j)(xxib) excepts it. Both instruments deduct income tax only from -earnings (SSI 2021/249 Part 6 Chapter 3; SSI 2012/319 regs 33 and 36), so in -law the supplement does not touch Council Tax Reduction at all. The model -deducts all of a family's income tax from its Council Tax Reduction income, an +27(1)(j)(xxib) excepts it. Nor does the tax on the supplement count: at +working age SSI 2021/249 deducts income tax only from earnings (Part 6 +Chapter 3), and at pension age SSI 2012/319 reg 31(12) disregards the tax +payable on income taken into account, which the supplement is not. So in law +the supplement does not touch Council Tax Reduction at all. The model deducts +all of a family's income tax from its Council Tax Reduction income, an existing approximation, so there the supplement, which is taxable (SI 2026/93), reaches that income through the tax charged on it and in no other way. diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py index afcd74ae1d..775889cd39 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py @@ -23,6 +23,8 @@ class council_tax_reduction_applicable_income(Variable): "https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/8", "https://www.legislation.gov.uk/ssi/2012/319/regulation/24", "https://www.legislation.gov.uk/ssi/2012/319/regulation/25", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/27", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/57", ] def formula(benunit, period, parameters): 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..476f38d968 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 @@ -8,9 +8,9 @@ class scottish_carer_supplement(Variable): 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, " - "at pension age and working age, do not count it as income; it is " - "taxable." + "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 @@ -19,6 +19,8 @@ class scottish_carer_supplement(Variable): "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", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/27", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/57", "https://www.legislation.gov.uk/uksi/2026/93/made", ] From 11fe6887adb9e8637e5bc36c525b3168f876a198 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 07:44:05 -0400 Subject: [PATCH 6/6] Address review: precise comments, scoped property claims, non-empty masks - Say Scottish CTR counts Carer Support Payment "with no cap at the Carer's Allowance amount" rather than "in full", which could be read as before the CSP Regs reg 16(2) reduction. - Label the 2025 YAML rates as the model's (46.38 and 92.04); the statutory 2025-26 amounts were 46.40 and 92.05 (#2110). - Property 1: claim that the award never rises only for carers by hours, which is what the test checks. Property 2: name the counted benefits the test compares. - Assert that each general-rules comparison covers at least one cell, so no property can pass on an empty mask; at the top of the pension grid every family follows the general rules with positive income. Co-Authored-By: Claude Opus 5.5 --- ...l_tax_reduction_carer_support_payment.yaml | 10 ++++++---- ...l_tax_reduction_carer_income_properties.py | 20 ++++++++++++------- ...council_tax_reduction_applicable_income.py | 3 ++- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml index d1390f0f49..d26a88c6f6 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml @@ -1,7 +1,7 @@ # Carer Support Payment in Council Tax Reduction income. # Scottish carers receive Carer Support Payment (CSP) in place of Carer's # Allowance (CA); the model moves them in 2025. Scottish Council Tax Reduction -# counts the CSP component in full: +# counts the CSP component, with no cap at the CA amount: # - working age: Council Tax Reduction (Scotland) Regulations 2021 (SSI # 2021/249) reg 57(1)(b)(iva), a closed list of unearned income; # - pension age: Council Tax Reduction (State Pension Credit) (Scotland) @@ -20,9 +20,11 @@ # Weekly rates. 2026: CSP and CA 86.45, Scottish Carer Supplement 11.70, # carer premium 48.15, personal allowance 95.55 (single, 25 or over), 256.00 # (single, pension age) and 383.35 (couple, pension age). 2025: CSP 83.30, no -# supplement, carer premium 46.38, personal allowance 92.04. 2024: CA 81.90, -# carer premium 45.60, personal allowance 90.50. Reduction is withdrawn at 20% -# of income above the applicable amount. Council tax is 2,000 throughout. +# supplement, and the model's carer premium 46.38 and personal allowance 92.04 +# (the statutory 2025-26 amounts were 46.40 and 92.05; see #2110). 2024: CA +# 81.90, carer premium 45.60, personal allowance 90.50. Reduction is withdrawn +# at 20% of income above the applicable amount. Council tax is 2,000 +# throughout. # The working-age cases take no Universal Credit, so the award does not turn # on how Universal Credit treats CSP. diff --git a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py index aacaf52fb4..afc9513f38 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py @@ -28,14 +28,15 @@ 1. Counts the component: claiming Carer Support Payment raises Council Tax Reduction income, before income tax and National Insurance and apart from the other benefits it counts, by exactly the Carer Support Payment - component (86.45 a week), not by the component plus the supplement. The - reduction itself never rises. Where both awards are partial and claiming - leaves the applicable amount unchanged (a carer by hours, who has the - carer premium either way), it falls by 20% of the rise in income. + component (86.45 a week), not by the component plus the supplement. For a + carer who qualifies by caring hours, and so has the carer premium either + way, the reduction itself never rises, and where both awards are partial + it falls by 20% of the rise in income. 2. Invariant to the supplement: setting the Scottish Carer Supplement to zero - leaves Carer Support Payment and every benefit counted in Council Tax - Reduction income unchanged, and changes that income only through income - tax: income plus income tax is unchanged. The supplement never lowers the + leaves Carer Support Payment and the other benefits counted in Council + Tax Reduction income (Child Benefit, the income-related benefits, + Universal Credit and tax credits) unchanged, and changes that income only + through income tax: income plus income tax is unchanged. The supplement never lowers the reduction, and where both awards are partial it raises it by 20% of the tax on the supplement. 3. Differential: Council Tax Reduction and Housing Benefit assess the same @@ -262,6 +263,9 @@ def test_council_tax_reduction_income_counts_the_carer_support_payment_component # both runs follow the general rules. general = g["general_rules"][i, :, 0] & g["general_rules"][i, :, 1] compared = (income[i, :, 0] > 0) & (income[i, :, 1] > 0) & general + # At the top of the pension grid every family follows the general + # rules with positive income, so the comparison is never empty. + assert compared.any(), fam rise = g["income_before_tax"][i, :, 0] - g["income_before_tax"][i, :, 1] assert np.allclose(rise[compared], CARER_SUPPORT_PAYMENT, atol=0.05), ( fam, @@ -305,6 +309,7 @@ def test_council_tax_reduction_income_ignores_the_scottish_carer_supplement( # both runs follow the general rules. general = g["general_rules"][i] & without["general_rules"][i] compared = (income > 0) & (income_without > 0) & general + assert compared.any(), fam assert np.allclose( (income + g["income_tax"][i])[compared], (income_without + without["income_tax"][i])[compared], @@ -344,6 +349,7 @@ def test_council_tax_reduction_and_housing_benefit_assess_the_same_carer_income( compared = ( ~passported & g["general_rules"][i] & (hb_income > 0) & (ctr_income > 0) ) + assert compared.any(), fam hb_before_adjustments = ( hb_income + g["housing_benefit_applicable_income_disregard"][i] diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py index 775889cd39..ca6c655ea0 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py @@ -47,7 +47,8 @@ def formula(benunit, period, parameters): personal_benefits = [ "carers_allowance", # Scottish carers receive Carer Support Payment in place of Carer's - # Allowance. Scottish Council Tax Reduction counts it in full: + # Allowance. Scottish Council Tax Reduction counts it, with no cap at + # the Carer's Allowance amount: # SSI 2021/249 reg 57(1)(b)(iva) at working age and SSI 2012/319 # reg 27(1)(j) at pension age. Neither counts the Scottish Carer # Supplement (reg 57(1) does not list it; reg 27(1)(j)(xxib)