Skip to content

Model the 2026 income-related ESA abolition and its UC claims - #2813

Merged
juaristi22 merged 23 commits into
mainfrom
fix-2562-esa-abolition
Nov 5, 2026
Merged

juaristi22 merged 23 commits into
mainfrom
fix-2562-esa-abolition

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2562

What and why

The Secretary of State appointed 1 July 2026 for the remaining old style income-related ESA awards in Great Britain to end under Welfare Reform Act 2012 s.33(1)(b) (SI 2025/1148 art. 3A, inserted by SI 2026/409). The Department for Communities appointed 1 October 2026 in Northern Ireland (SR 2025/176 art. 3A, inserted by SR 2026/115). Under each article, an appointee saving keeps an award in payment, and an award already due to end after a two-week run-on ends on that schedule. DWP had sent migration notices to 879,000 households on income-related ESA by December 2025, and most of them had claimed Universal Credit (LA Welfare Direct 6/2026, paras 51 to 54). At main (2da212a) the model pays an award in full every year unless the family claims Universal Credit or DWP closes another of its legacy benefits, and nothing in the model prompts a claim on the award's appointed day.

This PR:

  • adds gov.dwp.ESA.income.awards_payable.{great_britain,northern_ireland}, a 1/0 flag for each jurisdiction with fiscal_year_blend: true, so the model day-weights the date across each fiscal year from 6 April to 5 April;
  • adds esa_income_appointee_saving (person input, default false) for the art. 3A(2) saving. The saving applies when someone holds an appointment under reg 57 of the Claims and Payments Regulations 2013 immediately before the date, or the Secretary of State determined in the preceding six months that someone would likely need to hold one. In Northern Ireland, the Department for Communities makes that determination and reg 52 of the 2016 Regulations governs the appointment. The claimant and partner share one award, so when either of them reports the award and has the saving, the model keeps the whole claim payable; it ignores a saving for a member who reports nothing;
  • adds esa_income_payable_share (person), the share of the year the person's claim stays payable: 1 for a saved claim, otherwise the household's jurisdiction value. esa_income pays each claim for its share, one share for the couple's claim and one for each member outside it;
  • adds esa_income_abolition_reached (benefit unit): the claimant or partner reports an unsaved award with less than the whole year payable, from 2026-27. claims_uc_at_legacy_closure now reads it beside legacy_benefits_closed, so the family claims Universal Credit when the abolition reaches its award if would_claim_uc_at_legacy_closure. The claim ends the income-related ESA award (esa_income_report_closed) and, outside specified or temporary accommodation, the Housing Benefit award (housing_benefit_eligible), through the rules main already has;
  • has legacy_award_is_closed close an income-related ESA report once none of its claim stays payable, from 2027-28, under the same Pension Credit and pension-age conditions as Income Support and income-based JSA. DWP's move then ends the family's other legacy awards whether or not it claims, apart from the Housing Benefit exception below. The model neither prompts a claim for a saved award nor moves its family;
  • routes the rent of specified or temporary accommodation the way the law does, when the user sets in_specified_or_temporary_accommodation for the family. uc_housing_costs_element returns nil: the Secretary of State excludes these payments from rent payments (UC Regs 2013 Sch 1 paras 3(h), 3(i), 3A and 3B; Northern Ireland: SR 2016/216 Sch 1 paras 3(e), 3(ea), 4 and 4A). housing_benefit_eligible keeps an existing award through a Universal Credit claim (SI 2014/1230 reg 8(3); Northern Ireland: SR 2016/226 reg 6(3)) and through the move off legacy benefits, whichever legacy benefit closed (SI 2014/1230 reg 46(2); Northern Ireland: SR 2016/226 reg 47(2)), and lets a family with no award make a new claim (SI 2014/1230 reg 6A(2); Northern Ireland: SR 2016/226 reg 4A(2)). The tenure and capital tests still apply. The input doesn't change take-up (would_claim_housing_benefit): in a data-built simulation, families that report Housing Benefit claim it, and in a hand-entered one, a family also claims when claims_all_entitled_benefits is true. With no Universal Credit towards this rent, a family gets help with it only through Housing Benefit. A hand-entered family that reports another of the seven means-tested benefits and no Housing Benefit claims only what it reports and gets no help with the rent; to model the claim, a user sets housing_benefit_reported or would_claim_housing_benefit. The 1 July 2026 abolition of Housing Benefit doesn't reach these awards (SI 2025/1148 art. 7(4)(a), through reg 6A(2)). The route reads the input, not an award, so it adds no cycle through the benefit cap. Without the element, uc_work_allowance gives the higher work allowance (UC Regs 2013 reg 22(2)(a)). The benefit cap needs no formula change: DWP counts Housing Benefit as nil in the Universal Credit cap (uc_benefit_cap_reduction; reg 80(2A)), and the council doesn't apply the Housing Benefit cap to a claimant who receives Universal Credit (is_housing_benefit_benefit_cap_exempt_specified_benefit; HB Regs 2006 reg 75F(1)(g)). uc_non_dep_receives_pension_credit already fixes the element at nil in its branch, so the change leaves it alone;
  • has left_pension_route_at_legacy_closure count a protected mixed-age couple whose award the abolition reaches, so a couple that claims and has a Universal Credit award stops getting pension-age Housing Benefit beside it;
  • has own_report_is_paid apply the abolition's report closure to a member outside the couple only when both held amounts match the formula on open reports: esa_income and the claimant's and partner's part of it, claimant_or_partner_esa_income (holds_formula_award). Either amount can differ when a user enters esa_income, a reform replaces it, or disable_simulated_benefits holds it; own_report_is_paid then applies only the person's legacy closure and reads the benefit unit's held award. That award covers the whole unit, so an outside member with an open report stays paid while the unit's award stays positive, even if it leaves them nothing. A member outside the couple at pension age has no legacy closure, so a reform that replaces esa_income keeps that member on income-related ESA. holds_formula_award can't separate two or more outside reporters in one benefit unit when the held total and the couple's part both equal the formula's: own_report_is_paid then applies each one's report closure;
  • has disable_simulated_benefits hold esa_income_abolition_reached false with the other closures, so the reform prompts no claim.

Contributory ESA continues as new style ESA, and esa_contrib keeps its formula.

Which years change

Model year Great Britain Northern Ireland
2025-26 and earlier 1 (unchanged) 1 (unchanged)
2026-27 86/365 = 0.235616 (6 April to 30 June 2026: 25 + 31 + 30 days) 178/365 = 0.487671 (6 April to 30 September 2026: 25 + 31 + 30 + 31 + 31 + 30 days)
2027-28 onwards 0 0

In 2026-27 a family whose award the abolition reaches claims Universal Credit if would_claim_uc_at_legacy_closure, and the claim ends the award for the year. A family that doesn't claim keeps £5,000 x 86/365 = £1,178.08 of a £5,000 award in Great Britain (£2,438.36 in Northern Ireland) and its Housing Benefit. From 2027-28 every unsaved award has ended, and the model moves each affected family off its legacy benefits. Scotland and Wales follow the Great Britain date.

Under art. 3A(1)(b), an award already due to end at the end of a two-week run-on ends on that schedule. A claimant whose migration notice deadline falls on 22 June 2026 and who makes no claim keeps the award until 5 July, the last day of the two weeks beginning with the deadline day (SI 2014/1230 reg 46(1)(a)). The model has no deadline or claim dates, so it pays every unsaved award to 30 June (30 September in Northern Ireland).

Worked household

A single claimant aged 40 in a London council tenancy (rent £5,200 a year, council tax £1,500) reports £5,000 a year of income-related ESA and £4,000 of Housing Benefit, and would_claim_uc is false. The Housing Benefit means test pays £5,200 from the rent. On main every row below pays £5,000 of ESA and £5,200 of Housing Benefit, and no Universal Credit or Council Tax Reduction.

The ESA abolition doesn't end Housing Benefit by itself. The Secretary of State appointed 1 July 2026 for the remaining working-age Housing Benefit awards to end without a Universal Credit claim (SI 2025/1148 art. 7), and the Department for Communities appointed 1 October 2026 in Northern Ireland (SR 2025/176 art. 7), except in specified or temporary accommodation. So under the law this tenant has neither benefit in 2027-28 without a claim, as the model gives. The model doesn't apply art. 7's date: in 2026-27 it pays a family that doesn't claim a full year of Housing Benefit, an approximation that #1910 replaces.

Each cell gives the amount here and, in brackets, the change from main, in pounds. Council Tax Reduction stays £0 in every row.

Earnings would_claim_uc_at_legacy_closure Model year Income-related ESA Housing Benefit Universal Credit Household net income
0 False 2026-27 1,178.08 (−3,821.92) 5,200.00 (0.00) 0.00 (0.00) 4,798.08 (−3,821.92)
0 False 2027-28 0.00 (−5,000.00) 0.00 (−5,200.00) 0.00 (0.00) −1,680.00 (−10,200.00)
0 True 2026-27 0.00 (−5,000.00) 0.00 (−5,200.00) 10,298.80 (+10,298.80) 8,718.80 (+98.80)
0 True 2027-28 0.00 (−5,000.00) 0.00 (−5,200.00) 10,518.52 (+10,518.52) 8,838.52 (+318.52)
10,000 False 2026-27 1,178.08 (−3,821.92) 5,200.00 (0.00) 0.00 (0.00) 14,798.08 (−3,821.92)
10,000 False 2027-28 0.00 (−5,000.00) 0.00 (−5,200.00) 0.00 (0.00) 8,320.00 (−10,200.00)
10,000 True 2026-27 0.00 (−5,000.00) 0.00 (−5,200.00) 4,798.80 (+4,798.80) 13,218.80 (−5,401.20)
10,000 True 2027-28 0.00 (−5,000.00) 0.00 (−5,200.00) 5,018.52 (+5,018.52) 13,338.52 (−5,181.48)

With the flag at its default, true, the model gives the true rows' amounts, and every output matches main in 2025-26. With in_specified_or_temporary_accommodation, the tenant who doesn't claim keeps their £5,200 of Housing Benefit in 2027-28, for £3,520.00 of net income. The tenant without earnings who claims keeps the same £5,200 of Housing Benefit beside Universal Credit, which leaves the rent out: £5,098.80 in 2026-27 and £5,318.52 in 2027-28, the standard allowance alone. Net income comes to the same £8,718.80 and £8,838.52 as in the table, now split between the two benefits. With £10,000 of earnings, the claim brings no Universal Credit, so Universal Credit no longer passports the Housing Benefit means test, and Housing Benefit's 65% taper leaves £3,415.83 in 2026-27 (net income £11,835.83) and £4,838.89 in 2027-28 (£13,158.89), against £13,218.80 and £13,338.52 in the table. With any Universal Credit entitlement, the council disregards all income in the Housing Benefit means test (housing_benefit_on_passporting_benefit and the provisions it cites), so at £9,200 of earnings, with £38.80 of Universal Credit, the tenant receives the whole £5,200 of Housing Benefit and net income comes to £12,858.80.

Data

The certified data (policyengine-uk-data 1.58.1) supply no draw for would_claim_uc_at_legacy_closure, so the model uses its default, true. The model therefore has each affected family claim Universal Credit in 2026-27. In an affected family the claimant or partner reports an unsaved income-related ESA award, and the family neither reports Pension Credit nor consists of a single claimant, or joint claimants both, over the qualifying age for State Pension Credit. A family whose only report belongs to a member outside the couple makes no claim through this path. PolicyEngine/policyengine-uk-data#534, a draft pull request, would draw the flag at DWP's Move to Universal Credit claim rates. No dataset identifies appointees or specified or temporary accommodation, so the saving, the Housing Benefit routes and the Universal Credit exclusion stay off in dataset runs.

What else changes

  • A family that claims Universal Credit and would get more from Tax-Free Childcare can now forgo the claim in 2027-28. On main an income-related ESA report counted as an award the claim would end, so the family kept its claim; now it counts only while some of the year stays payable (forgoes_universal_credit_for_tax_free_childcare). With a £21,993 maximum award and a £20,000 income reduction, Universal Credit would pay £1,993 against £2,400 of Tax-Free Childcare, so the family now forgoes it.
  • A family that supplies in_specified_or_temporary_accommodation now keeps its Housing Benefit when any legacy closure moves it, the tax credit closure from 2025-26 and the Income Support and income-based JSA closures from 2026-27 included. In every year it also keeps the award beside a Universal Credit claim, can claim Housing Benefit afresh, and gets no Universal Credit housing costs element. That changes the model for such a family whatever moved it to Universal Credit, not only for the ESA abolition. Take-up stays as on main (would_claim_housing_benefit), so a hand-entered family that reports another of the seven means-tested benefits and no Housing Benefit claims only what it reports. It loses the Universal Credit housing costs element it gets on main and gets no help with the rent; to model the claim, a user sets housing_benefit_reported or would_claim_housing_benefit. Universal Credit falls by the element, so the model's Council Tax Reduction, which counts Universal Credit as income, can rise, and a family whose Universal Credit falls to nil loses the Housing Benefit passport.
  • has_own_esa_award, the support component default, the Universal Credit LCWRA default, the mixed-age Pension Credit saving and claims_all_entitled_benefits read the raw report as before. Their documentation now treats the report as the award's history: the work capability assessment behind an ended award still stands, and the report still records the family's claiming.
  • The model retains its existing working-age Housing Benefit rules; Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910 adds the closure of those awards on art. 7's dates.

What the model doesn't do

  • It doesn't model deadline days, claim dates, the two-week run-ons or the six-month window of art. 3A(2)(b).
  • It doesn't identify appointees in data.
  • For a family that does not claim Universal Credit in 2026-27, the model keeps the ESA report open for passports and screens throughout the year and pays its amount for the payable share.
  • in_specified_or_temporary_accommodation doesn't separate specified from temporary accommodation. Where the Housing Benefit cap applies, to a family without Universal Credit, the council counts Housing Benefit for specified accommodation as nil (HB Regs 2006 reg 75C(2)(a)), but benefit_cap_welfare_benefits counts it at its amount before the cap.
  • in_specified_or_temporary_accommodation doesn't make a family claim Housing Benefit. With no Universal Credit towards this rent, a family gets help with it only through Housing Benefit. A data-built family with the input and no Housing Benefit report, and a hand-entered family that reports another of the seven means-tested benefits and no Housing Benefit, claim only what they report and get no help with the rent. To model the claim, set housing_benefit_reported or would_claim_housing_benefit.

Invariants

The property tests (policyengine_uk/tests/test_esa_income_abolition_properties.py) check these for generated reported-award situations: working-age families of one to three adults, with or without a child, in five regions, with reports of £0 to £6,000, capital around the tariff threshold and limit, and every combination of saving and claim flags, in 2025-26 to 2027-28.

  1. Each fiscal year's awards_payable value equals the share of 6 April to 5 April before the statutory date, counted from the two dates (2015 to 2040).
  2. 0 <= esa_income_payable_share <= 1; the claimant and partner share one value; it equals 1 for every member of a saved claim.
  3. From 2027-28 the model pays an unsaved claim nothing, and a member outside the couple with an unsaved claim comes off income-related ESA.
  4. Contributory ESA never changes, whatever the saving and claim flags.
  5. Before 2026-27 every output matches the earlier rule for every saving and claim flag. The abolition never raises income-related ESA. A family that claims nothing and saves nothing gets its jurisdiction's share of the earlier award, to the penny.
  6. Turning the saving on never lowers esa_income or esa.
  7. A member outside the couple stays on income-related ESA only while their report stays open.
  8. Saving a member who reports nothing changes no output.
  9. The claim path never gives income-related ESA: turning would_claim_uc_at_legacy_closure on never raises esa_income, and a family that claims when the abolition reaches its award has no claimant_or_partner_esa_income that year.
  10. The model neither prompts a claim for a saved award nor closes it: esa_income_abolition_reached and legacy_benefits_closed stay false for the family, and a member outside the couple with a saved claim keeps their legacy benefits open.
  11. For families with no work, esa_income, claims_universal_credit and each outside member's status match amounts worked in the test from the regulations (the £6,000 tariff threshold, £1 a week for each £250 or part above it and the £16,000 limit; ESA Regs 2008 regs 110 and 118), the dates and the saving, without the model's code. The test's reference gives a couple's claim one share and pays a saved claim in full.
  12. With no reform holding esa_income, holds_formula_award is true for every benefit unit in every mode and year, so the comparison of the claimant's and partner's part changes no baseline result.

Specified or temporary accommodation

policyengine_uk/tests/test_housing_benefit_accommodation_dates_properties.py checks R1 to R3 and R5 to R7 for generated families with the input set: one or two adults aged 22 to 70, up to five children, council, housing association and private tenancies and owners, rent of £0 to £30,000, capital around the £16,000 limit, earnings, reported Housing Benefit, income-related ESA and Working Tax Credit, and every combination of claim and take-up flags, in five regions including Northern Ireland, in 2025-26 to 2027-28.

  • R1. uc_housing_costs_element is nil for every family.
  • R2. housing_benefit_eligible equals the tenure and capital tests alone, and stays the same when the test sets claims_universal_credit and legacy_benefits_closed to each of their four combinations.
  • R3. For a family with both awards, uc_benefit_cap_reduction equals that of the same family with Housing Benefit set to nil, housing_benefit_benefit_cap_reduction is nil, and housing_benefit equals housing_benefit_pre_benefit_cap.
  • R4. With the input false, no output this change touches differs. We checked this outside the repository's tests, with a differential on 600 synthetic families a year in 2025-26, 2026-27 and 2027-28: one or two adults aged 20 to 75, up to four children, six regions including Northern Ireland, five tenures, rents of £0 to £30,000, savings of £0 to £17,000, earnings, LCWRA, appointee savings, reports of the legacy benefits, Universal Credit and Pension Credit, random claim and take-up flags, and the input left unset or set false. On one such population, all 14 outputs the differential compares at 7fe7360fa with 5a5ac5fb1 (housing_benefit_eligible, housing_benefit_pre_benefit_cap, housing_benefit, uc_housing_costs_element, uc_work_allowance, universal_credit_pre_benefit_cap, universal_credit, uc_benefit_cap_reduction, housing_benefit_benefit_cap_reduction, is_housing_benefit_benefit_cap_exempt_specified_benefit, benefit_cap_welfare_benefits, claims_universal_credit, esa_income, household_net_income) match for every family and year. On a second population, with reports of six of the seven legacy benefits, it compares 25 outputs at 3c9890a19 with both 7fe7360fa and 5a5ac5fb1: the 14 above, would_claim_housing_benefit, pension_credit, working_tax_credit, child_tax_credit, income_support, tax_free_childcare, housing_benefit_on_passporting_benefit, council_tax_reduction, free_school_meals, is_scp_eligible and housing_benefit_entitlement. None of the 58,740 values differs from either base. At dc69f3ada, the differential compares the same 25 outputs on a third population built to the same description (600 families a year, reports of six legacy benefits, Universal Credit and Pension Credit, and claims_all_entitled_benefits left to the formula for half the families). With the input unset, false or true for every family, none of the 58,419 values in each mode differs from 7fe7360fa. With the input and would_claim_housing_benefit true for every family, none of the 58,419 values differs from 3c9890a19 with the input true. No dataset sets the input, so CI's aggregate report should show no move from this change.
  • On the second population, with the input set true for every family, household net income at 7fe7360fa falls against 5a5ac5fb1 for 72, 84 and 71 families in the three years, 70, 78 and 67 of them with no Housing Benefit take-up. At 3c9890a19 it falls for 7, 17 and 11 families and rises for 173, 182 and 184. Each family whose net income falls receives Housing Benefit in place of a larger element, or gets none from the means test: Housing Benefit's taper without a Universal Credit passport (3, 8 and 2), Housing Benefit paid weekly over 52 weeks beside Universal Credit, such as £161.10 × 52 = £8,377.20 against an £8,400 element (3, 8 and 6), the Housing Benefit cap for a family without Universal Credit (1, 0 and 2), and the means test taking Housing Benefit to nil (0, 1 and 1). With the input true, council_tax_reduction rises for 1, 3 and 4 families, and housing_benefit_on_passporting_benefit turns false for 4, 10 and 7.
  • On the third population, with the input true for every family and take-up left to the formula, household net income at dc69f3ada falls against 5a5ac5fb1 for 69, 57 and 58 families in the three years and rises for 32, 43 and 42, the same as at 7fe7360fa. Of the families whose net income falls, 65, 55 and 52 get neither Housing Benefit nor an element because they don't take Housing Benefit up. With would_claim_housing_benefit also true, it falls for 9, 10 and 9 families and rises for 147, 129 and 135, the same as at 3c9890a19: Housing Benefit's taper without a Universal Credit passport (3, 5 and 1), Housing Benefit beside Universal Credit a little under the element, such as £12,864.80 against a £12,900 element (3, 5 and 6), the Housing Benefit cap for a family without Universal Credit (0, 0 and 1), and the means test taking Housing Benefit to nil (3, 0 and 1). In both runs council_tax_reduction rises for 1, 2 and 0 families, and housing_benefit_on_passporting_benefit turns false for 7, 5 and 4.
  • R5. In a hand-entered situation, the input never changes would_claim_housing_benefit: with the input true, false or unset, it equals claims_all_entitled_benefits or a Housing Benefit report.
  • R6. With the input and would_claim_housing_benefit true, a family that passes the tenure and capital tests (housing_benefit_eligible) always ends up with Housing Benefit or a housing costs element, except where the Housing Benefit means test takes its entitlement to nil (housing_benefit_entitlement). The council caps Housing Benefit no lower than 50 pence a week (housing_benefit_benefit_cap_reduction), so the cap can't take that Housing Benefit to nil.
  • R7. With the input, a hand-entered family that reports income-related ESA or Working Tax Credit and no Housing Benefit, with claims_all_entitled_benefits and would_claim_housing_benefit left to their formulas, gets neither Housing Benefit nor a housing costs element.

test_keeping_awards_payable_is_saving_every_award checks that a reform keeping awards_payable at 1 gives the same outputs as saving every award.

Invariant 12 also follows from the code. esa_income applies income_related_esa_award_on_reports to open reports, so the first comparison matches. claimant_or_partner_award then finds that esa_income holds the formula's value and takes the same function on the claimant's and partner's open reports, so the second comparison matches unless it sets that part to zero. It does so only where the part, or the stored total that bounds it, falls within half a penny of zero, and then the formula's part also falls within half a penny of zero. is_on_income_related_esa already reads claimant_or_partner_esa_income for the claimant and partner, so the new read adds no cycle.

Test plan

Local runs at dc69f3ada

dc69f3ada6b6bee045ce4db88406311ba5701fcd adds one commit to 3c9890a19. We ran these with no survey data, in the locked environment (uv sync --frozen --extra dev).

YAML policy tests (policyengine-core test):

  • housing_benefit/housing_benefit_eligible.yaml: 15 passed. "A family in temporary accommodation that reports only Universal Credit claims no Housing Benefit and gets no help with the rent": no Housing Benefit, element nil, Universal Credit £5,098.80, net income £5,098.80 + £100 − £180 − £1,500 = £3,518.80. "The same family gets Housing Benefit for the rent when the user sets would_claim_housing_benefit": Housing Benefit £10,000, Universal Credit £5,098.80, net income £13,518.80, the same as its control outside that accommodation. The capital, owner-occupier and control cases from 3c9890a19 stay as they were.
  • housing_benefit/would_claim_housing_benefit.yaml: 6 passed. "The accommodation status doesn't make a family claim without a report" (claims_all_entitled_benefits false, no report) gives false; "A family with the accommodation status claims when it claims all entitled benefits" gives true; "Ineligible with the accommodation status set false" stays false.
  • esa_income/esa_income_abolition.yaml: 41 passed. "Great Britain 2026-27, the same London tenant without a Housing Benefit report claims only the benefits it reports and gets no help with the rent": no Housing Benefit, element nil, Universal Credit £5,098.80, net income £5,098.80 + £100 − £180 − £1,500 = £3,518.80. "Great Britain 2026-27, the same London tenant gets Housing Benefit for the rent when the user sets would_claim_housing_benefit": Housing Benefit £30,000, Universal Credit £5,098.80, net income £33,518.80.
  • finance/benefit/family/housing_benefit: 598 passed; finance/benefit/family/esa_income: 107; finance/benefit/family/universal_credit: 569; gov/dwp: 804.

Python tests (pytest -n 4): test_housing_benefit_accommodation_dates_properties.py 12 passed (R5 to R7); test_housing_benefit_accommodation_take_up_data_built.py 8; test_benefit_cap_uc_and_hb_properties.py 1; test_housing_benefit_universal_credit_passport_properties.py 6; policyengine_uk/tests/code_health 3,091 passed. The data-built file checks that a data-built family with the status and no Housing Benefit report doesn't claim and gets no help with the rent, that one with a report gets the whole rent, that one with would_claim_housing_benefit in the dataset gets the whole rent, that the same family hand-entered with claims_all_entitled_benefits false doesn't claim and gets no help with the rent, and that it gets the whole rent with would_claim_housing_benefit set.

Other checks at dc69f3ada: .github/check_fix_pr.py on this title and body, with the changes from the merge base with main, passes with 29 changed executable tests; uvx ruff@0.17.2 format --check . finds 2,170 files formatted; ruff check . and ruff check --select F811,F821 policyengine_uk pass; and git diff --check 3c9890a19..HEAD finds no whitespace errors.

We ran each mutant below as one change to a copy of the branch at dc69f3ada, against housing_benefit_eligible.yaml, would_claim_housing_benefit.yaml, esa_income_abolition.yaml, R1 to R3, R5 to R7 and the data-built test. Each one failed at least one of them:

Mutant (dc69f3ada) Failing cases Failing tests
Puts the status back into would_claim_housing_benefit's hand-entered branch 3: the family that reports only Universal Credit, the status with no report, and the London tenant without a Housing Benefit report R5, R7, the hand-entered family that doesn't claim
The route skips the capital limit 1: £20,000 of capital with the status R2
The route skips the tenure test 1: the owner occupier with the status R2
The housing costs element ignores the input 11: every routing case with the status that rents, both overrides included R1, R7, both data-built families with no report, the hand-entered family that doesn't claim
Drops the reg 8(3) route 4: the £5,200 claimant, the London tenant, the capped lone parent and Northern Ireland's reg 6(3) R2, both data-built families with a report
Drops the reg 46(2) route 2: the 2027-28 move off legacy benefits and the tax credit closure, each with the status R2
Drops the new-claim route 6: the new claim, the same family without take-up, both families that report another benefit, and both overrides R2, every data-built and hand-entered family without a report

The differential above (R4) compares dc69f3ada with 7fe7360fa and 3c9890a19.

Local runs at 7fe7360fa

We ran these with no survey data, in the locked environment (uv sync --frozen --extra dev).

YAML policy tests (policyengine-core test):

  • esa_income/esa_income_abolition.yaml: 39 passed. The branch adds three cases: a London tenant in council-arranged homelessness accommodation, with £30,000 of rent, whom the abolition moves to Universal Credit (Housing Benefit £30,000, Universal Credit £5,098.80, net income £33,518.80); the same tenant without the status (Housing Benefit nil, Universal Credit capped at £16,967, net income £15,387); and a lone parent of four whose Universal Credit the cap cuts by £2,442.20 to £17,821 while Housing Benefit pays the whole £15,000 rent. The case "a family in specified or temporary accommodation that claims Universal Credit" now keeps its £5,200 of Housing Benefit beside £5,098.80 of Universal Credit, where it had none.
  • housing_benefit/housing_benefit_eligible.yaml: 10 passed. The branch adds four cases: a new claim beside Universal Credit (reg 6A(2)); the same family without take-up gets no help with the rent; Northern Ireland's reg 6(3) with SR 2016/216's exclusion; and its control without the status.
  • universal_credit/work_allowance: 17 passed, two new: the higher work allowance (£8,520) in that accommodation, and the lower one (£5,124) without it.
  • universal_credit/housing_costs_element: 21 passed, one new.
  • housing_benefit/housing_benefit_residential_capital.yaml: 7 passed. housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml: 50 passed. housing_benefit/housing_benefit_universal_credit_passport.yaml: 9 passed.
  • Benefit cap cases: gov/dwp/benefit_cap: 116 passed; gov/dwp/pension_credit: 320 passed; gov/dwp/universal_credit: 16 passed; all of gov/dwp: 804 passed, benefit_cap.yaml (18) and benefit_cap_by_scheme.yaml (22) included.
  • Before the case edits, gov/dwp, finance/benefit/family and integration gave 2,588 passed and 1 failed, the case above that now keeps its Housing Benefit.

Python tests (pytest -n 4): test_housing_benefit_accommodation_dates.py 38 passed; test_housing_benefit_accommodation_dates_properties.py 9 passed (R1 to R3 new); test_housing_benefit_specified_accommodation_disregard_properties.py 11; test_esa_income_abolition_properties.py 31; test_esa_income_universal_credit_claim_properties.py 2; test_uc_legacy_closure_properties.py 4; test_legacy_closure_scope.py 21; test_benefit_cap_uc_and_hb_properties.py 1; test_benefit_cap_lha_scheme_split_properties.py 7; test_housing_benefit_pension_age_properties.py 4; policyengine_uk/tests/code_health 3,091 passed.

At 5a5ac5fb1, the 8 new routing cases fail and the 3 controls pass, and R1 and R2 fail; R3 passes there, as the cap needed no formula change. We ran each mutant below as one change to a copy of the branch, against the four YAML files above and R1 to R3:

Mutant Failing cases Failing properties
The housing costs element ignores the input 8: every new routing case with the status, and the element case R1
Drops the reg 8(3) route (an award with the status ends on a claim) 5: the £5,200 claimant, the London tenant, the capped lone parent, Northern Ireland's reg 6(3) and the higher work allowance R2
Drops the reg 46(2) route (an award with the status ends on the move) 2: the 2027-28 move off legacy benefits and the tax credit closure, each with the status R2
Drops the new-claim route 2: the new claim and the same family without take-up R2

7fe7360fa merged cleanly with main (0080274d7) and with #2807 at 71ba2752; on the #2807 merge, esa_income, housing_benefit, universal_credit/housing_costs_element and universal_credit/work_allowance passed 757 of 757.

Local runs at a3f8b1104

Each file or test ran alone at a3f8b1104, with -n 0, no survey data and a 4 GB memory cap (2 GB for mutants); the cap stopped any run that exceeded its time limit. Later commits up to 5a5ac5fb1 change only documentation, comments and the changelog.

YAML policy tests (policyengine-core test):

  • esa_income/esa_income_abolition.yaml: 36 passed. The branch added three cases: the 2027-28 ESA closure keeps Housing Benefit for a family with in_specified_or_temporary_accommodation, the same family without the status loses it, and a family with the status that claims Universal Credit, which at a3f8b1104 got no Housing Benefit and at 7fe7360fa keeps it.
  • housing_benefit/housing_benefit_eligible.yaml: 6 passed. The branch adds two cases: the tax credit closure keeps Housing Benefit with the status and ends it without.

Python tests (pytest):

  • test_esa_income_held_outside_award.py (new): 3 passed, one for each way of holding the award.
  • test_esa_income_abolition_properties.py: the 29 parameter and fixed-case tests passed in one run. CI ran the whole file at 5a5ac5fb1, and all 31 tests passed, including test_abolition_invariants, which now checks invariant 12, and test_awards_match_an_independent_reading.
  • test_uc_legacy_closure_properties.py: test_the_closure_flag_only_decides_closure_claims passed (357 s) and test_a_closure_claim_pays_the_universal_credit_of_any_claim passed (617 s), each at full size with a 3,400 s cap.
  • test_disable_simulated_benefits.py: CI ran the file at 5a5ac5fb1, and all 89 tests passed.
  • Synthetic probes of holds_formula_award at 5a5ac5fb1: 9 of 11 passed. The 2 failures reproduce the limits in the own_report_is_paid bullet above: a stored award that leaves an outside member nothing, and a reform that scales esa_income with a pension-age outside member.

