From b6d34c8621310f717cb909be937e22e57afb656b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 07:53:34 -0400 Subject: [PATCH] Tighten the HB passport tests after the #2000 review - The contributory-benefit property control asserted that a family's savings stay its capital. A pension-age family with only contributory benefits can still be passported by its guarantee credit, so hold guarantee_credit at nil there. - Base the three applicable-income arithmetic fixtures on earnings and working tax credit. They no longer rely on Child Benefit (disregarded by SI 2006/213 Sch 5 para 65), or on taking pension contributions from income other than earnings (reg 36(3)(b) takes them from earnings). The expected values are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../hb-passport-review-followups.changed.md | 1 + .../housing_benefit_applicable_income.yaml | 31 +++++++++++-------- ...ome_related_benefit_passport_properties.py | 11 +++++-- 3 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 changelog.d/hb-passport-review-followups.changed.md diff --git a/changelog.d/hb-passport-review-followups.changed.md b/changelog.d/hb-passport-review-followups.changed.md new file mode 100644 index 0000000000..738975e89f --- /dev/null +++ b/changelog.d/hb-passport-review-followups.changed.md @@ -0,0 +1 @@ +- Tighten the Housing Benefit passport tests: hold Guarantee Credit at nil in the contributory-benefit property control, and base the applicable-income arithmetic fixtures on earnings and working tax credit rather than Child Benefit. diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income.yaml index 8b3d0b0bf7..9cb749c7cd 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income.yaml @@ -47,18 +47,20 @@ housing_benefit_applicable_income: 1_000 - name: Pension contributions halved + # SI 2006/213 reg 36(3)(b): half of the pension contributions come off + # earnings. 3,250 - 1,500 / 2 = 2,500. period: 2025 input: # Benefits - child_benefit: 1_000 + child_benefit: 0 income_support: 0 jsa_income: 0 esa_income: 0 # Income - employment_income: 0 + employment_income: 3_250 self_employment_income: 0 property_income: 0 - private_pension_income: 2_250 + private_pension_income: 0 # Tax income_tax: 0 national_insurance: 0 @@ -71,23 +73,25 @@ housing_benefit_applicable_income: 2_500 - name: Added Tax and income + # Earnings plus working tax credit, which counts in full where Sch 5 + # para 56 does not apply. 3,250 + 2,000 - 1,500 / 2 = 4,500. period: 2025 input: # Benefits - child_benefit: 1_000 + child_benefit: 0 income_support: 0 jsa_income: 0 esa_income: 0 # Income - employment_income: 0 + employment_income: 3_250 self_employment_income: 0 - property_income: 1_000 - private_pension_income: 2_250 + property_income: 0 + private_pension_income: 0 # Tax income_tax: 0 national_insurance: 0 # Other - tax_credits: 1_000 + tax_credits: 2_000 pension_contributions: 1_500 housing_benefit_applicable_income_disregard: 0 housing_benefit_applicable_income_childcare_element: 0 @@ -95,23 +99,24 @@ housing_benefit_applicable_income: 4_500 - name: Pension contributions capped at 0 + # Applicable income is floored at nil: 3,250 - 100,000 / 2 < 0. period: 2025 input: # Benefits - child_benefit: 1_000 + child_benefit: 0 income_support: 0 jsa_income: 0 esa_income: 0 # Income - employment_income: 0 + employment_income: 3_250 self_employment_income: 0 - property_income: 1_000 - private_pension_income: 2_250 + property_income: 0 + private_pension_income: 0 # Tax income_tax: 0 national_insurance: 0 # Other - tax_credits: 1_000 + tax_credits: 0 pension_contributions: 100_000 housing_benefit_applicable_income_disregard: 0 housing_benefit_applicable_income_childcare_element: 0 diff --git a/policyengine_uk/tests/test_housing_benefit_income_related_benefit_passport_properties.py b/policyengine_uk/tests/test_housing_benefit_income_related_benefit_passport_properties.py index 1a7152e004..9a9fccd960 100644 --- a/policyengine_uk/tests/test_housing_benefit_income_related_benefit_passport_properties.py +++ b/policyengine_uk/tests/test_housing_benefit_income_related_benefit_passport_properties.py @@ -30,7 +30,9 @@ family in receipt equals the rent whether its members are of working age, a mixed-age couple or over State Pension age. 7. Contributory JSA and ESA are not a passport: moving an amount from - income-based to contribution-based JSA or ESA turns the passport off. + income-based to contribution-based JSA or ESA turns the passport off, and + with no guarantee credit either (which passports a pension-age family in + its own right) the family's savings stay its capital. """ import numpy as np @@ -287,6 +289,11 @@ def test_contributory_jsa_and_esa_are_not_a_passport(units, amount): if name.startswith("a") and name.endswith("_0"): person["jsa_contrib"] = {YEAR: amount} person["esa_contrib"] = {YEAR: amount} + # A pension-age family can still have its capital disregarded through the + # Guarantee Credit passport, so hold that at nil to isolate this one. + for name, benunit in contributory["benunits"].items(): + if name.startswith("b"): + benunit["guarantee_credit"] = {YEAR: 0.0} sim = Simulation(situation=contributory) on_benefit = np.asarray( sim.calculate("in_receipt_of_income_support_jsa_ib_or_esa_ir", YEAR) @@ -294,6 +301,6 @@ def test_contributory_jsa_and_esa_are_not_a_passport(units, amount): assert not on_benefit.any() capital = np.asarray(sim.calculate("housing_benefit_assessable_capital", YEAR)) savings = np.array([unit["savings"] for unit in units]) - # Without the passport the family's savings count as its capital (no + # With neither passport the family's savings count as its capital (no # non-dependants here, so no household apportionment). assert np.allclose(capital, savings, atol=0.01)