Repository navigation
Conversation
Apply the Schedule 5 WTC disregard separately from earnings disregards and reserve it before the childcare deduction. Add statutory examples, properties, differential checks and historical cutoff coverage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The WTC disregard tested the full capped childcare charge, while the additional earnings disregard reads the childcare deduction. A supplied deduction, a supplied limit or a reform that replaces either could then give a benefit unit both disregards. The WTC disregard now reads that deduction whenever it differs from the model's own calculation, and keeps the cycle-free full-charge test otherwise. Tests add the accommodation disregard and supplied deductions to the oracle, which now settles paragraph 17 by trying both outcomes. They add a property that the two disregards never both apply and a differential test that supplying the calculated deduction changes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The WTC disregard compares the same net earnings and accommodation amount as the additional earnings disregard. A cap on the accommodation amount would not change the test, since earnings already fall short where it binds. New tests cover a deduction with no formula and a negative WTC award. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2234 settles Schedule 4 paragraph 17 separately before and after the accommodation disregard starts on 5 October 2026, but its segment income left out the WTC disregard. Paragraph 56 follows paragraph 17's outcome, so each segment now has its own WTC disregard: the childcare limit takes it, hb_segment_deductions returns it, and entitlement deducts it. The annual variable combines the segments by days. The WTC and earnings decisions now share one paragraph 17 test and one check for a supplied or replaced childcare deduction, #2234's hb_uses_annual_override. Under a reform that keeps tax credits in 2026-27, a claimant in specified accommodation with £500 of earnings and £2,000 of WTC now gets £9,600.19 rather than £9,022.21. Current-law awards do not change: tax credits end before the accommodation disregard starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The WTC disregard is exactly nil where paragraph 56 does not apply, so the checks now count any positive amount rather than one above 2p. A pinned example gives earnings of £1,149.20, which cover the £260 standard disregard, a supplied nil deduction and the £889.20 additional amount exactly, with a 1p childcare charge and 1p of WTC. 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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Float32 stores £1,149.20 as £1,149.19995, so a plain float comparison would find that earnings fall short of the £260 standard disregard plus £889.20 and grant the WTC disregard instead of the additional earnings disregard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The populations now supply the earnings and tax-credit limit as well as the deduction, and the statutory oracle caps the deduction at the charge under a supplied limit. A pinned case gives a £10,000 limit against a £4,160 charge: the deduction stays at £4,160, the additional earnings disregard applies, and paragraph 56 does not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After 5 October a lone parent with £8,000 of earnings, £3,000 of other income, £2,000 of WTC and a £4,160 charge has paragraph 56 disregard £889.20, so childcare can draw on only £3,768.84. The award comes to £7,266.74; a limit read with the day-weighted annual WTC disregard would give £7,394.21. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The earnings and tax-credit limit now keeps back the WTC that Sch 5 para 56 disregards by testing Sch 4 para 17 with the whole capped childcare charge, so it reads neither the childcare deduction nor the housing_benefit_wtc_disregard formula. The WTC disregard and the earnings disregard then test para 17 with the deduction in force in each segment. For the model's own deduction the two tests agree, so the limit, the deduction and the disregard keep their values, and the direction runs one way: limit, deduction, WTC disregard. A reform that replaces the deduction, its limit or the WTC disregard with a formula that forwards to the model's own no longer meets a circular definition, and a replacement deduction can read the limit. A WTC disregard that an input supplies, or that a reform neutralizes, still reaches the limit and every segment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A supplied childcare deduction keeps its value above the charge and the limit, the annual additional earnings and WTC disregards can both be positive though no segment has both, and supplying the calculated annual deduction in a segmented year applies it in every segment. The test modules now build their forwarding reforms on demand, so they import where housing_benefit_wtc_disregard does not exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a year with two accommodation segments, the annual earnings and tax-credit limit keeps back the day-weighted WTC disregard, as each segment's limit keeps back its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The limit read a WTC disregard with no formula as a supplied amount, but core still computes adds and subtracts there, and calculates a defined_for mask before anything else, so an aggregate identity replacement cycled. The limit now reads the WTC disregard only where reading it reads no other variable: a neutralized variable, one with no formula, aggregate or mask (which core uprates, carries over or defaults from its own inputs), or a supplied input. hb_uses_annual_override still treats any variable without a formula as an annual replacement in every segment. Tests cover formula, adds and subtracts replacements, each as a list or a parameter path, in 2024 and in 2026-27 with tax credits kept, and an uprated input replacement with and without a mask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each reform system holds about 38 MB, and caching all eleven kept the main test module above the 2 GB cap the mutation runs use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each computed replacement now runs one simulation against outputs the model computes once for 2024 and once for 2026-27. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in Notes
Moves flagged for reviewNone. Spending by benefit and service
Caseloads
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileNo household's net income changes in any year. No change in any year: receipts by tax head; government balance, each component counted once; model totals; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles. Checks failing on both base and head (1)
Counted-once checkEach 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.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
Fixes #2730
What and why
The model now disregards up to £17.10 a week of Working Tax Credit (WTC) in Housing Benefit before it deducts childcare. Under Schedule 5 paragraph 56 of the Housing Benefit Regulations 2006, the authority administering Housing Benefit disregards that amount for a family that meets a paragraph 17(2) work condition when its earnings can't fund the additional earnings disregard. The new
housing_benefit_wtc_disregardtakes the lesser of WTC and £889.20 a year (£1,929.20 in 2020-21).housing_benefit_childcare_earnings_limitlets childcare draw only on the WTC left after it, andhousing_benefit_applicable_incomededucts it alongside the earnings disregard. The existinggov.dwp.tax_credits.activeparameter switches it off from model year 2025, when tax credits end.In the issue's example, a lone parent earns £5,200 a year, has a £1,300 standard disregard, pays £4,160 for childcare and gets £100 of WTC. The model now disregards the £100 and deducts £3,900 for childcare, where main deducts £4,000.
The model also settles the WTC disregard in each accommodation segment of the calculation that #2234 added to
housing_benefit_entitlement.The regulations
Under paragraph 56, the authority disregards up to £17.10 of WTC, "any amount of working tax credit up to £17.10", except "in a case which falls under sub-paragraph (1) of paragraph 17 of Schedule 4". Under paragraph 17(1), it raises the earnings disregard by £17.10 where "his net earnings equal or exceed the total" of the other earnings disregards, "the amount of child care charges calculated as deductible under regulation 27(1)(c)" and £17.10. Under regulation 40(2), it leaves out of other income "any sum, where applicable, specified in Schedule 5". Under regulation 27(1)(c), it then deducts childcare from earnings and, where earnings fall short and the family gets a tax credit, from "those earnings plus whichever credit" applies. In its BW2 guidance, DWP tells authorities to make that deduction "as the final step in the earnings calculation" (W2.131), to offset the rest against WTC or CTC "in addition to the existing disregards" (W2.181), and never to offset it against other income (W2.182).
The authority follows the same order under the pension-age and Northern Ireland regulations, and the model applies one calculation to all four:
Under SI 2020/371 reg 5 and SR 2020/53 reg 5, which the Secretary of State made, the authority read £37.10 for £17.10 in both disregards, in all four sets of regulations, from 6 April 2020 to the end of 4 April 2021. That period spans the 52 weeks from Monday 6 April 2020 to Sunday 4 April 2021. The model's fiscal year 2020 runs to 5 April 2021, and the model takes the £1,929.20 for those 52 weeks from the dated worker-disregard parameter it already has.
Where a claimant gets only the savings credit of Pension Credit, the authority may modify the Secretary of State's income figure under SI 2006/214 reg 27(4) "only in so far as necessary to take into account" the items in its list. The list includes "childcare charges taken into account under regulation 30(1)(c)" and the Schedule 4 paragraph 9(1) additional earnings disregard, but not the Schedule 5 paragraph 21 WTC disregard, and SR 2006/406 reg 25(4) has the same list. So on that route the model corrects the childcare charge for the disregarded WTC and deducts no WTC disregard from income.
Limit, deduction, WTC disregard
The paragraph 17 total includes the childcare deduction, and under regulation 27(2) that deduction draws on the WTC left after the paragraph 56 disregard, so each decision needs the other. The model settles them in one direction:
housing_benefit_childcare_earnings_limitkeeps back the WTC that the authority would disregard under paragraph 56 if childcare took the whole capped charge. It reads neither the deduction nor thehousing_benefit_wtc_disregardformula.housing_benefit_applicable_income_childcare_elementtakes the lesser of the capped charge and that limit.housing_benefit_wtc_disregardand the additional earnings disregard test paragraph 17 with the deduction in force in each segment.For the deduction the model calculates, the WTC the limit keeps back equals the WTC disregard. Write E for the earnings the standard and accommodation disregards leave, C for the capped charge, a for the additional amount, R for the lesser of WTC and a, and T for CTC. If E ≥ C + a, earnings reach the paragraph 17(1) total with the whole charge and with any smaller deduction, and neither test disregards WTC. Otherwise the limit keeps back R, and the deduction comes to D = min(C, E + (WTC − R) + T). Either D = C, or D < C and D ≥ E; either way E < D + a, so earnings fall short of the paragraph 17(1) total and the model disregards R. Keeping back no WTC would only enlarge the deduction, and earnings would still fall short, so R gives the only consistent answer.
test_limit_keeps_back_the_wtc_disregard_in_forcechecks the identity for every generated family, and the segment property checks it within each segment.A reform can replace the deduction with a formula that reads the limit. With half the capped charge, capped at the limit, the issue's family gets a £2,080 deduction and the additional earnings disregard, and income of £1,030.80. Where the limit binds, the WTC it keeps back stays consistent: with £2,000 of earnings, a £260 standard disregard, a £9,100 charge and £1,000 of WTC, the deduction comes to £1,850.80 and the model disregards £889.20 of WTC.
Supplied and replaced values
set_inputor a scenario supplies, and a disregard that a reform neutralizes. It also covers one that a reform leaves without a formula, anaddsorsubtractsaggregate or adefined_formask, which the engine (policyengine-core) uprates or carries over from its own earlier inputs. The model applies each of these to income, to the limit and in every segment, and neutralizinghousing_benefit_wtc_disregardgives main's calculation back. The engine computes any other replacement through a formula, an aggregate (addsorsubtracts, each a list or the path of a parameter that lists variables) or a mask it calculates first, and any of these could read the deduction. The model applies such a replacement to income and, as an annual amount, in every segment, but the limit keeps back what the whole-charge test gives, so a forwarding replacement leaves the limit's figure unchanged. When a reform only adds adefined_formask and keeps the model's formula, PolicyEngine masks the annual output but not the 2026-27 segments, or the award of a claimant outside the pension-age Regulations and the passporting benefits, as onmain: Apply eligibility-only Housing Benefit reforms to each 2026-27 period #2777 tracks that gap.set_inputor a reform supplies or replaces, or the lesser of the capped charge and a supplied or replaced limit, in every segment, and reads it in both paragraph 17 tests. Where the deduction itself comes from a dataset,set_inputor a reform, only a replacement formula that calls the limit reads it. The limit still keeps back the WTC that the whole-charge test gives, so it can differ from main: with the issue's family and a supplied nil deduction, it comes to £3,900 where main gives £4,000, while the model disregards no WTC.housing_benefit_applicable_income_disregardstays as supplied, and the model still makes the WTC decision's own paragraph 17 test from the earnings inputs, because a supplied total doesn't show whether it includes the additional amount.Accommodation segments
From 5 October 2026, the paragraph 17(3)(a) total also includes the paragraph 18 specified or temporary accommodation disregard. The Secretary of State made that change by SI 2026/753 reg 2(4), and replaced that regulation by SI 2026/978 reg 2(2) before it came into force on the date in reg 1. The Department for Communities made the same change in Northern Ireland by SR 2026/157 reg 2(4)(a). Since #2234, the model settles paragraph 17 separately before and after that date and combines the awards by days. The authority's paragraph 56 decision depends on the paragraph 17 outcome, so the model now gives each segment its own WTC disregard and its own limit.
hb_segment_deductionsreturns the segment's earnings disregard, WTC disregard and childcare deduction,housing_benefit_entitlementdeducts all three in each segment, and the annualhousing_benefit_wtc_disregardcombines the segments' amounts by days.Tax credits ended on 5 April 2025, before the accommodation disregard starts, so no current-law award changes. Take a reform that sets
gov.dwp.tax_credits.activeto true in 2026, and a single claimant aged 40 in specified accommodation. The claimant earns £500, has a £260 standard disregard, £2,000 of WTC and no childcare or other income, pays rent of £10,400 and has a nil applicable amount. Main gives £9,022.21, and the model now gives £9,600.19:The model needs a WTC disregard in each segment. With £2,000 of earnings, the same claimant's earnings cover £260 plus £889.20 before 5 October, so the model gives the additional earnings disregard and no WTC disregard. After 5 October the £1,740 accommodation amount leaves earnings short, and the model disregards £889.20 of WTC. The award comes to £9,114.03. A WTC disregard settled once from the annual accommodation amount would give both disregards before 5 October, and an award of £9,402.23. With £4,000 of earnings it would give neither disregard after 5 October, and £8,176.03 where the model gives £8,465.81.
Deducting the day-weighted annual amount in every segment matches the per-segment award while both segments stay on the taper. Once income after 5 October falls below a £2,000 applicable amount, it gives £10,268.74 instead of £10,124.25. Reading the childcare limit with that annual amount gives £7,394.22 instead of £7,266.74 for a lone parent whose limit binds after 5 October.
Invariants
For every generated population, with assessed earnings, disregards, childcare, credits and work conditions as inputs:
set_inputsupplies the deduction or its limit, and example tests cover a neutralized deduction, one with no formula and replacement formulas, one of which reads the limit.set_inputsupplies the deduction. Under current law that covers every family.addsorsubtracts, each listing the forwarding variable or naming a parameter that lists it. It holds in 2024 for families with and without WTC, and in 2026-27 with tax credits kept for families outside specified or temporary accommodation. On the example populations, every 2024 output stays identical and every 2026-27 output agrees within £0.001, the float32 rounding of the day weights. The computed replacements of the WTC disregard leave all six outputs of the eight test families unchanged in both years, and the generated populations allow 2p. In specified accommodation the model applies the replacement's annual amount in every segment, as for any replacement: a forwarding replacement of the WTC disregard, through a formula or an aggregate, gives £10,268.74 where the model gives £10,124.25.hb_uses_annual_override) gives main's answer for all 1,200 combinations of the states the engine reads: neutralization, a default or replacement formula,addsandsubtracts(none, empty, a list or a parameter path), adefined_formask, uprating and a supplied input.Test plan
housing_benefit_wtc_disregard.yamland the Python examples take their expected values from the regulations, and their comments work through the arithmetic by hand.make format: Ruff 0.17.2 formats and checks every changed Python file,ruff check --select F811,F821 policyengine_ukpasses, andgit diff --checkpasses.changelog.d/2730.fixed.mdThe tests build their own households and never load survey data. Every count below comes from a run at
6dc47664d, unless the row names another commit; CI runs the rest of the suite.test_housing_benefit_wtc_disregard.py: examples, Hypothesis properties, differential tests and replacement teststest_housing_benefit_wtc_disregard_segments.py: segment examples and propertieshousing_benefit_wtc_disregard.yamltest_housing_benefit_accommodation_dates_properties.pytest_housing_benefit_childcare_properties.py, which reads the limit3b53381c9, whose model code matches6dc47664dtest_housing_benefit_accommodation_dates.py, with a 3,600 s cap2da212abc)The differential ran main (
2da212abc) and this branch atfdc13f128over the same 15,552 family-years: 1,944 families in each of 2018, 2020, 2021, 2024, 2025, 2026 and 2027, and in 2026 with tax credits kept. Sincefdc13f128the model code changes only which replacements of the WTC disregard the limit reads, and none of these populations replaces it. Of each 1,944, 1,296 families have a calculated deduction, 324 a supplied nil deduction and 324 a supplied £1,000 limit. Earnings, WTC, childcare, the standard disregard, the work condition, pension age and specified accommodation vary; other income comes to £1,000, rent to £10,400 and the applicable amount to £2,000, and regions alternate between London and Northern Ireland. It compares the childcare deduction, earnings disregard, childcare limit, applicable income and award.None of the 5,832 family-years in 2025, 2026 and 2027 under current law changes.
Of 44 mutations across the changed model files, 42 make at least one test fail. The other two add a floor or a cap to the paragraph 17 test, which can't change its answer while the additional amount stays positive. Each mutation runs the main module, the segment module and the WTC YAML file in full, with Hypothesis shrinking off. The 15 that touch the override helpers or their call sites ran in full at
6dc47664d. The other 29 ran atfdc13f128, or for the main module and YAML file at9c2adcde9, whose model code and tests matchfdc13f128. Since then the model code changes only which replacements of the WTC disregard the limit reads, and the test modules keep every earlier test.Mutation results
housing_benefit_childcare_earnings_limit.pyfdc13f128)housing_benefit_childcare_earnings_limit.py>=becomes>in the paragraph 17 testfdc13f128)housing_benefit_childcare_earnings_limit.pyfdc13f128)housing_benefit_childcare_earnings_limit.pyfdc13f128)housing_benefit_childcare_earnings_limit.pyfdc13f128)housing_benefit_childcare_earnings_limit.pyfdc13f128)housing_benefit_childcare_earnings_limit.pyfdc13f128)housing_benefit_childcare_earnings_limit.pyfdc13f128)housing_benefit_childcare_earnings_limit.pyfdc13f128)housing_benefit_childcare_earnings_limit.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_childcare_earnings_limit.pyfdc13f128)housing_benefit_childcare_earnings_limit.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_childcare_earnings_limit.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_childcare_earnings_limit.py6dc47664d)housing_benefit_childcare_earnings_limit.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_childcare_earnings_limit.py6dc47664d)housing_benefit_childcare_earnings_limit.py6dc47664d)housing_benefit_childcare_earnings_limit.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_childcare_earnings_limit.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_childcare_earnings_limit.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_wtc_disregard.py6dc47664d)housing_benefit_wtc_disregard.py6dc47664d)housing_benefit_wtc_disregard.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_wtc_disregard.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_wtc_disregard.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_wtc_disregard.py6dc47664d)housing_benefit_wtc_disregard.py6dc47664d)housing_benefit_wtc_disregard.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_wtc_disregard.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_wtc_disregard.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_applicable_income_disregard.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_applicable_income_disregard.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_applicable_income_disregard.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_applicable_income.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_entitlement.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_entitlement.pyfdc13f128; main module and YAML at9c2adcde9)housing_benefit_specified_or_temporary_accommodation_disregard.py6dc47664d)housing_benefit_specified_or_temporary_accommodation_disregard.py6dc47664d)housing_benefit_specified_or_temporary_accommodation_disregard.py6dc47664d)housing_benefit_specified_or_temporary_accommodation_disregard.py6dc47664d)housing_benefit_specified_or_temporary_accommodation_disregard.py6dc47664d)housing_benefit_specified_or_temporary_accommodation_disregard.py6dc47664d)housing_benefit_specified_or_temporary_accommodation_disregard.py6dc47664d)housing_benefit_specified_or_temporary_accommodation_disregard.py6dc47664d)No test on main changes its expected value. #2719, #2700, #2727, #2731, #2735 and #2234 have merged, and this branch includes them. Of the open PRs, #1995, #2301 and #2302 also edit
housing_benefit_applicable_income.py, and #1969, #2265, #2308 and #2661 editpolicyengine_uk/utils/supplied_inputs.py; Git merges this branch with each of them without adding a conflict. No open PR edits the override helpers' file, the limit's file or the two test modules.Reproducer
Reproducer: policyengine_uk/tests/test_housing_benefit_wtc_disregard.py::test_issue_2730_reserves_wtc_before_childcare, policyengine_uk/tests/test_housing_benefit_wtc_disregard_segments.py::test_wtc_disregard_applies_before_and_after_5_october
axiom: TheAxiomFoundation/rulespec-uk#510 queued
🤖 Generated with Claude Code