Fails on origin/main (2da212a): with the reproducer's inputs (a single claimant aged 40 in London reporting £5,000), main pays £5,000 of income-related ESA in 2027-28 against £0 here, and £5,000 in 2026-27 against £1,178.08 without a claim.

We ran each mutant as one change to a copy of the branch, against esa_income_abolition.yaml unless the row names another test. The first row ran at a3f8b1104. The rest ran on the branch's earlier code, up to 68ccc0856, and the later commits leave their mutated lines unchanged. The benunit.sum row, the row for a saving for a member who reports nothing and the last two rows ran at 5a5ac5fb1.

Mutant Failing cases
holds_formula_award drops the claimant's and partner's comparison (test_esa_income_held_outside_award.py) 3 of 3: the £5,200 unit in the frozen, stored and replaced modes
The claim term leaves claims_uc_at_legacy_closure 5: the GB and NI 2026-27 claims, a couple with a saved partner who reports nothing, and both mixed-age couple cases
legacy_award_is_closed ignores income-related ESA 5: the GB and NI 2027-28 ends, the outside adult's 2027-28 end, the end of Housing Benefit without a claim, and the 2027-28 claim
The claim term ignores the saving 5: the saved awards in Great Britain (2027-28) and Northern Ireland (2026-27), both saved-couple cases, and the saved award's 2026-27 claim
The closure ignores the saving 3: the saved single and couple awards in 2027-28
Each member's own saving, not the claim's 2: both saved-couple cases in 2027-28
benunit.sum for the couple's share 4: both couple cases before and after the abolition, the saved partner who reports nothing, and the saved couple in 2027-28
A saving for a member who reports nothing saves the claim 2: both cases with a saved partner who reports nothing
The pension-age route ignores the abolition 1: the protected mixed-age couple
The claim term drops the Pension Credit age guard 1: the single claimant over the qualifying age
The reform stops holding the claim term (test_disable_simulated_benefits.py -k no_family_is_moved) 2: 2026-27 and 2027-28, where the reform would prompt the ESA claimant's claim
continuing_award drops the accommodation term (with housing_benefit_eligible.yaml) 2: the Great Britain 2027-28 move off legacy benefits and the tax credit closure, each with the status
own_report_is_paid treats every held award as the formula's (test_esa_income_held_outside_award.py) 3 of 3: the frozen, stored and replaced modes

Existing tests changed

No expected value in a test on main changes. At 7fe7360fa one case this branch added changes, as the list above records, and comments in benefit_cap_by_scheme.yaml, housing_benefit_universal_credit_passport.yaml, test_benefit_cap_uc_and_hb_properties.py and test_housing_benefit_universal_credit_passport_properties.py no longer say the model lacks the route; those cases still supply both awards so that they test the cap or the passport alone. Two property-test reference calculations now include Universal Credit claims prompted by the ESA abolition. Cases about other rules opt out of that claim.

  • test_uc_legacy_closure_properties.py: expected_closure closes an unsaved income-related ESA report from 2027-28 under the Pension Credit conditions; a new expected_reached gives the abolition's reach from 2026-27; a family claims on either; and a claiming family gets no income-related ESA. The flag property and the closure-claim differential treat a reached family as moved.
  • test_legacy_award_work_properties.py and legacy_award_work_reference.py: in 2026-27 the couple's ESA reports close when the abolition reaches them, because the family claims with would_claim_uc_at_legacy_closure at its default.
  • test_disable_simulated_benefits.py: new assertions that the abolition reaches the claimant's own award from 2026-27 without the reform, and that the reform prompts no claim.

Cases about other rules set would_claim_uc_at_legacy_closure false, so the family keeps the award they test:

  • esa_income_remunerative_work.yaml, the two 2026-27 limit cases: they test the permitted-work limit, and a claim would close the report.
  • pension_credit/mixed_age_couples.yaml, "HB alongside income-related ESA is working-age HB, so no saving": it tests the saving inference and the continuing Housing Benefit award, which a claim would end.
  • test_mixed_age_couple_properties.py, every generated family: its reference formulas from before the mixed-age couple rules read would_claim_uc alone.

In Case 1 of universal_credit/person_legacy_benefits_closed.yaml, the claimant gets esa_income_appointee_saving instead: the case checks that an outside member's closed report leaves the couple open, and from 2027-28 the claimant's own unsaved report would close the couple.

Thirteen existing YAML cases and four Python files test another rule on an income-related ESA award in 2026-27 or 2027-28, so each ESA reporter there gets esa_income_appointee_saving: true, which keeps the award payable for the whole year (inputs only):

  • esa_income/esa_income_capital.yaml: "has no tariff income at the lower capital threshold", "tariff income rounds incomplete capital steps up" and "remains eligible at the upper capital limit" (2026);
  • esa_income/esa_income_remunerative_work.yaml: "Issue 2395 outside closed IS report leaves the couple ESA award open" (2027);
  • legacy_award_readers/council_tax_reduction_passports.yaml: the five non-dependant cases (2027);
  • legacy_award_readers/housing_benefit_pension_age_regulations.yaml: the two ESA cases (2026);
  • legacy_award_readers/pension_credit_earnings_disregard.yaml: "Another member's income-related ESA leaves a single pensioner with the £5 earnings disregard" and "The partner's income-related ESA gives the couple the £20 earnings disregard" (2026);
  • test_esa_permitted_work_nlw_reforms.py (2026), the two 2027 Merton non-dependant tests in test_legacy_award_readers_properties.py, the 2027 cases of test_legacy_closure_scope.py, and every award in test_esa_income_universal_credit_claim_properties.py, whose 2027 cases test the Universal Credit claim under art. 3A(3).

Overlap

Reproducer

Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_abolition.yaml::Great Britain 2027-28 pays no income-related ESA

axiom: TheAxiomFoundation/rulespec-uk#518 queued

🤖 Generated with Claude Code

MaxGhenis and others added 18 commits October 31, 2026 20:07
Add gov.dwp.ESA.income.awards_payable, a 1/0 flag per jurisdiction with
fiscal_year_blend, so income-related ESA awards end on 1 July 2026 in Great
Britain and 1 October 2026 in Northern Ireland. esa_income pays each award
for esa_income_payable_share of the year, and esa_income_report_closed ends
the report once none of the year is payable. esa_income_appointee_saving
keeps an award saved under art. 3A(2). own_report_is_paid reads the same
closure as the award.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a UC claim

Hand-worked YAML cases for 2025-26, 2026-27 (86/365 in Great Britain,
178/365 in Northern Ireland) and 2027-28, the appointee saving, a saved
award with a Universal Credit claim, contributory-only and mixed awards, a
family the legacy closure already moved and a non-dependent adult's own
award. forgoes_universal_credit_for_tax_free_childcare counts an ESA report
as a legacy award a claim would end only while some of the year is payable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thirteen YAML cases in 2026-27 and 2027-28 test capital, work, passports,
Housing Benefit regulations and the Pension Credit disregard on an
income-related ESA award. The abolition now ends or shortens those awards,
so each reporter gets esa_income_appointee_saving (SI 2025/1148 art. 3A(2)),
which keeps the award payable all year; expected values are unchanged. The
UC-claim property test saves every award, so 2027 still tests art. 3A(3).
Add the abolition property tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bolition

Python tests that read income-related ESA in 2026-27 or 2027-28 for another
rule give their reporters esa_income_appointee_saving (SI 2025/1148 art.
3A(2)): the NLW permitted-work reform, the Merton non-dependant properties
and the legacy-closure scope cases. Under disable_simulated_benefits an
outside adult's own income-related ESA now ends with the abolition, as
income-based JSA ends with its scheme, so that test expects it. The property
test runs each population once with three copies (saving as drawn, every
award saved, none saved) and checks that a reform keeping the parameter at 1
equals saving every award. Add the changelog fragment and a paragraph on the
abolition to docs/engineering/esa-jsa-report-screening.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mber's status

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ordinary parameters take their 30 April value; the flag's fiscal_year_blend
day-weights each fiscal year. Drop a sentence about dated reform keys that
the current reform routes no longer follow, and say plainly that the
appointee saving is off in dataset runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lose it from 2027-28

- esa_income_abolition_reached: the claimant's or partner's unsaved
  income-related ESA claim is payable for less than the whole year; it
  joins legacy_benefits_closed in claims_uc_at_legacy_closure.
- legacy_award_is_closed closes an ESA report whose claim has nothing
  payable, under the Income Support and JSA Pension Credit guard.
- esa_income_payable_share is the claim's share: a couple's claim is
  saved when either member who reports it is saved.
- own_report_is_paid lets a stored award, not the abolition closure,
  decide an outside member's status, so disable_simulated_benefits keeps
  them on the award it pays; the reform holds the new claim term.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… claim

- esa_income_abolition.yaml: share cases opt out of the claim; new cases
  for the claim (GB, NI, couples, a protected mixed-age couple), the
  2027-28 move off legacy benefits, saved claims, a saved partner who
  reports nothing, and the Pension Credit age guard.
- Property tests: an independent reading of each award from the
  regulations, one share per couple, saving a non-reporter changes
  nothing, and the claim path never gives income-related ESA.
- The UC closure and work-screen references model the claim; families
  in tests about other rules set would_claim_uc_at_legacy_closure false
  or the saving.
- disable_simulated_benefits keeps an outside adult on the ESA it pays.
- own_report_is_paid reads the benefit unit population for the award.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aid's signature

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a legacy closure

A family that supplies in_specified_or_temporary_accommodation keeps an
existing Housing Benefit award when DWP moves it off a closed legacy
benefit: SI 2014/1230 reg 46(2) (NI: SR 2016/226 reg 47(2)) keeps such an
award at the deadline whichever legacy benefit closed, and SI 2025/1148
art. 7(4)(a) reaches the same cases through reg 6A(2). A Universal Credit
claim still ends the award, because the model's Universal Credit pays a
housing costs element for these homes.

Tests cover the ESA closure and the tax credit closure, each with a control
that leaves the status unset, and a family with the status that claims.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… amounts are the formula's

own_report_is_paid now applies the abolition's report closure to an
outside member's status only when the stored esa_income and the
claimant's and partner's part (claimant_or_partner_esa_income) both match
the formula on open reports (holds_formula_award). Under
disable_simulated_benefits, an entered award or a replacing reform, a held
couple part of £5,000 against a formula's £5,200 identifies the outside
adult's held £200, so their status no longer depends on whether the
claimant's own report happens to give the held total.

The new test holds the awards three ways and varies the claimant's
2027-28 report across £5,199, £5,200 and £5,201. Invariant 12 checks that
without a reform both amounts match the formula for every generated
benefit unit, so the extra comparison changes no baseline result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e who acts in its documentation

esa_income_abolition_reached lists every input it reads: the claimant's and
partner's ESA and Pension Credit reports, the household's country, the
saving, the Pension Credit age conditions and mixed-age couple status.
would_claim_uc_at_legacy_closure says the certified data (1.58.1) carry no
draw and that PolicyEngine/policyengine-uk-data#534 plans one.

Documentation and comments introduce the Department for Communities by
name and give the model, not a share or a year, the paying verbs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gelog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in active voice

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name the benefit cap and the nil claim where the docs say Housing Benefit
beside a claim would pay the rent twice, and state that the benefit unit's
held award decides an outside member's status. Words only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Aggregate impact on certified data: pass

Base 52b30809ff against head aa17931441, both on enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1, sha256 056e80fd8e91), fiscal years 2025-26 to 2031-32. Each year is a full microsimulation run on each side.

Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 44 (thresholds in report_spec.yaml; a flag does not fail the job).

Workflow run and full report

New warnings

  • Employment and Support Allowance (income-related) is not zero for every unit (inert:esa_income): 2026-27: zero for every unit; 2027-28: zero for every unit; 2028-29: zero for every unit

Notes

  • In the years a figure from fewer than 10 records is suppressed (spending.disabled_students_allowance), every total containing it, and the decile incomes, are rebuilt from their other components, so no published total includes it. Those totals are marked †. A change is withheld where the two sides leave out different figures and can't be lined up.

Moves flagged for review

Measure Year Base Head Change
Employment and Support Allowance (income-related) 2031-32 71k 0k -71.3k
Employment and Support Allowance (income-related) 2030-31 71k 0k -71.1k
Employment and Support Allowance (income-related) 2029-30 71k 0k -70.9k
Employment and Support Allowance (income-related) 2027-28 71k 0k -70.8k
Employment and Support Allowance (income-related) 2028-29 71k 0k -70.8k
Employment and Support Allowance (income-related) 2026-27 71k 0k -70.7k
Universal Credit 2031-32 £88.21bn £89.38bn +£1.18bn
Universal Credit 2030-31 £87.61bn £88.76bn +£1.15bn
Universal Credit 2029-30 £86.11bn £87.22bn +£1.12bn
Universal Credit 2028-29 £84.96bn £86.07bn +£1.11bn
Universal Credit 2027-28 £82.95bn £84.02bn +£1.08bn
Universal Credit 2026-27 £80.52bn £81.56bn +£1.04bn
Housing Benefit 2031-32 1,232k 1,168k -63.4k
Housing Benefit 2030-31 1,229k 1,166k -63.2k
Housing Benefit 2029-30 1,227k 1,164k -63.1k
Housing Benefit 2028-29 1,227k 1,164k -62.9k
Housing Benefit 2027-28 1,244k 1,181k -63.0k
Housing Benefit 2026-27 1,245k 1,182k -62.9k
Employment and Support Allowance (income-related) 2031-32 £0.92bn £0.00bn -£920.0m
Employment and Support Allowance (income-related) 2030-31 £0.90bn £0.00bn -£900.1m
Employment and Support Allowance (income-related) 2029-30 £0.88bn £0.00bn -£880.4m
Employment and Support Allowance (income-related) 2028-29 £0.86bn £0.00bn -£860.8m
Employment and Support Allowance (income-related) 2027-28 £0.84bn £0.00bn -£844.1m
Employment and Support Allowance (income-related) 2026-27 £0.82bn £0.00bn -£815.3m
Decile 3: mean household_net_income per household 2029-30 £37,197 £36,999 -£198
Housing Benefit 2031-32 £10.27bn £9.83bn -£446.9m
Housing Benefit 2030-31 £9.99bn £9.56bn -£433.4m
Housing Benefit 2029-30 £9.69bn £9.28bn -£419.6m
Housing Benefit 2028-29 £9.46bn £9.05bn -£407.2m
Housing Benefit 2027-28 £9.23bn £8.83bn -£392.6m
Housing Benefit 2026-27 £8.89bn £8.51bn -£376.3m
Decile 2: mean household_net_income per household 2027-28 £28,711 £28,619 -£92
Decile 3: mean household_net_income per household 2028-29 £36,611 £36,521 -£90
Universal Credit 2031-32 6,223k 6,295k +72.1k
Universal Credit 2030-31 6,271k 6,343k +71.9k
Universal Credit 2029-30 6,292k 6,363k +71.8k
Universal Credit 2028-29 6,297k 6,369k +71.6k
Universal Credit 2027-28 6,336k 6,408k +71.7k
Universal Credit 2026-27 6,347k 6,418k +71.6k
Decile 2: mean household_net_income per household 2031-32 £30,150 £30,086 -£64

4 more flags are in the report artifact.

Spending by benefit and service

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Universal Credit 0 +£1.04bn +£1.08bn +£1.11bn +£1.12bn +£1.15bn +£1.18bn
Housing Benefit 0 -£376.3m -£392.6m -£407.2m -£419.6m -£433.4m -£446.9m
Council Tax Reduction 0 -£3.1m -£3.2m -£3.5m -£3.5m -£3.7m -£3.8m
Employment and Support Allowance (income-related) 0 -£815.3m -£844.1m -£860.8m -£880.4m -£900.1m -£920.0m
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Caseloads

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Universal Credit 0 +71.6k +71.7k +71.6k +71.8k +71.9k +72.1k
Housing Benefit 0 -62.9k -63.0k -62.9k -63.1k -63.2k -63.4k
Council Tax Reduction 0 -1.1k -1.1k -1.1k -713 -715 -717
Employment and Support Allowance (income-related) 0 -70.7k -70.8k -70.8k -70.9k -71.1k -71.3k
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Government balance, each component counted once

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government spending 0 † -£153.1m † -£164.3m † -£164.6m † -£187.9m † -£191.0m † -£195.6m †
Government balance, each receipt once 0 † +£153.1m † +£164.3m † +£164.6m † +£187.9m † +£191.0m † +£195.6m †

Model totals

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_spending 0 † -£153.1m † -£164.3m † -£164.6m † -£187.9m † -£191.0m † -£195.6m †
household_benefits 0 † -£153.1m † -£164.3m † -£164.6m † -£187.9m † -£191.0m † -£195.6m †
gov_balance 0 † +£153.1m † +£164.3m † +£164.6m † +£187.9m † +£191.0m † +£195.6m †

Household income totals

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household net income 0 † -£153.1m † -£164.3m † -£164.6m † -£187.9m † -£191.0m † -£195.6m †
Household net income (HBAI definition) 0 -£153.1m -£164.3m -£164.6m -£187.9m -£191.0m -£195.6m

Poverty rates

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 0 +0.01pp +0.01pp +0.01pp +0.01pp +0.01pp +0.01pp
Absolute poverty, before housing costs: children 0 -0.00pp -0.00pp -0.00pp -0.00pp -0.00pp -0.00pp
Absolute poverty, before housing costs: working-age adults 0 +0.01pp +0.01pp +0.01pp +0.01pp +0.01pp +0.01pp
Absolute poverty, before housing costs: pensioners 0 +0.03pp +0.03pp +0.03pp +0.03pp +0.03pp +0.03pp
Absolute poverty, after housing costs: everyone 0 +0.01pp +0.01pp +0.01pp +0.01pp +0.01pp +0.02pp
Absolute poverty, after housing costs: working-age adults 0 +0.01pp +0.01pp +0.01pp +0.01pp +0.01pp +0.02pp
Absolute poverty, after housing costs: pensioners 0 +0.04pp +0.04pp +0.04pp +0.04pp +0.04pp +0.04pp
Relative poverty, before housing costs: everyone 0 +0.01pp +0.01pp -0.01pp +0.01pp +0.01pp -0.01pp
Relative poverty, before housing costs: children 0 -0.00pp -0.00pp -0.03pp -0.00pp -0.00pp -0.04pp
Relative poverty, before housing costs: working-age adults 0 +0.00pp +0.01pp -0.01pp +0.01pp +0.01pp -0.02pp
Relative poverty, before housing costs: pensioners 0 +0.03pp +0.03pp +0.02pp +0.03pp +0.03pp +0.03pp
Relative poverty, after housing costs: everyone 0 +0.01pp -0.05pp +0.00pp +0.02pp +0.01pp +0.01pp
Relative poverty, after housing costs: children 0 0 -0.09pp -0.01pp 0 0 -0.01pp
Relative poverty, after housing costs: working-age adults 0 +0.01pp -0.06pp -0.00pp +0.02pp +0.01pp +0.01pp
Relative poverty, after housing costs: pensioners 0 +0.04pp +0.02pp +0.04pp +0.04pp +0.04pp +0.04pp

People in poverty

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 0 +5.8k +6.6k +7.2k +7.2k +7.2k +6.9k
Absolute poverty, before housing costs: children 0 -52 -52 -52 -52 -52 -52
Absolute poverty, before housing costs: working-age adults 0 +2.2k +3.1k +3.7k +3.6k +3.6k +3.3k
Absolute poverty, before housing costs: pensioners 0 +3.6k +3.6k +3.6k +3.6k +3.6k +3.6k
Absolute poverty, after housing costs: everyone 0 +7.7k +8.8k +8.6k +9.2k +9.2k +13.8k
Absolute poverty, after housing costs: working-age adults 0 +3.3k +4.3k +4.2k +4.8k +4.8k +9.4k
Absolute poverty, after housing costs: pensioners 0 +4.4k +4.4k +4.4k +4.4k +4.4k +4.5k
Relative poverty, before housing costs: everyone 0 +4.4k +6.6k -4.2k +5.7k +7.1k -8.9k
Relative poverty, before housing costs: children 0 -70 -52 -4.4k -326 -52 -5.3k
Relative poverty, before housing costs: working-age adults 0 +925 +3.1k -2.4k +3.1k +3.5k -7.2k
Relative poverty, before housing costs: pensioners 0 +3.5k +3.6k +2.7k +3.0k +3.6k +3.6k
Relative poverty, after housing costs: everyone 0 +7.9k -35.8k +1.9k +10.6k +10.4k +8.4k
Relative poverty, after housing costs: children 0 0 -13.6k -1.5k 0 0 -1.2k
Relative poverty, after housing costs: working-age adults 0 +3.5k -24.0k -628 +6.6k +6.3k +4.7k
Relative poverty, after housing costs: pensioners 0 +4.4k +1.8k +4.0k +4.0k +4.2k +4.8k

Inequality

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs 0 +0.0000 +0.0001 +0.0000 +0.0001 +0.0001 +0.0001

Mean household net income in each side's own deciles

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household 0 † +£12 † +£3.19 † +£7.97 † -£6.08 † +£13 † +£14 †
Decile 2: mean household_net_income per household 0 † -£2.01 † -£92 † -£10 † +£6.81 † +£1.52 † -£64 †
Decile 3: mean household_net_income per household 0 † -£55 † +£7.21 † -£90 † -£198 † -£50 † -£30 †
Decile 4: mean household_net_income per household 0 † -£16 † -£14 † +£43 † +£91 † -£20 † +£13 †
Decile 5: mean household_net_income per household 0 † -£2.08 † +£23 † -£32 † -£3.37 † -£6.86 † -£8.91 †
Decile 6: mean household_net_income per household 0 † +£34 † +£42 † +£49 † +£36 † +£41 † +£39 †
Decile 7: mean household_net_income per household 0 † +£8.24 † +£8.19 † +£6.42 † +£12 † +£7.65 † +£9.60 †
Decile 8: mean household_net_income per household 0 † +£2.37 † +£2.39 † +£2.47 † +£2.46 † +£5.27 † +£2.48 †
Decile 9: mean household_net_income per household 0 † +£0.01 † -£0.01 † -£0.01 † -£0.01 † -£0.01 † -£0.01 †
Decile 10: mean household_net_income per household 0 † -£0.61 † -£0.64 † -£0.63 † -£0.62 † -£0.62 † -£0.63 †

Standard reform costings (change in gov_balance)

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp 0 +£2 -£0 -£2 -£0 +£0 +£0
Raise higher rate by 1pp 0 +£0 -£0 -£0 0 +£0 -£0
Raise personal allowance by ~800GBP/year 0 +£0 0 +£0 +£1 -£0 +£1
Raise child benefit by 25GBP/week per additional child 0 0 0 0 0 +£0 -£0
Reduce Universal Credit taper rate to 20% 0 -£13,572 -£13,492 -£12,526 -£12,929 -£13,599 -£14,650
Raise Class 1 main employee NICs rate to 10% 0 +£1 -£0 +£0 -£0 suppressed suppressed
Raise VAT standard rate by 2pp 0 +£7 +£23 -£27 -£11 -£6 +£14
Raise additional rate by 3pp 0 -£0 +£0 +£0 0 suppressed suppressed
Raise the Universal Credit standard allowance by 10% 0 -£37.1m -£38.6m -£40.0m -£41.0m -£42.0m -£42.9m
Raise the State Pension by 10% 0 +£5.1m +£5.2m +£5.4m +£5.6m +£5.8m +£5.9m

The report suppresses a reform's costing and target change in these years, for the reasons below (privacy.py):

  • Raise Class 1 main employee NICs rate to 10%, 2030-31 to 2031-32 (base and head): the reform changes income_tax in fewer than 10 records.
  • Raise additional rate by 3pp, 2030-31 to 2031-32 (base and head): the reform changes universal_credit in fewer than 10 records.

Standard reforms: change in each reform's target

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Reduce Universal Credit taper rate to 20%: change in universal_credit 0 +£13,571 +£13,492 +£12,526 +£12,930 +£13,599 +£14,653
Raise Class 1 main employee NICs rate to 10%: change in national_insurance 0 0 0 0 0 suppressed suppressed
Raise additional rate by 3pp: change in income_tax 0 0 0 0 0 suppressed suppressed
Raise the Universal Credit standard allowance by 10%: change in universal_credit 0 +£37.7m +£39.2m +£40.6m +£41.7m +£42.7m +£43.7m

Household net income by decile

Mean change in household net income per household, and as a share of the decile's base income; households stay in the base run's deciles.

Base decile 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1 0 +£2 (+0.0%) +£2 (+0.0%) +£3 (+0.0%) +£2 (+0.0%) +£2 (+0.0%) +£2 (+0.0%)
Decile 2 0 +£4 (+0.0%) +£3 (+0.0%) +£3 (+0.0%) +£3 (+0.0%) +£3 (+0.0%) +£3 (+0.0%)
Decile 3 0 0 0 +£1 (+0.0%) -£2 (-0.0%) -£2 (-0.0%) -£2 (-0.0%)
Decile 4 0 -£35 (-0.1%) -£34 (-0.1%) -£35 (-0.1%) -£36 (-0.1%) -£37 (-0.1%) -£37 (-0.1%)
Decile 5 0 -£12 (-0.0%) -£13 (-0.0%) -£14 (-0.0%) -£16 (-0.0%) -£17 (-0.0%) -£17 (-0.0%)
Decile 6 0 -£8 (-0.0%) -£10 (-0.0%) -£11 (-0.0%) -£10 (-0.0%) -£11 (-0.0%) -£11 (-0.0%)
Decile 7 0 -£2 (-0.0%) -£2 (-0.0%) -£2 (-0.0%) -£3 (-0.0%) -£2 (-0.0%) -£3 (-0.0%)
Decile 8 0 0 0 0 0 0 0
Decile 9 0 fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change
Decile 10 0 fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change fewer than 10 households change
all 0 -£5 (-0.0%) -£5 (-0.0%) -£5 (-0.0%) -£6 (-0.0%) -£6 (-0.0%) -£6 (-0.0%)
Year People gaining over £1 People losing over £1
2025-26 0.0% 0.0%
2026-27 0.1% 0.1%
2027-28 0.0% 0.1%
2028-29 0.0% 0.1%
2029-30 0.0% 0.1%
2030-31 0.0% 0.1%
2031-32 0.0% 0.1%

No change in any year: receipts by tax head; population.

Checks failing on both base and head (1)
  • VAT excluding private school fees is never negative (sign:vat): 2025-26: 76 units below zero, a weighted £-53,888,684 in total; 2026-27: 76 units below zero, a weighted £-55,777,260 in total; 2027-28: 76 units below zero, a weighted £-57,727,917 in total
Counted-once check

Each of 92 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.

  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; gov_spending; not a failure)
  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; household_benefits; not a failure)
Provenance
Side Commit Runtime Environment hash Partition hash Time
base main 52b30809ff uk 2.172.0 (files f8400aea019e), core 3.34.1, py 3.13.18 aarch64 18199314be15 7cb61324a46b 2085.26s
head #2813 merged into main aa17931441 uk 2.172.0 (files 8e0b2dc3c2d8), core 3.34.1, py 3.13.18 aarch64 18199314be15 7cb61324a46b 2073.85s

Dataset: hf://policyengine/policyengine-uk-data-private/enhanced_frs_2024_25.h5@2526edbbb687a3b54dbb11272e5389e4b62d2ac2, sha256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784 (verified on both sides), policyengine-uk-data 1.58.1.

