From a2bd3c4cdd3c5a2cd8695581a6c4a1bf6f618bb2 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 13:33:18 -0400 Subject: [PATCH 1/6] Apply the benefit cap to mixed-age couples on UC; keep them off pensioner CTR The benefit cap has no age exception. UC Regs 2013 regs 79, 82 and 83 cap every Universal Credit award, including a mixed-age couple's joint award (reg 3(2)(a)). The Housing Benefit cap (HB Regs 2006 Part 8A) reaches only Housing Benefit under the working-age regulations, which by reg 5 apply to a claimant or partner over the qualifying age for State Pension Credit only while either is on UC, Income Support, income-based JSA or income-related ESA. is_benefit_cap_exempt_other exempted any benefit unit with a member over State Pension age; it now exempts only one whose Housing Benefit is under the pension-age regulations (new housing_benefit_pension_age_regulations_apply). For Council Tax Reduction a pensioner must not be, nor have a partner who is, on those legacy benefits or have an award of UC (SI 2012/2885 reg 3(1); WSI 2013/3029 reg 3(1); SSI 2021/249 reg 3; SSI 2012/319 reg 12). The new council_tax_reduction_pensioner applies that test, and council_tax_reduction_household_has_pensioner uses it, so an English mixed-age couple on UC moves to the working-age (local) scheme. Co-Authored-By: Claude Opus 5.5 --- .../mixed-age-uc-benefit-cap-ctr.fixed.md | 1 + .../benefit_cap/benefit_cap_pension_age.yaml | 192 +++++++++++ .../council_tax_reduction_pensioner.yaml | 194 +++++++++++ ...efit_cap_and_ctr_pension_age_properties.py | 317 ++++++++++++++++++ ...g_benefit_pension_age_regulations_apply.py | 40 +++ .../gov/dwp/is_benefit_cap_exempt_other.py | 58 +--- ...l_tax_reduction_household_has_pensioner.py | 6 +- .../council_tax_reduction_pensioner.py | 42 +++ 8 files changed, 806 insertions(+), 44 deletions(-) create mode 100644 changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md create mode 100644 policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml create mode 100644 policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py create mode 100644 policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py create mode 100644 policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py diff --git a/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md b/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md new file mode 100644 index 0000000000..9c2b848db3 --- /dev/null +++ b/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md @@ -0,0 +1 @@ +Stop exempting mixed-age couples on Universal Credit from the benefit cap (UC Regs 2013 regs 79, 82 and 83 have no age exception) and stop treating them as pensioners for Council Tax Reduction (SI 2012/2885 reg 3, SI 2013/3029 reg 3, SSI 2021/249 reg 3). The age exemption now follows the Housing Benefit regulation that applies (HB Regs 2006 reg 5), through the new `housing_benefit_pension_age_regulations_apply`; the CTR pensioner test is the new `council_tax_reduction_pensioner`. diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml new file mode 100644 index 0000000000..49d45ca586 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml @@ -0,0 +1,192 @@ +# The benefit cap and State Pension age. +# +# The Universal Credit cap (UC Regs 2013 regs 79, 82 and 83) has no age +# exception, so it applies to a mixed-age couple's joint award (reg 3(2)(a)). +# The Housing Benefit cap (HB Regs 2006 Part 8A) reaches only Housing Benefit +# under the working-age regulations, which by reg 5 cover a claimant or partner +# over the qualifying age for State Pension Credit only while either of them +# is on Universal Credit, Income Support, income-based JSA or income-related +# ESA. Housing Benefit under the pension-age regulations (SI 2006/214) has no +# cap. +# +# The Universal Credit and Housing Benefit amounts before the cap are inputs, +# so these cases test the cap alone. The couple cap in London is 25,323. + +- name: A mixed-age couple on Universal Credit is capped + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + younger: + age: 50 + child_1: + age: 4 + child_2: + age: 7 + benunits: + benunit: + members: [older, younger, child_1, child_2] + universal_credit_pre_benefit_cap: 30_000 + child_benefit: 3_000 + uc_deductions: 0 + households: + household: + members: [older, younger, child_1, child_2] + region: LONDON + output: + is_uc_entitled: true + housing_benefit_pension_age_regulations_apply: false + is_benefit_cap_exempt_other: false + is_benefit_cap_exempt: false + benefit_cap: 25_323 + # 30,000 + 3,000 - 25,323 + benefit_cap_reduction: 7_677 + universal_credit: 22_323 + +- name: The same couple is excepted when its earnings reach the reg 82 threshold + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + younger: + age: 50 + employment_income: 20_000 + child_1: + age: 4 + child_2: + age: 7 + benunits: + benunit: + members: [older, younger, child_1, child_2] + universal_credit_pre_benefit_cap: 30_000 + child_benefit: 3_000 + uc_deductions: 0 + households: + household: + members: [older, younger, child_1, child_2] + region: LONDON + output: + is_benefit_cap_exempt_other: false + is_benefit_cap_exempt_earnings: true + is_benefit_cap_exempt: true + benefit_cap_reduction: 0 + universal_credit: 30_000 + +- name: A mixed-age couple on Housing Benefit and no working-age benefit is not capped + # Neither member is on Universal Credit or a legacy income-related benefit, + # so the pension-age Housing Benefit regulations apply (HB Regs 2006 reg + # 5(3)); they have no benefit cap. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + younger: + age: 50 + benunits: + benunit: + members: [older, younger] + would_claim_uc: false + would_claim_housing_benefit: true + housing_benefit_pre_benefit_cap: 30_000 + households: + household: + members: [older, younger] + region: LONDON + output: + is_uc_entitled: false + housing_benefit_pension_age_regulations_apply: true + is_benefit_cap_exempt_other: true + benefit_cap_reduction: 0 + housing_benefit: 30_000 + +- name: A mixed-age couple on income-based JSA has working-age Housing Benefit, which is capped + # HB Regs 2006 reg 5(1)(b): the working-age regulations, and so Part 8A, + # apply to a claimant over the qualifying age whose partner is on + # income-based JSA. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + younger: + age: 50 + benunits: + benunit: + members: [older, younger] + would_claim_uc: false + would_claim_housing_benefit: true + housing_benefit_pre_benefit_cap: 30_000 + jsa_income: 4_000 + households: + household: + members: [older, younger] + region: LONDON + output: + housing_benefit_pension_age_regulations_apply: false + is_benefit_cap_exempt_other: false + is_benefit_cap_exempt: false + benefit_cap: 25_323 + # 30,000 + 4,000 - 25,323 + benefit_cap_reduction: 8_677 + housing_benefit: 21_323 + +- name: A couple over State Pension age is not capped + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 72 + younger: + age: 68 + benunits: + benunit: + members: [older, younger] + would_claim_housing_benefit: true + housing_benefit_pre_benefit_cap: 30_000 + households: + household: + members: [older, younger] + region: LONDON + output: + is_uc_entitled: false + housing_benefit_pension_age_regulations_apply: true + is_benefit_cap_exempt_other: true + benefit_cap_reduction: 0 + housing_benefit: 30_000 + +- name: A working-age couple on Universal Credit is capped, as before + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 45 + younger: + age: 40 + child_1: + age: 4 + child_2: + age: 7 + benunits: + benunit: + members: [older, younger, child_1, child_2] + universal_credit_pre_benefit_cap: 30_000 + child_benefit: 3_000 + uc_deductions: 0 + households: + household: + members: [older, younger, child_1, child_2] + region: LONDON + output: + housing_benefit_pension_age_regulations_apply: false + is_benefit_cap_exempt: false + benefit_cap_reduction: 7_677 + universal_credit: 22_323 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml new file mode 100644 index 0000000000..a4bf1acdb7 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml @@ -0,0 +1,194 @@ +# Who is a pensioner for Council Tax Reduction. +# +# England (SI 2012/2885 reg 3(1)) and Wales (SI 2013/3029 reg 3(1)): a +# pensioner has attained the qualifying age for State Pension Credit and is +# not, nor is any partner, on Income Support, income-based JSA or +# income-related ESA, or a person with an award of Universal Credit. Scotland +# applies the same test through SSI 2021/249 reg 3 and SSI 2012/319 reg 12. A +# mixed-age couple on Universal Credit therefore claims under the working-age +# scheme. +# +# In England the pensioner scheme is national and working-age schemes are +# local; the model simulates five local schemes and otherwise keeps reported +# Council Tax Reduction. + +- name: An English mixed-age couple on Universal Credit uses the local working-age scheme + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + council_tax_benefit_reported: 700 + younger: + age: 50 + benunits: + benunit: + members: [older, younger] + universal_credit_pre_benefit_cap: 10_000 + households: + household: + members: [older, younger] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: false + council_tax_reduction_household_has_pensioner: false + council_tax_reduction_scheme_supported: false + simulated_council_tax_reduction_benunit: 0 + council_tax_benefit: 700 + +- name: An English mixed-age couple not on Universal Credit uses the pensioner scheme + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + council_tax_benefit_reported: 700 + younger: + age: 50 + benunits: + benunit: + members: [older, younger] + would_claim_uc: false + households: + household: + members: [older, younger] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + is_uc_entitled: false + council_tax_reduction_pensioner: true + council_tax_reduction_household_has_pensioner: true + council_tax_reduction_scheme_supported: true + # No income, so the full liability. + council_tax_benefit: 2_000 + +- name: An English mixed-age couple on income-based JSA is not a pensioner couple + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + council_tax_benefit_reported: 700 + younger: + age: 50 + benunits: + benunit: + members: [older, younger] + would_claim_uc: false + jsa_income: 4_000 + households: + household: + members: [older, younger] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: false + council_tax_reduction_household_has_pensioner: false + council_tax_benefit: 700 + +- name: An English couple over State Pension age is a pensioner couple + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 72 + younger: + age: 68 + benunits: + benunit: + members: [older, younger] + households: + household: + members: [older, younger] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: true + council_tax_reduction_household_has_pensioner: true + council_tax_reduction_scheme_supported: true + +- name: A Newham mixed-age couple on Universal Credit gets Newham's working-age scheme + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + younger: + age: 50 + benunits: + benunit: + members: [older, younger] + universal_credit_pre_benefit_cap: 10_000 + households: + household: + members: [older, younger] + country: ENGLAND + local_authority: NEWHAM + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_household_has_pensioner: false + council_tax_reduction_scheme_supported: true + +- name: A Welsh mixed-age couple on Universal Credit is not a pensioner couple + # Wales has one national scheme, so the scheme is still simulated. + period: 2026 + input: + people: + older: + age: 70 + younger: + age: 50 + benunits: + benunit: + members: [older, younger] + universal_credit_pre_benefit_cap: 10_000 + households: + household: + members: [older, younger] + country: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: false + council_tax_reduction_household_has_pensioner: false + council_tax_reduction_scheme_supported: true + +- name: A Scottish mixed-age couple on Universal Credit is not a pensioner couple + period: 2026 + input: + people: + older: + age: 70 + younger: + age: 50 + benunits: + benunit: + members: [older, younger] + universal_credit_pre_benefit_cap: 10_000 + households: + household: + members: [older, younger] + country: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: false + council_tax_reduction_household_has_pensioner: false + council_tax_reduction_scheme_supported: true diff --git a/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py new file mode 100644 index 0000000000..91667e74f1 --- /dev/null +++ b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py @@ -0,0 +1,317 @@ +"""Property tests: the benefit cap and Council Tax Reduction for families with a +member over State Pension age. + +The law: + +- The Universal Credit benefit cap has no age exception (UC Regs 2013 regs + 79, 82 and 83). A mixed-age couple can claim Universal Credit jointly (reg + 3(2)(a)), and the cap applies to that award. +- The Housing Benefit cap (HB Regs 2006 Part 8A) reaches only Housing Benefit + under the working-age regulations. By reg 5 those cover a claimant or + partner over the qualifying age for State Pension Credit only while either + of them is on Universal Credit, Income Support, income-based JSA or + income-related ESA. +- For Council Tax Reduction a "pensioner" has reached the qualifying age and + neither they nor any partner is on those legacy benefits or has an award of + Universal Credit (England SI 2012/2885 reg 3(1); Wales SI 2013/3029 reg + 3(1); Scotland SSI 2021/249 reg 3 and SSI 2012/319 reg 12). + +Invariants, for every drawn population of families: + +P1 Pension-age Housing Benefit regulations never apply to a family on + Universal Credit or a legacy income-related benefit, and the CTR + pensioner test never passes for one. +P2 Every Universal Credit award is capped unless a non-age exception applies: + such a family has a finite benefit cap. +P3 Differential against the formulas before this change. A family with no + member over State Pension age, or one over it with neither Universal + Credit nor a legacy income-related benefit, gets exactly the same benefit + cap, Universal Credit, Housing Benefit, Council Tax Reduction and household + net income. +P4 The change only removes exemptions: exempt now implies exempt before, the + cap is never higher, and Universal Credit and Housing Benefit never rise. + A CTR pensioner now was a CTR pensioner before. +P5 Scotland and Wales have one national scheme for all ages, so their Council + Tax Reduction does not change. +P6 Metamorphic: with a Universal Credit award, adding a member over State + Pension age to a couple's ages (making it mixed-age) never exempts it from + the cap. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st +from policyengine_core.reforms import Reform + +from policyengine_uk import Simulation +from policyengine_uk.model_api import * + +PERIOD = 2026 +PROPERTY_SETTINGS = settings( + max_examples=12, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +PENSION_AGE = st.integers(67, 95) +WORKING_AGE = st.integers(25, 60) +SHAPES = { + "single_pension": [PENSION_AGE], + "couple_pension": [PENSION_AGE, PENSION_AGE], + "mixed_age": [PENSION_AGE, WORKING_AGE], + "single_working": [WORKING_AGE], + "couple_working": [WORKING_AGE, WORKING_AGE], +} +PLACES = [ + ("ENGLAND", "MAIDSTONE", "SOUTH_EAST"), + ("ENGLAND", "NEWHAM", "LONDON"), + ("ENGLAND", "WESTMINSTER", "LONDON"), + ("ENGLAND", "OXFORD", "SOUTH_EAST"), + ("WALES", "CARDIFF", "WALES"), + ("SCOTLAND", "GLASGOW_CITY", "SCOTLAND"), +] +TENURES = ["RENT_FROM_COUNCIL", "RENT_PRIVATELY", "OWNED_OUTRIGHT"] + + +# The two formulas as they were before this change, kept as the reference for +# the differential invariants. +class is_benefit_cap_exempt_other(Variable): + value_type = bool + entity = BenUnit + label = "Benefit cap exemption before this change" + definition_period = YEAR + + def formula(benunit, period, parameters): + has_pensioner = benunit.any(benunit.members("is_SP_age", period)) + afcs = benunit("afcs", period) > 0 + esa_support_component = benunit("esa_contrib", period) > 0 + return has_pensioner | afcs | esa_support_component + + +class council_tax_reduction_household_has_pensioner(Variable): + value_type = bool + entity = Household + label = "CTR pensioner test before this change" + definition_period = YEAR + + def formula(household, period, parameters): + person = household.members + claimant_benunit = person.benunit("benunit_contains_household_head", period) + return household.any(claimant_benunit & person("is_SP_age", period)) + + +class before_this_change(Reform): + def apply(self): + self.update_variable(is_benefit_cap_exempt_other) + self.update_variable(council_tax_reduction_household_has_pensioner) + + +@st.composite +def families(draw): + shape = draw(st.sampled_from(sorted(SHAPES))) + country, local_authority, region = draw(st.sampled_from(PLACES)) + tenure = draw(st.sampled_from(TENURES)) + return dict( + shape=shape, + ages=[draw(age) for age in SHAPES[shape]], + children=draw(st.integers(0, 4)), + country=country, + local_authority=local_authority, + region=region, + tenure=tenure, + rent=0 if tenure == "OWNED_OUTRIGHT" else draw(st.integers(0, 30_000)), + earnings=draw(st.sampled_from([0, 0, 5_000, 25_000])), + state_pension=draw(st.sampled_from([0, 9_000, 12_000])), + would_claim_uc=draw(st.booleans()), + hb_reported=draw(st.sampled_from([0, 0, 5_000])), + ctb_reported=draw(st.sampled_from([0, 0, 700])), + legacy=draw(st.sampled_from([0, 0, 0, 3_000])), + ) + + +def situation(units): + people, benunits, households = {}, {}, {} + for i, unit in enumerate(units): + names = [] + for j, age in enumerate(unit["ages"]): + name = f"p{i}_{j}" + person = {"age": {PERIOD: age}} + if age >= 67: + person["state_pension_reported"] = {PERIOD: unit["state_pension"]} + else: + person["employment_income"] = {PERIOD: unit["earnings"]} + if j == 0: + person["housing_benefit_reported"] = {PERIOD: unit["hb_reported"]} + person["council_tax_benefit_reported"] = {PERIOD: unit["ctb_reported"]} + people[name] = person + names.append(name) + for k in range(unit["children"]): + name = f"p{i}_c{k}" + people[name] = {"age": {PERIOD: 2 + 3 * k}} + names.append(name) + benunit = { + "members": names, + "would_claim_uc": {PERIOD: unit["would_claim_uc"]}, + # claims_all_entitled_benefits sums reported benefits across the + # whole simulation, so it is set per family. + "claims_all_entitled_benefits": { + PERIOD: unit["hb_reported"] == 0 and unit["ctb_reported"] == 0 + }, + } + if unit["legacy"]: + benunit["jsa_income"] = {PERIOD: unit["legacy"]} + benunits[f"b{i}"] = benunit + households[f"h{i}"] = { + "members": names, + "country": {PERIOD: unit["country"]}, + "local_authority": {PERIOD: unit["local_authority"]}, + "region": {PERIOD: unit["region"]}, + "tenure_type": {PERIOD: unit["tenure"]}, + "rent": {PERIOD: unit["rent"]}, + "council_tax": {PERIOD: 1_800}, + "savings": {PERIOD: 0}, + } + return {"people": people, "benunits": benunits, "households": households} + + +BENUNIT = [ + "is_uc_entitled", + "housing_benefit_pension_age_regulations_apply", + "council_tax_reduction_pensioner", + "is_benefit_cap_exempt", + "is_benefit_cap_exempt_health_disability", + "is_benefit_cap_exempt_earnings", + "is_benefit_cap_exempt_other", + "benefit_cap", + "universal_credit", + "housing_benefit", + "council_tax_benefit", + "afcs", + "esa_contrib", +] +HOUSEHOLD = ["council_tax_reduction_household_has_pensioner", "household_net_income"] +BEFORE = [ + "is_benefit_cap_exempt", + "benefit_cap", + "universal_credit", + "housing_benefit", + "council_tax_benefit", + "council_tax_reduction_household_has_pensioner", + "household_net_income", +] + + +def calculate(units, before=False): + simulation = Simulation(situation=situation(units)) + if before: + simulation.apply_reform(before_this_change) + names = BEFORE + else: + names = BENUNIT + HOUSEHOLD + return {name: np.asarray(simulation.calculate(name, PERIOD)) for name in names} + + +def has_pension_age_member(unit): + return any(age >= 67 for age in unit["ages"]) + + +def close(a, b): + return (a == b) or abs(a - b) < 0.01 + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=20)) +def test_benefit_cap_and_ctr_pension_age_invariants(units): + now = calculate(units) + before = calculate(units, before=True) + for i, unit in enumerate(units): + on_uc = bool(now["is_uc_entitled"][i]) + legacy = unit["legacy"] > 0 + # P1 + if on_uc or legacy: + assert not now["housing_benefit_pension_age_regulations_apply"][i], unit + assert not now["council_tax_reduction_pensioner"][i], unit + # P2 + other_exception = ( + now["is_benefit_cap_exempt_health_disability"][i] + or now["is_benefit_cap_exempt_earnings"][i] + or now["afcs"][i] > 0 + or now["esa_contrib"][i] > 0 + ) + if on_uc and not other_exception: + assert not now["is_benefit_cap_exempt"][i], unit + assert np.isfinite(now["benefit_cap"][i]), unit + # P3 + if not has_pension_age_member(unit) or not (on_uc or legacy): + for name in BEFORE: + assert close(now[name][i], before[name][i]), (name, unit) + # P4 + if now["is_benefit_cap_exempt"][i]: + assert before["is_benefit_cap_exempt"][i], unit + assert now["benefit_cap"][i] <= before["benefit_cap"][i], unit + assert now["universal_credit"][i] <= before["universal_credit"][i] + 0.01 + assert now["housing_benefit"][i] <= before["housing_benefit"][i] + 0.01 + if now["council_tax_reduction_household_has_pensioner"][i]: + assert before["council_tax_reduction_household_has_pensioner"][i], unit + # P5 + if unit["country"] in ("WALES", "SCOTLAND"): + assert close( + now["council_tax_benefit"][i], before["council_tax_benefit"][i] + ), unit + + +@PROPERTY_SETTINGS +@given( + st.lists( + st.fixed_dictionaries( + dict( + working_age=WORKING_AGE, + pension_age=PENSION_AGE, + children=st.integers(0, 4), + uc=st.integers(1, 40_000), + child_benefit=st.integers(0, 5_000), + london=st.booleans(), + ) + ), + min_size=1, + max_size=20, + ) +) +def test_a_pension_age_partner_never_exempts_a_uc_award(couples): + # P6: the same couple on the same Universal Credit award, first with two + # working-age members, then with the older member over State Pension age. + def run(older_is_pension_age): + people, benunits, households = {}, {}, {} + for i, c in enumerate(couples): + older = c["pension_age"] if older_is_pension_age else c["working_age"] + names = [f"p{i}_a", f"p{i}_b"] + [ + f"p{i}_c{k}" for k in range(c["children"]) + ] + people[names[0]] = {"age": {PERIOD: older}} + people[names[1]] = {"age": {PERIOD: c["working_age"]}} + for k, name in enumerate(names[2:]): + people[name] = {"age": {PERIOD: 2 + 3 * k}} + benunits[f"b{i}"] = { + "members": names, + "universal_credit_pre_benefit_cap": {PERIOD: c["uc"]}, + "child_benefit": {PERIOD: c["child_benefit"]}, + "uc_deductions": {PERIOD: 0}, + } + households[f"h{i}"] = { + "members": names, + "region": {PERIOD: "LONDON" if c["london"] else "NORTH_WEST"}, + } + simulation = Simulation( + situation={"people": people, "benunits": benunits, "households": households} + ) + return { + name: np.asarray(simulation.calculate(name, PERIOD)) + for name in ["is_benefit_cap_exempt", "benefit_cap_reduction"] + } + + working, mixed = run(False), run(True) + for i, c in enumerate(couples): + assert mixed["is_benefit_cap_exempt"][i] == working["is_benefit_cap_exempt"][i] + assert close( + mixed["benefit_cap_reduction"][i], working["benefit_cap_reduction"][i] + ), c diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py new file mode 100644 index 0000000000..a8adaa8f21 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py @@ -0,0 +1,40 @@ +from policyengine_uk.model_api import * + + +class housing_benefit_pension_age_regulations_apply(Variable): + value_type = bool + entity = BenUnit + label = "Housing Benefit falls under the pension-age regulations" + documentation = ( + "Whether a Housing Benefit claim by this family falls under the Housing " + "Benefit (Persons who have attained the qualifying age for state " + "pension credit) Regulations 2006 rather than the Housing Benefit " + "Regulations 2006. That is the case where the claimant or partner has " + "reached the qualifying age for State Pension Credit and neither of " + "them is on Universal Credit, Income Support, income-based Jobseeker's " + "Allowance or income-related Employment and Support Allowance. The " + "benefit cap (Part 8A of the working-age regulations) does not reach " + "Housing Benefit under the pension-age regulations." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/5", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/5", + "https://www.legislation.gov.uk/nisr/2006/405/regulation/5", + ) + + def formula(benunit, period, parameters): + person = benunit.members + # Every member stands in for the claimant and partner, and is_SP_age + # for the qualifying age for State Pension Credit, as elsewhere in the + # model. + attained_qualifying_age = benunit.any(person("is_SP_age", period)) + # The Universal Credit award is read before the benefit cap, which + # depends on this variable. + on_universal_credit = benunit("is_uc_entitled", period) + on_income_related_benefit = ( + add(benunit, period, ["income_support", "jsa_income", "esa_income"]) > 0 + ) + return ( + attained_qualifying_age & ~on_universal_credit & ~on_income_related_benefit + ) diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py index 0fb1547506..22a3614a48 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py @@ -6,50 +6,24 @@ class is_benefit_cap_exempt_other(Variable): entity = BenUnit label = "Whether exempt from the benefits cap for non-health/disability reasons" definition_period = YEAR - reference = "https://www.gov.uk/benefit-cap/when-youre-not-affected" + reference = ( + "https://www.gov.uk/benefit-cap/when-youre-not-affected", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/79", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/83", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75A", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/5", + ) def formula(benunit, period, parameters): - # Check if anyone in benefit unit is over state pension age - person = benunit.members - over_pension_age = person("is_SP_age", period) - has_pensioner = benunit.any(over_pension_age) - - # UC-specific exemptions - # Limited capability for work and work-related activity - has_lcwra = benunit.any(person("uc_limited_capability_for_WRA", period)) - - # Carer element in UC indicates caring for someone with disability - gets_uc_carer_element = benunit("uc_carer_element", period) > 0 - - # Earnings exemption for UC (£846/month = £10,152/year) - # Note: Only check earned income, not UC amount itself to avoid circular dependency - uc_earned = benunit.sum( - benunit.members("employment_income", period) - + benunit.members("self_employment_income", period) - - benunit.members("income_tax", period) - - benunit.members("national_insurance", period) + # The benefit cap has no age exception. It applies to every Universal + # Credit award (UC Regs 2013 regs 79, 82 and 83), including a mixed-age + # couple's joint award (reg 3(2)(a)), and to Housing Benefit under the + # working-age regulations (HB Regs 2006 Part 8A). Housing Benefit under + # the pension-age regulations, which never apply to a family on + # Universal Credit, has no cap. + pension_age_housing_benefit = benunit( + "housing_benefit_pension_age_regulations_apply", period ) - earnings_threshold = 10_152 - meets_earnings_test = uc_earned >= earnings_threshold - - # Disability and carer benefits that exempt from cap - QUAL_PERSONAL_BENEFITS = [ - "attendance_allowance", - "carers_allowance", - "dla", # Disability Living Allowance (includes components) - "pip_dl", # PIP daily living component - "pip_m", # PIP mobility component - "iidb", # Industrial injuries disability benefit - ] - - # ESA and Working Tax Credit - QUAL_BENUNIT_BENEFITS = [ - "esa_income", # Income-based ESA - "working_tax_credit", # If getting WTC, likely working enough - ] - - qualifying_personal_benefits = add(benunit, period, QUAL_PERSONAL_BENEFITS) - qualifying_benunit_benefits = add(benunit, period, QUAL_BENUNIT_BENEFITS) # Check for Armed Forces Compensation Scheme payments afcs = benunit("afcs", period) > 0 @@ -57,4 +31,4 @@ def formula(benunit, period, parameters): # ESA contribution-based with support component esa_support_component = benunit("esa_contrib", period) > 0 - return has_pensioner | afcs | esa_support_component + return pension_age_housing_benefit | afcs | esa_support_component diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_household_has_pensioner.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_household_has_pensioner.py index 555231a5ab..26931bda3c 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_household_has_pensioner.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_household_has_pensioner.py @@ -4,10 +4,12 @@ class council_tax_reduction_household_has_pensioner(Variable): value_type = bool entity = Household - label = "CTR claimant benefit unit has a pension-age member" + label = "CTR claimant benefit unit is a pensioner" definition_period = YEAR + reference = "https://www.legislation.gov.uk/uksi/2012/2885/regulation/3" def formula(household, period, parameters): person = household.members claimant_benunit = person.benunit("benunit_contains_household_head", period) - return household.any(claimant_benunit & person("is_SP_age", period)) + pensioner = person.benunit("council_tax_reduction_pensioner", period) + return household.any(claimant_benunit & pensioner) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py new file mode 100644 index 0000000000..d514866e57 --- /dev/null +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py @@ -0,0 +1,42 @@ +from policyengine_uk.model_api import * + + +class council_tax_reduction_pensioner(Variable): + value_type = bool + entity = BenUnit + label = "Pensioner for Council Tax Reduction" + documentation = ( + "Whether the family's claim falls under the pension-age Council Tax " + "Reduction rules: the claimant has reached the qualifying age for " + "State Pension Credit, and neither the claimant nor any partner is on " + "Income Support, income-based Jobseeker's Allowance or income-related " + "Employment and Support Allowance, or has an award of Universal Credit. " + "A mixed-age couple on Universal Credit therefore claims under the " + "working-age rules in England, Wales and Scotland." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2012/2885/regulation/3", + "https://www.legislation.gov.uk/wsi/2013/3029/regulation/3", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/12", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/3", + ) + + def formula(benunit, period, parameters): + person = benunit.members + # Any member can be the claimant (the older member of a couple claims), + # and is_SP_age stands in for the qualifying age for State Pension + # Credit, as elsewhere in the model. + attained_qualifying_age = benunit.any(person("is_SP_age", period)) + income_related_benefit = benunit( + "council_tax_reduction_relevant_income_based_benefit", period + ) + # The award before the benefit cap: the cap reduces an award + # (UC Regs 2013 reg 81) rather than removing it. The rules that + # disregard an award held after both members reach the qualifying age + # (SI 2012/2885 reg 3(2); SSI 2021/249 reg 3(2)) are not modelled: + # Universal Credit here needs a working-age adult. + universal_credit_award = benunit("is_uc_entitled", period) + return ( + attained_qualifying_age & ~income_related_benefit & ~universal_credit_award + ) From 48a11ed3cc6ba020f57fc98405bea32f2e2ee412 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 15:17:17 -0400 Subject: [PATCH 2/6] Count a UC award only with a working-age claimant; widen tests after review Universal Credit needs a claimant or partner under the qualifying age for State Pension Credit (UC Regs 2013 reg 3(2)(a)), but is_uc_eligible counts any working-age adult, so the model pays UC to a pensioner whose only younger adult is a qualifying young person. Both new tests now count a UC award only where an is_uc_claimant member is under State Pension age, so such a family keeps the pension-age HB rules (no cap) and pensioner CTR, as on main. Tests: JSA cases move to 2025-26 and use reported JSA; assert Newham amounts; add a Newham non-dependant case, a pensioner with a qualifying young person, and a Cardiff case pinning the cap's effect on Welsh CTR through UC income. The property test adds the pensioner-with-young-person shape, weights draws towards mixed-age UC families, states P1 as an equivalence, and restates P5: Welsh and Scottish CTR changes only through Universal Credit. Co-Authored-By: Claude Opus 5.5 --- .../mixed-age-uc-benefit-cap-ctr.fixed.md | 2 +- .../benefit_cap/benefit_cap_pension_age.yaml | 42 ++++++- .../council_tax_reduction_pensioner.yaml | 111 +++++++++++++++++- ...efit_cap_and_ctr_pension_age_properties.py | 84 ++++++++----- ...g_benefit_pension_age_regulations_apply.py | 21 ++-- .../council_tax_reduction_pensioner.py | 34 ++++-- 6 files changed, 242 insertions(+), 52 deletions(-) diff --git a/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md b/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md index 9c2b848db3..f8774cace4 100644 --- a/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md +++ b/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md @@ -1 +1 @@ -Stop exempting mixed-age couples on Universal Credit from the benefit cap (UC Regs 2013 regs 79, 82 and 83 have no age exception) and stop treating them as pensioners for Council Tax Reduction (SI 2012/2885 reg 3, SI 2013/3029 reg 3, SSI 2021/249 reg 3). The age exemption now follows the Housing Benefit regulation that applies (HB Regs 2006 reg 5), through the new `housing_benefit_pension_age_regulations_apply`; the CTR pensioner test is the new `council_tax_reduction_pensioner`. +Stop exempting mixed-age couples on Universal Credit from the benefit cap (UC Regs 2013 regs 79, 82 and 83 have no age exception), and stop treating them as pensioners for Council Tax Reduction (SI 2012/2885 reg 3, WSI 2013/3029 reg 3, SSI 2021/249 reg 3). The cap's age exemption now follows which Housing Benefit regulations apply (HB Regs 2006 reg 5), through the new `housing_benefit_pension_age_regulations_apply`; the CTR pensioner test is the new `council_tax_reduction_pensioner`. A Universal Credit award counts only where a claimant or partner is under State Pension age, so a pensioner whose only younger adult is a qualifying young person keeps pensioner treatment. CTR amounts change only in England, where such couples move to the local working-age scheme (reported CTR outside the five simulated councils); in Wales and Scotland CTR changes only through the capped Universal Credit counted as income. diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml index 49d45ca586..9e0c44fc0f 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml @@ -108,8 +108,8 @@ - name: A mixed-age couple on income-based JSA has working-age Housing Benefit, which is capped # HB Regs 2006 reg 5(1)(b): the working-age regulations, and so Part 8A, # apply to a claimant over the qualifying age whose partner is on - # income-based JSA. - period: 2026 + # income-based JSA. Income-based JSA ended on 31 March 2026, so 2025-26. + period: 2025 absolute_error_margin: 0.01 input: people: @@ -117,18 +117,19 @@ age: 70 younger: age: 50 + jsa_income_reported: 4_000 benunits: benunit: members: [older, younger] would_claim_uc: false would_claim_housing_benefit: true housing_benefit_pre_benefit_cap: 30_000 - jsa_income: 4_000 households: household: members: [older, younger] region: LONDON output: + jsa_income: 4_000 housing_benefit_pension_age_regulations_apply: false is_benefit_cap_exempt_other: false is_benefit_cap_exempt: false @@ -190,3 +191,38 @@ is_benefit_cap_exempt: false benefit_cap_reduction: 7_677 universal_credit: 22_323 + +- name: A pensioner whose only younger adult is a qualifying young person is not capped + # The model's is_uc_eligible counts any working-age adult, so it pays this + # family Universal Credit. In law Universal Credit needs a claimant or + # partner under the qualifying age (UC Regs 2013 reg 3(2)(a)); the family + # stays under the pension-age Housing Benefit rules, which have no cap. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 70 + is_parent: true + young_person: + age: 18 + current_education: UPPER_SECONDARY + child: + age: 9 + benunits: + benunit: + members: [parent, young_person, child] + universal_credit_pre_benefit_cap: 30_000 + child_benefit: 3_000 + uc_deductions: 0 + households: + household: + members: [parent, young_person, child] + region: LONDON + output: + is_uc_claimant: [true, false, false] + is_uc_entitled: true + housing_benefit_pension_age_regulations_apply: true + is_benefit_cap_exempt_other: true + benefit_cap_reduction: 0 + universal_credit: 30_000 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml index a4bf1acdb7..d6a7dade4c 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml @@ -70,7 +70,8 @@ council_tax_benefit: 2_000 - name: An English mixed-age couple on income-based JSA is not a pensioner couple - period: 2026 + # Income-based JSA ended on 31 March 2026, so 2025-26. + period: 2025 absolute_error_margin: 0.01 input: people: @@ -79,11 +80,11 @@ council_tax_benefit_reported: 700 younger: age: 50 + jsa_income_reported: 4_000 benunits: benunit: members: [older, younger] would_claim_uc: false - jsa_income: 4_000 households: household: members: [older, younger] @@ -92,6 +93,7 @@ council_tax: 2_000 savings: 0 output: + jsa_income: 4_000 council_tax_reduction_pensioner: false council_tax_reduction_household_has_pensioner: false council_tax_benefit: 700 @@ -143,6 +145,75 @@ output: council_tax_reduction_household_has_pensioner: false council_tax_reduction_scheme_supported: true + # Newham's scheme on the Universal Credit route. The pensioner scheme + # would give the full 2,000 (income 10,000 is below the pension-age + # applicable amount). + newham_council_tax_reduction: 900.91 + council_tax_benefit: 900.91 + +- name: Newham's working-age non-dependant deduction applies to a mixed-age couple on UC + # The couple (the claimant benefit unit, with the oldest member) lives with + # a working son in his own benefit unit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + younger: + age: 50 + son: + age: 30 + employment_income: 30_000 + benunits: + couple: + members: [older, younger] + universal_credit_pre_benefit_cap: 10_000 + son: + members: [son] + households: + household: + members: [older, younger, son] + country: ENGLAND + local_authority: NEWHAM + council_tax: 2_000 + savings: 0 + output: + benunit_contains_household_head: [true, false] + council_tax_reduction_pensioner: [false, false] + council_tax_reduction_household_has_pensioner: false + newham_council_tax_reduction_non_dep_deductions: [392.08, 0] + council_tax_benefit: [508.83, 0] + +- name: A pensioner whose only younger adult is a qualifying young person stays a pensioner + # The model pays this family Universal Credit because is_uc_eligible counts + # any working-age adult. Universal Credit needs a claimant or partner under + # the qualifying age, so the award does not count against pensioner status. + period: 2026 + input: + people: + parent: + age: 70 + is_parent: true + young_person: + age: 18 + current_education: UPPER_SECONDARY + benunits: + benunit: + members: [parent, young_person] + universal_credit_pre_benefit_cap: 10_000 + households: + household: + members: [parent, young_person] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + is_uc_entitled: true + council_tax_reduction_pensioner: true + council_tax_reduction_household_has_pensioner: true + council_tax_reduction_scheme_supported: true - name: A Welsh mixed-age couple on Universal Credit is not a pensioner couple # Wales has one national scheme, so the scheme is still simulated. @@ -192,3 +263,39 @@ council_tax_reduction_pensioner: false council_tax_reduction_household_has_pensioner: false council_tax_reduction_scheme_supported: true + +- name: In Wales the cap reaches Council Tax Reduction through Universal Credit income + # The Welsh formula does not route on pensioner status, but it counts + # Universal Credit as income. Capping a mixed-age couple's award (cap + # 22,020 outside London; 30,000 + 3,000 - 22,020 = 10,980) lowers that + # income and so raises the reduction. Without the cap Universal Credit of + # 30,000 would take income far above the applicable amount. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + younger: + age: 50 + child: + age: 9 + benunits: + benunit: + members: [older, younger, child] + universal_credit_pre_benefit_cap: 30_000 + child_benefit: 3_000 + uc_deductions: 0 + households: + household: + members: [older, younger, child] + country: WALES + local_authority: CARDIFF + region: WALES + council_tax: 2_000 + savings: 0 + output: + benefit_cap_reduction: 10_980 + universal_credit: 19_020 + council_tax_reduction_applicable_income: 22_020 + council_tax_benefit: 1_582.84 diff --git a/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py index 91667e74f1..39c08c1b28 100644 --- a/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py +++ b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py @@ -16,11 +16,19 @@ Universal Credit (England SI 2012/2885 reg 3(1); Wales SI 2013/3029 reg 3(1); Scotland SSI 2021/249 reg 3 and SSI 2012/319 reg 12). +Universal Credit needs a claimant or partner under the qualifying age (UC +Regs 2013 reg 3(2)(a)). The model's is_uc_eligible counts any working-age +adult, so it can pay Universal Credit to a pensioner whose only younger adult +is a qualifying young person; such an award does not count as being on +Universal Credit here. "On Universal Credit" below means an award before the +cap with a claimant or partner under State Pension age. Legacy income-related +benefit is set as an input. + Invariants, for every drawn population of families: -P1 Pension-age Housing Benefit regulations never apply to a family on - Universal Credit or a legacy income-related benefit, and the CTR - pensioner test never passes for one. +P1 Both new tests hold exactly when a claimant or partner is over State + Pension age and the family is neither on Universal Credit nor on a legacy + income-related benefit. P2 Every Universal Credit award is capped unless a non-age exception applies: such a family has a finite benefit cap. P3 Differential against the formulas before this change. A family with no @@ -31,8 +39,10 @@ P4 The change only removes exemptions: exempt now implies exempt before, the cap is never higher, and Universal Credit and Housing Benefit never rise. A CTR pensioner now was a CTR pensioner before. -P5 Scotland and Wales have one national scheme for all ages, so their Council - Tax Reduction does not change. +P5 The model's Welsh and Scottish CTR formulas do not route on pensioner + status, but they count Universal Credit as income. So their CTR changes + only through Universal Credit: unchanged where Universal Credit is + unchanged, and never lower where the cap has lowered Universal Credit. P6 Metamorphic: with a Universal Credit award, adding a member over State Pension age to a couple's ages (making it mixed-age) never exempts it from the cap. @@ -48,20 +58,26 @@ PERIOD = 2026 PROPERTY_SETTINGS = settings( - max_examples=12, + max_examples=15, deadline=None, derandomize=True, suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], ) PENSION_AGE = st.integers(67, 95) WORKING_AGE = st.integers(25, 60) +# Qualifying young people are 18: at 19 the UC definition also needs the +# course to have started before 19, which is a separate input. +YOUNG_PERSON = st.just(18) SHAPES = { - "single_pension": [PENSION_AGE], - "couple_pension": [PENSION_AGE, PENSION_AGE], - "mixed_age": [PENSION_AGE, WORKING_AGE], - "single_working": [WORKING_AGE], - "couple_working": [WORKING_AGE, WORKING_AGE], + "single_pension": [("adult", PENSION_AGE)], + "couple_pension": [("adult", PENSION_AGE), ("adult", PENSION_AGE)], + "mixed_age": [("adult", PENSION_AGE), ("adult", WORKING_AGE)], + "single_working": [("adult", WORKING_AGE)], + "couple_working": [("adult", WORKING_AGE), ("adult", WORKING_AGE)], + "pension_with_young_person": [("adult", PENSION_AGE), ("young", YOUNG_PERSON)], } +# Weighted towards the families this change is about. +SHAPE_DRAWS = sorted(SHAPES) + ["mixed_age", "mixed_age", "pension_with_young_person"] PLACES = [ ("ENGLAND", "MAIDSTONE", "SOUTH_EAST"), ("ENGLAND", "NEWHAM", "LONDON"), @@ -108,12 +124,12 @@ def apply(self): @st.composite def families(draw): - shape = draw(st.sampled_from(sorted(SHAPES))) + shape = draw(st.sampled_from(SHAPE_DRAWS)) country, local_authority, region = draw(st.sampled_from(PLACES)) tenure = draw(st.sampled_from(TENURES)) return dict( shape=shape, - ages=[draw(age) for age in SHAPES[shape]], + members=[(role, draw(age)) for role, age in SHAPES[shape]], children=draw(st.integers(0, 4)), country=country, local_authority=local_authority, @@ -122,7 +138,7 @@ def families(draw): rent=0 if tenure == "OWNED_OUTRIGHT" else draw(st.integers(0, 30_000)), earnings=draw(st.sampled_from([0, 0, 5_000, 25_000])), state_pension=draw(st.sampled_from([0, 9_000, 12_000])), - would_claim_uc=draw(st.booleans()), + would_claim_uc=draw(st.sampled_from([True, True, False])), hb_reported=draw(st.sampled_from([0, 0, 5_000])), ctb_reported=draw(st.sampled_from([0, 0, 700])), legacy=draw(st.sampled_from([0, 0, 0, 3_000])), @@ -133,13 +149,18 @@ def situation(units): people, benunits, households = {}, {}, {} for i, unit in enumerate(units): names = [] - for j, age in enumerate(unit["ages"]): + has_young_person = any(role == "young" for role, _ in unit["members"]) + for j, (role, age) in enumerate(unit["members"]): name = f"p{i}_{j}" person = {"age": {PERIOD: age}} - if age >= 67: + if role == "young": + person["current_education"] = {PERIOD: "UPPER_SECONDARY"} + elif age >= 67: person["state_pension_reported"] = {PERIOD: unit["state_pension"]} else: person["employment_income"] = {PERIOD: unit["earnings"]} + if role == "adult" and has_young_person: + person["is_parent"] = {PERIOD: True} if j == 0: person["housing_benefit_reported"] = {PERIOD: unit["hb_reported"]} person["council_tax_benefit_reported"] = {PERIOD: unit["ctb_reported"]} @@ -211,8 +232,12 @@ def calculate(units, before=False): return {name: np.asarray(simulation.calculate(name, PERIOD)) for name in names} -def has_pension_age_member(unit): - return any(age >= 67 for age in unit["ages"]) +def has_pension_age_claimant(unit): + return any(role == "adult" and age >= 67 for role, age in unit["members"]) + + +def has_working_age_claimant(unit): + return any(role == "adult" and age < 67 for role, age in unit["members"]) def close(a, b): @@ -220,17 +245,17 @@ def close(a, b): @PROPERTY_SETTINGS -@given(st.lists(families(), min_size=1, max_size=20)) +@given(st.lists(families(), min_size=10, max_size=30)) def test_benefit_cap_and_ctr_pension_age_invariants(units): now = calculate(units) before = calculate(units, before=True) for i, unit in enumerate(units): - on_uc = bool(now["is_uc_entitled"][i]) + on_uc = bool(now["is_uc_entitled"][i]) and has_working_age_claimant(unit) legacy = unit["legacy"] > 0 # P1 - if on_uc or legacy: - assert not now["housing_benefit_pension_age_regulations_apply"][i], unit - assert not now["council_tax_reduction_pensioner"][i], unit + expected = has_pension_age_claimant(unit) and not (on_uc or legacy) + assert now["housing_benefit_pension_age_regulations_apply"][i] == expected + assert now["council_tax_reduction_pensioner"][i] == expected, unit # P2 other_exception = ( now["is_benefit_cap_exempt_health_disability"][i] @@ -242,7 +267,7 @@ def test_benefit_cap_and_ctr_pension_age_invariants(units): assert not now["is_benefit_cap_exempt"][i], unit assert np.isfinite(now["benefit_cap"][i]), unit # P3 - if not has_pension_age_member(unit) or not (on_uc or legacy): + if not has_pension_age_claimant(unit) or not (on_uc or legacy): for name in BEFORE: assert close(now[name][i], before[name][i]), (name, unit) # P4 @@ -255,9 +280,14 @@ def test_benefit_cap_and_ctr_pension_age_invariants(units): assert before["council_tax_reduction_household_has_pensioner"][i], unit # P5 if unit["country"] in ("WALES", "SCOTLAND"): - assert close( - now["council_tax_benefit"][i], before["council_tax_benefit"][i] - ), unit + ctr_now, ctr_before = ( + now["council_tax_benefit"][i], + before["council_tax_benefit"][i], + ) + if close(now["universal_credit"][i], before["universal_credit"][i]): + assert close(ctr_now, ctr_before), unit + else: + assert ctr_now >= ctr_before - 0.01, unit @PROPERTY_SETTINGS diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py index a8adaa8f21..e15a75f511 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py @@ -25,13 +25,20 @@ class housing_benefit_pension_age_regulations_apply(Variable): def formula(benunit, period, parameters): person = benunit.members - # Every member stands in for the claimant and partner, and is_SP_age - # for the qualifying age for State Pension Credit, as elsewhere in the - # model. - attained_qualifying_age = benunit.any(person("is_SP_age", period)) - # The Universal Credit award is read before the benefit cap, which - # depends on this variable. - on_universal_credit = benunit("is_uc_entitled", period) + claimant_or_partner = person("is_uc_claimant", period) + # is_SP_age stands in for the qualifying age for State Pension Credit, + # as elsewhere in the model. + over_qualifying_age = person("is_SP_age", period) + attained_qualifying_age = benunit.any(claimant_or_partner & over_qualifying_age) + # Universal Credit needs a claimant or partner under the qualifying age + # (WRA 2012 s.4(1)(b); UC Regs 2013 reg 3(2)(a)). is_uc_eligible counts + # any working-age adult, so the model can pay Universal Credit to a + # pensioner whose only younger adult is a qualifying young person; + # that award does not take the family out of the pension-age rules. + # The award is read before the benefit cap, which depends on this + # variable. + working_age_claimant = benunit.any(claimant_or_partner & ~over_qualifying_age) + on_universal_credit = benunit("is_uc_entitled", period) & working_age_claimant on_income_related_benefit = ( add(benunit, period, ["income_support", "jsa_income", "esa_income"]) > 0 ) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py index d514866e57..b1d2e378bb 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py @@ -11,32 +11,42 @@ class council_tax_reduction_pensioner(Variable): "State Pension Credit, and neither the claimant nor any partner is on " "Income Support, income-based Jobseeker's Allowance or income-related " "Employment and Support Allowance, or has an award of Universal Credit. " - "A mixed-age couple on Universal Credit therefore claims under the " - "working-age rules in England, Wales and Scotland." + "A mixed-age couple on Universal Credit is therefore not a pensioner in " + "England, Wales or Scotland. The model routes English schemes on this " + "test; the Welsh and Scottish formulas do not yet use it." ) definition_period = YEAR reference = ( "https://www.legislation.gov.uk/uksi/2012/2885/regulation/3", "https://www.legislation.gov.uk/wsi/2013/3029/regulation/3", - "https://www.legislation.gov.uk/ssi/2012/319/regulation/12", "https://www.legislation.gov.uk/ssi/2021/249/regulation/3", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/12", + "https://www.legislation.gov.uk/ssi/2012/303/regulation/12", ) def formula(benunit, period, parameters): person = benunit.members - # Any member can be the claimant (the older member of a couple claims), - # and is_SP_age stands in for the qualifying age for State Pension - # Credit, as elsewhere in the model. - attained_qualifying_age = benunit.any(person("is_SP_age", period)) + claimant_or_partner = person("is_uc_claimant", period) + # Either member of a couple can claim, so the older one can claim as a + # pensioner. is_SP_age stands in for the qualifying age for State + # Pension Credit, as elsewhere in the model. + over_qualifying_age = person("is_SP_age", period) + attained_qualifying_age = benunit.any(claimant_or_partner & over_qualifying_age) income_related_benefit = benunit( "council_tax_reduction_relevant_income_based_benefit", period ) # The award before the benefit cap: the cap reduces an award - # (UC Regs 2013 reg 81) rather than removing it. The rules that - # disregard an award held after both members reach the qualifying age - # (SI 2012/2885 reg 3(2); SSI 2021/249 reg 3(2)) are not modelled: - # Universal Credit here needs a working-age adult. - universal_credit_award = benunit("is_uc_entitled", period) + # (UC Regs 2013 reg 81) rather than removing it. An award counts only + # where a claimant or partner is under the qualifying age, which + # Universal Credit needs (UC Regs 2013 reg 3(2)(a)); is_uc_eligible + # counts any working-age adult, including a qualifying young person. + # This also stands in for the rules that disregard an award held after + # both members reach the qualifying age (SI 2012/2885 reg 3(2); + # WSI 2013/3029 reg 3(2); SSI 2021/249 reg 3(2)). + working_age_claimant = benunit.any(claimant_or_partner & ~over_qualifying_age) + universal_credit_award = ( + benunit("is_uc_entitled", period) & working_age_claimant + ) return ( attained_qualifying_age & ~income_related_benefit & ~universal_credit_award ) From efd9dd7effb7f4c9caa2bf3a17f028fd139a37f0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 16:26:24 -0400 Subject: [PATCH 3/6] Cite SI 2014/1230 reg 60C as the cap's only age exception Review found the cap does have one age-based exception: SI 2014/1230 reg 60C (NI: SR 2016/226 reg 61C) disapplies UC Regs reg 79 for a claim where every claimant has reached the qualifying age (tax credit migrants under reg 60A). It never covers a mixed-age couple, so no result changes; the comments now say so. Also attribute the April 2026 end of income-based JSA to the model's parameter rather than stating it as law. Co-Authored-By: Claude Opus 5.5 --- .../dwp/benefit_cap/benefit_cap_pension_age.yaml | 9 ++++++--- .../council_tax_reduction_pensioner.yaml | 3 ++- ...t_benefit_cap_and_ctr_pension_age_properties.py | 8 +++++--- ...ousing_benefit_pension_age_regulations_apply.py | 10 ++++++---- .../gov/dwp/is_benefit_cap_exempt_other.py | 14 +++++++++----- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml index 9e0c44fc0f..7e1fac946f 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml @@ -1,7 +1,9 @@ # The benefit cap and State Pension age. # -# The Universal Credit cap (UC Regs 2013 regs 79, 82 and 83) has no age -# exception, so it applies to a mixed-age couple's joint award (reg 3(2)(a)). +# The Universal Credit cap (UC Regs 2013 regs 79, 82 and 83) turns on earnings +# and benefit receipt, not age, so it applies to a mixed-age couple's joint +# award (reg 3(2)(a)). The one age-based exception, SI 2014/1230 reg 60C, +# covers only claims where every claimant has reached the qualifying age. # The Housing Benefit cap (HB Regs 2006 Part 8A) reaches only Housing Benefit # under the working-age regulations, which by reg 5 cover a claimant or partner # over the qualifying age for State Pension Credit only while either of them @@ -108,7 +110,8 @@ - name: A mixed-age couple on income-based JSA has working-age Housing Benefit, which is capped # HB Regs 2006 reg 5(1)(b): the working-age regulations, and so Part 8A, # apply to a claimant over the qualifying age whose partner is on - # income-based JSA. Income-based JSA ended on 31 March 2026, so 2025-26. + # income-based JSA. The model stops paying income-based JSA from April 2026 + # (gov.dwp.JSA.income.active), so this case is in 2025-26. period: 2025 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml index d6a7dade4c..d003776c67 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml @@ -70,7 +70,8 @@ council_tax_benefit: 2_000 - name: An English mixed-age couple on income-based JSA is not a pensioner couple - # Income-based JSA ended on 31 March 2026, so 2025-26. + # The model stops paying income-based JSA from April 2026 + # (gov.dwp.JSA.income.active), so this case is in 2025-26. period: 2025 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py index 39c08c1b28..aaa4b01951 100644 --- a/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py +++ b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py @@ -3,9 +3,11 @@ The law: -- The Universal Credit benefit cap has no age exception (UC Regs 2013 regs - 79, 82 and 83). A mixed-age couple can claim Universal Credit jointly (reg - 3(2)(a)), and the cap applies to that award. +- The Universal Credit benefit cap turns on earnings and benefit receipt, not + age (UC Regs 2013 regs 79, 82 and 83). A mixed-age couple can claim + Universal Credit jointly (reg 3(2)(a)), and the cap applies to that award. + The one age-based exception, SI 2014/1230 reg 60C, covers only claims where + every claimant has reached the qualifying age for State Pension Credit. - The Housing Benefit cap (HB Regs 2006 Part 8A) reaches only Housing Benefit under the working-age regulations. By reg 5 those cover a claimant or partner over the qualifying age for State Pension Credit only while either diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py index e15a75f511..bd1ea71037 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py @@ -31,10 +31,12 @@ def formula(benunit, period, parameters): over_qualifying_age = person("is_SP_age", period) attained_qualifying_age = benunit.any(claimant_or_partner & over_qualifying_age) # Universal Credit needs a claimant or partner under the qualifying age - # (WRA 2012 s.4(1)(b); UC Regs 2013 reg 3(2)(a)). is_uc_eligible counts - # any working-age adult, so the model can pay Universal Credit to a - # pensioner whose only younger adult is a qualifying young person; - # that award does not take the family out of the pension-age rules. + # (WRA 2012 s.4(1)(b); UC Regs 2013 reg 3(2)(a)), except for tax credit + # migrants (SI 2014/1230 reg 60A), whose award is exempt from the cap + # (reg 60C). is_uc_eligible counts any working-age adult, so the model + # can pay Universal Credit to a pensioner whose only younger adult is + # a qualifying young person; that award does not take the family out + # of the pension-age rules. # The award is read before the benefit cap, which depends on this # variable. working_age_claimant = benunit.any(claimant_or_partner & ~over_qualifying_age) diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py index 22a3614a48..df02432607 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py @@ -10,16 +10,20 @@ class is_benefit_cap_exempt_other(Variable): "https://www.gov.uk/benefit-cap/when-youre-not-affected", "https://www.legislation.gov.uk/uksi/2013/376/regulation/79", "https://www.legislation.gov.uk/uksi/2013/376/regulation/83", + "https://www.legislation.gov.uk/uksi/2014/1230/regulation/60C", "https://www.legislation.gov.uk/uksi/2006/213/regulation/75A", "https://www.legislation.gov.uk/uksi/2006/213/regulation/5", ) def formula(benunit, period, parameters): - # The benefit cap has no age exception. It applies to every Universal - # Credit award (UC Regs 2013 regs 79, 82 and 83), including a mixed-age - # couple's joint award (reg 3(2)(a)), and to Housing Benefit under the - # working-age regulations (HB Regs 2006 Part 8A). Housing Benefit under - # the pension-age regulations, which never apply to a family on + # The benefit cap applies to a Universal Credit award whatever the + # claimants' ages (UC Regs 2013 regs 79, 82 and 83), including a + # mixed-age couple's joint award (reg 3(2)(a)). The one age-based + # exception, SI 2014/1230 reg 60C (NI: SR 2016/226 reg 61C), covers + # only claims where every claimant has reached the qualifying age for + # State Pension Credit. The cap also applies to Housing Benefit under + # the working-age regulations (HB Regs 2006 Part 8A). Housing Benefit + # under the pension-age regulations, which never apply to a family on # Universal Credit, has no cap. pension_age_housing_benefit = benunit( "housing_benefit_pension_age_regulations_apply", period From d4206978dda205da22fb6363426e71f0947f8d16 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 18:18:39 -0400 Subject: [PATCH 4/6] Qualify the young-person guard and the Welsh case after independent review - The guard reads is_uc_claimant, which treats an 18-year-old as a dependant only when is_parent is set (as in the FRS datasets). Say so in the changelog and comments, and pin the no-is_parent result in YAML. - housing_benefit_pension_age_regulations_apply: note that treating a UC award with no working-age claimant as "not on UC" is right for the cap (reg 60C) but not for HB reg 5 itself. - council_tax_reduction_pensioner: "claimant or partner" in the docs; note that Scotland, unlike England and Wales, has no reg 60A disregard. - Cardiff YAML: the value is the model's pension-age formula, not the law. Co-Authored-By: Claude Opus 5.5 --- .../mixed-age-uc-benefit-cap-ctr.fixed.md | 2 +- .../benefit_cap/benefit_cap_pension_age.yaml | 35 +++++++++++++++++++ .../council_tax_reduction_pensioner.yaml | 11 +++--- ...g_benefit_pension_age_regulations_apply.py | 10 ++++-- .../council_tax_reduction_pensioner.py | 10 ++++-- 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md b/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md index f8774cace4..af55ca9b40 100644 --- a/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md +++ b/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md @@ -1 +1 @@ -Stop exempting mixed-age couples on Universal Credit from the benefit cap (UC Regs 2013 regs 79, 82 and 83 have no age exception), and stop treating them as pensioners for Council Tax Reduction (SI 2012/2885 reg 3, WSI 2013/3029 reg 3, SSI 2021/249 reg 3). The cap's age exemption now follows which Housing Benefit regulations apply (HB Regs 2006 reg 5), through the new `housing_benefit_pension_age_regulations_apply`; the CTR pensioner test is the new `council_tax_reduction_pensioner`. A Universal Credit award counts only where a claimant or partner is under State Pension age, so a pensioner whose only younger adult is a qualifying young person keeps pensioner treatment. CTR amounts change only in England, where such couples move to the local working-age scheme (reported CTR outside the five simulated councils); in Wales and Scotland CTR changes only through the capped Universal Credit counted as income. +Stop exempting mixed-age couples on Universal Credit from the benefit cap (UC Regs 2013 regs 79, 82 and 83 have no age exception), and stop treating them as pensioners for Council Tax Reduction (SI 2012/2885 reg 3, WSI 2013/3029 reg 3, SSI 2021/249 reg 3). The cap's age exemption now follows which Housing Benefit regulations apply (HB Regs 2006 reg 5), through the new `housing_benefit_pension_age_regulations_apply`; the CTR pensioner test is the new `council_tax_reduction_pensioner`. A Universal Credit award counts only where a claimant or partner is under State Pension age, so a pensioner whose only younger adult is a qualifying young person keeps pensioner treatment where relationships identify the parent (`is_parent`), as the FRS datasets do. CTR amounts change only in England, where such couples move to the local working-age scheme (reported CTR outside the five simulated councils); in Wales and Scotland CTR changes only through the capped Universal Credit counted as income. diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml index 7e1fac946f..61d549e400 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml @@ -229,3 +229,38 @@ is_benefit_cap_exempt_other: true benefit_cap_reduction: 0 universal_credit: 30_000 + +- name: Without is_parent the young person counts as a claimant, so the award is capped + # is_uc_claimant recognises a qualifying young person as a dependant only + # when a parent is identified (is_parent), as the FRS datasets do. Without + # it the model treats the 18-year-old as a partner under State Pension age, + # so the award counts as a working-age Universal Credit award and is + # capped. This pins that dependency; see the case above for the dependant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 70 + young_person: + age: 18 + current_education: UPPER_SECONDARY + child: + age: 9 + benunits: + benunit: + members: [parent, young_person, child] + universal_credit_pre_benefit_cap: 30_000 + child_benefit: 3_000 + uc_deductions: 0 + households: + household: + members: [parent, young_person, child] + region: LONDON + output: + is_uc_claimant: [true, true, false] + housing_benefit_pension_age_regulations_apply: false + is_benefit_cap_exempt: false + # 30,000 + 3,000 - 25,323 + benefit_cap_reduction: 7_677 + universal_credit: 22_323 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml index d003776c67..6a9e609fa1 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml @@ -266,11 +266,14 @@ council_tax_reduction_scheme_supported: true - name: In Wales the cap reaches Council Tax Reduction through Universal Credit income - # The Welsh formula does not route on pensioner status, but it counts - # Universal Credit as income. Capping a mixed-age couple's award (cap - # 22,020 outside London; 30,000 + 3,000 - 22,020 = 10,980) lowers that + # The model's Welsh formula does not route on pensioner status, but it + # counts Universal Credit as income. Capping a mixed-age couple's award + # (cap 22,020 outside London; 30,000 + 3,000 - 22,020 = 10,980) lowers that # income and so raises the reduction. Without the cap Universal Credit of - # 30,000 would take income far above the applicable amount. + # 30,000 would take income far above the applicable amount. 1,582.84 is the + # model's pension-age formula, not the law: the Welsh route for Universal + # Credit applicants (WSI 2013/3029 Sch 6 paras 3 and 9) would give this + # family the maximum reduction, which the model does not yet implement. period: 2026 absolute_error_margin: 0.01 input: diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py index bd1ea71037..fc22f264d7 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py @@ -14,7 +14,12 @@ class housing_benefit_pension_age_regulations_apply(Variable): "them is on Universal Credit, Income Support, income-based Jobseeker's " "Allowance or income-related Employment and Support Allowance. The " "benefit cap (Part 8A of the working-age regulations) does not reach " - "Housing Benefit under the pension-age regulations." + "Housing Benefit under the pension-age regulations. A Universal Credit " + "award counts only where a claimant or partner is under the qualifying " + "age. That is right for the cap, which SI 2014/1230 reg 60C disapplies " + "for a claim where every claimant has reached that age, but not for " + "reg 5 itself, under which such an award would bring in the " + "working-age regulations; the model has no such awards." ) definition_period = YEAR reference = ( @@ -36,7 +41,8 @@ def formula(benunit, period, parameters): # (reg 60C). is_uc_eligible counts any working-age adult, so the model # can pay Universal Credit to a pensioner whose only younger adult is # a qualifying young person; that award does not take the family out - # of the pension-age rules. + # of the pension-age rules. is_uc_claimant recognises the young person + # as a dependant only when is_parent is set, as in the FRS datasets. # The award is read before the benefit cap, which depends on this # variable. working_age_claimant = benunit.any(claimant_or_partner & ~over_qualifying_age) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py index b1d2e378bb..7e5f9f6997 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py @@ -7,8 +7,8 @@ class council_tax_reduction_pensioner(Variable): label = "Pensioner for Council Tax Reduction" documentation = ( "Whether the family's claim falls under the pension-age Council Tax " - "Reduction rules: the claimant has reached the qualifying age for " - "State Pension Credit, and neither the claimant nor any partner is on " + "Reduction rules: the claimant or partner has reached the qualifying " + "age for State Pension Credit, and neither of them is on " "Income Support, income-based Jobseeker's Allowance or income-related " "Employment and Support Allowance, or has an award of Universal Credit. " "A mixed-age couple on Universal Credit is therefore not a pensioner in " @@ -42,7 +42,11 @@ def formula(benunit, period, parameters): # counts any working-age adult, including a qualifying young person. # This also stands in for the rules that disregard an award held after # both members reach the qualifying age (SI 2012/2885 reg 3(2); - # WSI 2013/3029 reg 3(2); SSI 2021/249 reg 3(2)). + # WSI 2013/3029 reg 3(2); SSI 2021/249 reg 3(2)). England and Wales + # also disregard a tax credit migrant's award (UC (TP) Regs 2014 reg + # 60A) and Scotland does not; the model has no such awards. The guard + # relies on is_uc_claimant, which needs is_parent to recognise a + # qualifying young person as a dependant. working_age_claimant = benunit.any(claimant_or_partner & ~over_qualifying_age) universal_credit_award = ( benunit("is_uc_entitled", period) & working_age_claimant From 40207cb6cc891a918a17e99a6831a2dba48395e6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 18:21:39 -0400 Subject: [PATCH 5/6] Correct two comments flagged in the review confirmation The model does pay UC with no working-age claimant (the any-adult test), so say those are not awards in law rather than that none exist; and date the Welsh reg 60A disregard to schemes from 2026-27. Co-Authored-By: Claude Opus 5.5 --- .../housing_benefit_pension_age_regulations_apply.py | 5 ++++- .../council_tax_reduction_pensioner.py | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py index fc22f264d7..7d7ded13af 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py @@ -19,7 +19,10 @@ class housing_benefit_pension_age_regulations_apply(Variable): "age. That is right for the cap, which SI 2014/1230 reg 60C disapplies " "for a claim where every claimant has reached that age, but not for " "reg 5 itself, under which such an award would bring in the " - "working-age regulations; the model has no such awards." + "working-age regulations. The model has no lawful such awards (tax " + "credit migrants under SI 2014/1230 reg 60A are not modelled); the " + "ones it pays through is_uc_eligible's any-adult test are not awards " + "in law, so the pension-age regulations are the right result." ) definition_period = YEAR reference = ( diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py index 7e5f9f6997..8243dcc34b 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py @@ -42,9 +42,10 @@ def formula(benunit, period, parameters): # counts any working-age adult, including a qualifying young person. # This also stands in for the rules that disregard an award held after # both members reach the qualifying age (SI 2012/2885 reg 3(2); - # WSI 2013/3029 reg 3(2); SSI 2021/249 reg 3(2)). England and Wales - # also disregard a tax credit migrant's award (UC (TP) Regs 2014 reg - # 60A) and Scotland does not; the model has no such awards. The guard + # WSI 2013/3029 reg 3(2); SSI 2021/249 reg 3(2)). England, and Wales + # for schemes from 2026-27, also disregard a tax credit migrant's award + # (UC (TP) Regs 2014 reg 60A) and Scotland does not; the model does not + # model those migrants. The guard # relies on is_uc_claimant, which needs is_parent to recognise a # qualifying young person as a dependant. working_age_claimant = benunit.any(claimant_or_partner & ~over_qualifying_age) From c1984183e90e00b8760c41f39d85c1cb44f15d6d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 12:49:35 -0400 Subject: [PATCH 6/6] Read the claimant and partner from is_claimant_or_partner for the cap and CTR pensioner tests Vahid Ahmadi's review of #1944 (should-fix 1): the "UC award with a working-age claimant or partner" guard in housing_benefit_pension_age_regulations_apply and council_tax_reduction_pensioner now reads #1896's is_claimant_or_partner. It presumes an 18- or 19-year-old living with a pensioner to be their child, so a pensioner aged 70 with an 18-year-old in education and no is_parent is not capped and keeps pensioner CTR. The YAML case that pinned the old result now expects no cap, the reviewer's household is a new CTR case, and the property test draws is_parent on and off. Nit 2: the HB variable reuses council_tax_reduction_relevant_income_based_benefit (same three benefits). Nit 3: the changelog says household calculations for a mixed-age couple on UC in England outside the five simulated councils now show CTR of 0 unless a reported amount is entered (checked on this head). #1943's mixed-age case in uc_state_pension.yaml now expects the working-age route (reported CTR, 0) under SI 2012/2885 reg 3. Co-Authored-By: Claude Opus 5.5 --- .../mixed-age-uc-benefit-cap-ctr.fixed.md | 2 +- .../income/uc_state_pension.yaml | 16 ++++--- .../benefit_cap/benefit_cap_pension_age.yaml | 34 +++++++------- .../council_tax_reduction_pensioner.yaml | 44 +++++++++++++++++-- ...efit_cap_and_ctr_pension_age_properties.py | 17 ++++--- ...g_benefit_pension_age_regulations_apply.py | 28 ++++++------ .../council_tax_reduction_pensioner.py | 13 +++--- 7 files changed, 101 insertions(+), 53 deletions(-) diff --git a/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md b/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md index af55ca9b40..0757e315c3 100644 --- a/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md +++ b/changelog.d/mixed-age-uc-benefit-cap-ctr.fixed.md @@ -1 +1 @@ -Stop exempting mixed-age couples on Universal Credit from the benefit cap (UC Regs 2013 regs 79, 82 and 83 have no age exception), and stop treating them as pensioners for Council Tax Reduction (SI 2012/2885 reg 3, WSI 2013/3029 reg 3, SSI 2021/249 reg 3). The cap's age exemption now follows which Housing Benefit regulations apply (HB Regs 2006 reg 5), through the new `housing_benefit_pension_age_regulations_apply`; the CTR pensioner test is the new `council_tax_reduction_pensioner`. A Universal Credit award counts only where a claimant or partner is under State Pension age, so a pensioner whose only younger adult is a qualifying young person keeps pensioner treatment where relationships identify the parent (`is_parent`), as the FRS datasets do. CTR amounts change only in England, where such couples move to the local working-age scheme (reported CTR outside the five simulated councils); in Wales and Scotland CTR changes only through the capped Universal Credit counted as income. +Stop exempting mixed-age couples on Universal Credit from the benefit cap (UC Regs 2013 regs 79, 82 and 83 have no age exception), and stop treating them as pensioners for Council Tax Reduction (SI 2012/2885 reg 3, WSI 2013/3029 reg 3, SSI 2021/249 reg 3). The cap's age exemption now follows which Housing Benefit regulations apply (HB Regs 2006 reg 5), through the new `housing_benefit_pension_age_regulations_apply`; the CTR pensioner test is the new `council_tax_reduction_pensioner`. Both read the claimant and partner (`is_claimant_or_partner`), and a Universal Credit award counts only where one of them is under State Pension age, so a pensioner whose only younger member is an 18- or 19-year-old dependant keeps pensioner treatment, with or without `is_parent`. CTR amounts change only in England, where such couples move to the local working-age scheme; in Wales and Scotland CTR changes only through the capped Universal Credit counted as income. Outside the five English councils the model simulates (Newham, Merton, Kingston upon Thames, Westminster and Oxford), the working-age scheme uses reported CTR, so a household calculation for a mixed-age couple on Universal Credit in the rest of England now shows CTR of £0 unless a reported amount is entered. diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_state_pension.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_state_pension.yaml index c88a124cb8..456c6dbf9a 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_state_pension.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_state_pension.yaml @@ -78,12 +78,16 @@ uc_income_reduction: 9_000 # 14,243.64 - 9,000. Before this fix the award was 14,243.64. universal_credit: 5_243.64 - # The pension-age CTR income is State Pension plus UC: - # 9,000 + 5,243.64 = 14,243.64, below the applicable amount of - # 383.35 x 52 = 19,934.20, so council tax is met in full. Before this fix - # the inflated UC gave an income of 23,243.64 and CTR of - # 1,500 - 20% x (23,243.64 - 19,934.20) = 838.11. - council_tax_benefit: 1_500 + # A couple with an award of Universal Credit is not a pensioner for + # Council Tax Reduction, though one member is over the qualifying age + # (SI 2012/2885 reg 3(1)(a)(ii)(bb)), so it claims under the council's + # working-age scheme, not the national pensioner scheme. The household is + # in England (region defaults to London) and in Maidstone (the default + # local authority), whose scheme the model does not simulate, so it keeps + # reported Council Tax Reduction: none here. The pensioner scheme would + # have met the 1,500 in full. + council_tax_reduction_pensioner: false + council_tax_benefit: 0 - name: Same couple with the same amount of private pension instead gets the same UC period: 2026 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml index 61d549e400..b0232bd974 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml @@ -196,10 +196,11 @@ universal_credit: 22_323 - name: A pensioner whose only younger adult is a qualifying young person is not capped - # The model's is_uc_eligible counts any working-age adult, so it pays this - # family Universal Credit. In law Universal Credit needs a claimant or - # partner under the qualifying age (UC Regs 2013 reg 3(2)(a)); the family - # stays under the pension-age Housing Benefit rules, which have no cap. + # Universal Credit needs a claimant or partner under the qualifying age (UC + # Regs 2013 reg 3(2)(a)), and the model's is_uc_eligible needs one too, so + # the award here is an input the family could not hold in law. The + # 18-year-old is a dependant, not a partner, so the award does not take the + # family out of the pension-age Housing Benefit rules, which have no cap. period: 2026 absolute_error_margin: 0.01 input: @@ -223,19 +224,18 @@ members: [parent, young_person, child] region: LONDON output: - is_uc_claimant: [true, false, false] + is_claimant_or_partner: [true, false, false] is_uc_entitled: true housing_benefit_pension_age_regulations_apply: true is_benefit_cap_exempt_other: true benefit_cap_reduction: 0 universal_credit: 30_000 -- name: Without is_parent the young person counts as a claimant, so the award is capped - # is_uc_claimant recognises a qualifying young person as a dependant only - # when a parent is identified (is_parent), as the FRS datasets do. Without - # it the model treats the 18-year-old as a partner under State Pension age, - # so the award counts as a working-age Universal Credit award and is - # capped. This pins that dependency; see the case above for the dependant. +- name: Without is_parent the young person is still a dependant, so the award is not capped + # is_claimant_or_partner presumes a member under 20 and at least 16 years + # younger than the claimant to be their child when relationships are not + # given, so the 18-year-old is not a partner under State Pension age and + # the result matches the case above. period: 2026 absolute_error_margin: 0.01 input: @@ -258,9 +258,9 @@ members: [parent, young_person, child] region: LONDON output: - is_uc_claimant: [true, true, false] - housing_benefit_pension_age_regulations_apply: false - is_benefit_cap_exempt: false - # 30,000 + 3,000 - 25,323 - benefit_cap_reduction: 7_677 - universal_credit: 22_323 + is_claimant_or_partner: [true, false, false] + housing_benefit_pension_age_regulations_apply: true + is_benefit_cap_exempt_other: true + is_benefit_cap_exempt: true + benefit_cap_reduction: 0 + universal_credit: 30_000 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml index 6a9e609fa1..444244a941 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml @@ -187,9 +187,9 @@ council_tax_benefit: [508.83, 0] - name: A pensioner whose only younger adult is a qualifying young person stays a pensioner - # The model pays this family Universal Credit because is_uc_eligible counts - # any working-age adult. Universal Credit needs a claimant or partner under - # the qualifying age, so the award does not count against pensioner status. + # Universal Credit before the cap is an input here. Universal Credit needs a + # claimant or partner under the qualifying age, and the 18-year-old is a + # dependant, so the award does not count against pensioner status. period: 2026 input: people: @@ -216,6 +216,44 @@ council_tax_reduction_household_has_pensioner: true council_tax_reduction_scheme_supported: true +- name: A pensioner aged 70 with an 18-year-old in education and no is_parent keeps pensioner CTR + # is_claimant_or_partner presumes the 18-year-old to be the pensioner's + # child (under 20 and at least 16 years younger), so the family has no + # claimant or partner under State Pension age. The model gives it no + # Universal Credit, it is not capped, and it stays on the national + # pensioner scheme. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + young_person: + age: 18 + current_education: UPPER_SECONDARY + benunits: + benunit: + members: [pensioner, young_person] + households: + household: + members: [pensioner, young_person] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + is_claimant_or_partner: [true, false] + is_uc_eligible: false + is_uc_entitled: false + housing_benefit_pension_age_regulations_apply: true + is_benefit_cap_exempt: true + benefit_cap_reduction: 0 + council_tax_reduction_pensioner: true + council_tax_reduction_household_has_pensioner: true + council_tax_reduction_scheme_supported: true + # The pensioner scheme meets the 2,000 in full. + council_tax_benefit: 2_000 + - name: A Welsh mixed-age couple on Universal Credit is not a pensioner couple # Wales has one national scheme, so the scheme is still simulated. period: 2026 diff --git a/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py index aaa4b01951..ea0ece7cc3 100644 --- a/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py +++ b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py @@ -19,12 +19,12 @@ 3(1); Scotland SSI 2021/249 reg 3 and SSI 2012/319 reg 12). Universal Credit needs a claimant or partner under the qualifying age (UC -Regs 2013 reg 3(2)(a)). The model's is_uc_eligible counts any working-age -adult, so it can pay Universal Credit to a pensioner whose only younger adult -is a qualifying young person; such an award does not count as being on -Universal Credit here. "On Universal Credit" below means an award before the -cap with a claimant or partner under State Pension age. Legacy income-related -benefit is set as an input. +Regs 2013 reg 3(2)(a)). Claimant and partner are is_claimant_or_partner, which +presumes a member under 20 and at least 16 years younger than the claimant to +be their child, so a pensioner's qualifying young person is a dependant +whether or not is_parent is set; the draws cover both. "On Universal Credit" +below means an award before the cap with a claimant or partner under State +Pension age. Legacy income-related benefit is set as an input. Invariants, for every drawn population of families: @@ -144,6 +144,9 @@ def families(draw): hb_reported=draw(st.sampled_from([0, 0, 5_000])), ctb_reported=draw(st.sampled_from([0, 0, 700])), legacy=draw(st.sampled_from([0, 0, 0, 3_000])), + # Whether is_parent identifies the adult in a family with a young + # person, as the FRS datasets do; household inputs may leave it out. + parent_flag=draw(st.booleans()), ) @@ -161,7 +164,7 @@ def situation(units): person["state_pension_reported"] = {PERIOD: unit["state_pension"]} else: person["employment_income"] = {PERIOD: unit["earnings"]} - if role == "adult" and has_young_person: + if role == "adult" and has_young_person and unit["parent_flag"]: person["is_parent"] = {PERIOD: True} if j == 0: person["housing_benefit_reported"] = {PERIOD: unit["hb_reported"]} diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py index 7d7ded13af..34b106fba2 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py @@ -20,9 +20,10 @@ class housing_benefit_pension_age_regulations_apply(Variable): "for a claim where every claimant has reached that age, but not for " "reg 5 itself, under which such an award would bring in the " "working-age regulations. The model has no lawful such awards (tax " - "credit migrants under SI 2014/1230 reg 60A are not modelled); the " - "ones it pays through is_uc_eligible's any-adult test are not awards " - "in law, so the pension-age regulations are the right result." + "credit migrants under SI 2014/1230 reg 60A are not modelled), and its " + "Universal Credit eligibility test also needs a claimant or partner " + "under State Pension age, so the pension-age regulations are the right " + "result." ) definition_period = YEAR reference = ( @@ -33,7 +34,7 @@ class housing_benefit_pension_age_regulations_apply(Variable): def formula(benunit, period, parameters): person = benunit.members - claimant_or_partner = person("is_uc_claimant", period) + claimant_or_partner = person("is_claimant_or_partner", period) # is_SP_age stands in for the qualifying age for State Pension Credit, # as elsewhere in the model. over_qualifying_age = person("is_SP_age", period) @@ -41,17 +42,18 @@ def formula(benunit, period, parameters): # Universal Credit needs a claimant or partner under the qualifying age # (WRA 2012 s.4(1)(b); UC Regs 2013 reg 3(2)(a)), except for tax credit # migrants (SI 2014/1230 reg 60A), whose award is exempt from the cap - # (reg 60C). is_uc_eligible counts any working-age adult, so the model - # can pay Universal Credit to a pensioner whose only younger adult is - # a qualifying young person; that award does not take the family out - # of the pension-age rules. is_uc_claimant recognises the young person - # as a dependant only when is_parent is set, as in the FRS datasets. - # The award is read before the benefit cap, which depends on this - # variable. + # (reg 60C). An award counts here only with such a claimant or partner. + # A dependant is neither: is_claimant_or_partner presumes a member under + # 20 and at least 16 years younger than the claimant to be their child, + # so a pensioner's 18- or 19-year-old does not make the award a + # working-age one, whether or not is_parent identifies the pensioner as + # the parent. The award is read before the benefit cap, which depends + # on this variable. working_age_claimant = benunit.any(claimant_or_partner & ~over_qualifying_age) on_universal_credit = benunit("is_uc_entitled", period) & working_age_claimant - on_income_related_benefit = ( - add(benunit, period, ["income_support", "jsa_income", "esa_income"]) > 0 + # Reg 5 names the same three benefits as the CTR pensioner test. + on_income_related_benefit = benunit( + "council_tax_reduction_relevant_income_based_benefit", period ) return ( attained_qualifying_age & ~on_universal_credit & ~on_income_related_benefit diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py index 8243dcc34b..a9da7f9aa3 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.py @@ -26,7 +26,7 @@ class council_tax_reduction_pensioner(Variable): def formula(benunit, period, parameters): person = benunit.members - claimant_or_partner = person("is_uc_claimant", period) + claimant_or_partner = person("is_claimant_or_partner", period) # Either member of a couple can claim, so the older one can claim as a # pensioner. is_SP_age stands in for the qualifying age for State # Pension Credit, as elsewhere in the model. @@ -38,16 +38,17 @@ def formula(benunit, period, parameters): # The award before the benefit cap: the cap reduces an award # (UC Regs 2013 reg 81) rather than removing it. An award counts only # where a claimant or partner is under the qualifying age, which - # Universal Credit needs (UC Regs 2013 reg 3(2)(a)); is_uc_eligible - # counts any working-age adult, including a qualifying young person. + # Universal Credit needs (UC Regs 2013 reg 3(2)(a)). A dependant is + # neither: is_claimant_or_partner presumes a member under 20 and at + # least 16 years younger than the claimant to be their child, so a + # pensioner's 18- or 19-year-old does not count, whether or not + # is_parent identifies the pensioner as the parent. # This also stands in for the rules that disregard an award held after # both members reach the qualifying age (SI 2012/2885 reg 3(2); # WSI 2013/3029 reg 3(2); SSI 2021/249 reg 3(2)). England, and Wales # for schemes from 2026-27, also disregard a tax credit migrant's award # (UC (TP) Regs 2014 reg 60A) and Scotland does not; the model does not - # model those migrants. The guard - # relies on is_uc_claimant, which needs is_parent to recognise a - # qualifying young person as a dependant. + # model those migrants. working_age_claimant = benunit.any(claimant_or_partner & ~over_qualifying_age) universal_credit_award = ( benunit("is_uc_entitled", period) & working_age_claimant