From 30974b6661c9bb211f49d6abfa90af42979329c0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 10:52:31 -0400 Subject: [PATCH 1/6] Test that HB reg 5(1)(b) reads only the claimant's and partner's awards housing_benefit_pension_age_regulations_apply (#1944) reads council_tax_reduction_relevant_income_based_benefit, which #2027 scoped to claimant_or_partner_esa_income and claimant_or_partner_jsa_income, so the reader needs no change. Reg 5(1)(b) asks whether the claimant "or if he has a partner, his partner" is on Income Support, income-based JSA or income-related ESA; another member of the benefit unit claims in their own right. Adds YAML cases where another member's own ESA or JSA leaves a pensioner under the pension-age regulations, with partner controls, and adds the reader to FAMILY_READERS with the pension-age and young-single family shapes. The two excluded-adult cases fail with the CTR passport as it was before #2027 (88ecf58b8). Co-Authored-By: Claude Opus 5.5 --- ...using_benefit_pension_age_regulations.yaml | 100 ++++++++++++ .../test_legacy_award_readers_properties.py | 151 ++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/housing_benefit_pension_age_regulations.yaml diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/housing_benefit_pension_age_regulations.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/housing_benefit_pension_age_regulations.yaml new file mode 100644 index 0000000000..86273eb805 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/housing_benefit_pension_age_regulations.yaml @@ -0,0 +1,100 @@ +# HB Regs 2006 reg 5(1)(b): the working-age regulations apply to a claimant +# who has reached the qualifying age for State Pension Credit only if "he, or +# if he has a partner, his partner" is on Universal Credit, Income Support, +# income-based JSA or income-related ESA. "other" is a member who is neither +# the claimant, the partner nor a child or young person they are responsible +# for, so an award they report is their own claim and leaves the claimant +# under the pension-age regulations. The model stops paying income-based JSA +# from April 2026 (gov.dwp.JSA.income.active), so the JSA cases are in 2025. + +- name: Another member's income-related ESA leaves a pensioner under the pension-age HB regulations + period: 2026 + input: + people: + claimant: + age: 70 + is_claimant_or_partner: true + other: + age: 30 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + esa_income_reported: 5_000 + benunits: + benunit: + members: [claimant, other] + would_claim_uc: false + households: + household: + members: [claimant, other] + output: + esa_income: 5_000 + claimant_or_partner_esa_income: 0 + housing_benefit_pension_age_regulations_apply: true + +- name: Another member's income-based JSA leaves a pensioner under the pension-age HB regulations + period: 2025 + input: + people: + claimant: + age: 70 + is_claimant_or_partner: true + other: + age: 30 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + jsa_income_reported: 4_000 + benunits: + benunit: + members: [claimant, other] + would_claim_uc: false + households: + household: + members: [claimant, other] + output: + jsa_income: 4_000 + claimant_or_partner_jsa_income: 0 + housing_benefit_pension_age_regulations_apply: true + +- name: A younger partner's income-related ESA brings a pensioner under the working-age HB regulations + period: 2026 + input: + people: + claimant: + age: 70 + is_claimant_or_partner: true + partner: + age: 50 + is_claimant_or_partner: true + esa_income_reported: 5_000 + benunits: + benunit: + members: [claimant, partner] + would_claim_uc: false + households: + household: + members: [claimant, partner] + output: + claimant_or_partner_esa_income: 5_000 + housing_benefit_pension_age_regulations_apply: false + +- name: A younger partner's income-based JSA brings a pensioner under the working-age HB regulations + period: 2025 + input: + people: + claimant: + age: 70 + is_claimant_or_partner: true + partner: + age: 50 + is_claimant_or_partner: true + jsa_income_reported: 4_000 + benunits: + benunit: + members: [claimant, partner] + would_claim_uc: false + households: + household: + members: [claimant, partner] + output: + claimant_or_partner_jsa_income: 4_000 + housing_benefit_pension_age_regulations_apply: false diff --git a/policyengine_uk/tests/test_legacy_award_readers_properties.py b/policyengine_uk/tests/test_legacy_award_readers_properties.py index 7c6ef57d72..fe39060d11 100644 --- a/policyengine_uk/tests/test_legacy_award_readers_properties.py +++ b/policyengine_uk/tests/test_legacy_award_readers_properties.py @@ -70,6 +70,7 @@ "targeted_childcare_entitlement_eligible", "would_claim_IS", "income_support_eligible", + "housing_benefit_pension_age_regulations_apply", ] MEMBER_READERS = [ "is_scp_eligible", @@ -237,6 +238,156 @@ def test_other_members_awards_never_change_the_readers(drawn): ), (variable, drawn[i]) +# --- Further family shapes (begin) --- +# Some readers only bite for a claimant over state pension age (the Housing +# Benefit regulations that apply, the Pension Credit earnings disregard, the +# mixed-age couple saving) or for a single applicant under 25 (the Scotland +# and Wales council tax reduction personal allowance). The families above +# rarely take those shapes, so two more properties check every family reader +# on them. The claimant and partner seldom report an award themselves, and the +# added member's award survives tariff income on the savings drawn, so a +# reader that wrongly counts it changes. The invariant is about the readers' +# own scoping: the shapes pay no rent, so Universal Credit stays far below the +# benefit cap. The cap still counts every member's award (benefit_cap_reduction +# reads the benefit-unit totals), so with a binding cap another member's award +# can reach a reader through Universal Credit. +PENSION_AGE = 68 +# Whether the pension-age property enters is_mixed_age_couple, so that a +# reader of the couple's award reports is tested on those reports alone. +ENTER_IS_MIXED_AGE_COUPLE = False +AWARD_REPORTS = [ + "esa_income_reported", + "jsa_income_reported", + "income_support_reported", +] + + +@st.composite +def couple_award_reports(draw): + """Usually no award; otherwise £3,000 of one of them.""" + reports = dict.fromkeys(AWARD_REPORTS, 0) + award = draw(st.sampled_from([None] * 4 + AWARD_REPORTS)) + if award is not None: + reports[award] = 3_000 + return reports + + +@st.composite +def other_members_with_awards(draw): + """As other_members, with at least one award of £3,000 or more.""" + awards = { + "esa_income_reported": draw(st.sampled_from([0, 3_000, 5_000])), + "jsa_income_reported": draw(st.sampled_from([0, 3_000, 5_000])), + "income_support_reported": draw(st.sampled_from([0, 3_000, 5_000])), + } + if not any(awards.values()): + awards[draw(st.sampled_from(sorted(awards)))] = 5_000 + return { + "age": draw(st.integers(16, 60)), + "current_education": "NOT_IN_EDUCATION", + **awards, + } + + +@st.composite +def pension_age_families(draw): + """A claimant over state pension age, usually with a partner of working or + pension age (so many couples are mixed-age), and no dependants. Ages are + listed rather than ranged so that both sides of the 14 May 2019 cut-off + for the mixed-age couple saving (born by 1954) are drawn.""" + ages = [ + draw(st.sampled_from([PENSION_AGE, 72, 76, 81])), + draw(st.sampled_from([45, 58, 63, 70, 79])), + ] + adults = [] + for age in ages[: 1 + draw(st.sampled_from([0, 1, 1]))]: + adults.append( + { + "age": age, + "employment_income": draw(st.sampled_from([0, 3_000, 12_000])), + "housing_benefit_reported": draw(st.sampled_from([0, 3_000, 3_000])), + **draw(couple_award_reports()), + } + ) + household = { + "country": draw(st.sampled_from(["ENGLAND", "SCOTLAND", "WALES"])), + "savings": draw(st.sampled_from([0, 7_000, 20_000])), + } + return adults, [], household + + +@st.composite +def young_single_families(draw): + """A single applicant aged 18 to 24 in Scotland or Wales, no dependants.""" + adults = [ + { + "age": draw(st.integers(18, 24)), + "employment_income": draw(st.sampled_from([0, 3_000, 12_000])), + "current_education": "NOT_IN_EDUCATION", + **draw(couple_award_reports()), + } + ] + household = { + "country": draw(st.sampled_from(["SCOTLAND", "WALES"])), + "savings": draw(st.sampled_from([0, 7_000])), + } + return adults, [], household + + +def _family_readers_never_change(drawn, enter_is_mixed_age_couple=False): + without = [(*family, None) for family, _ in drawn] + with_other = [(*family, other) for family, other in drawn] + units = without + with_other + inputs = situation(units) + if enter_is_mixed_age_couple: + for i, (adults, _, _, _) in enumerate(units): + pension_age = [a["age"] >= PENSION_AGE for a in adults] + inputs["benunits"][f"b{i}"]["is_mixed_age_couple"] = { + YEAR: len(adults) == 2 and sum(pension_age) == 1 + } + sim = Simulation(situation=inputs) + flags = sim.calculate("is_claimant_or_partner", YEAR) + sizes = [len(a) + len(d) + (o is not None) for a, d, _, o in units] + starts = np.cumsum([0] + sizes[:-1]) + k = len(drawn) + for i in range(k): + assert not flags[starts[k + i] + sizes[k + i] - 1], drawn[i] + for variable in FAMILY_READERS: + values = sim.calculate(variable, YEAR) + for i in range(k): + assert np.isclose(values[i], values[k + i], atol=0.01), ( + variable, + drawn[i], + ) + + +@SETTINGS +@given( + st.lists( + st.tuples(pension_age_families(), other_members_with_awards()), + min_size=1, + max_size=6, + ) +) +def test_other_members_awards_never_change_the_readers_at_pension_age(drawn): + _family_readers_never_change(drawn, ENTER_IS_MIXED_AGE_COUPLE) + + +@SETTINGS +@given( + st.lists( + st.tuples(young_single_families(), other_members_with_awards()), + min_size=1, + max_size=6, + ) +) +def test_other_members_awards_never_change_the_readers_for_young_singles(drawn): + _family_readers_never_change(drawn) + + +# --- Further family shapes (end) --- + + @SETTINGS @given(st.lists(st.tuples(families(), other_members()), min_size=1, max_size=6)) def test_claimant_or_partner_awards_bounded_by_benefit_unit_awards(drawn): From 85b0e5ec7dc26e533cd82e7a0b80541f917f1db8 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 10:54:40 -0400 Subject: [PATCH 2/6] Test that the SI 2019/37 saving reads only the couple's award reports has_mixed_age_couple_pension_credit_saving (#1940) already reads only the claimant's and partner's Income Support, JSA and ESA reports (#1940's 94231204e), so the reader needs no change. SI 2006/214 reg 5 takes a couple out of the pension-age Housing Benefit regulations only where the claimant "or, if he has a partner, his partner" is on one of these awards; another member of the benefit unit claims in their own right. Adds YAML cases where another member's own ESA or Income Support leaves the couple's Housing Benefit saving in place, with a partner and a no-award control, and adds the reader to FAMILY_READERS, entering is_mixed_age_couple in the pension-age property so the reader is tested on the reports alone. The two excluded-adult cases fail when the reports are summed over every member of the benefit unit. Co-Authored-By: Claude Opus 5.5 --- ...ixed_age_couple_pension_credit_saving.yaml | 106 ++++++++++++++++++ .../test_legacy_award_readers_properties.py | 3 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/mixed_age_couple_pension_credit_saving.yaml diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/mixed_age_couple_pension_credit_saving.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/mixed_age_couple_pension_credit_saving.yaml new file mode 100644 index 0000000000..fe2646cca4 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/mixed_age_couple_pension_credit_saving.yaml @@ -0,0 +1,106 @@ +# SI 2019/37 arts 2(3) and 4: a mixed-age couple keeps Pension Credit while it +# stays entitled to Pension Credit or to Housing Benefit under the pension-age +# regulations (SI 2006/214). Those regulations do not apply where the claimant +# "or, if he has a partner, his partner" is on Income Support, income-based +# JSA or income-related ESA (reg 5). The saving is inferred from reported +# receipt. "other" is a member who is neither the claimant, the partner nor a +# child or young person they are responsible for, so an award they report is +# their own claim and leaves the couple's Housing Benefit under the +# pension-age regulations. is_mixed_age_couple is entered so the cases test +# the award reports alone. The older member, 75 in 2026, was born in 1951 and +# had reached the qualifying age by 14 May 2019. + +- name: Another member's income-related ESA leaves a mixed-age couple's Housing Benefit saving in place + period: 2026 + input: + people: + older: + age: 75 + is_claimant_or_partner: true + housing_benefit_reported: 3_000 + younger: + age: 60 + is_claimant_or_partner: true + other: + age: 30 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + esa_income_reported: 5_000 + benunits: + benunit: + members: [older, younger, other] + is_mixed_age_couple: true + households: + household: + members: [older, younger, other] + output: + has_mixed_age_couple_pension_credit_saving: true + +- name: Another member's Income Support leaves a mixed-age couple's Housing Benefit saving in place + period: 2026 + input: + people: + older: + age: 75 + is_claimant_or_partner: true + housing_benefit_reported: 3_000 + younger: + age: 60 + is_claimant_or_partner: true + other: + age: 30 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + income_support_reported: 4_000 + benunits: + benunit: + members: [older, younger, other] + is_mixed_age_couple: true + households: + household: + members: [older, younger, other] + output: + has_mixed_age_couple_pension_credit_saving: true + +- name: The younger partner's income-related ESA puts the couple under the working-age regulations, so no saving + period: 2026 + input: + people: + older: + age: 75 + is_claimant_or_partner: true + housing_benefit_reported: 3_000 + younger: + age: 60 + is_claimant_or_partner: true + esa_income_reported: 5_000 + benunits: + benunit: + members: [older, younger] + is_mixed_age_couple: true + households: + household: + members: [older, younger] + output: + has_mixed_age_couple_pension_credit_saving: false + +- name: A mixed-age couple reporting pension-age Housing Benefit and no income-related award keeps the saving + period: 2026 + input: + people: + older: + age: 75 + is_claimant_or_partner: true + housing_benefit_reported: 3_000 + younger: + age: 60 + is_claimant_or_partner: true + benunits: + benunit: + members: [older, younger] + is_mixed_age_couple: true + households: + household: + members: [older, younger] + output: + has_mixed_age_couple_pension_credit_saving: true diff --git a/policyengine_uk/tests/test_legacy_award_readers_properties.py b/policyengine_uk/tests/test_legacy_award_readers_properties.py index fe39060d11..9afebd4b1f 100644 --- a/policyengine_uk/tests/test_legacy_award_readers_properties.py +++ b/policyengine_uk/tests/test_legacy_award_readers_properties.py @@ -71,6 +71,7 @@ "would_claim_IS", "income_support_eligible", "housing_benefit_pension_age_regulations_apply", + "has_mixed_age_couple_pension_credit_saving", ] MEMBER_READERS = [ "is_scp_eligible", @@ -254,7 +255,7 @@ def test_other_members_awards_never_change_the_readers(drawn): PENSION_AGE = 68 # Whether the pension-age property enters is_mixed_age_couple, so that a # reader of the couple's award reports is tested on those reports alone. -ENTER_IS_MIXED_AGE_COUPLE = False +ENTER_IS_MIXED_AGE_COUPLE = True AWARD_REPORTS = [ "esa_income_reported", "jsa_income_reported", From 9e4994067a0cdce9bf1e587b62b1339889c3a344 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 11:23:42 -0400 Subject: [PATCH 3/6] Read only the couple's income-related ESA in the extended childcare criteria gov.dfe.extended_childcare_entitlement.disability_criteria (#2079) listed esa_income from 1 December 2022, read for each person by extended_childcare_entitlement_limited_capability_or_specified_benefit. That total also holds an award another member of the benefit unit claims in their own right, so another adult's own ESA let a non-working parent or partner meet SI 2022/1134 reg 14(4) or 15(4) ("a person entitled to a specified benefit", reg 11A(1)(e)). The list now names claimant_or_partner_esa_income, which keeps the model's convention that the couple's award counts for both members. Adds YAML cases, among them review r7's reproduction on #2027 (a working parent, a non-working partner, a three-year-old and an outside adult with ESA), which fail before the switch, and partner controls. Adds the reader to MEMBER_READERS. The working-age property seldom draws a family whose only award is the outside member's, so it did not catch the old list; the further family shapes now also check member readers and gain a working-parents shape, on which the old list fails. Co-Authored-By: Claude Opus 5.5 --- .../disability_criteria.yaml | 7 +- .../extended_childcare_entitlement.yaml | 168 ++++++++++++++++++ .../test_legacy_award_readers_properties.py | 63 ++++++- ...limited_capability_or_specified_benefit.py | 5 +- 4 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/extended_childcare_entitlement.yaml diff --git a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/disability_criteria.yaml b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/disability_criteria.yaml index b8e1b7da16..a4d7eedbfa 100644 --- a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/disability_criteria.yaml +++ b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/disability_criteria.yaml @@ -25,13 +25,16 @@ values: # SI 2022/1134 reg 14(4)(b) and the specified benefits in reg 14(5) as made, # moved to reg 11A from 30 March 2023 by SI 2023/276. Regs 14(4) and 15(4) ask # whether a person is entitled to a specified benefit, so an allowance reduced - # to nil by an overlapping benefit still counts. + # to nil by an overlapping benefit still counts. Income-related ESA is the + # claimant's and partner's award (claimant_or_partner_esa_income): another + # member of the benefit unit with an award of their own is not the parent or + # partner, so their award does not count. 2022-12-01: - uc_limited_capability_for_WRA # 14(4)(b)(i)-(ii): limited capability for work, or for work and work-related activity - is_entitled_to_carer_benefit # 11A(1)(a)-(b): carer's allowance, or carer's assistance under the Social Security (Scotland) Act 2018 s.28 - receives_uc_carer_element # 11A(1)(c): the carer element under UC Regs reg 29, on an award (reg 14(5)(a) as made: "an award of universal credit [...] that includes the carer element") - receives_limited_capability_for_work_credits # 11A(1)(d): credits for incapacity for work or limited capability for work - esa_contrib # 11A(1)(e): employment and support allowance (contribution-based) - - esa_income # 11A(1)(e): employment and support allowance (income-related) + - claimant_or_partner_esa_income # 11A(1)(e): employment and support allowance (income-related) - incapacity_benefit # 11A(1)(f)-(g): long-term, short-term and transitional incapacity benefit - sda # 11A(1)(h): severe disablement allowance diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/extended_childcare_entitlement.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/extended_childcare_entitlement.yaml new file mode 100644 index 0000000000..d97cc97027 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/extended_childcare_entitlement.yaml @@ -0,0 +1,168 @@ +# SI 2022/1134 reg 15(4): a partner who is not in qualifying paid work meets +# the partner condition if they are "a person entitled to a specified +# benefit" and the parent meets reg 14(3) (likewise reg 14(4) for the +# parent). Reg 11A(1)(e) lists employment and support allowance. The model +# reads income-related ESA as the claimant's and partner's award +# (claimant_or_partner_esa_income), which counts for both members of the +# couple. "other" is a member who is neither the claimant, the partner nor a +# child or young person they are responsible for, so an award they report is +# their own claim and does not make the partner entitled to a specified +# benefit. In each case the parent works and meets the income requirements, +# the partner does not work, and the child is three. + +- name: Another member's income-related ESA does not let a non-working partner meet the extended childcare partner condition + period: 2025 + input: + people: + parent: + age: 35 + is_parent: true + is_claimant_or_partner: true + in_work: true + employment_income: 30_000 + extended_childcare_entitlement_meets_income_requirements: true + partner: + age: 34 + is_parent: true + is_claimant_or_partner: true + in_work: false + extended_childcare_entitlement_meets_income_requirements: false + child: + age: 3 + is_claimant_or_partner: false + other: + age: 25 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + esa_income_reported: 5_000 + benunits: + benunit: + members: [parent, partner, child, other] + claims_all_entitled_benefits: false + would_claim_uc: false + households: + household: + members: [parent, partner, child, other] + country: ENGLAND + output: + esa_income: 5_000 + claimant_or_partner_esa_income: 0 + extended_childcare_entitlement_limited_capability_or_specified_benefit: [false, false, false, false] + extended_childcare_entitlement_work_condition: [true, false, false, false] + extended_childcare_entitlement_eligible: false + +- name: Another member's income-related ESA does not let a non-working partner meet the condition in 2023 + period: 2023 + input: + people: + parent: + age: 35 + is_parent: true + is_claimant_or_partner: true + in_work: true + employment_income: 30_000 + extended_childcare_entitlement_meets_income_requirements: true + partner: + age: 34 + is_parent: true + is_claimant_or_partner: true + in_work: false + extended_childcare_entitlement_meets_income_requirements: false + child: + age: 3 + is_claimant_or_partner: false + other: + age: 40 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + esa_income_reported: 4_000 + benunits: + benunit: + members: [parent, partner, child, other] + claims_all_entitled_benefits: false + would_claim_uc: false + households: + household: + members: [parent, partner, child, other] + country: ENGLAND + output: + esa_income: 4_000 + claimant_or_partner_esa_income: 0 + extended_childcare_entitlement_work_condition: [true, false, false, false] + extended_childcare_entitlement_eligible: false + +- name: The partner's own income-related ESA lets the non-working partner meet the extended childcare partner condition + period: 2025 + input: + people: + parent: + age: 35 + is_parent: true + is_claimant_or_partner: true + in_work: true + employment_income: 30_000 + extended_childcare_entitlement_meets_income_requirements: true + partner: + age: 34 + is_parent: true + is_claimant_or_partner: true + in_work: false + extended_childcare_entitlement_meets_income_requirements: false + esa_income_reported: 5_000 + child: + age: 3 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, partner, child] + claims_all_entitled_benefits: false + would_claim_uc: false + households: + household: + members: [parent, partner, child] + country: ENGLAND + output: + claimant_or_partner_esa_income: 5_000 + extended_childcare_entitlement_work_condition: [true, true, false] + extended_childcare_entitlement_eligible: true + +- name: The partner's own income-related ESA still counts when another member also reports an award + period: 2025 + input: + people: + parent: + age: 35 + is_parent: true + is_claimant_or_partner: true + in_work: true + employment_income: 30_000 + extended_childcare_entitlement_meets_income_requirements: true + partner: + age: 34 + is_parent: true + is_claimant_or_partner: true + in_work: false + extended_childcare_entitlement_meets_income_requirements: false + esa_income_reported: 5_000 + child: + age: 3 + is_claimant_or_partner: false + other: + age: 25 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + esa_income_reported: 5_000 + benunits: + benunit: + members: [parent, partner, child, other] + claims_all_entitled_benefits: false + would_claim_uc: false + households: + household: + members: [parent, partner, child, other] + country: ENGLAND + output: + esa_income: 10_000 + claimant_or_partner_esa_income: 5_000 + extended_childcare_entitlement_work_condition: [true, true, false, false] + extended_childcare_entitlement_eligible: true diff --git a/policyengine_uk/tests/test_legacy_award_readers_properties.py b/policyengine_uk/tests/test_legacy_award_readers_properties.py index 9afebd4b1f..c45ac40462 100644 --- a/policyengine_uk/tests/test_legacy_award_readers_properties.py +++ b/policyengine_uk/tests/test_legacy_award_readers_properties.py @@ -20,6 +20,11 @@ 71(1)(h)(iii), through reg 61(2)(b) and HB Regs 2006 reg 56(2)(a) (the student is on the award) and (c) (the student's applicable amount would include a disability or severe disability premium). +- Which Housing Benefit regulations apply to a pensioner: HB Regs 2006 reg + 5(1)(b); SI 2006/214 reg 5(2). The mixed-age couple Pension Credit saving: + SI 2019/37 arts 2(3) and 4, through SI 2006/214 reg 5. +- The extended childcare parent and partner conditions: SI 2022/1134 regs + 11A(1)(e), 14(4)(b) and 15(4). A member of the benefit unit who is neither the claimant, the partner nor a child or young person they are responsible for (for example a non-dependent @@ -79,6 +84,7 @@ "is_on_income_related_esa", "is_on_income_based_jsa", "is_on_income_support", + "extended_childcare_entitlement_limited_capability_or_specified_benefit", ] AWARDS = st.sampled_from([0, 0, 200, 3_000]) @@ -241,10 +247,10 @@ def test_other_members_awards_never_change_the_readers(drawn): # --- Further family shapes (begin) --- # Some readers only bite for a claimant over state pension age (the Housing -# Benefit regulations that apply, the Pension Credit earnings disregard, the -# mixed-age couple saving) or for a single applicant under 25 (the Scotland -# and Wales council tax reduction personal allowance). The families above -# rarely take those shapes, so two more properties check every family reader +# Benefit regulations that apply, the mixed-age couple saving), for a single +# applicant under 25, or for parents of a young child of whom only one works +# (the extended childcare partner condition). The families above rarely take +# those shapes, so three more properties check every family and member reader # on them. The claimant and partner seldom report an award themselves, and the # added member's award survives tariff income on the savings drawn, so a # reader that wrongly counts it changes. The invariant is about the readers' @@ -335,6 +341,35 @@ def young_single_families(draw): return adults, [], household +@st.composite +def working_parent_families(draw): + """Parents of a child aged one to four in England. One parent is in + qualifying paid work within the income limits and the other is not, so + the other meets the extended childcare partner condition only through + limited capability for work or a specified benefit (SI 2022/1134 regs + 14(4) and 15(4)).""" + working = { + "age": draw(st.integers(25, 45)), + "is_parent": True, + "in_work": True, + "employment_income": draw(st.sampled_from([20_000, 30_000])), + "extended_childcare_entitlement_meets_income_requirements": True, + **draw(couple_award_reports()), + } + not_working = { + "age": draw(st.integers(25, 45)), + "is_parent": True, + "in_work": False, + "extended_childcare_entitlement_meets_income_requirements": False, + **draw(couple_award_reports()), + } + household = { + "country": "ENGLAND", + "savings": draw(st.sampled_from([0, 7_000, 20_000])), + } + return [working, not_working], [{"age": draw(st.integers(1, 4))}], household + + def _family_readers_never_change(drawn, enter_is_mixed_age_couple=False): without = [(*family, None) for family, _ in drawn] with_other = [(*family, other) for family, other in drawn] @@ -360,6 +395,14 @@ def _family_readers_never_change(drawn, enter_is_mixed_age_couple=False): variable, drawn[i], ) + for variable in MEMBER_READERS: + values = sim.calculate(variable, YEAR) + for i in range(k): + n = sizes[i] + assert np.array_equal( + values[starts[i] : starts[i] + n], + values[starts[k + i] : starts[k + i] + n], + ), (variable, drawn[i]) @SETTINGS @@ -386,6 +429,18 @@ def test_other_members_awards_never_change_the_readers_for_young_singles(drawn): _family_readers_never_change(drawn) +@SETTINGS +@given( + st.lists( + st.tuples(working_parent_families(), other_members_with_awards()), + min_size=1, + max_size=6, + ) +) +def test_other_members_awards_never_change_the_readers_for_working_parents(drawn): + _family_readers_never_change(drawn) + + # --- Further family shapes (end) --- diff --git a/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_limited_capability_or_specified_benefit.py b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_limited_capability_or_specified_benefit.py index 79846e1c91..4f2c8228ff 100644 --- a/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_limited_capability_or_specified_benefit.py +++ b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_limited_capability_or_specified_benefit.py @@ -14,7 +14,10 @@ class extended_childcare_entitlement_limited_capability_or_specified_benefit(Var "parent and partner conditions without the work or income conditions " "when the other meets them. A benefit-unit-level benefit in the list " "(income-related ESA) counts for both members of a couple; the " - "Universal Credit carer element counts for the carer only." + "Universal Credit carer element counts for the carer only. " + "Income-related ESA is read as the claimant's and partner's award " + "(claimant_or_partner_esa_income): another member of the benefit unit " + "with an award of their own claims in their own right." ) definition_period = YEAR reference = ( From f5286b0547e9527b43ef3981472aa9f87393ff2d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 11:29:18 -0400 Subject: [PATCH 4/6] Add a changelog entry for the reader switches Co-Authored-By: Claude Opus 5.5 --- .../legacy-award-reader-switches-1944-2000-1908-1940.fixed.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/legacy-award-reader-switches-1944-2000-1908-1940.fixed.md diff --git a/changelog.d/legacy-award-reader-switches-1944-2000-1908-1940.fixed.md b/changelog.d/legacy-award-reader-switches-1944-2000-1908-1940.fixed.md new file mode 100644 index 0000000000..e3e6f0500a --- /dev/null +++ b/changelog.d/legacy-award-reader-switches-1944-2000-1908-1940.fixed.md @@ -0,0 +1,3 @@ +- Count only the parent's and partner's own income-related ESA in the extended childcare parent and partner conditions (SI 2022/1134 regs 11A(1)(e), 14(4) and 15(4)): another member of the benefit unit with an award of their own no longer lets a non-working parent or partner meet them. +- Add tests that the choice between the working-age and pension-age Housing Benefit regulations (HB Regs 2006 reg 5(1)(b)) reads only the claimant's and partner's Income Support, income-based JSA and income-related ESA, as it already does. +- Add tests that the mixed-age couple Pension Credit saving (SI 2019/37) reads only the couple's own Income Support, JSA and ESA reports, as it already does. From d5ce48f399a8682b7d97081787d8550a11da1262 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 11:41:43 -0400 Subject: [PATCH 5/6] Quote SI 2022/1134 reg 15(4)(c) and SI 2006/214 reg 5(2) exactly in the YAML notes The extended childcare file attributed reg 14(4)(b)(iii)'s words to reg 15(4); reg 15(4)(c) reads "is entitled to a specified benefit" and needs the partner of a parent who meets reg 14(3). The mixed-age couple file now quotes reg 5(2) as written. Comments only. Co-Authored-By: Claude Opus 5.5 --- .../extended_childcare_entitlement.yaml | 8 ++++---- .../mixed_age_couple_pension_credit_saving.yaml | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/extended_childcare_entitlement.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/extended_childcare_entitlement.yaml index d97cc97027..54a89f9b4d 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/extended_childcare_entitlement.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/extended_childcare_entitlement.yaml @@ -1,7 +1,7 @@ -# SI 2022/1134 reg 15(4): a partner who is not in qualifying paid work meets -# the partner condition if they are "a person entitled to a specified -# benefit" and the parent meets reg 14(3) (likewise reg 14(4) for the -# parent). Reg 11A(1)(e) lists employment and support allowance. The model +# SI 2022/1134 reg 15(4)(c): the partner of a parent who meets reg 14(3) +# meets the partner condition, without working, if they are "entitled to a +# specified benefit" (reg 14(4)(b)(iii) likewise for the parent). Reg +# 11A(1)(e) lists employment and support allowance. The model # reads income-related ESA as the claimant's and partner's award # (claimant_or_partner_esa_income), which counts for both members of the # couple. "other" is a member who is neither the claimant, the partner nor a diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/mixed_age_couple_pension_credit_saving.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/mixed_age_couple_pension_credit_saving.yaml index fe2646cca4..93921ebd58 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/mixed_age_couple_pension_credit_saving.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/mixed_age_couple_pension_credit_saving.yaml @@ -1,14 +1,14 @@ # SI 2019/37 arts 2(3) and 4: a mixed-age couple keeps Pension Credit while it # stays entitled to Pension Credit or to Housing Benefit under the pension-age # regulations (SI 2006/214). Those regulations do not apply where the claimant -# "or, if he has a partner, his partner" is on Income Support, income-based -# JSA or income-related ESA (reg 5). The saving is inferred from reported -# receipt. "other" is a member who is neither the claimant, the partner nor a -# child or young person they are responsible for, so an award they report is -# their own claim and leaves the couple's Housing Benefit under the -# pension-age regulations. is_mixed_age_couple is entered so the cases test -# the award reports alone. The older member, 75 in 2026, was born in 1951 and -# had reached the qualifying age by 14 May 2019. +# ("he, or if he has a partner, his partner") is on Income Support, +# income-based JSA or income-related ESA (reg 5(2)). The saving is inferred +# from reported receipt. "other" is a member who is neither the claimant, the +# partner nor a child or young person they are responsible for, so an award +# they report is their own claim and leaves the couple's Housing Benefit under +# the pension-age regulations. is_mixed_age_couple is entered so the cases +# test the award reports alone. The older member, 75 in 2026, was born in 1951 +# and had reached the qualifying age by 14 May 2019. - name: Another member's income-related ESA leaves a mixed-age couple's Housing Benefit saving in place period: 2026 From 4b43357d6f5c9a71996d3acc6fc637d87d72eead Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 12:13:36 -0400 Subject: [PATCH 6/6] Say that savings above the capital limit screen the added award to nil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review r1 (nit): the further-shapes comment said the added member's award survives tariff income on every savings draw, but the pension-age and working-parents shapes also draw £20,000, above the £16,000 limit for income-related ESA and income-based JSA. Comment only. Co-Authored-By: Claude Opus 5.5 --- .../test_legacy_award_readers_properties.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/policyengine_uk/tests/test_legacy_award_readers_properties.py b/policyengine_uk/tests/test_legacy_award_readers_properties.py index c45ac40462..281503f15b 100644 --- a/policyengine_uk/tests/test_legacy_award_readers_properties.py +++ b/policyengine_uk/tests/test_legacy_award_readers_properties.py @@ -251,13 +251,15 @@ def test_other_members_awards_never_change_the_readers(drawn): # applicant under 25, or for parents of a young child of whom only one works # (the extended childcare partner condition). The families above rarely take # those shapes, so three more properties check every family and member reader -# on them. The claimant and partner seldom report an award themselves, and the -# added member's award survives tariff income on the savings drawn, so a -# reader that wrongly counts it changes. The invariant is about the readers' -# own scoping: the shapes pay no rent, so Universal Credit stays far below the -# benefit cap. The cap still counts every member's award (benefit_cap_reduction -# reads the benefit-unit totals), so with a binding cap another member's award -# can reach a reader through Universal Credit. +# on them. The claimant and partner seldom report an award themselves, and on +# the savings drawn below the £16,000 capital limit the added member's award +# survives tariff income, so a reader that wrongly counts it changes (£20,000 +# of savings screens income-related ESA and income-based JSA to nil). The +# invariant is about the readers' own scoping: the shapes pay no rent, so +# Universal Credit stays far below the benefit cap. The cap still counts every +# member's award (benefit_cap_reduction reads the benefit-unit totals), so +# with a binding cap another member's award can reach a reader through +# Universal Credit. PENSION_AGE = 68 # Whether the pension-age property enters is_mixed_age_couple, so that a # reader of the couple's award reports is tested on those reports alone.