Receipts by tax head: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Final charge on cash interest in non-cash ISAs £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Lifetime ISA withdrawal charge £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Income tax £294.46bn £315.23bn £335.82bn £351.28bn £366.96bn £385.36bn £400.00bn
National Insurance (employee and self-employed) £51.70bn £54.14bn £56.13bn £57.73bn £59.76bn £61.64bn £63.51bn
National Insurance (employer) £148.71bn £155.37bn £160.88bn £165.09bn £172.06bn £177.36bn £182.57bn
Employer NI change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Employer NI change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Capital gains tax £12.60bn £13.15bn £13.60bn £14.11bn £14.67bn £15.24bn £15.80bn
Student loan repayments £7.82bn £8.13bn £8.39bn £8.58bn £8.79bn £9.04bn £9.29bn
VAT excluding private school fees £249.37bn £256.87bn £266.33bn £272.70bn £278.97bn £285.24bn £291.59bn
Change in VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
VAT on private school fees £1.95bn £2.01bn £2.09bn £2.14bn £2.20bn £2.26bn £2.31bn
Change in private school VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Fuel duties £24.71bn £24.13bn £26.00bn £26.83bn £26.51bn £25.78bn £24.63bn
Alcohol duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tobacco duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Vehicle excise duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Betting and gaming duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
TV licence fee £4.26bn £4.40bn £4.41bn £4.42bn £4.43bn £4.44bn £4.45bn
Council tax £49.21bn £52.16bn £54.89bn £57.95bn £61.16bn £64.47bn £67.91bn
High value council tax surcharge £0.00bn £0.00bn £0.00bn £0.48bn £0.49bn £0.50bn £0.51bn
Northern Ireland domestic rates £0.55bn £0.58bn £0.60bn £0.63bn £0.66bn £0.69bn £0.72bn
Business rates £34.68bn £36.20bn £38.00bn £39.46bn £42.27bn £43.25bn £44.29bn
Stamp duty land tax on the household's own purchases £6.88bn £7.32bn £7.66bn £8.06bn £8.47bn £8.91bn £9.35bn
Stamp duty land tax paid by companies £4.72bn £4.57bn £5.29bn £6.03bn £6.37bn £6.92bn £7.45bn
Land transaction tax (Wales) £0.07bn £0.08bn £0.08bn £0.09bn £0.10bn £0.10bn £0.11bn
Land and buildings transaction tax (Scotland) £0.31bn £0.32bn £0.33bn £0.35bn £0.36bn £0.38bn £0.40bn
Unspecified tax change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Unspecified tax change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax excluding the main residence £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Land value tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Carbon tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Spending by benefit and service: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
State Pension £121.68bn £125.92bn £126.72bn £129.49bn £132.99bn £136.66bn £140.42bn
Pension Credit £6.66bn £7.03bn £7.29bn £7.48bn £7.60bn £7.84bn £8.03bn
Winter Fuel Payment £1.86bn £1.85bn £1.79bn £1.77bn £1.78bn £1.78bn £1.78bn
Pension Age Winter Heating Payment (Scotland) £0.19bn £0.20bn £0.20bn £0.21bn £0.21bn £0.22bn £0.22bn
Universal Credit £75.48bn £81.56bn £84.02bn £86.07bn £87.22bn £88.76bn £89.38bn
Housing Benefit £8.56bn £8.51bn £8.83bn £9.05bn £9.28bn £9.56bn £9.83bn
Council Tax Reduction £2.07bn £2.19bn £2.31bn £2.44bn £2.58bn £2.72bn £2.86bn
Employment and Support Allowance (income-related) £0.79bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Employment and Support Allowance (contribution-based) £5.20bn £5.37bn £5.56bn £5.69bn £5.82bn £5.95bn £6.09bn
Jobseeker's Allowance (income-based) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Jobseeker's Allowance (contribution-based) £0.29bn £0.30bn £0.31bn £0.31bn £0.32bn £0.33bn £0.34bn
Income Support £0.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Working Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Incapacity Benefit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Personal Independence Payment £26.95bn £28.03bn £29.05bn £30.09bn £30.75bn £31.51bn £32.21bn
Disability Living Allowance £7.49bn £7.79bn £8.07bn £8.36bn £8.55bn £8.76bn £8.95bn
Attendance Allowance £8.68bn £9.03bn £9.36bn £9.69bn £9.90bn £10.15bn £10.38bn
Carer's Allowance £4.48bn £4.66bn £4.83bn £5.00bn £5.11bn £5.27bn £5.42bn
Carer Support Payment (Scotland) £0.31bn £0.32bn £0.33bn £0.34bn £0.35bn £0.36bn £0.37bn
Carer's Allowance Supplement (Scotland) £0.00bn £0.04bn £0.04bn £0.05bn £0.05bn £0.05bn £0.05bn
Severe Disablement Allowance £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn
Industrial Injuries Disablement Benefit £0.50bn £0.52bn £0.54bn £0.55bn £0.56bn £0.58bn £0.59bn
Armed Forces Compensation Scheme £0.61bn £0.63bn £0.65bn £0.66bn £0.68bn £0.69bn £0.71bn
War Widow's or Widower's Pension £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Benefit £16.19bn £16.82bn £17.39bn £18.00bn £18.39bn £18.84bn £19.25bn
Scottish Child Payment £0.49bn £0.52bn £0.53bn £0.55bn £0.56bn £0.57bn £0.56bn
Two Child Limit Payment (Scotland, counterfactual) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Healthy Start vouchers £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn
Maternity Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Maternity Pay £3.30bn £3.41bn £3.54bn £3.62bn £3.70bn £3.78bn £3.87bn
Statutory Paternity Pay £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Sick Pay £1.12bn £1.16bn £1.20bn £1.23bn £1.26bn £1.29bn £1.32bn
Sure Start Maternity Grant £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn
Bereavement Support Payment £0.67bn £0.69bn £0.71bn £0.73bn £0.75bn £0.76bn £0.78bn
Widowed Parent's Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tax-Free Childcare £0.56bn £0.57bn £0.57bn £0.58bn £0.60bn £0.61bn £0.59bn
Extended childcare entitlement £2.99bn £3.50bn £3.59bn £3.71bn £3.78bn £3.86bn £3.93bn
Universal childcare entitlement £2.06bn £2.22bn £2.29bn £2.37bn £2.43bn £2.48bn £2.53bn
Targeted childcare entitlement £0.50bn £0.51bn £0.52bn £0.54bn £0.55bn £0.57bn £0.56bn
Childcare Offer for Wales £0.26bn £0.28bn £0.28bn £0.29bn £0.30bn £0.31bn £0.32bn
Care to Learn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Childcare Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Parents' Learning Allowance £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn
Adult Dependants' Grant £0.06bn £0.06bn £0.04bn £0.04bn £0.04bn £0.04bn £0.04bn
Travel Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
NHS spending £203.89bn £204.30bn £204.56bn £204.91bn £205.34bn £205.85bn £206.35bn
State education spending £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn
Transport subsidy spending £25.05bn £25.10bn £25.14bn £25.18bn £25.23bn £25.30bn £25.36bn
Cost-of-living support payment £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Bills Rebate £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Household Energy Payment £0.00bn £2.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Price Guarantee subsidy £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disability basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Other public spending budget change £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Caseloads: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus 0k 0k 0k 0k 0k 0k 0k
State Pension 11,264k 11,145k 10,819k 10,700k 10,723k 10,750k 10,776k
Pension Credit 1,486k 1,480k 1,472k 1,452k 1,450k 1,455k 1,464k
Winter Fuel Payment 7,844k 7,783k 7,473k 7,375k 7,391k 7,409k 7,427k
Pension Age Winter Heating Payment (Scotland) 803k 802k 800k 797k 799k 801k 802k
Universal Credit 6,342k 6,418k 6,408k 6,369k 6,363k 6,343k 6,295k
Housing Benefit 1,249k 1,182k 1,181k 1,164k 1,164k 1,166k 1,168k
Council Tax Reduction 3,176k 3,194k 3,199k 3,213k 3,223k 3,235k 3,241k
Employment and Support Allowance (income-related) 71k 0k 0k 0k 0k 0k 0k
Employment and Support Allowance (contribution-based) 719k 720k 721k 722k 724k 726k 728k
Jobseeker's Allowance (income-based) 0k 0k 0k 0k 0k 0k 0k
Jobseeker's Allowance (contribution-based) 67k 67k 67k 67k 67k 68k 68k
Income Support 25k 0k 0k 0k 0k 0k 0k
Working Tax Credit 0k 0k 0k 0k 0k 0k 0k
Child Tax Credit 0k 0k 0k 0k 0k 0k 0k
Incapacity Benefit 0k 0k 0k 0k 0k 0k 0k
Personal Independence Payment 3,567k 3,575k 3,579k 3,585k 3,593k 3,602k 3,611k
Disability Living Allowance 1,291k 1,294k 1,296k 1,298k 1,300k 1,304k 1,307k
Attendance Allowance 1,695k 1,698k 1,700k 1,703k 1,707k 1,711k 1,715k
Carer's Allowance 1,034k 1,037k 1,038k 1,040k 1,042k 1,060k 1,062k
Carer Support Payment (Scotland) 71k 71k 71k 72k 72k 72k 72k
Carer's Allowance Supplement (Scotland) 0k 71k 71k 72k 72k 72k 72k
Severe Disablement Allowance 4k 4k 4k 4k 4k 4k 4k
Industrial Injuries Disablement Benefit 137k 137k 137k 138k 138k 138k 139k
Armed Forces Compensation Scheme 93k 93k 94k 94k 94k 94k 94k
War Widow's or Widower's Pension 0k 0k 0k 0k 0k 0k 0k
Child Benefit 8,123k 8,120k 8,120k 8,130k 8,145k 8,161k 8,178k
Scottish Child Payment 351k 352k 352k 353k 350k 348k 337k
Two Child Limit Payment (Scotland, counterfactual) 0k 0k 0k 0k 0k 0k 0k
Healthy Start vouchers 200k 200k 200k 201k 201k 201k 202k
Maternity Allowance 0k 0k 0k 0k 0k 0k 0k
Statutory Maternity Pay 333k 333k 334k 334k 335k 336k 337k
Statutory Paternity Pay 0k 0k 0k 0k 0k 0k 0k
Statutory Sick Pay 181k 182k 182k 182k 182k 183k 183k
Sure Start Maternity Grant 26k 26k 26k 26k 26k 26k 26k
Bereavement Support Payment 98k 98k 98k 98k 98k 99k 99k
Widowed Parent's Allowance 0k 0k 0k 0k 0k 0k 0k
Tax-Free Childcare 1,043k 1,027k 1,014k 1,009k 1,011k 1,009k 993k
Extended childcare entitlement 795k 800k 796k 797k 798k 798k 792k
Universal childcare entitlement 1,071k 1,072k 1,072k 1,073k 1,076k 1,079k 1,074k
Targeted childcare entitlement 165k 156k 154k 154k 155k 155k 150k
Childcare Offer for Wales 39k 39k 39k 39k 39k 39k 39k
Care to Learn 0k 0k 0k 0k 0k 0k 0k
Childcare Grant 0k 0k 0k 0k 0k 0k 0k
Parents' Learning Allowance 98k 98k 98k 99k 99k 99k 99k
Adult Dependants' Grant 18k 18k 12k 12k 12k 12k 12k
Travel Grant 0k 0k 0k 0k 0k 0k 0k
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund 0k 0k 0k 0k 0k 0k 0k
Cost-of-living support payment 0k 0k 0k 0k 0k 0k 0k
Energy Bills Rebate 0k 0k 0k 0k 0k 0k 0k
Household Energy Payment 0k 21,065k 0k 0k 0k 0k 0k
Energy Price Guarantee subsidy 0k 0k 0k 0k 0k 0k 0k
Basic income 0k 0k 0k 0k 0k 0k 0k
Disability basic income 0k 0k 0k 0k 0k 0k 0k
Government balance, each component counted once: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government receipts, each component once £892.00bn £934.66bn £980.50bn £1,015.93bn £1,054.24bn £1,091.56bn £1,124.89bn
Government spending £529.38bn † £545.51bn † £550.60bn † £559.34bn † £567.01bn † £575.76bn † £583.40bn †
Government balance, each receipt once £362.62bn † £389.16bn † £429.90bn † £456.59bn † £487.23bn † £515.80bn † £541.49bn †
Benefits in household_benefits but not in gov_spending £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Model totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_tax £892.00bn £934.66bn £980.50bn £1,015.93bn £1,054.24bn £1,091.56bn £1,124.89bn
household_tax £491.97bn £520.41bn £551.21bn £576.01bn £601.00bn £626.70bn £648.42bn
pre_budget_change_household_tax £484.15bn £512.28bn £542.82bn £566.95bn £591.72bn £617.16bn £638.61bn
gov_spending £529.38bn † £545.51bn † £550.60bn † £559.34bn † £567.01bn † £575.76bn † £583.40bn †
household_benefits £529.38bn † £545.51bn † £550.60bn † £559.34bn † £567.01bn † £575.76bn † £583.40bn †
gov_balance £362.62bn † £389.16bn † £429.90bn † £456.59bn † £487.23bn † £515.80bn † £541.49bn †
Household income totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household market income £1,637.96bn £1,702.77bn £1,757.35bn £1,806.41bn £1,855.97bn £1,912.43bn £1,970.44bn
Household net income £1,636.69bn † £1,687.66bn † £1,715.29bn † £1,747.33bn † £1,778.50bn † £1,816.83bn † £1,859.49bn †
Household net income (HBAI definition) £1,449.57bn £1,499.67bn £1,530.70bn £1,565.01bn £1,597.70bn £1,635.41bn £1,676.42bn
Population: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
People 69,245k 69,383k 69,473k 69,592k 69,738k 69,912k 70,080k
Benefit units 35,110k 35,181k 35,226k 35,286k 35,360k 35,449k 35,534k
Households 31,046k 31,108k 31,149k 31,201k 31,267k 31,345k 31,420k
Poverty rates: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 16.3% 15.5% 16.4% 16.3% 16.5% 16.5% 16.6%
Absolute poverty, before housing costs: children 24.7% 22.1% 23.3% 22.9% 23.1% 23.2% 23.5%
Absolute poverty, before housing costs: working-age adults 14.1% 13.8% 14.7% 14.7% 14.8% 14.8% 15.0%
Absolute poverty, before housing costs: pensioners 13.7% 13.4% 14.0% 13.8% 14.0% 14.0% 13.9%
Absolute poverty, after housing costs: everyone 21.6% 21.1% 22.1% 22.6% 22.9% 23.1% 23.5%
Absolute poverty, after housing costs: children 33.3% 31.3% 32.3% 32.5% 32.9% 33.4% 34.2%
Absolute poverty, after housing costs: working-age adults 19.9% 19.8% 20.8% 21.6% 21.9% 22.0% 22.3%
Absolute poverty, after housing costs: pensioners 12.7% 12.4% 13.2% 13.2% 13.3% 13.4% 13.6%
Relative poverty, before housing costs: everyone 17.0% 16.0% 16.4% 16.3% 16.4% 16.3% 16.6%
Relative poverty, before housing costs: children 25.9% 22.9% 23.3% 22.9% 23.0% 22.9% 23.4%
Relative poverty, before housing costs: working-age adults 14.5% 14.1% 14.6% 14.7% 14.7% 14.7% 14.9%
Relative poverty, before housing costs: pensioners 14.5% 14.1% 14.0% 13.8% 13.8% 13.9% 13.9%
Relative poverty, after housing costs: everyone 21.4% 21.0% 21.2% 21.3% 21.3% 21.5% 21.7%
Relative poverty, after housing costs: children 33.1% 31.1% 31.1% 31.0% 31.1% 31.2% 31.8%
Relative poverty, after housing costs: working-age adults 19.9% 19.8% 20.2% 20.4% 20.5% 20.6% 20.8%
Relative poverty, after housing costs: pensioners 12.5% 12.4% 12.2% 11.8% 11.7% 11.9% 12.1%
People in poverty: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 11,278k 10,784k 11,409k 11,372k 11,483k 11,520k 11,657k
Absolute poverty, before housing costs: children 3,688k 3,314k 3,496k 3,445k 3,480k 3,502k 3,549k
Absolute poverty, before housing costs: working-age adults 5,965k 5,895k 6,311k 6,366k 6,423k 6,434k 6,524k
Absolute poverty, before housing costs: pensioners 1,624k 1,575k 1,601k 1,561k 1,579k 1,584k 1,584k
Absolute poverty, after housing costs: everyone 14,944k 14,607k 15,319k 15,730k 15,946k 16,150k 16,440k
Absolute poverty, after housing costs: children 4,975k 4,682k 4,837k 4,888k 4,945k 5,040k 5,179k
Absolute poverty, after housing costs: working-age adults 8,463k 8,465k 8,976k 9,348k 9,496k 9,588k 9,715k
Absolute poverty, after housing costs: pensioners 1,506k 1,461k 1,506k 1,494k 1,505k 1,522k 1,546k
Relative poverty, before housing costs: everyone 11,750k 11,088k 11,394k 11,360k 11,422k 11,423k 11,642k
Relative poverty, before housing costs: children 3,872k 3,423k 3,496k 3,440k 3,466k 3,454k 3,544k
Relative poverty, before housing costs: working-age adults 6,159k 6,014k 6,304k 6,360k 6,392k 6,393k 6,514k
Relative poverty, before housing costs: pensioners 1,720k 1,652k 1,594k 1,560k 1,565k 1,576k 1,584k
Relative poverty, after housing costs: everyone 14,849k 14,560k 14,740k 14,811k 14,884k 15,024k 15,242k
Relative poverty, after housing costs: children 4,942k 4,660k 4,660k 4,662k 4,679k 4,707k 4,805k
Relative poverty, after housing costs: working-age adults 8,426k 8,444k 8,687k 8,823k 8,880k 8,974k 9,067k
Relative poverty, after housing costs: pensioners 1,482k 1,457k 1,394k 1,327k 1,326k 1,343k 1,370k
Inequality: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs 0.3080 0.3053 0.3059 0.3058 0.3063 0.3069 0.3085
Mean household net income in each side's own deciles: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household £17,681 † £18,010 † £17,918 † £18,159 † £18,301 † £18,544 † £18,730 †
Decile 2: mean household_net_income per household £27,939 † £29,021 † £28,619 † £29,003 † £29,283 † £29,692 † £30,086 †
Decile 3: mean household_net_income per household £34,320 † £35,430 † £36,080 † £36,521 † £36,999 † £37,686 † £38,157 †
Decile 4: mean household_net_income per household £38,933 † £40,671 † £41,709 † £42,770 † £43,424 † £44,112 † £44,445 †
Decile 5: mean household_net_income per household £43,239 † £45,565 † £46,038 † £46,927 † £47,708 † £48,577 † £50,273 †
Decile 6: mean household_net_income per household £47,927 † £49,244 † £50,934 † £51,988 † £52,981 † £53,983 † £54,719 †
Decile 7: mean household_net_income per household £55,662 † £56,579 † £57,450 † £57,676 † £58,741 † £59,664 † £61,092 †
Decile 8: mean household_net_income per household £68,789 † £70,805 † £71,860 † £73,469 † £73,883 † £75,326 † £77,180 †
Decile 9: mean household_net_income per household £78,890 † £81,013 † £80,861 † £82,646 † £85,464 † £86,946 † £89,509 †
Decile 10: mean household_net_income per household £117,005 † £120,339 † £124,741 † £126,631 † £125,971 † £128,600 † £130,937 †
Standard reform costings (change in gov_balance): head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp £6.41bn £6.66bn £6.63bn £6.85bn £7.03bn £7.25bn £7.49bn
Raise higher rate by 1pp £3.61bn £3.87bn £4.00bn £4.22bn £4.44bn £4.71bn £4.83bn
Raise personal allowance by ~800GBP/year £-3.80bn £-3.92bn £-3.98bn £-4.04bn £-4.10bn £-4.23bn £-1.95bn
Raise child benefit by 25GBP/week per additional child £-1.99bn £-1.75bn £-1.55bn £-1.38bn £-1.30bn £-1.18bn £-1.07bn
Reduce Universal Credit taper rate to 20% £-20.77bn £-21.32bn £-21.75bn £-21.99bn £-22.34bn £-22.75bn £-22.94bn
Raise Class 1 main employee NICs rate to 10% £10.89bn £11.30bn £11.71bn £12.01bn £12.31bn suppressed suppressed
Raise VAT standard rate by 2pp £24.89bn £25.71bn £26.63bn £27.24bn £27.87bn £28.50bn £29.14bn
Raise additional rate by 3pp £2.90bn £3.15bn £3.22bn £3.41bn £3.61bn suppressed suppressed
Raise the Universal Credit standard allowance by 10% £-3.06bn £-3.22bn £-3.30bn £-3.39bn £-3.48bn £-3.53bn £-3.54bn
Raise the State Pension by 10% £-7.78bn £-8.01bn £-8.24bn £-8.46bn £-8.81bn £-8.54bn £-8.88bn
Standard reforms: change in each reform's target: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp: change in income_tax £6.55bn £6.86bn £6.79bn £7.00bn £7.20bn £7.44bn £7.66bn
Raise higher rate by 1pp: change in income_tax £3.61bn £3.92bn £4.01bn £4.23bn £4.45bn £4.72bn £4.84bn
Raise personal allowance by ~800GBP/year: change in income_tax £-3.93bn £-4.07bn £-4.13bn £-4.18bn £-4.22bn £-4.40bn £-1.75bn
Raise child benefit by 25GBP/week per additional child: change in child_benefit £2.33bn £2.14bn £1.94bn £1.76bn £1.65bn £1.52bn £1.40bn
Reduce Universal Credit taper rate to 20%: change in universal_credit £20.74bn £21.31bn £21.78bn £22.04bn £22.37bn £22.78bn £22.99bn
Raise Class 1 main employee NICs rate to 10%: change in national_insurance £11.15bn £11.63bn £12.00bn £12.29bn £12.61bn suppressed suppressed
Raise VAT standard rate by 2pp: change in vat £24.69bn £25.51bn £26.42bn £27.03bn £27.65bn £28.28bn £28.91bn
Raise additional rate by 3pp: change in income_tax £2.90bn £3.15bn £3.22bn £3.42bn £3.62bn suppressed suppressed
Raise the Universal Credit standard allowance by 10%: change in universal_credit £3.06bn £3.22bn £3.32bn £3.41bn £3.47bn £3.53bn £3.55bn
Raise the State Pension by 10%: change in state_pension £10.68bn £11.17bn £11.28bn £11.60bn £12.03bn £12.47bn £12.97bn

A member who reports nothing has no ESA report to close, so
esa_income_report_closed can differ from person_legacy_benefits_closed for
them with no effect on any award.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 5a5ac5fb1c7547b08a0b6dc042c4e8e253050abf. Two blockers remain.

C1 — Preserve temporary-accommodation Housing Benefit on the new UC migration path. The new ESA-abolition trigger induces a UC claim, but housing_benefit_eligible.py:108 still removes HB even for qualifying temporary accommodation. UC then includes that rent and caps it. In a native 2026 case—a40-year-old London WRAG claimant in qualifying council-arranged homelessness accommodation, £30,000 eligible annual rent, £5,000 reported ESA, no earnings/capital/other cap exemption, would_claim_uc=false and migration take-up=true—the head pays HB£0 and UC£16,967. Final household income is £15,387.

TPreg8(3) preserves this HB after a UC claim; UCSch1 paras3(i)/3B excludes this rent from UC, and HBreg75F(1)(g) exempts HB while UC is received. An otherwise identical, explicitly source-backed control changing only those three routing intermediates calculates HB£30,000 plus UC£5,098.80, and household income £33,518.80: £18,131.80 more. Please compose the new migration trigger with both housing rules and cover the capped combined final award. The underlying limitation predates this PR, but this PR adds the affected claim path and a new test accepting HB's loss; the documented dependency on #1910 leaves this path incorrect.

C2 — Include the new no-claim HB saving in the queued parity record. The changed HB continuation implements GBreg46(2) and NIreg47(2). Queue518 quotes only the run-on limbs46(1)/47(1), expressly excludes wider migration, and contains no HB-saving output or companion case/addendum. Those provisions are absent at the pinned rulespec main. docs/engineering/skills/github-prs.md:141–157 requires the changed provision's dispatch-ready module/corpus target, operative quotation, outputs and same-source tests. Add the HB savings/cases to the issue or link a complete companion record. Completing a billed encoder run is not required for this correction.

Review COMPLETE.82 focused tests/diagnostics passed; two expensive properties were deliberately excluded locally, with the broader frozen suite green at this head. The temporary-accommodation diagnostic confirms the counterexample; passing the new HB-loss expectation does not resolve it.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Picking this up for the UK hub. Reply here if you're mid-change and I'll stop.


Generated by Claude Code

…Universal Credit

Universal Credit leaves that rent out of its housing costs element (UC Regs
2013 Sch 1 paras 3(h), 3(i), 3A and 3B; SR 2016/216 Sch 1 paras 3(e), 3(ea),
4 and 4A). Housing Benefit for it survives a Universal Credit claim (SI
2014/1230 reg 8(3); SR 2016/226 reg 6(3)) and a migration deadline (reg
46(2); reg 47(2)), and a family there can make a new claim (reg 6A(2); reg
4A(2)).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6JY861guYKTfTZajLq
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_TITLE.txt

Model the 2026 income-related ESA abolition and its UC claims


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_BODY.md

Fixes #2562

## What and why

