Cap the eligible rent at the LHA before the Housing Benefit taper - #1926
Conversation
housing_benefit_entitlement took the taper off the rent, capped the result at the Local Housing Allowance, then subtracted non-dependant deductions: max(0, min(max(0, rent - taper), LHA_cap) - deductions). Whenever the rent exceeded the LHA by more than the taper, the cap swallowed the taper, so a private tenant with income above the applicable amount kept the full LHA. Where a maximum rent (LHA) applies it is the eligible rent (SI 2006/213 and SI 2006/214 reg 12D(2)(a); NI SR 2006/405 and 2006/406 reg 13C(2)(a)), and it is the rent where the rent is lower (reg 13D(5); NI reg 14D(5)). The appropriate maximum Housing Benefit is the eligible rent less non-dependant deductions (regs 70 and 50; NI 68 and 48), and 65% of the excess of income over the applicable amount is deducted from it (SSCBA 1992 s.130(3)(b), regs 71 and 51; NI s.129(3)(b), regs 69(b) and 49(b)). The award is now max(0, eligible rent - deductions - taper), with eligible rent LHA_cap for LHA tenants and the rent otherwise. Social tenants are unchanged. A single 70-year-old private renter with rent 30,000, LHA 9,467.64 and a taper of 1,063.92 now gets 8,403.72 instead of 9,467.64. The existing "Reduced rent with LHA" test expected the capped-after-taper 1,000; the law gives max(0, 1,000 - 6,500) = 0. Adds YAML cases (including rulespec-uk pipeline case 4: 2,928.87 becomes 1,928.87), Hypothesis property tests for the bounds, closed form, monotonicity and the exact difference from the old order, and corrects the Housing Benefit docs page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The end-to-end property now adds a working-age non-dependant to some pension-age households, so the closed form is checked against the model's own non-dependant deductions as well as its applicable income and amount. The changelog and the Housing Benefit docs page name the Northern Ireland provisions (SR 2006/405 regs 13C, 14D, 68 and 69; SR 2006/406 regs 13C, 14D, 48 and 49), and the changelog says the old order cut the taper by the excess of the rent over the LHA, removing it only when that excess was at least the taper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ffer Hypothesis favours small draws, so the generated LHA rates were often a few pounds and the taper removed every award, where capping before and after the taper agree. In the derandomized run only 1 of 76 generated units fell where the orders differ, and the end-to-end test passed against the old formula. The LHA and the applicable amount now start at realistic levels, the populations hold 10 to 40 units (5 to 20 households end to end), and each test runs an explicit example with a unit for each term of old - new = min(rent - LHA_cap, taper, old), worked by hand. The end-to-end example is the single pensioner from the issue, alone and with a working-age non-dependant, and a pension-age couple. All three tests now fail against the old order. Example counts drop to 15, 10 and 10 to keep the file near a minute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Reviewed: approve with nits. The new order is The council-tenant result is unchanged (I checked the algebra), and zero rent, rent below the LHA and non-dependant deductions above the rent all floor at 0. I hand-checked all 9 YAML cases. Locally the HB and Pension Credit YAML tests pass (120) and the property tests pass (3). Merged with #1927, 136 pass.
The aggregate is small, about −£2m. |
The fragment was a paragraph; docs/engineering/skills/github-prs.md asks for a short entry. It now says what changed and cites the GB working-age and pension-age provisions. The PR body keeps the detail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LHA_cap is min(rent, the BRMA LHA rate): the maximum rent (LHA) of SI 2006/213 and 2006/214 reg 13D(5), not an applicable amount. In the end to end LHA-order case, say that the applicable income input overrides the income computed from the State Pension. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, Vahid. All four points are handled:
Head 066cb2d. There is no change in behaviour since your review. |
Codex review — ready to mergeReviewed commit No actionable code/test defect found. The property suite includes explicit examples at all three distinct bounds of the intended difference, monotonicity checks, and final Housing Benefit for pensioner households with and without non-dependants. These exercise the change beyond a copied formula oracle. Confidence: high. No confirmed policy defect in the changed rent-cap/taper order. The current formula first determines eligible rent ( Sources: GB LHA cap, GB maximum/taper, 65%, pension-age eligible rent, NI rent taper. The LHA-cap label and explanatory test/body nits are addressed at066cb2dd. The separate NI rates rebate20% taper is outside this changed rent calculation and is pre-existing; do not count it as a regression introduced here. LHA transitional-protection/category rules are likewise not newly implemented by this ordering fix. Coordinate subsequent shared-file changes without dropping this order. Validation: 16 passed, 1 warning. Changed implementation and relevant final-award/dependency tests were checked at this exact commit. 9 official source originals were inspected and their cached bytes verified. No material review gaps remain within this scope; private population-impact claims were not independently rerun. Focused tests used an existing cached Python 3.13 environment (Core 3.32.9, NumPy 2.1.3, pandas 2.3.1, microdf-python 1.2.1, Hypothesis 6.168.2, pytest 8.4.2) with this PR’s isolated source snapshot. No dependencies were installed; this was not a freshly synced lock environment or a full-suite/population run. This is a review comment, not a formal GitHub review vote. Live check before posting (2026-10-02T10:41:05.635577+00:00): same commit, CLEAN; all reported CI checks pass. |
…-capital-derived-income Resolve the four means-test income formulas: keep #1896's claimant/partner member filter and this branch's income-from-capital rules (no property income, legacy_means_test_income_tax in the tax line, home-letting income). Keep #1952's Pension Credit references and this branch's Sch IV para 18 reference, and #1926's HB docs with this branch's sentence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #1917
Problem
housing_benefit_entitlementtook the taper off the rent, capped the result at the Local Housing Allowance, and then took off non-dependant deductions:max(0, min(max(0, rent - taper), LHA_cap) - non_dep_deductions). When the rent exceeded the LHA by at least the taper, the cap absorbed the whole taper. When the excess was smaller, the cap still cut the taper by the excess.The first row is also reproduced end to end from household inputs (a single 70-year-old with State Pension £16,000 and no Guarantee Credit):
housing_benefitis £9,467.64 on main and £8,403.72 here.Law
Current revised texts, read on legislation.gov.uk (CLML
data.xml) on 2026-09-30.LHA_capmodels asmin_(benunit_rent, BRMA_LHA_rate).So the award is
max(0, eligible rent − non-dependant deductions − taper), with eligible rentmin(rent, LHA)for LHA tenants: the cap comes first, then the deductions and the taper.Change
housing_benefit_entitlement:Social (non-LHA) tenants get the same result as before: for them the old formula reduces to
max(0, rent − taper − deductions). The variable now has adocumentationstring andreferences to all the provisions above (GB working age and pension age, and NI).Also:
housing_benefit_entitlement.yaml, "Reduced rent with LHA": the expectation was the capped-after-taper value. Rent 8,000,LHA_cap1,000, taper 0.65 × (11,000 − 1,000) = 6,500: the law gives max(0, 1,000 − 6,500) = 0; main gave min(8,000 − 6,500, 1,000) = 1,000. The other three cases there are unchanged.benunit_rentcapped at the LHA and described the taper against the rent. It now describes the cap, the maximum benefit and the taper in the legal order.Other places checked for the same post-taper cap, none found:
simulated_council_tax_reduction_benunit,_legacy.py):max(0, liability × maximum support − taper − non-dependant deductions), no rent cap.uc_housing_costs_element): caps the rent atuc_LHA_capinside the housing element, before the award-level taper.housing_benefit_baseline_entitlementonly reads the baseline simulation'shousing_benefit_entitlement, so it follows this change.Invariants
For every benefit unit, with taper = 0.65 × max(0, applicable income − applicable amount):
LHA_cap= min(rent, LHA rate).housing_benefitsatisfies invariant 2 in the model's own applicable income, applicable amount and non-dependant deductions.Invariants 1, 2 and 5 also hold on every one of the 61,223 benefit units in the Enhanced FRS runs below (checked in the diagnostics script).
Tests
housing_benefit_lha_order.yaml, 9 cases, every expectation computed by hand with the arithmetic in comments: the Bug case at unit level and end to end from household inputs; rent below the LHA; a taper between the rent excess and the LHA; a taper above the LHA; no taper; rulespec-uk pipeline case 4; a council tenant with case 4's numbers; deductions above the LHA. Applicable income and amount are inputs, so the Housing Benefit earnings disregard and the Guarantee Credit passport (both being fixed separately) cannot move any expectation. The end-to-end case also setsBRMA_LHA_rateto 9,467.64, the value the model computes for that household in 2026, so it pins the order and not the LHA table.test_housing_benefit_lha_order_properties.py: Hypothesis (derandomize=True), invariants 1-6, three tests:finance/benefit/family/housing_benefit/andgov/dwp/pension_credit/, plusuc_legacy_mutual_exclusion.yaml): 122 passed, 0 failed, re-run on the head 02aa1eb;policyengine-core test policyengine_uk/tests/policy -c policyengine_uk): 1,285 passed, 0 failed, on 3a45d23. Later commits change only comments, docs, the changelog and the property tests;-m "not microsimulation"): 318 passed, 15 skipped, 30 deselected (local, in the builder's worktree; the log does not record the commit).Test: passed on ee18b85, the head before the changelog-only commit (run 36728861200); on the head 02aa1eb it is queued (run 36739417546).ruff format --check .is clean.Dataset impact (Enhanced FRS 2024-25, current weights, real runs of main vs this branch)
Who moves (benefit units receiving HB on main, weighted):
Only those who have the LHA applied, pay rent above it and have income above the applicable amount can lose, and all 12 of those records do. They are six pairs of records with identical inputs and different weights. Nobody gains, and nobody loses all their HB. The loss is the taper for 10 records (1.6k weighted) and the excess of rent over the LHA for 2 (0.2k). The effect is small because most HB recipients who pay rent above the LHA have no taper. For 334.1k of the 368.6k (52 records) the model computes Guarantee Credit above zero and
housing_benefit_applicable_incomeis 0, which is what the current Guarantee Credit passport, unchanged here, gives (it sets applicable income to 0 when someone in the unit is over State Pension age andguarantee_credit> 0). Of those, 48 records (332.9k) receive Pension Credit and 4 (1.2k) do not (see "Not in this PR"). The rest have applicable income at or below the applicable amount.housing_benefit_entitlementalso changes for 655 records (654k weighted, 593 of them working-age) that do not receive HB (not eligible or not claiming), sohousing_benefitdoes not change for them.Caveat: the Enhanced FRS weights were calibrated to an HB total under the old code. The figures are from
impact/dataset_impact.pyandimpact/compare_impact.pyon a read-only copy ofenhanced_frs_2024_25.h5; the breakdown is fromimpact/diag_lha.py, which also checks that main's entitlement equals the old formula, this branch's equals the new one, and old − new = min(rent −LHA_cap, taper, old) on every benefit unit.Not in this PR
benunit_rent,LHA_cap,BRMA_LHA_rate) is unchanged.impact/combined.jsonandimpact/combined_vs_main.json) gives Housing Benefit £14.227bn → £13.780bn in 2026 (−£0.447bn), with 170.2k benefit units losing, none gaining, and 80.4k of the losers losing all their HB. That run does not include Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910 or Split pension-age Housing Benefit allowances by when State Pension age was attained #1913, and has not been repeated with them.housing_benefit_entitlementbyhousing_benefit_payable_shareinhousing_benefit_pre_benefit_cap, and that share is the part of the year before the abolition date (1 July 2026 in Great Britain, 1 October 2026 in Northern Ireland) for a unit with no one over State Pension age. None of the 4 working-age records that lose here has a member over State Pension age, so their loss would be scaled down by that share. Not run.LHA_eligibleis "renting and no member in social housing". SI 2006/213 reg 13C(5) (same words in SI 2006/214 reg 13C(5)) says the maximum rent (LHA) rules do "not apply in a case where" the landlord is a registered social landlord or registered provider of social housing, "paragraph 4(1)(b) of Schedule 3 to the Consequential Provisions Regulations (savings provision) applies", "the tenancy is an excluded tenancy of a type mentioned in any of paragraphs 4 to 11 of Schedule 2", the claim relates to "a houseboat, caravan or mobile home" or "rent payable in relation to a hostel", or the rent officer has found "a substantial part of the rent" attributable to board and attendance.LHA_eligibledoes not test any of the cases after the first. The model'saccommodation_typehas aMOBILEcategory ("Caravan/Mobile home or houseboat"), butLHA_eligibledoes not read it, so it treats those cases as LHA cases too, and the new order reaches them. Unchanged by this PR.domestic_ratesenters household tax and net income only), so NI households get the rent side only, before and after this change.git merge-treeof this head with each of Replace generic child and adult flags with each programme's legal definitions #1896 (16940d8), Take Housing Benefit earnings disregards from net earnings only #1908 (8bed988), Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910 (bd9272a), Split pension-age Housing Benefit allowances by when State Pension age was attained #1913 (f159ff4) and Key the Housing Benefit Guarantee Credit passport on receipt #1927 (d8c24d9) is clean.housing_benefit_assessable_capital.py,housing_benefit_applicable_income_disregard.pyandLHA_category.py, none of which this PR touches. The end-to-end YAML case setsBRMA_LHA_rateas an input, so a change in the LHA category cannot move it.housing_benefit_pre_benefit_cap.py, the direct consumer ofhousing_benefit_entitlement: it multiplies the entitlement byhousing_benefit_payable_share. That scales whatever this PR's formula returns and does not depend on the order inside it.axiom: SI 2006/213 regs 12D(2)(a), 70 and 71 (and SI 2006/214 regs 50-51) — TheAxiomFoundation/rulespec-uk#370 queued
🤖 Generated with Claude Code