The Secretary of State appointed 1 July 2026 for the remaining old style income-related ESA awards in Great Britain to end under Welfare Reform Act 2012 s.33(1)(b) ([SI 2025/1148 art. 3A](https://www.legislation.gov.uk/uksi/2025/1148/article/3A), inserted by SI 2026/409). The Department for Communities appointed 1 October 2026 in Northern Ireland ([SR 2025/176 art. 3A](https://www.legislation.gov.uk/nisr/2025/176/article/3A), inserted by SR 2026/115). Under each article, an appointee saving keeps an award in payment, and an award already due to end after a two-week run-on ends on that schedule. DWP had sent migration notices to 879,000 households on income-related ESA by December 2025, and most of them had claimed Universal Credit ([LA Welfare Direct 6/2026](https://www.gov.uk/government/publications/la-welfare-direct-bulletins-2026/la-welfare-direct-62026#update-move-to-universal-credit), paras 51 to 54). At `main` (2da212abc) the model pays an award in full every year unless the family claims Universal Credit or DWP closes another of its legacy benefits, and nothing in the model prompts a claim on the award's appointed day.

This PR:

- adds `gov.dwp.ESA.income.awards_payable.{great_britain,northern_ireland}`, a 1/0 flag for each jurisdiction with `fiscal_year_blend: true`, so the model day-weights the date across each fiscal year from 6 April to 5 April;
- adds `esa_income_appointee_saving` (person input, default false) for the art. 3A(2) saving. The saving applies when someone holds an appointment under reg 57 of the Claims and Payments Regulations 2013 immediately before the date, or the Secretary of State determined in the preceding six months that someone would likely need to hold one. In Northern Ireland, the Department for Communities makes that determination and reg 52 of the 2016 Regulations governs the appointment. The claimant and partner share one award, so when either of them reports the award and has the saving, the model keeps the whole claim payable; it ignores a saving for a member who reports nothing;
- adds `esa_income_payable_share` (person), the share of the year the person's claim stays payable: 1 for a saved claim, otherwise the household's jurisdiction value. `esa_income` pays each claim for its share, one share for the couple's claim and one for each member outside it;
- adds `esa_income_abolition_reached` (benefit unit): the claimant or partner reports an unsaved award with less than the whole year payable, from 2026-27. `claims_uc_at_legacy_closure` now reads it beside `legacy_benefits_closed`, so the family claims Universal Credit when the abolition reaches its award if `would_claim_uc_at_legacy_closure`. The claim ends the income-related ESA award (`esa_income_report_closed`) and, outside specified or temporary accommodation, the Housing Benefit award (`housing_benefit_eligible`), through the rules `main` already has;
- has `legacy_award_is_closed` close an income-related ESA report once none of its claim stays payable, from 2027-28, under the same Pension Credit and pension-age conditions as Income Support and income-based JSA. DWP's move then ends the family's other legacy awards whether or not it claims, apart from the Housing Benefit exception below. The model neither prompts a claim for a saved award nor moves its family;
- routes the rent of specified or temporary accommodation the way the law does, when the user sets `in_specified_or_temporary_accommodation` for the family. `uc_housing_costs_element` returns nil: the Secretary of State excludes these payments from rent payments ([UC Regs 2013 Sch 1 paras 3(h), 3(i), 3A and 3B](https://www.legislation.gov.uk/uksi/2013/376/schedule/1/paragraph/3); Northern Ireland: [SR 2016/216 Sch 1 paras 3(e), 3(ea), 4 and 4A](https://www.legislation.gov.uk/nisr/2016/216/schedule/1/paragraph/3)). `housing_benefit_eligible` keeps an existing award through a Universal Credit claim ([SI 2014/1230 reg 8(3)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/8); Northern Ireland: [SR 2016/226 reg 6(3)](https://www.legislation.gov.uk/nisr/2016/226/regulation/6)) and through the move off legacy benefits, whichever legacy benefit closed ([SI 2014/1230 reg 46(2)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/46); Northern Ireland: [SR 2016/226 reg 47(2)](https://www.legislation.gov.uk/nisr/2016/226/regulation/47)), and lets a family with no award make a new claim ([SI 2014/1230 reg 6A(2)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/6A); Northern Ireland: [SR 2016/226 reg 4A(2)](https://www.legislation.gov.uk/nisr/2016/226/regulation/4A)). The tenure and capital tests still apply, and `would_claim_housing_benefit` still decides who claims. The 1 July 2026 abolition of Housing Benefit doesn't reach these awards ([SI 2025/1148 art. 7(4)(a)](https://www.legislation.gov.uk/uksi/2025/1148/article/7), through reg 6A(2)). The route reads the input, not an award, so it adds no cycle through the benefit cap. Without the element, `uc_work_allowance` gives the higher work allowance ([UC Regs 2013 reg 22(2)(a)](https://www.legislation.gov.uk/uksi/2013/376/regulation/22)). The benefit cap needs no formula change: the Universal Credit cap counts Housing Benefit as nil (`uc_benefit_cap_reduction`; [reg 80(2A)](https://www.legislation.gov.uk/uksi/2013/376/regulation/80)), and a Housing Benefit claimant receiving Universal Credit is outside the Housing Benefit cap (`is_housing_benefit_benefit_cap_exempt_specified_benefit`; [HB Regs 2006 reg 75F(1)(g)](https://www.legislation.gov.uk/uksi/2006/213/regulation/75F)). `uc_non_dep_receives_pension_credit` already fixes the element at nil in its branch, so the change leaves it alone;
- has `left_pension_route_at_legacy_closure` count a protected mixed-age couple whose award the abolition reaches, so a couple that claims and has a Universal Credit award stops getting pension-age Housing Benefit beside it;
- has `own_report_is_paid` apply the abolition's report closure to a member outside the couple only when both held amounts match the formula on open reports: `esa_income` and the claimant's and partner's part of it, `claimant_or_partner_esa_income` (`holds_formula_award`). Either amount can differ when a user enters `esa_income`, a reform replaces it, or `disable_simulated_benefits` holds it; `own_report_is_paid` then applies only the person's legacy closure and reads the benefit unit's held award. That award covers the whole unit, so an outside member with an open report stays paid while the unit's award stays positive, even if it leaves them nothing. A member outside the couple at pension age has no legacy closure, so a reform that replaces `esa_income` keeps that member on income-related ESA. `holds_formula_award` can't separate two or more outside reporters in one benefit unit when the held total and the couple's part both equal the formula's: `own_report_is_paid` then applies each one's report closure;
- has `disable_simulated_benefits` hold `esa_income_abolition_reached` false with the other closures, so the reform prompts no claim.

Contributory ESA continues as new style ESA, and `esa_contrib` keeps its formula.

### Which years change

| Model year | Great Britain | Northern Ireland |
|---|---|---|
| 2025-26 and earlier | 1 (unchanged) | 1 (unchanged) |
| 2026-27 | 86/365 = 0.235616 (6 April to 30 June 2026: 25 + 31 + 30 days) | 178/365 = 0.487671 (6 April to 30 September 2026: 25 + 31 + 30 + 31 + 31 + 30 days) |
| 2027-28 onwards | 0 | 0 |

In 2026-27 a family whose award the abolition reaches claims Universal Credit if `would_claim_uc_at_legacy_closure`, and the claim ends the award for the year. A family that doesn't claim keeps £5,000 x 86/365 = £1,178.08 of a £5,000 award in Great Britain (£2,438.36 in Northern Ireland) and its Housing Benefit. From 2027-28 every unsaved award has ended, and the model moves each affected family off its legacy benefits. Scotland and Wales follow the Great Britain date.

Under art. 3A(1)(b), an award already due to end at the end of a two-week run-on ends on that schedule. A claimant whose migration notice deadline falls on 22 June 2026 and who makes no claim keeps the award until 5 July, the last day of the two weeks beginning with the deadline day ([SI 2014/1230 reg 46(1)(a)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/46)). The model has no deadline or claim dates, so it pays every unsaved award to 30 June (30 September in Northern Ireland).

### Worked household

A single claimant aged 40 in a London council tenancy (rent £5,200 a year, council tax £1,500) reports £5,000 a year of income-related ESA and £4,000 of Housing Benefit, and `would_claim_uc` is false. The Housing Benefit means test pays £5,200 from the rent. On `main` every row below pays £5,000 of ESA and £5,200 of Housing Benefit, and no Universal Credit or Council Tax Reduction.

The ESA abolition doesn't end Housing Benefit by itself. The Secretary of State appointed 1 July 2026 for the remaining working-age Housing Benefit awards to end without a Universal Credit claim ([SI 2025/1148 art. 7](https://www.legislation.gov.uk/uksi/2025/1148/article/7)), and the Department for Communities appointed 1 October 2026 in Northern Ireland ([SR 2025/176 art. 7](https://www.legislation.gov.uk/nisr/2025/176/article/7)), except in specified or temporary accommodation. So under the law this tenant has neither benefit in 2027-28 without a claim, as the model gives. The model doesn't apply art. 7's date: in 2026-27 it pays a family that doesn't claim a full year of Housing Benefit, an approximation that #1910 replaces.

Each cell gives the amount here and, in brackets, the change from `main`, in pounds. Council Tax Reduction stays £0 in every row.

| Earnings | `would_claim_uc_at_legacy_closure` | Model year | Income-related ESA | Housing Benefit | Universal Credit | Household net income |
|---:|---|---|---:|---:|---:|---:|
| 0 | False | 2026-27 | 1,178.08 (−3,821.92) | 5,200.00 (0.00) | 0.00 (0.00) | 4,798.08 (−3,821.92) |
| 0 | False | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 0.00 (0.00) | −1,680.00 (−10,200.00) |
| 0 | True | 2026-27 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 10,298.80 (+10,298.80) | 8,718.80 (+98.80) |
| 0 | True | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 10,518.52 (+10,518.52) | 8,838.52 (+318.52) |
| 10,000 | False | 2026-27 | 1,178.08 (−3,821.92) | 5,200.00 (0.00) | 0.00 (0.00) | 14,798.08 (−3,821.92) |
| 10,000 | False | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 0.00 (0.00) | 8,320.00 (−10,200.00) |
| 10,000 | True | 2026-27 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 4,798.80 (+4,798.80) | 13,218.80 (−5,401.20) |
| 10,000 | True | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 5,018.52 (+5,018.52) | 13,338.52 (−5,181.48) |

With the flag at its default, true, the model gives the true rows' amounts, and every output matches `main` in 2025-26. With `in_specified_or_temporary_accommodation`, the tenant who doesn't claim keeps their £5,200 of Housing Benefit in 2027-28, for £3,520.00 of net income. The tenant without earnings who claims keeps the same £5,200 of Housing Benefit beside Universal Credit, which leaves the rent out: £5,098.80 in 2026-27 and £5,318.52 in 2027-28, the standard allowance alone. Net income comes to the same £8,718.80 and £8,838.52 as in the table, now split between the two benefits. With £10,000 of earnings, the claim brings no Universal Credit, so Universal Credit no longer passports the Housing Benefit means test, and Housing Benefit's 65% taper leaves £3,415.83 in 2026-27 (net income £11,835.83) and £4,838.89 in 2027-28 (£13,158.89), against £13,218.80 and £13,338.52 in the table.

### Data

The certified data (policyengine-uk-data 1.58.1) supply no draw for `would_claim_uc_at_legacy_closure`, so the model uses its default, true. The model therefore has each affected family claim Universal Credit in 2026-27. In an affected family the claimant or partner reports an unsaved income-related ESA award, and the family neither reports Pension Credit nor consists of a single claimant, or joint claimants both, over the qualifying age for State Pension Credit. A family whose only report belongs to a member outside the couple makes no claim through this path. PolicyEngine/policyengine-uk-data#534, a draft pull request, would draw the flag at DWP's Move to Universal Credit claim rates. No dataset identifies appointees or specified or temporary accommodation, so the saving, the Housing Benefit routes and the Universal Credit exclusion stay off in dataset runs.

### What else changes

- A family that claims Universal Credit and would get more from Tax-Free Childcare can now forgo the claim in 2027-28. On `main` an income-related ESA report counted as an award the claim would end, so the family kept its claim; now it counts only while some of the year stays payable (`forgoes_universal_credit_for_tax_free_childcare`). With a £21,993 maximum award and a £20,000 income reduction, Universal Credit would pay £1,993 against £2,400 of Tax-Free Childcare, so the family now forgoes it.
- A family that supplies `in_specified_or_temporary_accommodation` now keeps its Housing Benefit when any legacy closure moves it, the tax credit closure from 2025-26 and the Income Support and income-based JSA closures from 2026-27 included. In every year it also keeps the award beside a Universal Credit claim, can claim Housing Benefit afresh, and gets no Universal Credit housing costs element. That changes the model for such a family whatever moved it to Universal Credit, not only for the ESA abolition.
- `has_own_esa_award`, the support component default, the Universal Credit LCWRA default, the mixed-age Pension Credit saving and `claims_all_entitled_benefits` read the raw report as before. Their documentation now treats the report as the award's history: the work capability assessment behind an ended award still stands, and the report still records the family's claiming.
- The model retains its existing working-age Housing Benefit rules; #1910 adds the closure of those awards on art. 7's dates.

### What the model doesn't do

- It doesn't model deadline days, claim dates, the two-week run-ons or the six-month window of art. 3A(2)(b).
- It doesn't identify appointees in data.
- For a family that does not claim Universal Credit in 2026-27, the model keeps the ESA report open for passports and screens throughout the year and pays its amount for the payable share.
- `in_specified_or_temporary_accommodation` doesn't separate specified from temporary accommodation. Where the Housing Benefit cap applies, to a family without Universal Credit, [HB Regs 2006 reg 75C(2)(a)](https://www.legislation.gov.uk/uksi/2006/213/regulation/75C) counts Housing Benefit for specified accommodation as nil, but `benefit_cap_welfare_benefits` counts it at its amount before the cap.

## Invariants

The property tests (`policyengine_uk/tests/test_esa_income_abolition_properties.py`) check these for generated reported-award situations: working-age families of one to three adults, with or without a child, in five regions, with reports of £0 to £6,000, capital around the tariff threshold and limit, and every combination of saving and claim flags, in 2025-26 to 2027-28.

1. Each fiscal year's `awards_payable` value equals the share of 6 April to 5 April before the statutory date, counted from the two dates (2015 to 2040).
2. 0 <= `esa_income_payable_share` <= 1; the claimant and partner share one value; it equals 1 for every member of a saved claim.
3. From 2027-28 the model pays an unsaved claim nothing, and a member outside the couple with an unsaved claim isn't on income-related ESA.
4. Contributory ESA never changes, whatever the saving and claim flags.
5. Before 2026-27 every output matches the earlier rule for every saving and claim flag. The abolition never raises income-related ESA. A family that claims nothing and saves nothing gets its jurisdiction's share of the earlier award, to the penny.
6. Turning the saving on never lowers `esa_income` or `esa`.
7. A member outside the couple is on income-related ESA only while their report stays open.
8. Saving a member who reports nothing changes no output.
9. The claim path never gives income-related ESA: turning `would_claim_uc_at_legacy_closure` on never raises `esa_income`, and a family that claims when the abolition reaches its award has no `claimant_or_partner_esa_income` that year.
10. The model neither prompts a claim for a saved award nor closes it: `esa_income_abolition_reached` and `legacy_benefits_closed` stay false for the family, and a member outside the couple with a saved claim keeps their legacy benefits open.
11. For families with no work, `esa_income`, `claims_universal_credit` and each outside member's status match amounts worked in the test from the regulations (the £6,000 tariff threshold, £1 a week for each £250 or part above it and the £16,000 limit; ESA Regs 2008 regs 110 and 118), the dates and the saving, without the model's code. The test's reference gives a couple's claim one share and pays a saved claim in full.
12. With no reform holding `esa_income`, `holds_formula_award` is true for every benefit unit in every mode and year, so the comparison of the claimant's and partner's part changes no baseline result.

### Specified or temporary accommodation

`policyengine_uk/tests/test_housing_benefit_accommodation_dates_properties.py` checks R1 to R3 for generated families with the input set: one or two adults aged 22 to 70, up to five children, council, housing association and private tenancies and owners, rent of £0 to £30,000, capital around the £16,000 limit, earnings, reported Housing Benefit, income-related ESA and Working Tax Credit, and every combination of claim and take-up flags, in five regions including Northern Ireland, in 2025-26 to 2027-28.

- R1. `uc_housing_costs_element` is nil for every family.
- R2. `housing_benefit_eligible` equals the tenure and capital tests alone, and stays the same when the test sets `claims_universal_credit` and `legacy_benefits_closed` to each of their four combinations.
- R3. For a family with both awards, `uc_benefit_cap_reduction` equals that of the same family with Housing Benefit set to nil, `housing_benefit_benefit_cap_reduction` is nil, and `housing_benefit` equals `housing_benefit_pre_benefit_cap`.
- R4. With the input false, nothing this change touches reads differently. A scratch differential, not a repository test, checks this against `5a5ac5fb1` on 600 synthetic families a year in 2025-26, 2026-27 and 2027-28: one or two adults aged 20 to 75, up to four children, six regions including Northern Ireland, five tenures, rents of £0 to £30,000, savings of £0 to £17,000, earnings, LCWRA, appointee savings, reports of all seven legacy benefits, Universal Credit and Pension Credit, random claim and take-up flags, and the input left unset or set false. All 14 outputs it compares (`housing_benefit_eligible`, `housing_benefit_pre_benefit_cap`, `housing_benefit`, `uc_housing_costs_element`, `uc_work_allowance`, `universal_credit_pre_benefit_cap`, `universal_credit`, `uc_benefit_cap_reduction`, `housing_benefit_benefit_cap_reduction`, `is_housing_benefit_benefit_cap_exempt_specified_benefit`, `benefit_cap_welfare_benefits`, `claims_universal_credit`, `esa_income`, `household_net_income`) match for every family and year. No dataset sets the input, so CI's aggregate report should show no move from this change.

`test_keeping_awards_payable_is_saving_every_award` checks that a reform keeping `awards_payable` at 1 gives the same outputs as saving every award.

Invariant 12 also follows from the code. `esa_income` applies `income_related_esa_award_on_reports` to open reports, so the first comparison matches. `claimant_or_partner_award` then finds that `esa_income` holds the formula's value and takes the same function on the claimant's and partner's open reports, so the second comparison matches unless it sets that part to zero. It does so only where the part, or the stored total that bounds it, falls within half a penny of zero, and then the formula's part also falls within half a penny of zero. `is_on_income_related_esa` already reads `claimant_or_partner_esa_income` for the claimant and partner, so the new read adds no cycle.

## Test plan

### Local runs at `7fe7360fa`

With no survey data, in the locked environment (`uv sync --frozen --extra dev`).

YAML policy tests (`policyengine-core test`):

- `esa_income/esa_income_abolition.yaml`: 39 passed. Three cases are new: the review's London tenant in council-arranged homelessness accommodation, with £30,000 of rent, whom the abolition moves to Universal Credit (Housing Benefit £30,000, Universal Credit £5,098.80, net income £33,518.80); the same tenant without the status (Housing Benefit nil, Universal Credit capped at £16,967, net income £15,387); and a lone parent of four whose Universal Credit the cap cuts by £2,442.20 to £17,821 while Housing Benefit pays the whole £15,000 rent. The case "a family in specified or temporary accommodation that claims Universal Credit" now keeps its £5,200 of Housing Benefit beside £5,098.80 of Universal Credit, where it had none.
- `housing_benefit/housing_benefit_eligible.yaml`: 10 passed. Four cases are new: a new claim beside Universal Credit (reg 6A(2)); the same family without take-up gets no help with the rent; Northern Ireland's reg 6(3) with SR 2016/216's exclusion; and its control without the status.
- `universal_credit/work_allowance`: 17 passed, two new: the higher work allowance (£8,520) in that accommodation, and the lower one (£5,124) without it.
- `universal_credit/housing_costs_element`: 21 passed, one new.
- `housing_benefit/housing_benefit_residential_capital.yaml`: 7 passed. `housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml`: 50 passed. `housing_benefit/housing_benefit_universal_credit_passport.yaml`: 9 passed.
- Benefit cap cases: `gov/dwp/benefit_cap`: 116 passed; `gov/dwp/pension_credit`: 320 passed; `gov/dwp/universal_credit`: 16 passed; all of `gov/dwp`: 804 passed, `benefit_cap.yaml` (18) and `benefit_cap_by_scheme.yaml` (22) included.
- Before the case edits, `gov/dwp`, `finance/benefit/family` and `integration` gave 2,588 passed and 1 failed, the case above that now keeps its Housing Benefit.

Python tests (`pytest -n 4`): `test_housing_benefit_accommodation_dates.py` 38 passed; `test_housing_benefit_accommodation_dates_properties.py` 9 passed (R1 to R3 new); `test_housing_benefit_specified_accommodation_disregard_properties.py` 11; `test_esa_income_abolition_properties.py` 31; `test_esa_income_universal_credit_claim_properties.py` 2; `test_uc_legacy_closure_properties.py` 4; `test_legacy_closure_scope.py` 21; `test_benefit_cap_uc_and_hb_properties.py` 1; `test_benefit_cap_lha_scheme_split_properties.py` 7; `test_housing_benefit_pension_age_properties.py` 4; `policyengine_uk/tests/code_health` 3,091 passed.

At `5a5ac5fb1`, the 8 new routing cases fail and the 3 controls pass, and R1 and R2 fail; R3 passes there, as the cap needed no formula change. Each mutant below ran in a scratch copy against the four YAML files above and R1 to R3:

| Mutant | Failing cases | Failing properties |
|---|---|---|
| The housing costs element ignores the input | 8: every new routing case with the status, and the element case | R1 |
| The reg 8(3) route dropped (an award with the status ends on a claim) | 5: the £5,200 claimant, the review's London tenant, the capped lone parent, Northern Ireland's reg 6(3) and the higher work allowance | R2 |
| The reg 46(2) route dropped (an award with the status ends on the move) | 2: the 2027-28 move off legacy benefits and the tax credit closure, each with the status | R2 |
| The new-claim route dropped | 2: the new claim and the same family without take-up | R2 |

No take-up guard exists to remove: `would_claim_housing_benefit` decides take-up for every route, as before.

The merge with `main` (`0080274d7`) and with #2807's head (`71ba2752`) are clean; on the #2807 merge, `esa_income`, `housing_benefit`, `universal_credit/housing_costs_element` and `universal_credit/work_allowance` passed 757 of 757.

### Local runs at `a3f8b1104`

Each file or test ran alone at `a3f8b1104`, with `-n 0`, no survey data and a 4 GB memory cap (2 GB for mutants); the cap stopped any run that exceeded its time limit. Later commits up to `5a5ac5fb1` change only documentation, comments and the changelog.

YAML policy tests (`policyengine-core test`):

- `esa_income/esa_income_abolition.yaml`: 36 passed. Three cases were new: the 2027-28 ESA closure keeps Housing Benefit for a family with `in_specified_or_temporary_accommodation`, the same family without the status loses it, and a family with the status that claims Universal Credit, which at `a3f8b1104` got no Housing Benefit and at `7fe7360fa` keeps it.
- `housing_benefit/housing_benefit_eligible.yaml`: 6 passed. Two cases are new: the tax credit closure keeps Housing Benefit with the status and ends it without.

Python tests (`pytest`):

- `test_esa_income_held_outside_award.py` (new): 3 passed, one for each way of holding the award.
- `test_esa_income_abolition_properties.py`: the 29 parameter and fixed-case tests passed in one run. CI ran the whole file at `5a5ac5fb1`, and all 31 tests passed, including `test_abolition_invariants`, which now checks invariant 12, and `test_awards_match_an_independent_reading`.
- `test_uc_legacy_closure_properties.py`: `test_the_closure_flag_only_decides_closure_claims` passed (357 s) and `test_a_closure_claim_pays_the_universal_credit_of_any_claim` passed (617 s), each at full size with a 3,400 s cap.
- `test_disable_simulated_benefits.py`: CI ran the file at `5a5ac5fb1`, and all 89 tests passed.
- Synthetic probes of `holds_formula_award` at `5a5ac5fb1`: 9 of 11 passed. The 2 failures reproduce the limits in the `own_report_is_paid` bullet above: a stored award that leaves an outside member nothing, and a reform that scales `esa_income` with a pension-age outside member.

Fails on `origin/main` (2da212abc): with the reproducer's inputs (a single claimant aged 40 in London reporting £5,000), `main` pays £5,000 of income-related ESA in 2027-28 against £0 here, and £5,000 in 2026-27 against £1,178.08 without a claim.

Each mutant ran in a scratch copy against `esa_income_abolition.yaml` unless the row names another test. The first row ran at `a3f8b1104`. The rest ran on the branch's earlier code, up to `68ccc0856`, and the later commits leave their mutated lines unchanged. The `benunit.sum` row, the row for a saving for a member who reports nothing and the last two rows ran at `5a5ac5fb1`.

| Mutant | Failing cases |
|---|---|
| `holds_formula_award` drops the claimant's and partner's comparison (`test_esa_income_held_outside_award.py`) | 3 of 3: the £5,200 unit in the frozen, stored and replaced modes |
| The claim term leaves `claims_uc_at_legacy_closure` | 5: the GB and NI 2026-27 claims, a couple with a saved partner who reports nothing, and both mixed-age couple cases |
| `legacy_award_is_closed` ignores income-related ESA | 5: the GB and NI 2027-28 ends, the outside adult's 2027-28 end, the end of Housing Benefit without a claim, and the 2027-28 claim |
| The claim term ignores the saving | 5: the saved awards in Great Britain (2027-28) and Northern Ireland (2026-27), both saved-couple cases, and the saved award's 2026-27 claim |
| The closure ignores the saving | 3: the saved single and couple awards in 2027-28 |
| Each member's own saving, not the claim's | 2: both saved-couple cases in 2027-28 |
| `benunit.sum` for the couple's share | 4: both couple cases before and after the abolition, the saved partner who reports nothing, and the saved couple in 2027-28 |
| A saving for a member who reports nothing saves the claim | 2: both cases with a saved partner who reports nothing |
| The pension-age route ignores the abolition | 1: the protected mixed-age couple |
| The claim term drops the Pension Credit age guard | 1: the single claimant over the qualifying age |
| The reform stops holding the claim term (`test_disable_simulated_benefits.py -k no_family_is_moved`) | 2: 2026-27 and 2027-28, where the reform would prompt the ESA claimant's claim |
| `continuing_award` drops the accommodation term (with `housing_benefit_eligible.yaml`) | 2: the Great Britain 2027-28 move off legacy benefits and the tax credit closure, each with the status |
| `own_report_is_paid` treats every held award as the formula's (`test_esa_income_held_outside_award.py`) | 3 of 3: the frozen, stored and replaced modes |

### Existing tests changed

No expected value in a test on `main` changes. At `7fe7360fa` one case this branch added changes, as the list above records, and comments in `benefit_cap_by_scheme.yaml`, `housing_benefit_universal_credit_passport.yaml`, `test_benefit_cap_uc_and_hb_properties.py` and `test_housing_benefit_universal_credit_passport_properties.py` no longer say the model lacks the route; those cases still supply both awards so that they test the cap or the passport alone. Two property-test reference calculations now include Universal Credit claims prompted by the ESA abolition. Cases about other rules opt out of that claim.

- `test_uc_legacy_closure_properties.py`: `expected_closure` closes an unsaved income-related ESA report from 2027-28 under the Pension Credit conditions; a new `expected_reached` gives the abolition's reach from 2026-27; a family claims on either; and a claiming family gets no income-related ESA. The flag property and the closure-claim differential treat a reached family as moved.
- `test_legacy_award_work_properties.py` and `legacy_award_work_reference.py`: in 2026-27 the couple's ESA reports close when the abolition reaches them, because the family claims with `would_claim_uc_at_legacy_closure` at its default.
- `test_disable_simulated_benefits.py`: new assertions that the abolition reaches the claimant's own award from 2026-27 without the reform, and that the reform prompts no claim.

Cases about other rules set `would_claim_uc_at_legacy_closure` false, so the family keeps the award they test:

- `esa_income_remunerative_work.yaml`, the two 2026-27 limit cases: they test the permitted-work limit, and a claim would close the report.
- `pension_credit/mixed_age_couples.yaml`, "HB alongside income-related ESA is working-age HB, so no saving": it tests the saving inference and the continuing Housing Benefit award, which a claim would end.
- `test_mixed_age_couple_properties.py`, every generated family: its reference formulas from before the mixed-age couple rules read `would_claim_uc` alone.

`universal_credit/person_legacy_benefits_closed.yaml` Case 1 gives the claimant `esa_income_appointee_saving` instead: it tests that an outside member's closed report leaves the couple open, and from 2027-28 the claimant's own unsaved report would close the couple.

Thirteen existing YAML cases and four Python files test another rule on an income-related ESA award in 2026-27 or 2027-28, so each ESA reporter there gets `esa_income_appointee_saving: true`, which keeps the award payable for the whole year (inputs only):

- `esa_income/esa_income_capital.yaml`: "has no tariff income at the lower capital threshold", "tariff income rounds incomplete capital steps up" and "remains eligible at the upper capital limit" (2026);
- `esa_income/esa_income_remunerative_work.yaml`: "Issue 2395 outside closed IS report leaves the couple ESA award open" (2027);
- `legacy_award_readers/council_tax_reduction_passports.yaml`: the five non-dependant cases (2027);
- `legacy_award_readers/housing_benefit_pension_age_regulations.yaml`: the two ESA cases (2026);
- `legacy_award_readers/pension_credit_earnings_disregard.yaml`: "Another member's income-related ESA leaves a single pensioner with the £5 earnings disregard" and "The partner's income-related ESA gives the couple the £20 earnings disregard" (2026);
- `test_esa_permitted_work_nlw_reforms.py` (2026), the two 2027 Merton non-dependant tests in `test_legacy_award_readers_properties.py`, the 2027 cases of `test_legacy_closure_scope.py`, and every award in `test_esa_income_universal_credit_claim_properties.py`, whose 2027 cases test the Universal Credit claim under art. 3A(3).

### Overlap

- #2807 (`fix-2565-esa-pension-credit`, head `1d6df1a43`) merges cleanly. Whichever branch merges second adds `esa_income_appointee_saving: true` to the ESA reporters of Cases 4, 5 and 6 (the member who reports), Case 12 (the member outside the couple) and Cases 13 and 14 (both members) of `esa_income_pension_age.yaml`, because each asserts a full-year income-related ESA award in 2026-27. The six additions apply cleanly to the merged tree. With them, `esa_income_pension_age.yaml` and `esa_income_abolition.yaml` passed 60 of 60 on the merge of `5a5ac5fb1` and `1d6df1a43`.
- #1910 (working-age Housing Benefit, head `2d0e49417`) adds the same kind of term for Housing Benefit to `claims_uc_at_legacy_closure`, and the same flag line to the `mixed_age_couples.yaml` case. Beyond its existing conflicts with `main` (`housing_benefit_eligible.py` and `housing_benefit/takeup.yaml`), it conflicts with this branch in those two files; a merge ORs both abolition terms and keeps one copy of the flag line. #1910 makes the same two routing changes as this branch, so it also conflicts in `housing_benefit_eligible.py` and `uc_housing_costs_element.py`. In `uc_housing_costs_element.py` both return nil for `in_specified_or_temporary_accommodation`, and a merge keeps either text. In `housing_benefit_eligible.py` both OR the input into the routes before the tenure and capital tests and drop it from the continuing award; a merge keeps #1910's continuing award, which adds `housing_benefit_payable_share`, and one accommodation term. SI 2025/1148 art. 7(4)(b) and SR 2025/176 art. 7(4)(b) keep Housing Benefit for an appointee-saved income-related ESA claimant, and `housing_benefit_payable_share` at #1910's head has no input for that saving. Whichever PR lands second makes it read `esa_income_appointee_saving`, or the merged model ends that Housing Benefit on the appointed day.
- #2688, #2670, #2302, #2301 and #2300 conflict with this branch only where they already conflict with `main`. #2279, #1969, #2007 and #2756 merge cleanly.

- [x] For policy changes: YAML tests whose expected values come from an external source: `esa_income_abolition.yaml` works each amount by hand from art. 3A's dates and ESA Regs 2008 reg 118, and the Housing Benefit cases from SI 2014/1230 regs 6A(2), 8(3) and 46(2), SR 2016/226 reg 6(3), the Universal Credit rates and caps in the parameter files, and the means test with no income.
- [x] `make format` equivalent on the changed Python files: `ruff format --check`, `ruff check`, `ruff check --select F811,F821 policyengine_uk`, `git diff --check` (ruff 0.17.2).
- [x] Changelog fragment `changelog.d/2562.fixed.md`.

## Reproducer

Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_abolition.yaml::Great Britain 2027-28 pays no income-related ESA

axiom: TheAxiomFoundation/rulespec-uk#518 queued

🤖 Generated with [Claude Code](https://claude.com/claude-code)



Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Head: 7fe7360fa9008437670055067761db88d8bfc94e on branch claude/temp-accommodation-housing-benefit-iyg9d6, one commit on top of 5a5ac5fb1c7547b08a0b6dc042c4e8e253050abf. Nothing went to fix-2562-esa-abolition or #2807, and no PR was opened. The three hub documents are in the three comments above.

C1: route the rent the way the law does

  • Universal Credit leaves the rent out. uc_housing_costs_element.py:52-54 returns nil when in_specified_or_temporary_accommodation is set. It now has documentation and references for UC Regs 2013 Sch 1 paras 3, 3A and 3B, and SR 2016/216 Sch 1 paras 3, 4 and 4A. uc_work_allowance follows with no code change: the new case "A claimant with earnings and limited capability in temporary accommodation gets the higher work allowance" (uc_work_allowance.yaml:36) gives £8,520, against £5,124 for its control. uc_non_dep_receives_pension_credit already sets the element to nil for every benefit unit in its branch, so this change doesn't affect it.
  • Housing Benefit keeps and opens the award. In housing_benefit_eligible.py, the input is ORed into the routes at line 127, ahead of the tenure and capital tests, and dropped from continuing_award (line 103). An existing award therefore survives a Universal Credit claim (reg 8(3); NI reg 6(3)) and the migration deadline (reg 46(2); NI reg 47(2)), and a family with no award can make a new claim (reg 6A(2); NI reg 4A(2)). would_claim_housing_benefit still decides take-up. I rewrote the documentation and comments that said the model leaves these rules out, and left art. 7's date as it was. I also updated claims_universal_credit, claims_uc_at_legacy_closure and in_specified_or_temporary_accommodation.
  • The benefit cap. No formula changed, as expected. The capped case and property R3 confirm it. I rewrote the comment in is_housing_benefit_benefit_cap_exempt_specified_benefit.py:140. utils/benefit_cap.py makes no claim that eligibility rules out a claimant, so I left it alone. I also rewrote the same stale claim in test_benefit_cap_uc_and_hb_properties.py, benefit_cap_by_scheme.yaml, housing_benefit_universal_credit_passport.yaml and test_housing_benefit_universal_credit_passport_properties.py: those cases still supply both awards, so they test the cap or the passport alone. benefit_cap_welfare_benefits.py:21-28 still states the reg 75C(2)(a) limit, now scoped to where the Housing Benefit cap applies.
  • Cycle check. No CycleError came up in any case, property or differential run. The new route reads the input, not an award.

Cases and figures (each worked by hand in its YAML comment from the parameter files)

  • esa_income_abolition.yaml:728, the review's London tenant: Housing Benefit £30,000; Universal Credit 12 × £424.90 = £5,098.80, under London's £16,967 single cap; household net income £30,000 + £5,098.80 + £100 Household Energy Payment − £180 TV licence − £1,500 council tax = £33,518.80. That matches the review. The review's income figures require council_tax: 1_500, which the brief didn't list; I set it, as in the PR's worked household. The family also needs an existing award (housing_benefit_reported: 30_000), because an ESA report makes claims_all_entitled_benefits false and so leaves no take-up for a new claim.
  • :779, the control: Housing Benefit nil; Universal Credit £35,098.80 capped by £18,131.80 to £16,967; income £15,387. That matches the review.
  • :816, the capped combined award: a lone parent of four outside London. Universal Credit before the cap is £5,098.80 + 12 × (£351.88 + 3 × £303.94) = £20,263.20. Child Benefit is (£27.05 + 3 × £17.90) × 52 = £4,199. Together they exceed the £22,020 cap by £2,442.20, so Universal Credit comes to £17,821, while Housing Benefit pays the whole £15,000 and its own cap makes no reduction.
  • housing_benefit_eligible.yaml:105 and the case after it: a new claim pays £10,000 of Housing Benefit beside £5,098.80 of Universal Credit; with take-up false, Housing Benefit and the element are both nil. :165 and the case after it: Northern Ireland's reg 6(3) case and its control.
  • Changed existing case: esa_income_abolition.yaml:694, formerly "…that claims Universal Credit gets no Housing Benefit", now "…keeps its Housing Benefit". Its expected values went from housing_benefit_eligible: false, housing_benefit: 0 to true and £5,200, plus element nil and Universal Credit £5,098.80. I rewrote its comment. No other existing case failed: before I edited any YAML, gov/dwp, finance/benefit/family and integration gave 2,588 passed and only this one failed.

Properties (test_housing_benefit_accommodation_dates_properties.py:448, 457, 496)

R1 element nil; R2 eligibility equals the tenure and capital tests and doesn't change across all four combinations of forced claims_universal_credit and legacy_benefits_closed; R3 one cap with both awards. The draws lean towards eligible families. In a probe at the committed settings, 14 of 56 families received both awards and 6 of those reached the Universal Credit cap.

R4, the scratch differential against 5a5ac5fb1 with the input false: 600 synthetic families a year in 2025, 2026 and 2027. The population covers one or two adults aged 20 to 75, up to four children, six regions including Northern Ireland, five tenures, rents of £0 to £30,000, savings of £0 to £17,000, earnings, LCWRA, appointee savings, reports of HB, ESA, WTC, IS, JSA, UC and Pension Credit, random claim and take-up flags, and the input unset or explicitly false. I compared 14 outputs: housing_benefit_eligible, housing_benefit_pre_benefit_cap, housing_benefit, uc_housing_costs_element, uc_work_allowance, universal_credit_pre_benefit_cap, universal_credit, uc_benefit_cap_reduction, housing_benefit_benefit_cap_reduction, is_housing_benefit_benefit_cap_exempt_specified_benefit, benefit_cap_welfare_benefits, claims_universal_credit, esa_income and household_net_income. That is 25,200 values, and 0 differ.

Mutations and revert checks (scratch copies)

Each mutant ran against the four touched YAML files and R1 to R3.

Mutant Result
Element not nil for the input killed: 8 cases, R1
Reg 8(3) route dropped killed: 5 cases, R2
Reg 46(2) route dropped killed: 2 cases (2027-28 move, tax credit closure), R2
New-claim route dropped killed: 2 cases, R2
Take-up guard removed not applicable: I added no guard, and would_claim_housing_benefit still decides take-up

At 5a5ac5fb1 the 8 new routing cases fail and the 3 controls pass. R1 and R2 fail there and R3 passes, because the cap needed no change.

Tests at 7fe7360fa

  • YAML: esa_income_abolition.yaml 39; housing_benefit_eligible.yaml 10; housing_benefit_residential_capital.yaml 7; …specified_or_temporary_accommodation_disregard.yaml 50; gov/dwp/benefit_cap 116; gov/dwp/pension_credit 320; gov/dwp/universal_credit 16; all of gov/dwp 804 (benefit_cap.yaml 18 and benefit_cap_by_scheme.yaml 22 included); universal_credit/housing_costs_element 21; universal_credit/work_allowance 17; housing_benefit_universal_credit_passport.yaml 9; UC_maximum_amount.yaml 1; integration/uc_child_responsibility_housing_contribution.yaml 12. All passed.
  • Python, -n 4: test_housing_benefit_accommodation_dates.py 38; …_dates_properties.py 9; test_housing_benefit_specified_accommodation_disregard_properties.py 11; test_esa_income_abolition_properties.py 31; test_esa_income_universal_credit_claim_properties.py 2; test_uc_legacy_closure_properties.py 4; test_legacy_closure_scope.py 21; test_benefit_cap_uc_and_hb_properties.py 1; test_benefit_cap_lha_scheme_split_properties.py 7; test_housing_benefit_pension_age_properties.py 4; code_health 3,091. All passed.
  • check_fix_pr.py on the posted title and body: PASS (27 changed executable tests). ruff@0.17.2 format --check, check . and --select F811,F821: clean. git diff --check 5a5ac5fb1..HEAD: clean.
  • Merges: clean with main (0080274d7) and with Bar income-related ESA at pensionable age and on Pension Credit #2807's current head 71ba2752. On the Bar income-related ESA at pensionable age and on Pension Credit #2807 merge, esa_income, housing_benefit, universal_credit/housing_costs_element and universal_credit/work_allowance passed 757 of 757. Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910 (2d0e4941) now also conflicts in uc_housing_costs_element.py. It makes the same change there, and the PR body says how the merge resolves it.

C2: rulespec-uk#518

AXIOM_518.md adds 11 module rows and extends the reg 46 and 47 rows to paragraph (2). It adds source URLs and operative excerpts quoted only from the law file. It adds five required outputs, companion cases 12 to 20 worked from the law, the matching review_finding additions, and a new paragraph in "What PolicyEngine models". The pinned target and blocker section stays as it was.

Judgment calls and limits

  • In the AXIOM record I didn't check the release's corpus locks for SI 2013/376, SI 2006/213, SR 2016/216 or SR 2016/226; I can't reach axiom-corpus from here. The record says so and asks the corpus update to check them. It also has no captured-original hashes for the new excerpts and says that too. It leaves the reg 2 definitions and the housing costs element provisions unquoted and names them as bindings the encoder still needs. It quotes no Northern Ireland benefit cap exception, because legislation.gov.uk has no reg 75F in SR 2006/405.
  • With earnings, a family in this accommodation can now have lower net income than without the status. In the worked household with £10,000 of earnings, the claim brings no Universal Credit, so the Housing Benefit means test (65% taper) applies: £11,835.83 against £13,218.80 in 2026-27. That follows the law's routing, and the PR body states it.
  • The change applies to every route to Universal Credit, not only the ESA abolition (the PR body's "What else changes" says so). The input defaults to false and no dataset sets it, so the aggregate impact should not move.
  • I skipped nothing that needs survey data. No test here uses it.
  • The live PR body had one passive phrase ("an appointment was likely to be needed"), which I rewrote. I also added "Local runs at 7fe7360fa" and labelled the older list "at a3f8b1104".

Generated by Claude Code

@MaxGhenis
MaxGhenis dismissed juaristi22’s stale review November 2, 2026 23:52

We answered both findings at 7fe7360fa. C1: a family in specified or temporary accommodation keeps Housing Benefit through a Universal Credit claim and the move off legacy benefits, and can claim afresh (SI 2014/1230 regs 6A(2), 8(3) and 46(2); SR 2016/226 regs 4A(2), 6(3) and 47(2)), and Universal Credit leaves that rent out. Your London tenant now gets Housing Benefit of £30,000 and Universal Credit of £5,098.80, for household income of £33,518.80; esa_income_abolition.yaml holds that case, its control and a capped combined award. C2: TheAxiomFoundation/rulespec-uk#518 now carries regs 46(2) and 47(2), the other routing provisions, their outputs and companion cases 12 to 20.

MaxGhenis added a commit that referenced this pull request Nov 3, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed 7fe7360fa9008437670055067761db88d8bfc94e. Review COMPLETE, with no open findings or material evidence gaps.

  • C1 RESOLVED: the statutory specified/temporary-accommodation route now preserves new and continuing Housing Benefit claims alongside UC and after legacy closure, with tenure, capital and take-up checks retained. The original London case now pays £30,000 HB plus £5,098.80 UC, zero UC housing element and no cap reduction, giving £33,518.80 household net income. The ordinary-accommodation control, GB/NI new claims, continuation, joint cap and work-allowance cases pass.
  • C2 RESOLVED: the actual rulespec-uk#518 record now includes the GB/NI continuation and new-claim rules, operative text, outputs, companion cases and execution blocker.

109 repository tests and two valid native diagnostics passed. The accommodation input is the statutory status for the assessment period. Current-head Python/aggregate CI is still running, so approval is deferred until the required checks pass; the completed code/source review does not need repeating for that CI-only step.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Verdict: REQUEST_CHANGES at 7fe7360fa9008437670055067761db88d8bfc94e

This review covers the delta 5a5ac5fb1..7fe7360fa (temporary accommodation routing), in a detached scratch checkout with uv sync --frozen --extra dev, no survey data, and HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset.

Previous round (review 5740395595)

  • C1, keep the Housing Benefit for temporary accommodation on the migration path: partly closed. The routing now matches the quoted law, and the review's case passes as committed (Housing Benefit £30,000, Universal Credit £5,098.80, net income £33,518.80). But the committed case adds housing_benefit_reported: 30_000, which the review's case didn't have. Without it, the delta makes that tenant £11,868.20 worse off than at 5a5ac5fb1 (P1 below).
  • C2, the Housing Benefit saving in rulespec-uk#518: closed, apart from companion case 13's date (P2 below). Reg 46(2) and NI reg 47(2) are in the module table, the excerpts, the outputs, the companion cases and review_finding.

P1

1. Setting the input without a Housing Benefit report removes all help with the rent

housing_benefit_eligible now opens Housing Benefit for the input, and uc_housing_costs_element sets the element to nil. In a hand-entered situation, would_claim_housing_benefit (would_claim_housing_benefit.py:17-23) is claims_all_entitled_benefits | reported_hb, and claims_all_entitled_benefits is false once the family reports any of the seven benefits. So a family that reports income-related ESA or Universal Credit, but no Housing Benefit, loses the element and gets no Housing Benefit to replace it. This is the review's own case as the review stated it, with no Housing Benefit report.

I ran it with trap.py: a single claimant aged 40 in London, a council tenancy, £1,500 of council tax, in 2026.

Situation 5a5ac5fb1 7fe7360fa
£5,000 ESA reported, £30,000 rent, would_claim_uc: false, would_claim_uc_at_legacy_closure: true, input true HB 0, element £30,000, UC £16,967, net £15,387 HB 0, element 0, UC £5,098.80, net £3,518.80
£5,000 UC reported, £10,000 rent, would_claim_uc: true, input true HB 0, element £10,000, UC £15,098.80, net £13,518.80 HB 0, element 0, UC £5,098.80, net £3,518.80

With the input false, both households stay at £15,387 and £13,518.80 at the head. A user who supplies the status to get the law's routing gets a result £11,868.20 (or £10,000) lower than without it, and lower than the base. Under reg 8(3) and Sch 1 para 3(i), the rent moves to Housing Benefit; it doesn't disappear. My R4 population with the input true shows the same pattern: 35 to 40 families a year lose net income because they are eligible for Housing Benefit, have no Housing Benefit take-up, and lose the element (cmp2.py: 2025 38 of 46 losers, 2026 35 of 42, 2027 40 of 45). The body says only that "the same family without take-up gets no help with the rent". It doesn't tell a reader that any other report switches take-up off.

Fix I tried in a scratch copy: in the hand-entered branch of would_claim_housing_benefit:

        accommodation = benunit("in_specified_or_temporary_accommodation", period)
        return benunit("claims_all_entitled_benefits", period) | reported_hb | accommodation

The data branch doesn't change, and no dataset sets the input, so R4 and the aggregate report stay as they are. With the fix, both rows above give £33,518.80 and £13,518.80, the second matching the control. esa_income_abolition.yaml, housing_benefit/ and universal_credit/ gave 1 failed and 1,197 passed. The one failure is "The same family that doesn't take Housing Benefit up gets no help with the rent" (housing_benefit_eligible.yaml:133), which then needs would_claim_housing_benefit: false as an input in place of claims_all_entitled_benefits: false. If the hub prefers to keep take-up as it is, the documentation of in_specified_or_temporary_accommodation and the body's "What the model doesn't do" should instead say: "A hand-entered family that reports any benefit but not Housing Benefit doesn't take Housing Benefit up, so setting in_specified_or_temporary_accommodation leaves it with no help with the rent unless the user also sets housing_benefit_reported or would_claim_housing_benefit."

P2

2. rulespec-uk#518 companion case 13: the Northern Ireland deadline date conflicts with art. 3A

Case 13 gives a Northern Ireland claimant with an unsaved income-related ESA award a deadline day of 22 June 2027, and says reg 47(1)(a) ends the award on 5 July 2027. Under SR 2025/176 art. 3A(1), as quoted in the issue, the amending provisions come into operation for an unsaved award on 1 October 2026, nine months before that deadline. The case states no saving, so the encoder would find no award left for reg 47(1)(a) to end. Its own arithmetic is right: 22 June plus 13 days is 5 July.

Replacement for case 13:

  1. Northern Ireland, the same with Housing Executive accommodation under Part II of the Housing (Northern Ireland) Order 1988 (SR 2016/216 Sch 1 para 4A(2)), deadline day 22 September 2026, no claim: the income-related ESA award terminates on 5 October 2026 (reg 47(1)(a)), and SR 2025/176 art. 3A(1)(b) appoints no day for it; the Housing Benefit award doesn't terminate under reg 47 (reg 47(2)).

The review_finding bullet ("Repeat for Northern Ireland under regulation 47 …") names no date, so it needs no change.

P3

  1. Council Tax Reduction isn't mentioned. With the input true, council_tax_reduction rises for 4 to 6 of my 600 families a year (2025 +£3,420.30 in total, 2026 +£5,593.16, 2027 +£2,127.71). It counts universal_credit as income (council_tax_reduction_applicable_income.py:111), and Universal Credit falls by the element. housing_benefit_on_passporting_benefit also turns false for 5 to 8 families a year whose Universal Credit falls to nil. Add to the body's "What else changes" bullet on the input: "Universal Credit falls by the element, so Council Tax Reduction, which counts Universal Credit as income, can rise, and a family whose Universal Credit falls to nil loses the Housing Benefit passport."
  2. The earnings comparison has a cliff that the body could state. I reran the figures: £11,835.83 against £13,218.80 in 2026-27, and £13,158.89 against £13,338.52 in 2027-28, as the body says. The routing comes from the law (reg 8(3), Sch 1 para 3(i)). The size of the gap comes from the model's Housing Benefit means test, under which any Universal Credit entitlement disregards all income (housing_benefit_on_passporting_benefit, citing HB Regs 2006 reg 2(3B) and Sch 4 para 12, Sch 5 para 4 and Sch 6 para 5, none of which the law file quotes). At £9,200 of earnings, Universal Credit is £38.80, Housing Benefit pays all £5,200 and net income is £12,858.80; at £10,000 it is £11,835.83. Suggested addition after "…against £13,218.80 and £13,338.52 in the table.": "Any Universal Credit entitlement disregards all income in the Housing Benefit means test, so at £9,200 of earnings, with £38.80 of Universal Credit, Housing Benefit pays the whole £5,200 and net income is £12,858.80."
  3. Overlap section. Bar income-related ESA at pensionable age and on Pension Credit #2807's head is now a1771bb2e. The body's Bar income-related ESA at pensionable age and on Pension Credit #2807 bullet still describes 1d6df1a43, and "Local runs at 7fe7360fa" names 71ba2752. Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910 also conflicts with main and this branch in CHANGELOG.md and pyproject.toml, which the body leaves out. Replacement for the Bar income-related ESA at pensionable age and on Pension Credit #2807 bullet's first sentence: "Bar income-related ESA at pensionable age and on Pension Credit #2807 (fix-2565-esa-pension-credit, head a1771bb2e) merges cleanly, and on the merge esa_income, housing_benefit and gov/dwp/pension_credit passed 1,040 of 1,040."
  4. rulespec-uk#518 case 12 names Sch 1 para 3B(2), Case 1, but leaves out para 3B(2)(a). Replace "temporary accommodation that a local authority makes available under Part VII of the Housing Act 1996" with "temporary accommodation whose rent payments are payable to a local authority that makes it available under Part VII of the Housing Act 1996".
  5. Writing rule (a rule doesn't act). These texts in the delta make a regulation the actor:
    • uc_work_allowance.yaml:33, "UC Regs 2013 Sch 1 paras 3(h) and (i) leave rent … out of rent payments", becomes "The Secretary of State excludes rent for specified or temporary accommodation from rent payments (UC Regs 2013 Sch 1 paras 3(h) and (i))";
    • housing_benefit_eligible.yaml:162-164, "SR 2016/226 reg 6(3) keeps Housing Benefit … and SR 2016/216 Sch 1 paras 3(e) and (ea) exclude that rent", becomes "A Universal Credit claim doesn't end Housing Benefit for specified or temporary accommodation (SR 2016/226 reg 6(3)), and the Department for Communities excludes that rent from rent payments (SR 2016/216 Sch 1 paras 3(e) and (ea))";
    • housing_benefit_eligible.yaml:210, "SR 2016/226 reg 6(2A) ends the award", becomes "The claim ends the award (SR 2016/226 reg 6(2A))";
    • esa_income_abolition.yaml:717, "Universal Credit Regs 2013 Sch 1 paras 3(h) and (i) exclude this rent", becomes "The Secretary of State excludes this rent from rent payments (UC Regs 2013 Sch 1 paras 3(h) and (i))";
    • benefit_cap_welfare_benefits.py:26, "reg. 75C(2)(a) counts Housing Benefit for specified accommodation as nil", becomes "the council counts Housing Benefit for specified accommodation as nil (reg. 75C(2)(a))".
  6. No test pins the tenure and capital tests on the new route. My mutants M5 and M6 below were killed only by R2, not by any YAML case. A case with £20,000 of capital and the input true (housing_benefit_eligible: false, element nil) would pin the capital limit in YAML.

Checked and sound

  • Routing against the law file. housing_benefit_eligible.py:127 gives (pension_age | continuing_award | accommodation) & (social | lha_eligible) & (capital <= limit), with continuing_award = already_claiming & ~claiming_uc & ~migrated at :103. For the input, the claim term (reg 8(3); NI reg 6(3)) and the closure term (reg 46(2); NI reg 47(2)) no longer matter, and a family with no award gets the route (reg 6A(2); NI reg 4A(2)). Regs 6A(2), 8(3) and 46(2) only lift the transitional bars and terminations, so keeping the tenure test and the capital limit is right. Placing the input inside the routes, rather than ORing it outside the tenure and capital tests, changes results: my M5 and M6 (the input skips the capital test, or the tenure test) both change R2's result. I found no family the formula routes differently from the quoted provisions, given the input. The input can't test the Sch 1 paras 3A, 3B, 4 and 4A conditions, and the body says so. Where the claimant is over pension age, reg 8(2B) disapplies reg 8, and the pension-age route already applies.
  • Universal Credit nil. uc_housing_costs_element.py:52-54 returns nil for every tenure, matching Sch 1 para 3(h) and (i) (NI para 3(e) and (ea)). uc_work_allowance then gives the higher amount (reg 22(2)(a)): £8,520 against £5,124. The claim about uc_non_dep_receives_pension_credit is right: its branch sets the element to nil.
  • Worked cases, by hand from the parameter files, then rerun: £424.90 × 12 = £5,098.80; London single cap £16,967; £30,000 + £5,098.80 + £100 − £180 − £1,500 = £33,518.80; the control £35,098.80 − £16,967 = £18,131.80 and net £15,387. The lone parent: (£351.88 + 3 × £303.94) × 12 = £15,164.40, plus £5,098.80 = £20,263.20; Child Benefit (£27.05 + 3 × £17.90) × 52 = £4,199; the excess over £22,020 is £2,442.20, leaving £17,821. The two-child limit ends from 6 April 2026 (child/limit/child_count.yaml), so all three younger children get the element. The case at :694 has a reported award, the input and a claim at the abolition, so it falls within reg 8(3), and £5,200 and £5,098.80 follow. policyengine-core test on esa_income_abolition.yaml, housing_benefit_eligible.yaml, universal_credit/work_allowance, universal_credit/housing_costs_element, housing_benefit_universal_credit_passport.yaml and benefit_cap_by_scheme.yaml gave 118 passed (39 + 10 + 17 + 21 + 9 + 22).
  • Benefit cap. In the capped case, Universal Credit bears the whole £2,442.20 reduction, and Housing Benefit has no reduction and stays at £15,000 (reg 80(2A), reg 75F(1)(g)). R3 checks the same for generated families. The rewritten comments in is_housing_benefit_benefit_cap_exempt_specified_benefit.py:139-143, benefit_cap_welfare_benefits.py:19-29, test_benefit_cap_uc_and_hb_properties.py and benefit_cap_by_scheme.yaml state the rule correctly, apart from item 7's actor. In the input-true run, the Housing Benefit cap still reduces 2 to 3 families a year that have Housing Benefit and no Universal Credit, as reg 75F(1)(g) leaves it.
  • R4 differential (diff_run.py, cmp.py). 600 generated families a year in 2025, 2026 and 2027: one or two adults aged 20 to 75, 0 to 4 children, London, North West, South East, Scotland, Wales and Northern Ireland, five tenures, rents of £0 to £30,000, savings of £0 to £17,000, earnings, LCWRA, appointee savings, reports of all seven legacy benefits plus UC and Pension Credit, random claim and take-up flags, and the input unset or false. 24 outputs: the hand-off's 14 plus would_claim_housing_benefit, pension_credit, working_tax_credit, child_tax_credit, income_support, tax_free_childcare, housing_benefit_on_passporting_benefit, council_tax_reduction, free_school_meals and is_scp_eligible. 5a5ac5fb1 against 7fe7360fa: 56,391 values, 0 differ.
  • The same population with the input true (2026; 2025 and 2027 are similar): uc_housing_costs_element falls for 305 families (−£3,457,612.64 in total), housing_benefit_eligible turns true for 171, housing_benefit rises for 97 (+£1,105,489.67), uc_work_allowance rises for 267, universal_credit falls for 109, uc_benefit_cap_reduction falls for 32, is_housing_benefit_benefit_cap_exempt_specified_benefit turns true for 76, housing_benefit_benefit_cap_reduction rises for 2, council_tax_reduction rises for 6, and household_net_income rises for 70 and falls for 42 (item 1 explains 35 of the falls; the other 7 come from Housing Benefit's taper replacing the element). Free school meals and the Scottish Child Payment don't move.
  • Properties. pytest -n 4 on test_housing_benefit_accommodation_dates_properties.py, test_benefit_cap_uc_and_hb_properties.py and test_housing_benefit_universal_credit_passport_properties.py gave 16 passed (547 s). R2 does loop over all four combinations of forced claims_universal_credit and legacy_benefits_closed (2 × 2 nested loops). R1, R2 and R3 can each fail; see the mutants below.
  • Mutants, each in a scratch copy against the four YAML files and R1 to R3:
Mutant YAML Properties
The element ignores the input killed, 8 cases R1
The reg 8(3) route dropped (accommodation & ~(already_claiming & claiming_uc)) killed, 5 R2
The reg 46(2) route dropped (accommodation & ~(already_claiming & migrated)) killed, 2 R2
The new-claim route dropped (accommodation & already_claiming) killed, 2 R2
M5 (mine): the route skips the capital limit survived R2 kills it
M6 (mine): the route skips the tenure test survived R2 kills it

The formula has no Northern Ireland branch, so no mutant separates reg 47(2), and I added no take-up guard mutant, since none exists.

  • Readers. Only housing_benefit_pre_benefit_cap and housing_benefit_applicable_amount read housing_benefit_eligible. Only housing_benefit_eligible, uc_housing_costs_element, the earnings disregard, housing_benefit_residential_capital_exception and benefit_cap_welfare_benefits read the input. Item 3 covers the knock-on readers.
  • Cycles (cyc.py). A Universal Credit standard allowance reform beside a baseline, with the input true, for a single tenant and for a household with a second, pension-age benefit unit gave no CycleError. A branch that set the input false calculated, and simulation.branches was empty afterwards.
  • Merges. merge-tree with origin/main is clean. main moved from 0080274d7 to f4a317019 (Derive economic assumption index horizon from the growth series #2756 and a version bump), and no file under variables/ or parameters/gov/dwp changed. With Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910 (2d0e49417), the merge conflicts in housing_benefit_eligible.py, uc_housing_costs_element.py, claims_uc_at_legacy_closure.py, mixed_age_couples.yaml, takeup.yaml, CHANGELOG.md and pyproject.toml. Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910 makes the same element nil and ORs protected_accommodation into the routes, as the body says. Bar income-related ESA at pensionable age and on Pension Credit #2807 at a1771bb2e merges cleanly, and on that merge finance/benefit/family/esa_income, finance/benefit/family/housing_benefit and gov/dwp/pension_credit gave 1,040 passed, 0 failed, the new pension_credit.py change included.
  • Body figures. I reran: £8,718.80 and £8,838.52; £5,098.80 and £5,318.52; £3,415.83 and £11,835.83; £4,838.89 and £13,158.89; £3,520.00 for the tenant who doesn't claim in 2027-28. All match. Every backticked file and variable exists at the head, except esa_income_pension_age.yaml and housing_benefit_payable_share, which the body places on Bar income-related ESA at pensionable age and on Pension Credit #2807 and Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910.
  • Hub voice pass. Diffing the .author.md files against the applied versions, no fact changed. The applied body drops "No take-up guard exists to remove…" and the Bar income-related ESA at pensionable age and on Pension Credit #2807 head in the Overlap bullet, and replaces "the review's London tenant" with "a London tenant". In Add weights CSV and readme #518, the applied text rewords case 5's determination with the same meaning.
  • Add weights CSV and readme #518 excerpts. Each new excerpt matches the law file character by character, apart from line breaks joined with a space, as the record says ("form" in para 3A(5), the spaces before the commas in para 3B(2), the curly apostrophes and the "..." elisions all match). Every quoted excerpt is in the law file. Case 14: entitlement from 10 June 2026 makes reg 8(2)(a)'s day 9 June, and the two weeks beginning 10 June end on 23 June 2026, as the case says. Case 12: the two weeks beginning 22 June 2026 end on 5 July. Cases 15 to 20 follow from regs 6(3) and 4A(2), Sch 1 paras 3(h), (i), (ea), 3A(2), 3A(4), 3B(2), 3B(3), 4(4) and 4A(2), reg 22(2)(a), reg 80(2A) and reg 75F(1)(g) as quoted. The corpus-lock disclosure (SI 2013/376, SI 2006/213, SR 2016/216 and SR 2016/226 not checked) and the missing captured-original hash are stated, and "Pinned target and execution blocker" still states the blocker.
  • git diff --check 5a5ac5fb1..7fe7360fa is clean. CI's Lint, Fix evidence (the later run), Changelog fragments, Test / YAML, Documentation and the smoke imports passed.

CI at the head

  • Pending: Test / Python 1/7 to 7/7 and Aggregate impact, still in progress after about an hour.
  • Cancelled: "Fix evidence" (check run 125630617004) ran for two seconds at 23:52:39 and was replaced six seconds later by run 125630635008, which passed. Its log has expired (404), and the delta doesn't cause it.
  • Failed: none.

What I couldn't check

  • Whether the Housing Benefit passport for Universal Credit and the capital limit follow the law (HB Regs 2006 reg 2(3B), Sch 4 to 6, reg 43): the law file doesn't quote them.
  • SI 2014/1230 reg 6A(3), which the law file leaves out, and on which case 16's "can't claim" limb also depends.
  • Reg 2 of either set of Transitional Regulations (the definitions of specified and temporary accommodation).
  • Northern Ireland's benefit cap exception: the law file has no reg 75F for SR 2006/405.
  • The Python shards and the aggregate report, which hadn't finished.
  • The hand-off's 2,588-case pre-edit run and the 757-of-757 Bar income-related ESA at pensionable age and on Pension Credit #2807 merge at 71ba2752, which I didn't rerun; I ran the merge at the new head instead.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Picking this up for the UK hub. Reply here if you're mid-change and I'll stop.


Generated by Claude Code

…using Benefit take-up

A hand-entered family with in_specified_or_temporary_accommodation now
claims Housing Benefit whatever else it reports, since Universal Credit
leaves that rent out of its housing costs element. Data-built simulations
keep their reporters-only rule. Adds YAML cases for take-up, the tenure
and capital tests on the route, properties R5 and R6, a data-built test,
and names an actor in the branch's comments that made a regulation act.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6KjKZVLNEzacp8jh34

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up at unchanged 7fe7360fa9008437670055067761db88d8bfc94e, after assessing the review received just after my previous comment. Review COMPLETE; original C1 and C2 remain RESOLVED. One nonblocking correction remains before approval:

A1 — Explain the HB take-up default beside the accommodation input. For hand-entered situations, the model treats reported benefits as the complete claimed set. Reporting another benefit without HB therefore makes would_claim_housing_benefit false. The statutory accommodation input removes the UC housing element regardless of whether HB is claimed. Please explain that this combination leaves rent unsupported unless the user also sets housing_benefit_reported or would_claim_housing_benefit, and show the explicit take-up override. Automatically claiming HB would be a different take-up convention; the lower result compared with the former UC-rent route alone does not establish a statutory routing error.

A2 RESOLVED before publication: the actual rulespec-uk#518 case13 now uses the 22 September 2026 deadline / 5 October 2026 termination and art.3A(1)(b)'s pending-run-on exception. Case12's rent-payee premise is also explicit. The updated record was checked against the original operative sources.

The routing repair and GB/NI HB-saving coverage remain validated. No model tests were repeated for these contract/source clarifications. Approval remains on hold for A1 and current-head CI.

… comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6KjKZVLNEzacp8jh34
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_TITLE.txt

Model the 2026 income-related ESA abolition and its UC claims


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_BODY.md

Fixes #2562

## What and why

The Secretary of State appointed 1 July 2026 for the remaining old style income-related ESA awards in Great Britain to end under Welfare Reform Act 2012 s.33(1)(b) ([SI 2025/1148 art. 3A](https://www.legislation.gov.uk/uksi/2025/1148/article/3A), inserted by SI 2026/409). The Department for Communities appointed 1 October 2026 in Northern Ireland ([SR 2025/176 art. 3A](https://www.legislation.gov.uk/nisr/2025/176/article/3A), inserted by SR 2026/115). Under each article, an appointee saving keeps an award in payment, and an award already due to end after a two-week run-on ends on that schedule. DWP had sent migration notices to 879,000 households on income-related ESA by December 2025, and most of them had claimed Universal Credit ([LA Welfare Direct 6/2026](https://www.gov.uk/government/publications/la-welfare-direct-bulletins-2026/la-welfare-direct-62026#update-move-to-universal-credit), paras 51 to 54). At `main` (2da212abc) the model pays an award in full every year unless the family claims Universal Credit or DWP closes another of its legacy benefits, and nothing in the model prompts a claim on the award's appointed day.

This PR:

- adds `gov.dwp.ESA.income.awards_payable.{great_britain,northern_ireland}`, a 1/0 flag for each jurisdiction with `fiscal_year_blend: true`, so the model day-weights the date across each fiscal year from 6 April to 5 April;
- adds `esa_income_appointee_saving` (person input, default false) for the art. 3A(2) saving. The saving applies when someone holds an appointment under reg 57 of the Claims and Payments Regulations 2013 immediately before the date, or the Secretary of State determined in the preceding six months that someone would likely need to hold one. In Northern Ireland, the Department for Communities makes that determination and reg 52 of the 2016 Regulations governs the appointment. The claimant and partner share one award, so when either of them reports the award and has the saving, the model keeps the whole claim payable; it ignores a saving for a member who reports nothing;
- adds `esa_income_payable_share` (person), the share of the year the person's claim stays payable: 1 for a saved claim, otherwise the household's jurisdiction value. `esa_income` pays each claim for its share, one share for the couple's claim and one for each member outside it;
- adds `esa_income_abolition_reached` (benefit unit): the claimant or partner reports an unsaved award with less than the whole year payable, from 2026-27. `claims_uc_at_legacy_closure` now reads it beside `legacy_benefits_closed`, so the family claims Universal Credit when the abolition reaches its award if `would_claim_uc_at_legacy_closure`. The claim ends the income-related ESA award (`esa_income_report_closed`) and, outside specified or temporary accommodation, the Housing Benefit award (`housing_benefit_eligible`), through the rules `main` already has;
- has `legacy_award_is_closed` close an income-related ESA report once none of its claim stays payable, from 2027-28, under the same Pension Credit and pension-age conditions as Income Support and income-based JSA. DWP's move then ends the family's other legacy awards whether or not it claims, apart from the Housing Benefit exception below. The model neither prompts a claim for a saved award nor moves its family;
- routes the rent of specified or temporary accommodation the way the law does, when the user sets `in_specified_or_temporary_accommodation` for the family. `uc_housing_costs_element` returns nil: the Secretary of State excludes these payments from rent payments ([UC Regs 2013 Sch 1 paras 3(h), 3(i), 3A and 3B](https://www.legislation.gov.uk/uksi/2013/376/schedule/1/paragraph/3); Northern Ireland: [SR 2016/216 Sch 1 paras 3(e), 3(ea), 4 and 4A](https://www.legislation.gov.uk/nisr/2016/216/schedule/1/paragraph/3)). `housing_benefit_eligible` keeps an existing award through a Universal Credit claim ([SI 2014/1230 reg 8(3)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/8); Northern Ireland: [SR 2016/226 reg 6(3)](https://www.legislation.gov.uk/nisr/2016/226/regulation/6)) and through the move off legacy benefits, whichever legacy benefit closed ([SI 2014/1230 reg 46(2)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/46); Northern Ireland: [SR 2016/226 reg 47(2)](https://www.legislation.gov.uk/nisr/2016/226/regulation/47)), and lets a family with no award make a new claim ([SI 2014/1230 reg 6A(2)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/6A); Northern Ireland: [SR 2016/226 reg 4A(2)](https://www.legislation.gov.uk/nisr/2016/226/regulation/4A)). The tenure and capital tests still apply. In a hand-entered situation, the input also makes the family claim Housing Benefit (`would_claim_housing_benefit`), because Universal Credit leaves that rent out and a report of another benefit doesn't show whether the family claims it; a user who sets `would_claim_housing_benefit` false still turns take-up off. A data-built simulation keeps its reporters-only rule. The 1 July 2026 abolition of Housing Benefit doesn't reach these awards ([SI 2025/1148 art. 7(4)(a)](https://www.legislation.gov.uk/uksi/2025/1148/article/7), through reg 6A(2)). The route reads the input, not an award, so it adds no cycle through the benefit cap. Without the element, `uc_work_allowance` gives the higher work allowance ([UC Regs 2013 reg 22(2)(a)](https://www.legislation.gov.uk/uksi/2013/376/regulation/22)). The benefit cap needs no formula change: the Universal Credit cap counts Housing Benefit as nil (`uc_benefit_cap_reduction`; [reg 80(2A)](https://www.legislation.gov.uk/uksi/2013/376/regulation/80)), and a Housing Benefit claimant receiving Universal Credit is outside the Housing Benefit cap (`is_housing_benefit_benefit_cap_exempt_specified_benefit`; [HB Regs 2006 reg 75F(1)(g)](https://www.legislation.gov.uk/uksi/2006/213/regulation/75F)). `uc_non_dep_receives_pension_credit` already fixes the element at nil in its branch, so the change leaves it alone;
- has `left_pension_route_at_legacy_closure` count a protected mixed-age couple whose award the abolition reaches, so a couple that claims and has a Universal Credit award stops getting pension-age Housing Benefit beside it;
- has `own_report_is_paid` apply the abolition's report closure to a member outside the couple only when both held amounts match the formula on open reports: `esa_income` and the claimant's and partner's part of it, `claimant_or_partner_esa_income` (`holds_formula_award`). Either amount can differ when a user enters `esa_income`, a reform replaces it, or `disable_simulated_benefits` holds it; `own_report_is_paid` then applies only the person's legacy closure and reads the benefit unit's held award. That award covers the whole unit, so an outside member with an open report stays paid while the unit's award stays positive, even if it leaves them nothing. A member outside the couple at pension age has no legacy closure, so a reform that replaces `esa_income` keeps that member on income-related ESA. `holds_formula_award` can't separate two or more outside reporters in one benefit unit when the held total and the couple's part both equal the formula's: `own_report_is_paid` then applies each one's report closure;
- has `disable_simulated_benefits` hold `esa_income_abolition_reached` false with the other closures, so the reform prompts no claim.

Contributory ESA continues as new style ESA, and `esa_contrib` keeps its formula.

### Which years change

| Model year | Great Britain | Northern Ireland |
|---|---|---|
| 2025-26 and earlier | 1 (unchanged) | 1 (unchanged) |
| 2026-27 | 86/365 = 0.235616 (6 April to 30 June 2026: 25 + 31 + 30 days) | 178/365 = 0.487671 (6 April to 30 September 2026: 25 + 31 + 30 + 31 + 31 + 30 days) |
| 2027-28 onwards | 0 | 0 |

In 2026-27 a family whose award the abolition reaches claims Universal Credit if `would_claim_uc_at_legacy_closure`, and the claim ends the award for the year. A family that doesn't claim keeps £5,000 x 86/365 = £1,178.08 of a £5,000 award in Great Britain (£2,438.36 in Northern Ireland) and its Housing Benefit. From 2027-28 every unsaved award has ended, and the model moves each affected family off its legacy benefits. Scotland and Wales follow the Great Britain date.

Under art. 3A(1)(b), an award already due to end at the end of a two-week run-on ends on that schedule. A claimant whose migration notice deadline falls on 22 June 2026 and who makes no claim keeps the award until 5 July, the last day of the two weeks beginning with the deadline day ([SI 2014/1230 reg 46(1)(a)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/46)). The model has no deadline or claim dates, so it pays every unsaved award to 30 June (30 September in Northern Ireland).

### Worked household

A single claimant aged 40 in a London council tenancy (rent £5,200 a year, council tax £1,500) reports £5,000 a year of income-related ESA and £4,000 of Housing Benefit, and `would_claim_uc` is false. The Housing Benefit means test pays £5,200 from the rent. On `main` every row below pays £5,000 of ESA and £5,200 of Housing Benefit, and no Universal Credit or Council Tax Reduction.

The ESA abolition doesn't end Housing Benefit by itself. The Secretary of State appointed 1 July 2026 for the remaining working-age Housing Benefit awards to end without a Universal Credit claim ([SI 2025/1148 art. 7](https://www.legislation.gov.uk/uksi/2025/1148/article/7)), and the Department for Communities appointed 1 October 2026 in Northern Ireland ([SR 2025/176 art. 7](https://www.legislation.gov.uk/nisr/2025/176/article/7)), except in specified or temporary accommodation. So under the law this tenant has neither benefit in 2027-28 without a claim, as the model gives. The model doesn't apply art. 7's date: in 2026-27 it pays a family that doesn't claim a full year of Housing Benefit, an approximation that #1910 replaces.

Each cell gives the amount here and, in brackets, the change from `main`, in pounds. Council Tax Reduction stays £0 in every row.

| Earnings | `would_claim_uc_at_legacy_closure` | Model year | Income-related ESA | Housing Benefit | Universal Credit | Household net income |
|---:|---|---|---:|---:|---:|---:|
| 0 | False | 2026-27 | 1,178.08 (−3,821.92) | 5,200.00 (0.00) | 0.00 (0.00) | 4,798.08 (−3,821.92) |
| 0 | False | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 0.00 (0.00) | −1,680.00 (−10,200.00) |
| 0 | True | 2026-27 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 10,298.80 (+10,298.80) | 8,718.80 (+98.80) |
| 0 | True | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 10,518.52 (+10,518.52) | 8,838.52 (+318.52) |
| 10,000 | False | 2026-27 | 1,178.08 (−3,821.92) | 5,200.00 (0.00) | 0.00 (0.00) | 14,798.08 (−3,821.92) |
| 10,000 | False | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 0.00 (0.00) | 8,320.00 (−10,200.00) |
| 10,000 | True | 2026-27 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 4,798.80 (+4,798.80) | 13,218.80 (−5,401.20) |
| 10,000 | True | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 5,018.52 (+5,018.52) | 13,338.52 (−5,181.48) |

With the flag at its default, true, the model gives the true rows' amounts, and every output matches `main` in 2025-26. With `in_specified_or_temporary_accommodation`, the tenant who doesn't claim keeps their £5,200 of Housing Benefit in 2027-28, for £3,520.00 of net income. The tenant without earnings who claims keeps the same £5,200 of Housing Benefit beside Universal Credit, which leaves the rent out: £5,098.80 in 2026-27 and £5,318.52 in 2027-28, the standard allowance alone. Net income comes to the same £8,718.80 and £8,838.52 as in the table, now split between the two benefits. With £10,000 of earnings, the claim brings no Universal Credit, so Universal Credit no longer passports the Housing Benefit means test, and Housing Benefit's 65% taper leaves £3,415.83 in 2026-27 (net income £11,835.83) and £4,838.89 in 2027-28 (£13,158.89), against £13,218.80 and £13,338.52 in the table. Any Universal Credit entitlement disregards all income in the Housing Benefit means test (`housing_benefit_on_passporting_benefit` and the provisions it cites), so at £9,200 of earnings, with £38.80 of Universal Credit, Housing Benefit pays the whole £5,200 and net income comes to £12,858.80.

### Data

The certified data (policyengine-uk-data 1.58.1) supply no draw for `would_claim_uc_at_legacy_closure`, so the model uses its default, true. The model therefore has each affected family claim Universal Credit in 2026-27. In an affected family the claimant or partner reports an unsaved income-related ESA award, and the family neither reports Pension Credit nor consists of a single claimant, or joint claimants both, over the qualifying age for State Pension Credit. A family whose only report belongs to a member outside the couple makes no claim through this path. PolicyEngine/policyengine-uk-data#534, a draft pull request, would draw the flag at DWP's Move to Universal Credit claim rates. No dataset identifies appointees or specified or temporary accommodation, so the saving, the Housing Benefit routes and the Universal Credit exclusion stay off in dataset runs.

### What else changes

- A family that claims Universal Credit and would get more from Tax-Free Childcare can now forgo the claim in 2027-28. On `main` an income-related ESA report counted as an award the claim would end, so the family kept its claim; now it counts only while some of the year stays payable (`forgoes_universal_credit_for_tax_free_childcare`). With a £21,993 maximum award and a £20,000 income reduction, Universal Credit would pay £1,993 against £2,400 of Tax-Free Childcare, so the family now forgoes it.
- A family that supplies `in_specified_or_temporary_accommodation` now keeps its Housing Benefit when any legacy closure moves it, the tax credit closure from 2025-26 and the Income Support and income-based JSA closures from 2026-27 included. In every year it also keeps the award beside a Universal Credit claim, can claim Housing Benefit afresh, and gets no Universal Credit housing costs element. That changes the model for such a family whatever moved it to Universal Credit, not only for the ESA abolition. In a hand-entered situation the family now also takes Housing Benefit up whatever else it reports (`would_claim_housing_benefit`); before, a report of any of the seven means-tested benefits but not Housing Benefit switched take-up off and left it with no help with the rent. Universal Credit falls by the element, so Council Tax Reduction, which counts Universal Credit as income, can rise, and a family whose Universal Credit falls to nil loses the Housing Benefit passport.
- `has_own_esa_award`, the support component default, the Universal Credit LCWRA default, the mixed-age Pension Credit saving and `claims_all_entitled_benefits` read the raw report as before. Their documentation now treats the report as the award's history: the work capability assessment behind an ended award still stands, and the report still records the family's claiming.
- The model retains its existing working-age Housing Benefit rules; #1910 adds the closure of those awards on art. 7's dates.

### What the model doesn't do

- It doesn't model deadline days, claim dates, the two-week run-ons or the six-month window of art. 3A(2)(b).
- It doesn't identify appointees in data.
- For a family that does not claim Universal Credit in 2026-27, the model keeps the ESA report open for passports and screens throughout the year and pays its amount for the payable share.
- `in_specified_or_temporary_accommodation` doesn't separate specified from temporary accommodation. Where the Housing Benefit cap applies, to a family without Universal Credit, the council counts Housing Benefit for specified accommodation as nil ([HB Regs 2006 reg 75C(2)(a)](https://www.legislation.gov.uk/uksi/2006/213/regulation/75C)), but `benefit_cap_welfare_benefits` counts it at its amount before the cap.
- A data-built simulation with `in_specified_or_temporary_accommodation` and no Housing Benefit report still gets no help with the rent: its take-up follows reported Housing Benefit, and Universal Credit leaves the rent out.

## Invariants

The property tests (`policyengine_uk/tests/test_esa_income_abolition_properties.py`) check these for generated reported-award situations: working-age families of one to three adults, with or without a child, in five regions, with reports of £0 to £6,000, capital around the tariff threshold and limit, and every combination of saving and claim flags, in 2025-26 to 2027-28.

1. Each fiscal year's `awards_payable` value equals the share of 6 April to 5 April before the statutory date, counted from the two dates (2015 to 2040).
2. 0 <= `esa_income_payable_share` <= 1; the claimant and partner share one value; it equals 1 for every member of a saved claim.
3. From 2027-28 the model pays an unsaved claim nothing, and a member outside the couple with an unsaved claim comes off income-related ESA.
4. Contributory ESA never changes, whatever the saving and claim flags.
5. Before 2026-27 every output matches the earlier rule for every saving and claim flag. The abolition never raises income-related ESA. A family that claims nothing and saves nothing gets its jurisdiction's share of the earlier award, to the penny.
6. Turning the saving on never lowers `esa_income` or `esa`.
7. A member outside the couple stays on income-related ESA only while their report stays open.
8. Saving a member who reports nothing changes no output.
9. The claim path never gives income-related ESA: turning `would_claim_uc_at_legacy_closure` on never raises `esa_income`, and a family that claims when the abolition reaches its award has no `claimant_or_partner_esa_income` that year.
10. The model neither prompts a claim for a saved award nor closes it: `esa_income_abolition_reached` and `legacy_benefits_closed` stay false for the family, and a member outside the couple with a saved claim keeps their legacy benefits open.
11. For families with no work, `esa_income`, `claims_universal_credit` and each outside member's status match amounts worked in the test from the regulations (the £6,000 tariff threshold, £1 a week for each £250 or part above it and the £16,000 limit; ESA Regs 2008 regs 110 and 118), the dates and the saving, without the model's code. The test's reference gives a couple's claim one share and pays a saved claim in full.
12. With no reform holding `esa_income`, `holds_formula_award` is true for every benefit unit in every mode and year, so the comparison of the claimant's and partner's part changes no baseline result.

### Specified or temporary accommodation

`policyengine_uk/tests/test_housing_benefit_accommodation_dates_properties.py` checks R1 to R3, R5 and R6 for generated families with the input set: one or two adults aged 22 to 70, up to five children, council, housing association and private tenancies and owners, rent of £0 to £30,000, capital around the £16,000 limit, earnings, reported Housing Benefit, income-related ESA and Working Tax Credit, and every combination of claim and take-up flags, in five regions including Northern Ireland, in 2025-26 to 2027-28.

- R1. `uc_housing_costs_element` is nil for every family.
- R2. `housing_benefit_eligible` equals the tenure and capital tests alone, and stays the same when the test sets `claims_universal_credit` and `legacy_benefits_closed` to each of their four combinations.
- R3. For a family with both awards, `uc_benefit_cap_reduction` equals that of the same family with Housing Benefit set to nil, `housing_benefit_benefit_cap_reduction` is nil, and `housing_benefit` equals `housing_benefit_pre_benefit_cap`.
- R4. With the input false, nothing this change touches reads differently. A scratch differential, not a repository test, checks this for `3c9890a19` against `7fe7360fa` and against `5a5ac5fb1` on a population of 600 synthetic families a year, and checks `7fe7360fa` against `5a5ac5fb1` on another population of 600 synthetic families a year in 2025-26, 2026-27 and 2027-28: one or two adults aged 20 to 75, up to four children, six regions including Northern Ireland, five tenures, rents of £0 to £30,000, savings of £0 to £17,000, earnings, LCWRA, appointee savings, reports of all seven legacy benefits, Universal Credit and Pension Credit, random claim and take-up flags, and the input left unset or set false. All 14 outputs it compares (`housing_benefit_eligible`, `housing_benefit_pre_benefit_cap`, `housing_benefit`, `uc_housing_costs_element`, `uc_work_allowance`, `universal_credit_pre_benefit_cap`, `universal_credit`, `uc_benefit_cap_reduction`, `housing_benefit_benefit_cap_reduction`, `is_housing_benefit_benefit_cap_exempt_specified_benefit`, `benefit_cap_welfare_benefits`, `claims_universal_credit`, `esa_income`, `household_net_income`) match for every family and year. No dataset sets the input, so CI's aggregate report should show no move from this change. At `3c9890a19` the differential's population has one or two adults aged 20 to 75, up to four children, six regions including Northern Ireland, five tenures, rents of £0 to £30,000, savings of £0 to £17,000, earnings, LCWRA, appointee savings, reports of six legacy benefits, Universal Credit and Pension Credit, random claim and take-up flags, and the input left unset or set false. It compares 25 outputs: the 14 above, `would_claim_housing_benefit`, `pension_credit`, `working_tax_credit`, `child_tax_credit`, `income_support`, `tax_free_childcare`, `housing_benefit_on_passporting_benefit`, `council_tax_reduction`, `free_school_meals`, `is_scp_eligible` and `housing_benefit_entitlement`. In 2025-26, 2026-27 and 2027-28 none of the 58,740 values differs from either base. With the input set true for every family, against `5a5ac5fb1`, household net income at `7fe7360fa` falls for 72, 84 and 71 families in the three years, of which 70, 78 and 67 have no Housing Benefit take-up. At `3c9890a19` it falls for 7, 17 and 11 families, and rises for 173, 182 and 184. Each family whose net income falls receives Housing Benefit in place of a larger element, or gets none from the means test: Housing Benefit's taper without a Universal Credit passport (3, 8 and 2), Housing Benefit paid weekly over 52 weeks beside Universal Credit, such as £161.10 x 52 = £8,377.20 against an £8,400 element (3, 8 and 6), the Housing Benefit cap for a family without Universal Credit (1, 0 and 2), and the means test taking Housing Benefit to nil (0, 1 and 1). With the input true, `council_tax_reduction` rises for 1, 3 and 4 families, and `housing_benefit_on_passporting_benefit` turns false for 4, 10 and 7.
- R5. In a hand-entered situation, the input makes `would_claim_housing_benefit` true whatever else the family reports; with the input false, it equals `claims_all_entitled_benefits` or a Housing Benefit report.
- R6. With the input, a family that passes the tenure and capital tests (`housing_benefit_eligible`) never ends up with neither Housing Benefit nor a housing costs element, except where the Housing Benefit means test takes its entitlement to nil (`housing_benefit_entitlement`). The Housing Benefit cap leaves the 50 pence weekly minimum, so it can't take that Housing Benefit to nil.

`test_keeping_awards_payable_is_saving_every_award` checks that a reform keeping `awards_payable` at 1 gives the same outputs as saving every award.

Invariant 12 also follows from the code. `esa_income` applies `income_related_esa_award_on_reports` to open reports, so the first comparison matches. `claimant_or_partner_award` then finds that `esa_income` holds the formula's value and takes the same function on the claimant's and partner's open reports, so the second comparison matches unless it sets that part to zero. It does so only where the part, or the stored total that bounds it, falls within half a penny of zero, and then the formula's part also falls within half a penny of zero. `is_on_income_related_esa` already reads `claimant_or_partner_esa_income` for the claimant and partner, so the new read adds no cycle.

## Test plan

### Local runs at `3c9890a19`

With no survey data, in the locked environment (`uv sync --frozen --extra dev`).

YAML policy tests (`policyengine-core test`):

- `housing_benefit/housing_benefit_eligible.yaml`: 14 passed. Four cases are new: a family in temporary accommodation that reports only Universal Credit takes Housing Benefit up (Housing Benefit £10,000, Universal Credit £5,098.80, net income £13,518.80); its control without the status, at the same £13,518.80 through the element; £20,000 of capital with the status (no Housing Benefit, element nil); and an owner occupier with the status (no Housing Benefit). "The same family that doesn't take Housing Benefit up gets no help with the rent" now sets `would_claim_housing_benefit: false` as its input in place of `claims_all_entitled_benefits: false`, with the same outputs.
- `housing_benefit/would_claim_housing_benefit.yaml`: 5 passed, two new: the status with no report and `claims_all_entitled_benefits` false gives take-up, and the status set false doesn't.
- `esa_income/esa_income_abolition.yaml`: 40 passed, one new: the London tenant with £30,000 of rent who reports income-related ESA and no Housing Benefit takes Housing Benefit up (Housing Benefit £30,000, Universal Credit £5,098.80, net income £33,518.80).
- `finance/benefit/family/housing_benefit`: 596 passed; `universal_credit/work_allowance`: 17; `universal_credit/housing_costs_element`: 21; `housing_benefit/housing_benefit_universal_credit_passport.yaml`: 9; `gov/dwp/benefit_cap_by_scheme.yaml`: 22.
- `finance/benefit/family/universal_credit`: 569 passed; `finance/benefit/family/esa_income`: 106; `gov/dwp`: 804.

Python tests (`pytest -n 4`): `test_housing_benefit_accommodation_dates_properties.py` 11 passed (R5 and R6 new); `test_benefit_cap_uc_and_hb_properties.py` 1; `test_housing_benefit_universal_credit_passport_properties.py` 6; `test_housing_benefit_accommodation_take_up_data_built.py` (new) 5; `policyengine_uk/tests/code_health` 3,091 passed. The new file checks that a data-built family with the status and no Housing Benefit report doesn't claim and gets no help with the rent, that one with a report gets the whole rent, and that the same family hand-entered claims.

Each mutant below ran at `3c9890a19` in a scratch copy against `housing_benefit_eligible.yaml`, `would_claim_housing_benefit.yaml`, `esa_income_abolition.yaml`, R1 to R3, R5, R6 and the data-built test. Each one failed at least one of them:

| Mutant (`3c9890a19`) | Failing cases | Failing tests |
|---|---|---|
| The status dropped from `would_claim_housing_benefit` | 3: the family that reports only Universal Credit, the status with no report, and the London tenant without a Housing Benefit report | R5, R6, the hand-entered family |
| The status moved above the data-built branch | none | both data-built families with no report |
| The route skips the capital limit | 1: £20,000 of capital with the status | R2 |
| The route skips the tenure test | 1: the owner occupier with the status | R2 |
| The housing costs element ignores the input | 9: every routing case with the status that rents | R1, both data-built families with no report |
| The reg 8(3) route dropped | 4: the £5,200 claimant, the London tenant, the capped lone parent and Northern Ireland's reg 6(3) | R2, both data-built families with a report |
| The reg 46(2) route dropped | 2: the 2027-28 move off legacy benefits and the tax credit closure, each with the status | R2 |
| The new-claim route dropped | 4: the new claim, the same family without take-up, the family that reports only Universal Credit, and the London tenant without a Housing Benefit report | R2, both data-built families with no report, the hand-entered family |

The differential above (R4) compares `3c9890a19` with `7fe7360fa`.

### Local runs at `7fe7360fa`

With no survey data, in the locked environment (`uv sync --frozen --extra dev`).

YAML policy tests (`policyengine-core test`):

- `esa_income/esa_income_abolition.yaml`: 39 passed. Three cases are new: a London tenant in council-arranged homelessness accommodation, with £30,000 of rent, whom the abolition moves to Universal Credit (Housing Benefit £30,000, Universal Credit £5,098.80, net income £33,518.80); the same tenant without the status (Housing Benefit nil, Universal Credit capped at £16,967, net income £15,387); and a lone parent of four whose Universal Credit the cap cuts by £2,442.20 to £17,821 while Housing Benefit pays the whole £15,000 rent. The case "a family in specified or temporary accommodation that claims Universal Credit" now keeps its £5,200 of Housing Benefit beside £5,098.80 of Universal Credit, where it had none.
- `housing_benefit/housing_benefit_eligible.yaml`: 10 passed. Four cases are new: a new claim beside Universal Credit (reg 6A(2)); the same family without take-up gets no help with the rent; Northern Ireland's reg 6(3) with SR 2016/216's exclusion; and its control without the status.
- `universal_credit/work_allowance`: 17 passed, two new: the higher work allowance (£8,520) in that accommodation, and the lower one (£5,124) without it.
- `universal_credit/housing_costs_element`: 21 passed, one new.
- `housing_benefit/housing_benefit_residential_capital.yaml`: 7 passed. `housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml`: 50 passed. `housing_benefit/housing_benefit_universal_credit_passport.yaml`: 9 passed.
- Benefit cap cases: `gov/dwp/benefit_cap`: 116 passed; `gov/dwp/pension_credit`: 320 passed; `gov/dwp/universal_credit`: 16 passed; all of `gov/dwp`: 804 passed, `benefit_cap.yaml` (18) and `benefit_cap_by_scheme.yaml` (22) included.
- Before the case edits, `gov/dwp`, `finance/benefit/family` and `integration` gave 2,588 passed and 1 failed, the case above that now keeps its Housing Benefit.

Python tests (`pytest -n 4`): `test_housing_benefit_accommodation_dates.py` 38 passed; `test_housing_benefit_accommodation_dates_properties.py` 9 passed (R1 to R3 new); `test_housing_benefit_specified_accommodation_disregard_properties.py` 11; `test_esa_income_abolition_properties.py` 31; `test_esa_income_universal_credit_claim_properties.py` 2; `test_uc_legacy_closure_properties.py` 4; `test_legacy_closure_scope.py` 21; `test_benefit_cap_uc_and_hb_properties.py` 1; `test_benefit_cap_lha_scheme_split_properties.py` 7; `test_housing_benefit_pension_age_properties.py` 4; `policyengine_uk/tests/code_health` 3,091 passed.

At `5a5ac5fb1`, the 8 new routing cases fail and the 3 controls pass, and R1 and R2 fail; R3 passes there, as the cap needed no formula change. Each mutant below ran in a scratch copy against the four YAML files above and R1 to R3:

| Mutant | Failing cases | Failing properties |
|---|---|---|
| The housing costs element ignores the input | 8: every new routing case with the status, and the element case | R1 |
| The reg 8(3) route dropped (an award with the status ends on a claim) | 5: the £5,200 claimant, the London tenant, the capped lone parent, Northern Ireland's reg 6(3) and the higher work allowance | R2 |
| The reg 46(2) route dropped (an award with the status ends on the move) | 2: the 2027-28 move off legacy benefits and the tax credit closure, each with the status | R2 |
| The new-claim route dropped | 2: the new claim and the same family without take-up | R2 |

This head merges cleanly with `main` (`0080274d7`) and with #2807 at `71ba2752`; on the #2807 merge, `esa_income`, `housing_benefit`, `universal_credit/housing_costs_element` and `universal_credit/work_allowance` passed 757 of 757.

### Local runs at `a3f8b1104`

Each file or test ran alone at `a3f8b1104`, with `-n 0`, no survey data and a 4 GB memory cap (2 GB for mutants); the cap stopped any run that exceeded its time limit. Later commits up to `5a5ac5fb1` change only documentation, comments and the changelog.

YAML policy tests (`policyengine-core test`):

- `esa_income/esa_income_abolition.yaml`: 36 passed. Three cases were new: the 2027-28 ESA closure keeps Housing Benefit for a family with `in_specified_or_temporary_accommodation`, the same family without the status loses it, and a family with the status that claims Universal Credit, which at `a3f8b1104` got no Housing Benefit and at `7fe7360fa` keeps it.
- `housing_benefit/housing_benefit_eligible.yaml`: 6 passed. Two cases are new: the tax credit closure keeps Housing Benefit with the status and ends it without.

Python tests (`pytest`):

- `test_esa_income_held_outside_award.py` (new): 3 passed, one for each way of holding the award.
- `test_esa_income_abolition_properties.py`: the 29 parameter and fixed-case tests passed in one run. CI ran the whole file at `5a5ac5fb1`, and all 31 tests passed, including `test_abolition_invariants`, which now checks invariant 12, and `test_awards_match_an_independent_reading`.
- `test_uc_legacy_closure_properties.py`: `test_the_closure_flag_only_decides_closure_claims` passed (357 s) and `test_a_closure_claim_pays_the_universal_credit_of_any_claim` passed (617 s), each at full size with a 3,400 s cap.
- `test_disable_simulated_benefits.py`: CI ran the file at `5a5ac5fb1`, and all 89 tests passed.
- Synthetic probes of `holds_formula_award` at `5a5ac5fb1`: 9 of 11 passed. The 2 failures reproduce the limits in the `own_report_is_paid` bullet above: a stored award that leaves an outside member nothing, and a reform that scales `esa_income` with a pension-age outside member.

Fails on `origin/main` (2da212abc): with the reproducer's inputs (a single claimant aged 40 in London reporting £5,000), `main` pays £5,000 of income-related ESA in 2027-28 against £0 here, and £5,000 in 2026-27 against £1,178.08 without a claim.

Each mutant ran in a scratch copy against `esa_income_abolition.yaml` unless the row names another test. The first row ran at `a3f8b1104`. The rest ran on the branch's earlier code, up to `68ccc0856`, and the later commits leave their mutated lines unchanged. The `benunit.sum` row, the row for a saving for a member who reports nothing and the last two rows ran at `5a5ac5fb1`.

| Mutant | Failing cases |
|---|---|
| `holds_formula_award` drops the claimant's and partner's comparison (`test_esa_income_held_outside_award.py`) | 3 of 3: the £5,200 unit in the frozen, stored and replaced modes |
| The claim term leaves `claims_uc_at_legacy_closure` | 5: the GB and NI 2026-27 claims, a couple with a saved partner who reports nothing, and both mixed-age couple cases |
| `legacy_award_is_closed` ignores income-related ESA | 5: the GB and NI 2027-28 ends, the outside adult's 2027-28 end, the end of Housing Benefit without a claim, and the 2027-28 claim |
| The claim term ignores the saving | 5: the saved awards in Great Britain (2027-28) and Northern Ireland (2026-27), both saved-couple cases, and the saved award's 2026-27 claim |
| The closure ignores the saving | 3: the saved single and couple awards in 2027-28 |
| Each member's own saving, not the claim's | 2: both saved-couple cases in 2027-28 |
| `benunit.sum` for the couple's share | 4: both couple cases before and after the abolition, the saved partner who reports nothing, and the saved couple in 2027-28 |
| A saving for a member who reports nothing saves the claim | 2: both cases with a saved partner who reports nothing |
| The pension-age route ignores the abolition | 1: the protected mixed-age couple |
| The claim term drops the Pension Credit age guard | 1: the single claimant over the qualifying age |
| The reform stops holding the claim term (`test_disable_simulated_benefits.py -k no_family_is_moved`) | 2: 2026-27 and 2027-28, where the reform would prompt the ESA claimant's claim |
| `continuing_award` drops the accommodation term (with `housing_benefit_eligible.yaml`) | 2: the Great Britain 2027-28 move off legacy benefits and the tax credit closure, each with the status |
| `own_report_is_paid` treats every held award as the formula's (`test_esa_income_held_outside_award.py`) | 3 of 3: the frozen, stored and replaced modes |

### Existing tests changed

No expected value in a test on `main` changes. At `7fe7360fa` one case this branch added changes, as the list above records, and comments in `benefit_cap_by_scheme.yaml`, `housing_benefit_universal_credit_passport.yaml`, `test_benefit_cap_uc_and_hb_properties.py` and `test_housing_benefit_universal_credit_passport_properties.py` no longer say the model lacks the route; those cases still supply both awards so that they test the cap or the passport alone. Two property-test reference calculations now include Universal Credit claims prompted by the ESA abolition. Cases about other rules opt out of that claim.

- `test_uc_legacy_closure_properties.py`: `expected_closure` closes an unsaved income-related ESA report from 2027-28 under the Pension Credit conditions; a new `expected_reached` gives the abolition's reach from 2026-27; a family claims on either; and a claiming family gets no income-related ESA. The flag property and the closure-claim differential treat a reached family as moved.
- `test_legacy_award_work_properties.py` and `legacy_award_work_reference.py`: in 2026-27 the couple's ESA reports close when the abolition reaches them, because the family claims with `would_claim_uc_at_legacy_closure` at its default.
- `test_disable_simulated_benefits.py`: new assertions that the abolition reaches the claimant's own award from 2026-27 without the reform, and that the reform prompts no claim.

Cases about other rules set `would_claim_uc_at_legacy_closure` false, so the family keeps the award they test:

- `esa_income_remunerative_work.yaml`, the two 2026-27 limit cases: they test the permitted-work limit, and a claim would close the report.
- `pension_credit/mixed_age_couples.yaml`, "HB alongside income-related ESA is working-age HB, so no saving": it tests the saving inference and the continuing Housing Benefit award, which a claim would end.
- `test_mixed_age_couple_properties.py`, every generated family: its reference formulas from before the mixed-age couple rules read `would_claim_uc` alone.

`universal_credit/person_legacy_benefits_closed.yaml` Case 1 gives the claimant `esa_income_appointee_saving` instead: it tests that an outside member's closed report leaves the couple open, and from 2027-28 the claimant's own unsaved report would close the couple.

Thirteen existing YAML cases and four Python files test another rule on an income-related ESA award in 2026-27 or 2027-28, so each ESA reporter there gets `esa_income_appointee_saving: true`, which keeps the award payable for the whole year (inputs only):

- `esa_income/esa_income_capital.yaml`: "has no tariff income at the lower capital threshold", "tariff income rounds incomplete capital steps up" and "remains eligible at the upper capital limit" (2026);
- `esa_income/esa_income_remunerative_work.yaml`: "Issue 2395 outside closed IS report leaves the couple ESA award open" (2027);
- `legacy_award_readers/council_tax_reduction_passports.yaml`: the five non-dependant cases (2027);
- `legacy_award_readers/housing_benefit_pension_age_regulations.yaml`: the two ESA cases (2026);
- `legacy_award_readers/pension_credit_earnings_disregard.yaml`: "Another member's income-related ESA leaves a single pensioner with the £5 earnings disregard" and "The partner's income-related ESA gives the couple the £20 earnings disregard" (2026);
- `test_esa_permitted_work_nlw_reforms.py` (2026), the two 2027 Merton non-dependant tests in `test_legacy_award_readers_properties.py`, the 2027 cases of `test_legacy_closure_scope.py`, and every award in `test_esa_income_universal_credit_claim_properties.py`, whose 2027 cases test the Universal Credit claim under art. 3A(3).

### Overlap

- `git merge-tree --write-tree --name-only` at `3c9890a19` merges cleanly with `main` at `f4a317019`.
- #2807 (`fix-2565-esa-pension-credit`, head `254669a0d`) already merges `7fe7360fa`, and `3c9890a19` merges cleanly with it. On that merge, `finance/benefit/family/esa_income` (131), `finance/benefit/family/housing_benefit` (596) and `gov/dwp/pension_credit` (320) passed 1,047 of 1,047. #2807's head already gives the ESA reporters of Cases 4, 5, 6, 12, 13 and 14 of `esa_income_pension_age.yaml` `esa_income_appointee_saving: true`, because each asserts a full-year income-related ESA award in 2026-27.
- #1910 (`abolish-working-age-housing-benefit`, head `2d0e49417`) conflicts with `main` at `f4a317019` in `CHANGELOG.md`, `pyproject.toml`, `housing_benefit_eligible.py` and `housing_benefit/takeup.yaml`. With `3c9890a19` it conflicts in those four files and in `claims_uc_at_legacy_closure.py`, `mixed_age_couples.yaml` and `uc_housing_costs_element.py`. #1910 adds the same kind of term for Housing Benefit to `claims_uc_at_legacy_closure`, and the same flag line to the `mixed_age_couples.yaml` case; a merge ORs both abolition terms and keeps one copy of the flag line. #1910 makes the same two routing changes as this branch, which explains the conflicts in `housing_benefit_eligible.py` and `uc_housing_costs_element.py`. In `uc_housing_costs_element.py` both return nil for `in_specified_or_temporary_accommodation`, and a merge keeps either text. In `housing_benefit_eligible.py` both OR the input into the routes before the tenure and capital tests and drop it from the continuing award; a merge keeps #1910's continuing award, which adds `housing_benefit_payable_share`, and one accommodation term. Under SI 2025/1148 art. 7(4)(b) and SR 2025/176 art. 7(4)(b), an appointee-saved income-related ESA claimant keeps Housing Benefit, and `housing_benefit_payable_share` at #1910's head has no input for that saving. Whichever PR lands second makes it read `esa_income_appointee_saving`, or the merged model ends that Housing Benefit on the appointed day. #1910 doesn't change `would_claim_housing_benefit`, so a merge keeps this branch's take-up for the status.
- #2688, #2670, #2302, #2301 and #2300 conflict with this branch only where they already conflict with `main`. #2279, #1969, #2007 and #2756 merge cleanly.

- [x] For policy changes: YAML tests whose expected values come from an external source: `esa_income_abolition.yaml` works each amount by hand from art. 3A's dates and ESA Regs 2008 reg 118, and the Housing Benefit cases from SI 2014/1230 regs 6A(2), 8(3) and 46(2), SR 2016/226 reg 6(3), the Universal Credit rates and caps in the parameter files, and the means test with no income.
- [x] `make format` equivalent on the changed Python files: `ruff format --check`, `ruff check`, `ruff check --select F811,F821 policyengine_uk`, `git diff --check` (ruff 0.17.2).
- [x] Changelog fragment `changelog.d/2562.fixed.md`.

## Reproducer

Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_abolition.yaml::Great Britain 2027-28 pays no income-related ESA

axiom: TheAxiomFoundation/rulespec-uk#518 queued

🤖 Generated with [Claude Code](https://claude.com/claude-code)





Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Head: 3c9890a19abb714575050f8d8699ed9968a577e3 on branch claude/housing-benefit-tenure-capital-vcm2pv, two commits on top of 7fe7360fa9008437670055067761db88d8bfc94e. I didn't push to fix-2562-esa-abolition, opened no PR and merged nothing. The branch has no PR, so it started no CI.

Findings and how I resolved them

P1 item 1, take-up (C1). I made the code fix.

  • policyengine_uk/variables/gov/dwp/housing_benefit/would_claim_housing_benefit.py:28-38: the hand-entered branch now returns claims_all_entitled_benefits | reported_hb | in_specified_or_temporary_accommodation. The built_from_data branch still returns reported_hb. The documentation (:9-21) says hand-entered families with the status also claim, and why. I also replaced the comment that cited "d1455 … (PR 1831 review)" with one that says what the code does, following the repository's rule against fix history.
  • in_specified_or_temporary_accommodation.py:27-29: adds the sentence the brief asked for.
  • Cases. The arithmetic sits in the YAML comments, worked from the parameter files. My figures match the brief's.
    • esa_income_abolition.yaml, "Great Britain 2026-27, the same London tenant without a Housing Benefit report takes Housing Benefit up for the rent": HB £30,000, UC £5,098.80, net £33,518.80.
    • housing_benefit_eligible.yaml, "A family in temporary accommodation that reports only Universal Credit takes Housing Benefit up for the rent": net £13,518.80. Its control "The same family outside that accommodation gets the rent through Universal Credit" also gives £13,518.80.
    • housing_benefit_eligible.yaml:133 now sets would_claim_housing_benefit: false as its input in place of claims_all_entitled_benefits: false, with the same outputs.
    • would_claim_housing_benefit.yaml: "The accommodation status claims without a report" gives true. "Ineligible with the accommodation status set false" gives false, and the existing "Ineligible" case (input unset) stays false.
  • Properties in test_housing_benefit_accommodation_dates_properties.py:
    • R5 (test_r5_the_status_takes_housing_benefit_up): the input true gives take-up for every family. With the input false, take-up equals claims_all_entitled_benefits | reported_hb. Building the input-false simulation needed a status argument on accommodation_simulation.
    • R6 (test_r6_an_eligible_family_keeps_help_with_the_rent): with the input, an eligible family never ends up with no HB and no element unless housing_benefit_entitlement is nil. The docstring states that exception. It also notes that the HB cap leaves the 50p weekly minimum (housing_benefit_benefit_cap_reduction), so the cap can't produce that outcome.
  • Data branch: the new file test_housing_benefit_accommodation_take_up_data_built.py builds a one-row UKMultiYearDataset, the way test_data_source_provenance.py does. It runs for both Simulation and Microsimulation. The status with no report gives no take-up, HB 0 and element 0. The status with a report gives HB £10,000. The same family hand-entered claims.
  • Body: "What the model doesn't do" now has the data-built sentence.

P3 item 8, tenure and capital (C2). Two new cases in housing_benefit_eligible.yaml:

  • "A family in temporary accommodation with capital above the limit gets no Housing Benefit": £20,000 of savings gives housing_benefit_eligible false and the element nil.
  • "An owner occupier with the status gets no Housing Benefit": OWNED_OUTRIGHT.

M5 and M6 now each fail one YAML case.

P3 item 7, writing (C3). I applied the five replacements as written:

  • uc_work_allowance.yaml:33-35
  • housing_benefit_eligible.yaml:162-165
  • housing_benefit_eligible.yaml:210 (now "The claim ends the award (SR 2016/226 reg 6(2A))")
  • esa_income_abolition.yaml (the reg 8(3) case's comment)
  • benefit_cap_welfare_benefits.py:22-28

I then read every comment, YAML name and documentation string the branch adds against main (2da212a) and gave each place where a regulation, a rule or the cap acted a named actor or an "under …" construction:

  • the two awards_payable.yaml descriptions;
  • the esa_income_abolition.yaml header and the art. 3A(2) and 3A(3) cases;
  • the two "Universal Credit cap counts" comments, and the cap case's name, now "…, DWP caps Universal Credit and leaves temporary accommodation Housing Benefit whole";
  • the 13 "SI 2025/1148 art. 3A(2) saves this award" comments across five YAML files;
  • test_esa_permitted_work_nlw_reforms.py and test_esa_income_universal_credit_claim_properties.py;
  • esa_income_appointee_saving.py, esa_income_payable_share.py (the documentation and the comment at :54), and housing_benefit_eligible.py:110-112;
  • the passive sentence in test_housing_benefit_universal_credit_passport_properties.py;
  • the property docstring "Whether a saving keeps the claim…";
  • the case renamed to "The couple's whole award stays payable after the abolition under the claimant's saving".

The esa_income_abolition.yaml header also still said "a claim ends it there too, because the model's Universal Credit pays the rent". That stopped being true at 7fe7360fa, so I rewrote it to cite regs 8(3) and 46(2).

P3 items 3, 4 and 5 (body).

  • Item 3's sentence is in the "What else changes" bullet, after the take-up change.
  • Item 4's sentence is in, with the figures rerun at 3c9890a19: £9,200 of earnings gives UC £38.80, HB £5,200 and net £12,858.80. It attributes the disregard to housing_benefit_on_passporting_benefit and the provisions that variable cites, without quoting them.
  • I rewrote "Overlap" from merge-trees run at 3c9890a19 against the live heads:

Items 2 and 6. I left these alone, as the brief asked.

Tests at 3c9890a19

No survey data; uv sync --frozen --extra dev.

YAML (policyengine-core test):

Target Passed
housing_benefit_eligible.yaml 14
would_claim_housing_benefit.yaml 5
esa_income_abolition.yaml 40
finance/benefit/family/housing_benefit 596
universal_credit/work_allowance 17
universal_credit/housing_costs_element 21
housing_benefit_universal_credit_passport.yaml 9
benefit_cap_by_scheme.yaml 22

Extra YAML runs:

  • finance/benefit/family/universal_credit: 569 passed. finance/benefit/family/esa_income: 106. gov/dwp: 804. All passed, none failed.
  • No integration directory exists at this head, so I couldn't rerun the body's older 2,588-case figure.

Python (-n 4):

  • test_housing_benefit_accommodation_dates_properties.py, test_benefit_cap_uc_and_hb_properties.py, test_housing_benefit_universal_credit_passport_properties.py and test_housing_benefit_accommodation_take_up_data_built.py together: 23 passed (11 + 1 + 6 + 5) in 486 s.
  • policyengine_uk/tests/code_health: 3,091 passed.

Other checks:

  • .github/check_fix_pr.py on the final title and body, with the changes from the merge base with main to the head, run as pr_fix_evidence.yaml runs it: PASS, 29 changed executable tests.
  • uvx ruff@0.17.2 format --check .: 2,170 files already formatted.
  • uvx ruff@0.17.2 check . and check --select F811,F821 policyengine_uk: all checks passed.
  • git diff --check 7fe7360fa..HEAD: clean.
  • The Bar income-related ESA at pensionable age and on Pension Credit #2807 merge (3c9890a19 + 254669a0d): esa_income 131, housing_benefit 596, gov/dwp/pension_credit 320, for 1,047 of 1,047.

Mutations

Each mutant ran at 3c9890a19 in a scratch worktree, with PYTHONPATH pointing there. Each ran against the three YAML files, R1–R3, R5, R6 and the data-built file. All eight were killed.

Mutant YAML failures Python failures
Status dropped from would_claim_housing_benefit 3 (one in each file) R5, R6, the hand-entered test
Status moved above the built_from_data branch 0 both data-built no-report tests
M5: the route skips capital 1 (£20,000 capital) R2
M6: the route skips tenure 1 (owner occupier) R2
The element ignores the input 9 R1, both data-built no-report tests
reg 8(3) route dropped 4 R2, both data-built report tests
reg 46(2) route dropped 2 R2
New-claim route dropped 4 R2, both data-built no-report tests, the hand-entered test

The second mutant fails only the data-built test, because no YAML case is data-built. The regression tests also fail without the change: the first mutant restores the pre-change take-up rule.

Differential (R4)

diff_run.py is my own population: 600 families a year in 2025, 2026 and 2027, built as the body describes, with 25 outputs. It ran on worktrees at 5a5ac5fb1, 7fe7360fa and 3c9890a19.

The input unset or false (half unset, half false): 58,740 values, 0 differ between 3c9890a19 and 7fe7360fa, and 0 between 3c9890a19 and 5a5ac5fb1.

The input true for every family, against 5a5ac5fb1:

2025 2026 2027
7fe7360fa: net income falls 72 84 71
…of which no HB take-up 70 78 67
3c9890a19: net income falls 7 17 11
3c9890a19: net income rises 173 182 184

Why each family at 3c9890a19 falls:

  • HB's taper without a UC passport: 3, 8 and 2.
  • HB paid weekly beside UC, slightly under the element: 3, 8 and 6. For example £161.10 × 52 = £8,377.20 against an £8,400 element, a loss of £22.80.
  • The HB cap for a family without UC: 1, 0 and 2.
  • The means test takes HB to nil: 0, 1 and 1.

None of these families lacks take-up. My population has more non-take-up losers than the review's 35–40, because it draws more benefit reports, but every one of them stops falling.

Also with the input true: council_tax_reduction rises for 1, 3 and 4 families, and housing_benefit_on_passporting_benefit turns false for 4, 10 and 7.

Judgment calls

  • I put both of the review's rows in YAML: row 1 in esa_income_abolition.yaml, row 2 and its control in housing_benefit_eligible.yaml.
  • I numbered the new properties R5 and R6, because the body's R4 is the scratch differential. In the body, R4 now sits before them.
  • I didn't cite HB Regs 2006 reg 43 in the capital case, because the law file doesn't quote it. The comment names the parameter the formula reads.
  • In the body, the What-and-why bullet also states the take-up change, and the changelog fragment gains one clause.
  • I left the body's older "Local runs" sections, and the "Add overseas and other real estate to property wealth and capital #2688, Fix structural reforms for their requested periods #2670…" Overlap line, as they stood. I didn't rerun those other PRs.

Skipped

  • No survey-data test, and no aggregate report. The Python shards are left to CI.
  • I didn't edit rulespec-uk#518.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Picking this up for the UK hub. Reply here if you're mid-change and I'll stop.


Generated by Claude Code

…emporary accommodation

would_claim_housing_benefit no longer reads in_specified_or_temporary_accommodation:
a hand-entered family claims when claims_all_entitled_benefits is true or it
reports Housing Benefit, and a data-built one when it reports Housing Benefit.
The documentation of both variables now says that a hand-entered family with
the status that reports another means-tested benefit and no Housing Benefit
gets no help with the rent, and that setting housing_benefit_reported or
would_claim_housing_benefit models the claim. The cases and properties R5 to
R7 pin both results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6KQ9PJBdyEd9CqHdqK
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_TITLE.txt

Model the 2026 income-related ESA abolition and its UC claims

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_BODY.md

Fixes #2562

## What and why

The Secretary of State appointed 1 July 2026 for the remaining old style income-related ESA awards in Great Britain to end under Welfare Reform Act 2012 s.33(1)(b) ([SI 2025/1148 art. 3A](https://www.legislation.gov.uk/uksi/2025/1148/article/3A), inserted by SI 2026/409). The Department for Communities appointed 1 October 2026 in Northern Ireland ([SR 2025/176 art. 3A](https://www.legislation.gov.uk/nisr/2025/176/article/3A), inserted by SR 2026/115). Under each article, an appointee saving keeps an award in payment, and an award already due to end after a two-week run-on ends on that schedule. DWP had sent migration notices to 879,000 households on income-related ESA by December 2025, and most of them had claimed Universal Credit ([LA Welfare Direct 6/2026](https://www.gov.uk/government/publications/la-welfare-direct-bulletins-2026/la-welfare-direct-62026#update-move-to-universal-credit), paras 51 to 54). At `main` (2da212abc) the model pays an award in full every year unless the family claims Universal Credit or DWP closes another of its legacy benefits, and nothing in the model prompts a claim on the award's appointed day.

This PR:

- adds `gov.dwp.ESA.income.awards_payable.{great_britain,northern_ireland}`, a 1/0 flag for each jurisdiction with `fiscal_year_blend: true`, so the model day-weights the date across each fiscal year from 6 April to 5 April;
- adds `esa_income_appointee_saving` (person input, default false) for the art. 3A(2) saving. The saving applies when someone holds an appointment under reg 57 of the Claims and Payments Regulations 2013 immediately before the date, or the Secretary of State determined in the preceding six months that someone would likely need to hold one. In Northern Ireland, the Department for Communities makes that determination and reg 52 of the 2016 Regulations governs the appointment. The claimant and partner share one award, so when either of them reports the award and has the saving, the model keeps the whole claim payable; it ignores a saving for a member who reports nothing;
- adds `esa_income_payable_share` (person), the share of the year the person's claim stays payable: 1 for a saved claim, otherwise the household's jurisdiction value. `esa_income` pays each claim for its share, one share for the couple's claim and one for each member outside it;
- adds `esa_income_abolition_reached` (benefit unit): the claimant or partner reports an unsaved award with less than the whole year payable, from 2026-27. `claims_uc_at_legacy_closure` now reads it beside `legacy_benefits_closed`, so the family claims Universal Credit when the abolition reaches its award if `would_claim_uc_at_legacy_closure`. The claim ends the income-related ESA award (`esa_income_report_closed`) and, outside specified or temporary accommodation, the Housing Benefit award (`housing_benefit_eligible`), through the rules `main` already has;
- has `legacy_award_is_closed` close an income-related ESA report once none of its claim stays payable, from 2027-28, under the same Pension Credit and pension-age conditions as Income Support and income-based JSA. DWP's move then ends the family's other legacy awards whether or not it claims, apart from the Housing Benefit exception below. The model neither prompts a claim for a saved award nor moves its family;
- routes the rent of specified or temporary accommodation the way the law does, when the user sets `in_specified_or_temporary_accommodation` for the family. `uc_housing_costs_element` returns nil: the Secretary of State excludes these payments from rent payments ([UC Regs 2013 Sch 1 paras 3(h), 3(i), 3A and 3B](https://www.legislation.gov.uk/uksi/2013/376/schedule/1/paragraph/3); Northern Ireland: [SR 2016/216 Sch 1 paras 3(e), 3(ea), 4 and 4A](https://www.legislation.gov.uk/nisr/2016/216/schedule/1/paragraph/3)). `housing_benefit_eligible` keeps an existing award through a Universal Credit claim ([SI 2014/1230 reg 8(3)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/8); Northern Ireland: [SR 2016/226 reg 6(3)](https://www.legislation.gov.uk/nisr/2016/226/regulation/6)) and through the move off legacy benefits, whichever legacy benefit closed ([SI 2014/1230 reg 46(2)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/46); Northern Ireland: [SR 2016/226 reg 47(2)](https://www.legislation.gov.uk/nisr/2016/226/regulation/47)), and lets a family with no award make a new claim ([SI 2014/1230 reg 6A(2)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/6A); Northern Ireland: [SR 2016/226 reg 4A(2)](https://www.legislation.gov.uk/nisr/2016/226/regulation/4A)). The tenure and capital tests still apply. The input doesn't change take-up (`would_claim_housing_benefit`): a data-built simulation claims for families that report Housing Benefit, and a hand-entered one also claims when `claims_all_entitled_benefits` is true. Universal Credit pays nothing towards this rent, so a family gets help with it only through Housing Benefit. A hand-entered family that reports another of the seven means-tested benefits and no Housing Benefit claims only what it reports, and gets none; to model the claim, a user sets `housing_benefit_reported` or `would_claim_housing_benefit`. The 1 July 2026 abolition of Housing Benefit doesn't reach these awards ([SI 2025/1148 art. 7(4)(a)](https://www.legislation.gov.uk/uksi/2025/1148/article/7), through reg 6A(2)). The route reads the input, not an award, so it adds no cycle through the benefit cap. Without the element, `uc_work_allowance` gives the higher work allowance ([UC Regs 2013 reg 22(2)(a)](https://www.legislation.gov.uk/uksi/2013/376/regulation/22)). The benefit cap needs no formula change: the Universal Credit cap counts Housing Benefit as nil (`uc_benefit_cap_reduction`; [reg 80(2A)](https://www.legislation.gov.uk/uksi/2013/376/regulation/80)), and a Housing Benefit claimant receiving Universal Credit is outside the Housing Benefit cap (`is_housing_benefit_benefit_cap_exempt_specified_benefit`; [HB Regs 2006 reg 75F(1)(g)](https://www.legislation.gov.uk/uksi/2006/213/regulation/75F)). `uc_non_dep_receives_pension_credit` already fixes the element at nil in its branch, so the change leaves it alone;
- has `left_pension_route_at_legacy_closure` count a protected mixed-age couple whose award the abolition reaches, so a couple that claims and has a Universal Credit award stops getting pension-age Housing Benefit beside it;
- has `own_report_is_paid` apply the abolition's report closure to a member outside the couple only when both held amounts match the formula on open reports: `esa_income` and the claimant's and partner's part of it, `claimant_or_partner_esa_income` (`holds_formula_award`). Either amount can differ when a user enters `esa_income`, a reform replaces it, or `disable_simulated_benefits` holds it; `own_report_is_paid` then applies only the person's legacy closure and reads the benefit unit's held award. That award covers the whole unit, so an outside member with an open report stays paid while the unit's award stays positive, even if it leaves them nothing. A member outside the couple at pension age has no legacy closure, so a reform that replaces `esa_income` keeps that member on income-related ESA. `holds_formula_award` can't separate two or more outside reporters in one benefit unit when the held total and the couple's part both equal the formula's: `own_report_is_paid` then applies each one's report closure;
- has `disable_simulated_benefits` hold `esa_income_abolition_reached` false with the other closures, so the reform prompts no claim.

Contributory ESA continues as new style ESA, and `esa_contrib` keeps its formula.

### Which years change

| Model year | Great Britain | Northern Ireland |
|---|---|---|
| 2025-26 and earlier | 1 (unchanged) | 1 (unchanged) |
| 2026-27 | 86/365 = 0.235616 (6 April to 30 June 2026: 25 + 31 + 30 days) | 178/365 = 0.487671 (6 April to 30 September 2026: 25 + 31 + 30 + 31 + 31 + 30 days) |
| 2027-28 onwards | 0 | 0 |

In 2026-27 a family whose award the abolition reaches claims Universal Credit if `would_claim_uc_at_legacy_closure`, and the claim ends the award for the year. A family that doesn't claim keeps £5,000 x 86/365 = £1,178.08 of a £5,000 award in Great Britain (£2,438.36 in Northern Ireland) and its Housing Benefit. From 2027-28 every unsaved award has ended, and the model moves each affected family off its legacy benefits. Scotland and Wales follow the Great Britain date.

Under art. 3A(1)(b), an award already due to end at the end of a two-week run-on ends on that schedule. A claimant whose migration notice deadline falls on 22 June 2026 and who makes no claim keeps the award until 5 July, the last day of the two weeks beginning with the deadline day ([SI 2014/1230 reg 46(1)(a)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/46)). The model has no deadline or claim dates, so it pays every unsaved award to 30 June (30 September in Northern Ireland).

### Worked household

A single claimant aged 40 in a London council tenancy (rent £5,200 a year, council tax £1,500) reports £5,000 a year of income-related ESA and £4,000 of Housing Benefit, and `would_claim_uc` is false. The Housing Benefit means test pays £5,200 from the rent. On `main` every row below pays £5,000 of ESA and £5,200 of Housing Benefit, and no Universal Credit or Council Tax Reduction.

The ESA abolition doesn't end Housing Benefit by itself. The Secretary of State appointed 1 July 2026 for the remaining working-age Housing Benefit awards to end without a Universal Credit claim ([SI 2025/1148 art. 7](https://www.legislation.gov.uk/uksi/2025/1148/article/7)), and the Department for Communities appointed 1 October 2026 in Northern Ireland ([SR 2025/176 art. 7](https://www.legislation.gov.uk/nisr/2025/176/article/7)), except in specified or temporary accommodation. So under the law this tenant has neither benefit in 2027-28 without a claim, as the model gives. The model doesn't apply art. 7's date: in 2026-27 it pays a family that doesn't claim a full year of Housing Benefit, an approximation that #1910 replaces.

Each cell gives the amount here and, in brackets, the change from `main`, in pounds. Council Tax Reduction stays £0 in every row.

| Earnings | `would_claim_uc_at_legacy_closure` | Model year | Income-related ESA | Housing Benefit | Universal Credit | Household net income |
|---:|---|---|---:|---:|---:|---:|
| 0 | False | 2026-27 | 1,178.08 (−3,821.92) | 5,200.00 (0.00) | 0.00 (0.00) | 4,798.08 (−3,821.92) |
| 0 | False | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 0.00 (0.00) | −1,680.00 (−10,200.00) |
| 0 | True | 2026-27 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 10,298.80 (+10,298.80) | 8,718.80 (+98.80) |
| 0 | True | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 10,518.52 (+10,518.52) | 8,838.52 (+318.52) |
| 10,000 | False | 2026-27 | 1,178.08 (−3,821.92) | 5,200.00 (0.00) | 0.00 (0.00) | 14,798.08 (−3,821.92) |
| 10,000 | False | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 0.00 (0.00) | 8,320.00 (−10,200.00) |
| 10,000 | True | 2026-27 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 4,798.80 (+4,798.80) | 13,218.80 (−5,401.20) |
| 10,000 | True | 2027-28 | 0.00 (−5,000.00) | 0.00 (−5,200.00) | 5,018.52 (+5,018.52) | 13,338.52 (−5,181.48) |

With the flag at its default, true, the model gives the true rows' amounts, and every output matches `main` in 2025-26. With `in_specified_or_temporary_accommodation`, the tenant who doesn't claim keeps their £5,200 of Housing Benefit in 2027-28, for £3,520.00 of net income. The tenant without earnings who claims keeps the same £5,200 of Housing Benefit beside Universal Credit, which leaves the rent out: £5,098.80 in 2026-27 and £5,318.52 in 2027-28, the standard allowance alone. Net income comes to the same £8,718.80 and £8,838.52 as in the table, now split between the two benefits. With £10,000 of earnings, the claim brings no Universal Credit, so Universal Credit no longer passports the Housing Benefit means test, and Housing Benefit's 65% taper leaves £3,415.83 in 2026-27 (net income £11,835.83) and £4,838.89 in 2027-28 (£13,158.89), against £13,218.80 and £13,338.52 in the table. With any Universal Credit entitlement, the council disregards all income in the Housing Benefit means test (`housing_benefit_on_passporting_benefit` and the provisions it cites), so at £9,200 of earnings, with £38.80 of Universal Credit, the tenant receives the whole £5,200 of Housing Benefit and net income comes to £12,858.80.

### Data

The certified data (policyengine-uk-data 1.58.1) supply no draw for `would_claim_uc_at_legacy_closure`, so the model uses its default, true. The model therefore has each affected family claim Universal Credit in 2026-27. In an affected family the claimant or partner reports an unsaved income-related ESA award, and the family neither reports Pension Credit nor consists of a single claimant, or joint claimants both, over the qualifying age for State Pension Credit. A family whose only report belongs to a member outside the couple makes no claim through this path. PolicyEngine/policyengine-uk-data#534, a draft pull request, would draw the flag at DWP's Move to Universal Credit claim rates. No dataset identifies appointees or specified or temporary accommodation, so the saving, the Housing Benefit routes and the Universal Credit exclusion stay off in dataset runs.

### What else changes

- A family that claims Universal Credit and would get more from Tax-Free Childcare can now forgo the claim in 2027-28. On `main` an income-related ESA report counted as an award the claim would end, so the family kept its claim; now it counts only while some of the year stays payable (`forgoes_universal_credit_for_tax_free_childcare`). With a £21,993 maximum award and a £20,000 income reduction, Universal Credit would pay £1,993 against £2,400 of Tax-Free Childcare, so the family now forgoes it.
- A family that supplies `in_specified_or_temporary_accommodation` now keeps its Housing Benefit when any legacy closure moves it, the tax credit closure from 2025-26 and the Income Support and income-based JSA closures from 2026-27 included. In every year it also keeps the award beside a Universal Credit claim, can claim Housing Benefit afresh, and gets no Universal Credit housing costs element. That changes the model for such a family whatever moved it to Universal Credit, not only for the ESA abolition. Take-up stays as on `main` (`would_claim_housing_benefit`), so a hand-entered family that reports another of the seven means-tested benefits and no Housing Benefit claims only what it reports, and now gets no help with the rent, where before the element paid it; setting `housing_benefit_reported` or `would_claim_housing_benefit` models the claim. Universal Credit falls by the element, so the model's Council Tax Reduction, which counts Universal Credit as income, can rise, and a family whose Universal Credit falls to nil loses the Housing Benefit passport.
- `has_own_esa_award`, the support component default, the Universal Credit LCWRA default, the mixed-age Pension Credit saving and `claims_all_entitled_benefits` read the raw report as before. Their documentation now treats the report as the award's history: the work capability assessment behind an ended award still stands, and the report still records the family's claiming.
- The model retains its existing working-age Housing Benefit rules; #1910 adds the closure of those awards on art. 7's dates.

### What the model doesn't do

- It doesn't model deadline days, claim dates, the two-week run-ons or the six-month window of art. 3A(2)(b).
- It doesn't identify appointees in data.
- For a family that does not claim Universal Credit in 2026-27, the model keeps the ESA report open for passports and screens throughout the year and pays its amount for the payable share.
- `in_specified_or_temporary_accommodation` doesn't separate specified from temporary accommodation. Where the Housing Benefit cap applies, to a family without Universal Credit, the council counts Housing Benefit for specified accommodation as nil ([HB Regs 2006 reg 75C(2)(a)](https://www.legislation.gov.uk/uksi/2006/213/regulation/75C)), but `benefit_cap_welfare_benefits` counts it at its amount before the cap.
- `in_specified_or_temporary_accommodation` doesn't make a family claim Housing Benefit. Universal Credit pays nothing towards this rent, so a family gets help with it only through Housing Benefit. A data-built family with the input and no Housing Benefit report, and a hand-entered family that reports another of the seven means-tested benefits and no Housing Benefit, claim only what they report and get no help with the rent. To model the claim, set `housing_benefit_reported` or `would_claim_housing_benefit`.

## Invariants

The property tests (`policyengine_uk/tests/test_esa_income_abolition_properties.py`) check these for generated reported-award situations: working-age families of one to three adults, with or without a child, in five regions, with reports of £0 to £6,000, capital around the tariff threshold and limit, and every combination of saving and claim flags, in 2025-26 to 2027-28.

1. Each fiscal year's `awards_payable` value equals the share of 6 April to 5 April before the statutory date, counted from the two dates (2015 to 2040).
2. 0 <= `esa_income_payable_share` <= 1; the claimant and partner share one value; it equals 1 for every member of a saved claim.
3. From 2027-28 the model pays an unsaved claim nothing, and a member outside the couple with an unsaved claim comes off income-related ESA.
4. Contributory ESA never changes, whatever the saving and claim flags.
5. Before 2026-27 every output matches the earlier rule for every saving and claim flag. The abolition never raises income-related ESA. A family that claims nothing and saves nothing gets its jurisdiction's share of the earlier award, to the penny.
6. Turning the saving on never lowers `esa_income` or `esa`.
7. A member outside the couple stays on income-related ESA only while their report stays open.
8. Saving a member who reports nothing changes no output.
9. The claim path never gives income-related ESA: turning `would_claim_uc_at_legacy_closure` on never raises `esa_income`, and a family that claims when the abolition reaches its award has no `claimant_or_partner_esa_income` that year.
10. The model neither prompts a claim for a saved award nor closes it: `esa_income_abolition_reached` and `legacy_benefits_closed` stay false for the family, and a member outside the couple with a saved claim keeps their legacy benefits open.
11. For families with no work, `esa_income`, `claims_universal_credit` and each outside member's status match amounts worked in the test from the regulations (the £6,000 tariff threshold, £1 a week for each £250 or part above it and the £16,000 limit; ESA Regs 2008 regs 110 and 118), the dates and the saving, without the model's code. The test's reference gives a couple's claim one share and pays a saved claim in full.
12. With no reform holding `esa_income`, `holds_formula_award` is true for every benefit unit in every mode and year, so the comparison of the claimant's and partner's part changes no baseline result.

### Specified or temporary accommodation

`policyengine_uk/tests/test_housing_benefit_accommodation_dates_properties.py` checks R1 to R3 and R5 to R7 for generated families with the input set: one or two adults aged 22 to 70, up to five children, council, housing association and private tenancies and owners, rent of £0 to £30,000, capital around the £16,000 limit, earnings, reported Housing Benefit, income-related ESA and Working Tax Credit, and every combination of claim and take-up flags, in five regions including Northern Ireland, in 2025-26 to 2027-28.

- R1. `uc_housing_costs_element` is nil for every family.
- R2. `housing_benefit_eligible` equals the tenure and capital tests alone, and stays the same when the test sets `claims_universal_credit` and `legacy_benefits_closed` to each of their four combinations.
- R3. For a family with both awards, `uc_benefit_cap_reduction` equals that of the same family with Housing Benefit set to nil, `housing_benefit_benefit_cap_reduction` is nil, and `housing_benefit` equals `housing_benefit_pre_benefit_cap`.
- R4. With the input false, nothing this change touches reads differently. A scratch differential, not a repository test, checks this on 600 synthetic families a year in 2025-26, 2026-27 and 2027-28: one or two adults aged 20 to 75, up to four children, six regions including Northern Ireland, five tenures, rents of £0 to £30,000, savings of £0 to £17,000, earnings, LCWRA, appointee savings, reports of the legacy benefits, Universal Credit and Pension Credit, random claim and take-up flags, and the input left unset or set false. On one such population, all 14 outputs it compares at `7fe7360fa` with `5a5ac5fb1` (`housing_benefit_eligible`, `housing_benefit_pre_benefit_cap`, `housing_benefit`, `uc_housing_costs_element`, `uc_work_allowance`, `universal_credit_pre_benefit_cap`, `universal_credit`, `uc_benefit_cap_reduction`, `housing_benefit_benefit_cap_reduction`, `is_housing_benefit_benefit_cap_exempt_specified_benefit`, `benefit_cap_welfare_benefits`, `claims_universal_credit`, `esa_income`, `household_net_income`) match for every family and year. On a second population, which draws reports of six of the seven legacy benefits, it compares 25 outputs at `3c9890a19` with both `7fe7360fa` and `5a5ac5fb1`: the 14 above, `would_claim_housing_benefit`, `pension_credit`, `working_tax_credit`, `child_tax_credit`, `income_support`, `tax_free_childcare`, `housing_benefit_on_passporting_benefit`, `council_tax_reduction`, `free_school_meals`, `is_scp_eligible` and `housing_benefit_entitlement`. None of the 58,740 values differs from either base. At `dc69f3ada`, a third population built to the same description (600 families a year, reports of six legacy benefits, Universal Credit and Pension Credit, `claims_all_entitled_benefits` left to the formula for half the families) compares the same 25 outputs. With the input unset, false or true for every family, none of the 58,419 values in each mode differs from `7fe7360fa`. With the input and `would_claim_housing_benefit` true for every family, none of the 58,419 values differs from `3c9890a19` with the input true. No dataset sets the input, so CI's aggregate report should show no move from this change.
- With the input set true for every family, against `5a5ac5fb1`, on the second population, household net income at `7fe7360fa` falls for 72, 84 and 71 families in the three years, 70, 78 and 67 of them with no Housing Benefit take-up. At `3c9890a19` it falls for 7, 17 and 11 families and rises for 173, 182 and 184. Each family whose net income falls receives Housing Benefit in place of a larger element, or gets none from the means test: Housing Benefit's taper without a Universal Credit passport (3, 8 and 2), Housing Benefit paid weekly over 52 weeks beside Universal Credit, such as £161.10 × 52 = £8,377.20 against an £8,400 element (3, 8 and 6), the Housing Benefit cap for a family without Universal Credit (1, 0 and 2), and the means test taking Housing Benefit to nil (0, 1 and 1). With the input true, `council_tax_reduction` rises for 1, 3 and 4 families, and `housing_benefit_on_passporting_benefit` turns false for 4, 10 and 7.
- On the third population, with the input true for every family and take-up left to the formula, household net income at `dc69f3ada` falls against `5a5ac5fb1` for 69, 57 and 58 families in the three years and rises for 32, 43 and 42, the same as at `7fe7360fa`. Of the families whose net income falls, 65, 55 and 52 get neither Housing Benefit nor an element because they don't take Housing Benefit up. With `would_claim_housing_benefit` also true, it falls for 9, 10 and 9 families and rises for 147, 129 and 135, the same as at `3c9890a19`: Housing Benefit's taper without a Universal Credit passport (3, 5 and 1), Housing Benefit beside Universal Credit a little under the element, such as £12,864.80 against a £12,900 element (3, 5 and 6), the Housing Benefit cap for a family without Universal Credit (0, 0 and 1), and the means test taking Housing Benefit to nil (3, 0 and 1). In both runs `council_tax_reduction` rises for 1, 2 and 0 families, and `housing_benefit_on_passporting_benefit` turns false for 7, 5 and 4.
- R5. In a hand-entered situation, the input never changes `would_claim_housing_benefit`: with the input true, false or unset, it equals `claims_all_entitled_benefits` or a Housing Benefit report.
- R6. With the input and `would_claim_housing_benefit` true, a family that passes the tenure and capital tests (`housing_benefit_eligible`) never ends up with neither Housing Benefit nor a housing costs element, except where the Housing Benefit means test takes its entitlement to nil (`housing_benefit_entitlement`). The council caps Housing Benefit no lower than 50 pence a week (`housing_benefit_benefit_cap_reduction`), so the cap can't take that Housing Benefit to nil.
- R7. With the input, a hand-entered family that reports income-related ESA or Working Tax Credit and no Housing Benefit, with `claims_all_entitled_benefits` and `would_claim_housing_benefit` left to their formulas, gets neither Housing Benefit nor a housing costs element.

`test_keeping_awards_payable_is_saving_every_award` checks that a reform keeping `awards_payable` at 1 gives the same outputs as saving every award.

Invariant 12 also follows from the code. `esa_income` applies `income_related_esa_award_on_reports` to open reports, so the first comparison matches. `claimant_or_partner_award` then finds that `esa_income` holds the formula's value and takes the same function on the claimant's and partner's open reports, so the second comparison matches unless it sets that part to zero. It does so only where the part, or the stored total that bounds it, falls within half a penny of zero, and then the formula's part also falls within half a penny of zero. `is_on_income_related_esa` already reads `claimant_or_partner_esa_income` for the claimant and partner, so the new read adds no cycle.

## Test plan

### Local runs at `dc69f3ada`

`dc69f3ada6b6bee045ce4db88406311ba5701fcd` sits one commit on `3c9890a19`. With no survey data, in the locked environment (`uv sync --frozen --extra dev`).

YAML policy tests (`policyengine-core test`):

- `housing_benefit/housing_benefit_eligible.yaml`: 15 passed. "A family in temporary accommodation that reports only Universal Credit claims no Housing Benefit and gets no help with the rent": no Housing Benefit, element nil, Universal Credit £5,098.80, net income £5,098.80 + £100 − £180 − £1,500 = £3,518.80. "The same family gets Housing Benefit for the rent when the user sets would_claim_housing_benefit": Housing Benefit £10,000, Universal Credit £5,098.80, net income £13,518.80, the same as its control outside that accommodation. The capital, owner-occupier and control cases from `3c9890a19` stay as they were.
- `housing_benefit/would_claim_housing_benefit.yaml`: 6 passed. "The accommodation status doesn't make a family claim without a report" (`claims_all_entitled_benefits` false, no report) gives false; "A family with the accommodation status claims when it claims all entitled benefits" gives true; "Ineligible with the accommodation status set false" stays false.
- `esa_income/esa_income_abolition.yaml`: 41 passed. "Great Britain 2026-27, the same London tenant without a Housing Benefit report claims only the benefits it reports and gets no help with the rent": no Housing Benefit, element nil, Universal Credit £5,098.80, net income £5,098.80 + £100 − £180 − £1,500 = £3,518.80. "Great Britain 2026-27, the same London tenant gets Housing Benefit for the rent when the user sets would_claim_housing_benefit": Housing Benefit £30,000, Universal Credit £5,098.80, net income £33,518.80.
- `finance/benefit/family/housing_benefit`: 598 passed; `finance/benefit/family/esa_income`: 107; `finance/benefit/family/universal_credit`: 569; `gov/dwp`: 804.

Python tests (`pytest -n 4`): `test_housing_benefit_accommodation_dates_properties.py` 12 passed (R5 to R7); `test_housing_benefit_accommodation_take_up_data_built.py` 8; `test_benefit_cap_uc_and_hb_properties.py` 1; `test_housing_benefit_universal_credit_passport_properties.py` 6; `policyengine_uk/tests/code_health` 3,091 passed. The data-built file checks that a data-built family with the status and no Housing Benefit report doesn't claim and gets no help with the rent, that one with a report gets the whole rent, that one with `would_claim_housing_benefit` in the dataset gets the whole rent, that the same family hand-entered with `claims_all_entitled_benefits` false doesn't claim and gets no help with the rent, and that it gets the whole rent with `would_claim_housing_benefit` set.

Other checks at `dc69f3ada`: `.github/check_fix_pr.py` on this title and body, with the changes from the merge base with `main`, passes with 29 changed executable tests; `uvx ruff@0.17.2 format --check .` finds 2,170 files formatted; `ruff check .` and `ruff check --select F811,F821 policyengine_uk` pass; `git diff --check 3c9890a19..HEAD` is clean.

Each mutant below ran at `dc69f3ada` in a scratch copy against `housing_benefit_eligible.yaml`, `would_claim_housing_benefit.yaml`, `esa_income_abolition.yaml`, R1 to R3, R5 to R7 and the data-built test. Each one failed at least one of them:

| Mutant (`dc69f3ada`) | Failing cases | Failing tests |
|---|---|---|
| The status put back into `would_claim_housing_benefit`'s hand-entered branch | 3: the family that reports only Universal Credit, the status with no report, and the London tenant without a Housing Benefit report | R5, R7, the hand-entered family that doesn't claim |
| The route skips the capital limit | 1: £20,000 of capital with the status | R2 |
| The route skips the tenure test | 1: the owner occupier with the status | R2 |
| The housing costs element ignores the input | 11: every routing case with the status that rents, both overrides included | R1, R7, both data-built families with no report, the hand-entered family that doesn't claim |
| The reg 8(3) route dropped | 4: the £5,200 claimant, the London tenant, the capped lone parent and Northern Ireland's reg 6(3) | R2, both data-built families with a report |
| The reg 46(2) route dropped | 2: the 2027-28 move off legacy benefits and the tax credit closure, each with the status | R2 |
| The new-claim route dropped | 6: the new claim, the same family without take-up, both families that report another benefit, and both overrides | R2, every data-built and hand-entered family without a report |

The differential above (R4) compares `dc69f3ada` with `7fe7360fa` and `3c9890a19`.

### Local runs at `3c9890a19`

`3c9890a19` made hand-entered families with the status take Housing Benefit up; `dc69f3ada` returns take-up to the hand-entered convention and keeps the rest. Its other runs: `universal_credit/work_allowance` 17 passed, `universal_credit/housing_costs_element` 21, `housing_benefit/housing_benefit_universal_credit_passport.yaml` 9, `gov/dwp/benefit_cap_by_scheme.yaml` 22; R4's second population above.

### Local runs at `7fe7360fa`

With no survey data, in the locked environment (`uv sync --frozen --extra dev`).

YAML policy tests (`policyengine-core test`):

- `esa_income/esa_income_abolition.yaml`: 39 passed. Three cases are new: a London tenant in council-arranged homelessness accommodation, with £30,000 of rent, whom the abolition moves to Universal Credit (Housing Benefit £30,000, Universal Credit £5,098.80, net income £33,518.80); the same tenant without the status (Housing Benefit nil, Universal Credit capped at £16,967, net income £15,387); and a lone parent of four whose Universal Credit the cap cuts by £2,442.20 to £17,821 while Housing Benefit pays the whole £15,000 rent. The case "a family in specified or temporary accommodation that claims Universal Credit" now keeps its £5,200 of Housing Benefit beside £5,098.80 of Universal Credit, where it had none.
- `housing_benefit/housing_benefit_eligible.yaml`: 10 passed. Four cases are new: a new claim beside Universal Credit (reg 6A(2)); the same family without take-up gets no help with the rent; Northern Ireland's reg 6(3) with SR 2016/216's exclusion; and its control without the status.
- `universal_credit/work_allowance`: 17 passed, two new: the higher work allowance (£8,520) in that accommodation, and the lower one (£5,124) without it.
- `universal_credit/housing_costs_element`: 21 passed, one new.
- `housing_benefit/housing_benefit_residential_capital.yaml`: 7 passed. `housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml`: 50 passed. `housing_benefit/housing_benefit_universal_credit_passport.yaml`: 9 passed.
- Benefit cap cases: `gov/dwp/benefit_cap`: 116 passed; `gov/dwp/pension_credit`: 320 passed; `gov/dwp/universal_credit`: 16 passed; all of `gov/dwp`: 804 passed, `benefit_cap.yaml` (18) and `benefit_cap_by_scheme.yaml` (22) included.
- Before the case edits, `gov/dwp`, `finance/benefit/family` and `integration` gave 2,588 passed and 1 failed, the case above that now keeps its Housing Benefit.

Python tests (`pytest -n 4`): `test_housing_benefit_accommodation_dates.py` 38 passed; `test_housing_benefit_accommodation_dates_properties.py` 9 passed (R1 to R3 new); `test_housing_benefit_specified_accommodation_disregard_properties.py` 11; `test_esa_income_abolition_properties.py` 31; `test_esa_income_universal_credit_claim_properties.py` 2; `test_uc_legacy_closure_properties.py` 4; `test_legacy_closure_scope.py` 21; `test_benefit_cap_uc_and_hb_properties.py` 1; `test_benefit_cap_lha_scheme_split_properties.py` 7; `test_housing_benefit_pension_age_properties.py` 4; `policyengine_uk/tests/code_health` 3,091 passed.

At `5a5ac5fb1`, the 8 new routing cases fail and the 3 controls pass, and R1 and R2 fail; R3 passes there, as the cap needed no formula change. Each mutant below ran in a scratch copy against the four YAML files above and R1 to R3:

| Mutant | Failing cases | Failing properties |
|---|---|---|
| The housing costs element ignores the input | 8: every new routing case with the status, and the element case | R1 |
| The reg 8(3) route dropped (an award with the status ends on a claim) | 5: the £5,200 claimant, the London tenant, the capped lone parent, Northern Ireland's reg 6(3) and the higher work allowance | R2 |
| The reg 46(2) route dropped (an award with the status ends on the move) | 2: the 2027-28 move off legacy benefits and the tax credit closure, each with the status | R2 |
| The new-claim route dropped | 2: the new claim and the same family without take-up | R2 |

This head merges cleanly with `main` (`0080274d7`) and with #2807 at `71ba2752`; on the #2807 merge, `esa_income`, `housing_benefit`, `universal_credit/housing_costs_element` and `universal_credit/work_allowance` passed 757 of 757.

### Local runs at `a3f8b1104`

Each file or test ran alone at `a3f8b1104`, with `-n 0`, no survey data and a 4 GB memory cap (2 GB for mutants); the cap stopped any run that exceeded its time limit. Later commits up to `5a5ac5fb1` change only documentation, comments and the changelog.

YAML policy tests (`policyengine-core test`):

- `esa_income/esa_income_abolition.yaml`: 36 passed. Three cases were new: the 2027-28 ESA closure keeps Housing Benefit for a family with `in_specified_or_temporary_accommodation`, the same family without the status loses it, and a family with the status that claims Universal Credit, which at `a3f8b1104` got no Housing Benefit and at `7fe7360fa` keeps it.
- `housing_benefit/housing_benefit_eligible.yaml`: 6 passed. Two cases are new: the tax credit closure keeps Housing Benefit with the status and ends it without.

Python tests (`pytest`):

- `test_esa_income_held_outside_award.py` (new): 3 passed, one for each way of holding the award.
- `test_esa_income_abolition_properties.py`: the 29 parameter and fixed-case tests passed in one run. CI ran the whole file at `5a5ac5fb1`, and all 31 tests passed, including `test_abolition_invariants`, which now checks invariant 12, and `test_awards_match_an_independent_reading`.
- `test_uc_legacy_closure_properties.py`: `test_the_closure_flag_only_decides_closure_claims` passed (357 s) and `test_a_closure_claim_pays_the_universal_credit_of_any_claim` passed (617 s), each at full size with a 3,400 s cap.
- `test_disable_simulated_benefits.py`: CI ran the file at `5a5ac5fb1`, and all 89 tests passed.
- Synthetic probes of `holds_formula_award` at `5a5ac5fb1`: 9 of 11 passed. The 2 failures reproduce the limits in the `own_report_is_paid` bullet above: a stored award that leaves an outside member nothing, and a reform that scales `esa_income` with a pension-age outside member.

Fails on `origin/main` (2da212abc): with the reproducer's inputs (a single claimant aged 40 in London reporting £5,000), `main` pays £5,000 of income-related ESA in 2027-28 against £0 here, and £5,000 in 2026-27 against £1,178.08 without a claim.

Each mutant ran in a scratch copy against `esa_income_abolition.yaml` unless the row names another test. The first row ran at `a3f8b1104`. The rest ran on the branch's earlier code, up to `68ccc0856`, and the later commits leave their mutated lines unchanged. The `benunit.sum` row, the row for a saving for a member who reports nothing and the last two rows ran at `5a5ac5fb1`.

| Mutant | Failing cases |
|---|---|
| `holds_formula_award` drops the claimant's and partner's comparison (`test_esa_income_held_outside_award.py`) | 3 of 3: the £5,200 unit in the frozen, stored and replaced modes |
| The claim term leaves `claims_uc_at_legacy_closure` | 5: the GB and NI 2026-27 claims, a couple with a saved partner who reports nothing, and both mixed-age couple cases |
| `legacy_award_is_closed` ignores income-related ESA | 5: the GB and NI 2027-28 ends, the outside adult's 2027-28 end, the end of Housing Benefit without a claim, and the 2027-28 claim |
| The claim term ignores the saving | 5: the saved awards in Great Britain (2027-28) and Northern Ireland (2026-27), both saved-couple cases, and the saved award's 2026-27 claim |
| The closure ignores the saving | 3: the saved single and couple awards in 2027-28 |
| Each member's own saving, not the claim's | 2: both saved-couple cases in 2027-28 |
| `benunit.sum` for the couple's share | 4: both couple cases before and after the abolition, the saved partner who reports nothing, and the saved couple in 2027-28 |
| A saving for a member who reports nothing saves the claim | 2: both cases with a saved partner who reports nothing |
| The pension-age route ignores the abolition | 1: the protected mixed-age couple |
| The claim term drops the Pension Credit age guard | 1: the single claimant over the qualifying age |
| The reform stops holding the claim term (`test_disable_simulated_benefits.py -k no_family_is_moved`) | 2: 2026-27 and 2027-28, where the reform would prompt the ESA claimant's claim |
| `continuing_award` drops the accommodation term (with `housing_benefit_eligible.yaml`) | 2: the Great Britain 2027-28 move off legacy benefits and the tax credit closure, each with the status |
| `own_report_is_paid` treats every held award as the formula's (`test_esa_income_held_outside_award.py`) | 3 of 3: the frozen, stored and replaced modes |

### Existing tests changed

No expected value in a test on `main` changes. At `7fe7360fa` one case this branch added changes, as the list above records, and comments in `benefit_cap_by_scheme.yaml`, `housing_benefit_universal_credit_passport.yaml`, `test_benefit_cap_uc_and_hb_properties.py` and `test_housing_benefit_universal_credit_passport_properties.py` no longer say the model lacks the route; those cases still supply both awards so that they test the cap or the passport alone. Two property-test reference calculations now include Universal Credit claims prompted by the ESA abolition. Cases about other rules opt out of that claim.

- `test_uc_legacy_closure_properties.py`: `expected_closure` closes an unsaved income-related ESA report from 2027-28 under the Pension Credit conditions; a new `expected_reached` gives the abolition's reach from 2026-27; a family claims on either; and a claiming family gets no income-related ESA. The flag property and the closure-claim differential treat a reached family as moved.
- `test_legacy_award_work_properties.py` and `legacy_award_work_reference.py`: in 2026-27 the couple's ESA reports close when the abolition reaches them, because the family claims with `would_claim_uc_at_legacy_closure` at its default.
- `test_disable_simulated_benefits.py`: new assertions that the abolition reaches the claimant's own award from 2026-27 without the reform, and that the reform prompts no claim.

Cases about other rules set `would_claim_uc_at_legacy_closure` false, so the family keeps the award they test:

- `esa_income_remunerative_work.yaml`, the two 2026-27 limit cases: they test the permitted-work limit, and a claim would close the report.
- `pension_credit/mixed_age_couples.yaml`, "HB alongside income-related ESA is working-age HB, so no saving": it tests the saving inference and the continuing Housing Benefit award, which a claim would end.
- `test_mixed_age_couple_properties.py`, every generated family: its reference formulas from before the mixed-age couple rules read `would_claim_uc` alone.

`universal_credit/person_legacy_benefits_closed.yaml` Case 1 gives the claimant `esa_income_appointee_saving` instead: it tests that an outside member's closed report leaves the couple open, and from 2027-28 the claimant's own unsaved report would close the couple.

Thirteen existing YAML cases and four Python files test another rule on an income-related ESA award in 2026-27 or 2027-28, so each ESA reporter there gets `esa_income_appointee_saving: true`, which keeps the award payable for the whole year (inputs only):

- `esa_income/esa_income_capital.yaml`: "has no tariff income at the lower capital threshold", "tariff income rounds incomplete capital steps up" and "remains eligible at the upper capital limit" (2026);
- `esa_income/esa_income_remunerative_work.yaml`: "Issue 2395 outside closed IS report leaves the couple ESA award open" (2027);
- `legacy_award_readers/council_tax_reduction_passports.yaml`: the five non-dependant cases (2027);
- `legacy_award_readers/housing_benefit_pension_age_regulations.yaml`: the two ESA cases (2026);
- `legacy_award_readers/pension_credit_earnings_disregard.yaml`: "Another member's income-related ESA leaves a single pensioner with the £5 earnings disregard" and "The partner's income-related ESA gives the couple the £20 earnings disregard" (2026);
- `test_esa_permitted_work_nlw_reforms.py` (2026), the two 2027 Merton non-dependant tests in `test_legacy_award_readers_properties.py`, the 2027 cases of `test_legacy_closure_scope.py`, and every award in `test_esa_income_universal_credit_claim_properties.py`, whose 2027 cases test the Universal Credit claim under art. 3A(3).

### Overlap

- `git merge-tree --write-tree --name-only` at `dc69f3ada` merges cleanly with `main` at `52b30809f`.
- #2807 (`fix-2565-esa-pension-credit`, head `254669a0d`) already merges `7fe7360fa`, and `dc69f3ada` merges cleanly with it. On the merge with `3c9890a19`, `finance/benefit/family/esa_income` (131), `finance/benefit/family/housing_benefit` (596) and `gov/dwp/pension_credit` (320) passed 1,047 of 1,047. #2807's head already gives the ESA reporters of Cases 4, 5, 6, 12, 13 and 14 of `esa_income_pension_age.yaml` `esa_income_appointee_saving: true`, because each asserts a full-year income-related ESA award in 2026-27.
- #1910 (`abolish-working-age-housing-benefit`, head `2d0e49417`) conflicts with `main` at `f4a317019` in `CHANGELOG.md`, `pyproject.toml`, `housing_benefit_eligible.py` and `housing_benefit/takeup.yaml`. With `dc69f3ada` it conflicts in those four files and in `claims_uc_at_legacy_closure.py`, `mixed_age_couples.yaml` and `uc_housing_costs_element.py`. #1910 adds the same kind of term for Housing Benefit to `claims_uc_at_legacy_closure`, and the same flag line to the `mixed_age_couples.yaml` case; a merge ORs both abolition terms and keeps one copy of the flag line. The conflicts in `housing_benefit_eligible.py` and `uc_housing_costs_element.py` come from #1910 making the same two routing changes as this branch. In `uc_housing_costs_element.py` both return nil for `in_specified_or_temporary_accommodation`, and a merge keeps either text. In `housing_benefit_eligible.py` both OR the input into the routes before the tenure and capital tests and drop it from the continuing award; a merge keeps #1910's continuing award, which adds `housing_benefit_payable_share`, and one accommodation term. Under SI 2025/1148 art. 7(4)(b) and SR 2025/176 art. 7(4)(b), an appointee-saved income-related ESA claimant keeps Housing Benefit, and `housing_benefit_payable_share` at #1910's head has no input for that saving. Whichever PR lands second makes it read `esa_income_appointee_saving`, or the merged model ends that Housing Benefit on the appointed day. #1910 doesn't change `would_claim_housing_benefit`, so a merge keeps the hand-entered take-up rule.
- #2688, #2670, #2302, #2301 and #2300 conflict with this branch only where they already conflict with `main`. #2279, #1969, #2007 and #2756 merge cleanly.

- [x] For policy changes: YAML tests whose expected values come from an external source: `esa_income_abolition.yaml` works each amount by hand from art. 3A's dates and ESA Regs 2008 reg 118, and the Housing Benefit cases from SI 2014/1230 regs 6A(2), 8(3) and 46(2), SR 2016/226 reg 6(3), the Universal Credit rates and caps in the parameter files, and the means test with no income.
- [x] `make format` equivalent on the changed Python files: `ruff format --check`, `ruff check`, `ruff check --select F811,F821 policyengine_uk`, `git diff --check` (ruff 0.17.2).
- [x] Changelog fragment `changelog.d/2562.fixed.md`.

## Reproducer

Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/esa_income/esa_income_abolition.yaml::Great Britain 2027-28 pays no income-related ESA

axiom: TheAxiomFoundation/rulespec-uk#518 queued

🤖 Generated with [Claude Code](https://claude.com/claude-code)







Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Head: dc69f3ada6b6bee045ce4db88406311ba5701fcd on branch claude/takeup-specified-accommodation-je0qk0. It's one commit on top of 3c9890a19abb714575050f8d8699ed9968a577e3. I didn't push to fix-2562-esa-abolition, and I opened no PR, so no CI ran.

Findings and how I resolved them

Take-up (juaristi22's A1; this replaces the earlier review's P1 item 1 fix).

  • policyengine_uk/variables/gov/dwp/housing_benefit/would_claim_housing_benefit.py:24-30: the formula is back to 7fe7360fa's. A data-built simulation returns reported_hb. A hand-entered one returns claims_all_entitled_benefits | reported_hb. I kept 3c9890a19's comment rewrite and dropped its accommodation clause.
  • The documentation (:9-21) now says:
    • the status doesn't change take-up;
    • Universal Credit pays nothing towards that rent;
    • a hand-entered family that reports another of the seven benefits and no Housing Benefit gets no help with the rent;
    • setting housing_benefit_reported or would_claim_housing_benefit models the claim.
  • in_specified_or_temporary_accommodation.py:26-32: I replaced the sentence with the text the brief gives.
  • changelog.d/2562.fixed.md: I removed the take-up clause.

Cases. I worked the arithmetic from the parameter files, and it's in each case's comment.

  • esa_income_abolition.yaml:779 (the London tenant, convention): the case is renamed to "…without a Housing Benefit report claims only the benefits it reports and gets no help with the rent". It gives no Housing Benefit, element nil, Universal Credit £5,098.80 and net income £3,518.80.
  • esa_income_abolition.yaml (the next case, new): "…the same London tenant gets Housing Benefit for the rent when the user sets would_claim_housing_benefit" gives Housing Benefit £30,000, Universal Credit £5,098.80 and net income £33,518.80.
  • housing_benefit_eligible.yaml:221 (convention): "…reports only Universal Credit claims no Housing Benefit and gets no help with the rent" gives no Housing Benefit, element nil and net income £3,518.80.
  • housing_benefit_eligible.yaml (the next case, new): with the override, net income is £13,518.80, which matches the control. I added a note on the control's comment that it matches.
  • would_claim_housing_benefit.yaml:37-58: "The accommodation status doesn't make a family claim without a report" gives false. The new "A family with the accommodation status claims when it claims all entitled benefits" gives true. "Ineligible with the accommodation status set false" stays as it was.

Properties (test_housing_benefit_accommodation_dates_properties.py). The docstring describes R5 to R7.

  • accommodation_simulation now accepts status=None, which leaves the input unset, and claims_all: None, which leaves claims_all_entitled_benefits to its formula.
  • R5 (test_r5_the_status_never_changes_take_up): with the input true, false or unset, take-up equals claims_all_entitled_benefits | reported.
  • R6: now sets would_claim_housing_benefit: true. I kept the 50p note. It still holds: housing_benefit_benefit_cap_reduction still caps at entitlement - minimum_weekly_amount.
  • R7 (new): each family has no Housing Benefit report, a report of income-related ESA or Working Tax Credit (I force a £5,000 ESA report where neither was drawn), and both take-up flags left to their formulas. The test checks that take-up is false, Housing Benefit is nil and the element is nil.

Data-built test (test_housing_benefit_accommodation_take_up_data_built.py):

  • The data-built checks stay as they were.
  • New: a data-built family with would_claim_housing_benefit in the dataset gets the whole rent (Simulation and Microsimulation).
  • The hand-entered check now expects no claim, no Housing Benefit and element nil.
  • New: the hand-entered override gets the whole rent.

Body. I changed these parts:

  • the routing bullet in "What and why";
  • the take-up sentence in "What else changes";
  • the last bullet of "What the model doesn't do";
  • R4 (a third population and a fourth bullet), R5, R6 and R7 (new);
  • a new "Local runs at dc69f3ada" section with the mutation table;
  • the 3c9890a19 section, now cut down to the runs I didn't repeat;
  • Overlap.

The title is unchanged.

Tests at dc69f3ada

No survey data; uv sync --frozen --extra dev.

YAML:

Target Passed
housing_benefit_eligible.yaml 15
would_claim_housing_benefit.yaml 6
esa_income_abolition.yaml 41
finance/benefit/family/housing_benefit 598
finance/benefit/family/esa_income 107
finance/benefit/family/universal_credit 569
gov/dwp 804

None failed.

Python, -n 4 each:

File Passed
test_housing_benefit_accommodation_dates_properties.py 12
test_housing_benefit_accommodation_take_up_data_built.py 8
test_benefit_cap_uc_and_hb_properties.py 1
test_housing_benefit_universal_credit_passport_properties.py 6
policyengine_uk/tests/code_health 3,091

None failed. No renamed test sits in shard_durations.toml.

Other checks:

  • .github/check_fix_pr.py on the final title and body, with changes from the merge base with main, run as pr_fix_evidence.yaml runs it: PASS, 29 changed executable tests.
  • uvx ruff@0.17.2 format --check .: 2,170 files already formatted.
  • ruff check . and --select F811,F821 policyengine_uk: pass.
  • git diff --check 3c9890a19..HEAD: clean.

Merges (git merge-tree --write-tree --name-only):

Mutations

Each mutant ran in its own scratch worktree at dc69f3ada, with PYTHONPATH set to that worktree. Each ran against the three YAML files, R1 to R3, R5 to R7 and the data-built file. All 7 were killed.

Mutant YAML failures Python failures
Accommodation term put back into would_claim_housing_benefit 3 (one per file) R5, R7, hand-entered no-claim
M5: route skips capital 1 (£20,000 capital) R2
M6: route skips tenure 1 (owner occupier) R2
Element ignores the input 11 R1, R7, both data-built no-report, hand-entered no-claim
reg 8(3) route dropped 4 R2, both data-built with a report
reg 46(2) route dropped 2 R2
New-claim route dropped 6 R2, both data-built no-report, both data-built override, both hand-entered

handoff-690.md's "status moved above the data-built branch" mutant no longer applies, because the formula has no status term, so I didn't run it. R6 doesn't kill the take-up mutant, as expected: it sets take-up as an input.

Differential

I didn't have handoff-690.md's diff_run.py. I built a population to the body's R4 description and ran it on worktrees at 5a5ac5fb1, 7fe7360fa, 3c9890a19 and dc69f3ada:

  • 600 families a year in 2025, 2026 and 2027;
  • one or two adults aged 20 to 75, 0 to 4 children;
  • six regions including Northern Ireland, five tenures;
  • rent of £0 to £30,000, savings of £0 to £17,000, earnings, LCWRA and appointee savings;
  • reports of the six legacy benefits plus Universal Credit and Pension Credit;
  • random claim flags, with claims_all_entitled_benefits set for half the families and left to the formula for the rest;
  • the same 25 outputs.

The body calls it the third population.

  • Input unset, false or true against 7fe7360fa: 58,419 values in each mode across the three years, 0 differ.
  • Input true plus would_claim_housing_benefit true at head, against 3c9890a19 with the input true: 58,419 values, 0 differ.
  • Net income with the input true, against 5a5ac5fb1:
Run at head 2025 2026 2027
Convention: falls 69 57 58
Convention: rises 32 43 42
Override: falls 9 10 9
Override: rises 147 129 135

The convention run matches 7fe7360fa exactly, and the override run matches 3c9890a19 exactly.

  • Why the convention falls happen: 65, 55 and 52 of them don't take Housing Benefit up and get neither Housing Benefit nor an element.
  • Why the override falls happen (my own classification):
    • Housing Benefit's taper without a Universal Credit passport: 3, 5 and 1;
    • Housing Benefit beside Universal Credit a little under the element, for example £12,864.80 against £12,900: 3, 5 and 6;
    • the Housing Benefit cap without Universal Credit: 0, 0 and 1;
    • the means test taking Housing Benefit to nil: 3, 0 and 1.
  • In both runs, council_tax_reduction rises for 1, 2 and 0 families, and housing_benefit_on_passporting_benefit turns false for 7, 5 and 4.

Judgment calls

  • The differential population is my rebuild, not handoff-690.md's. Its totals (58,419) differ from the 58,740 in the body's second population, so I kept both in R4 and labelled each by commit.
  • I replaced the body's "Local runs at 3c9890a19" section with the head section. I kept a short 3c9890a19 paragraph for the runs I didn't repeat: work_allowance, housing_costs_element, the passport YAML, benefit_cap_by_scheme.yaml, and the Bar income-related ESA at pensionable age and on Pension Credit #2807 merge's 1,047.
  • housing_benefit_eligible.yaml's "The same family that doesn't take Housing Benefit up gets no help with the rent" still sets would_claim_housing_benefit: false, as 3c9890a19 left it. Its outputs are the same either way.
  • R7 forces an income-related ESA report where the draw gave neither ESA nor Working Tax Credit, so every family reports one of the seven benefits.

Skipped


Generated by Claude Code

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the newly adopted head dc69f3ada6b6bee045ce4db88406311ba5701fcd. Review COMPLETE: C1, C2, A1 and A2 are resolved, with no open findings or material evidence gaps.

The actual description and accommodation input now explain the report-driven HB take-up convention, the no-HB-report default and how to model an explicit HB claim. The protected reported-HB award still reaches final HB, UC and household net income correctly. The current rulespec-uk#518 handoff remains consistent.

All 69 focused current-head cases passed. Unchanged validated behavior from the preceding review was retained without repeating its full suite. Exact-head hosted CI is still running, so approval is deferred until the required checks pass.

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at exact head dc69f3ada6b6bee045ce4db88406311ba5701fcd. The required checks now pass at this commit. The completed independent code/source review is retained, all prior findings remain resolved, and no material evidence gaps remain. No local test rerun was needed for this CI-only follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Abolish income-related ESA from 1 July 2026 (NI 1 October 2026)

3 participants