Repository navigation
Give Universal Credit and Housing Benefit their own benefit cap and LHA shared-accommodation rules - #2089
Conversation
…HA shared-accommodation rules One benefit cap (rate, exceptions, reduction) and one LHA category served both schemes although the law differs. Each scheme now has its own: - UC: uc_benefit_cap (reg 80A), is_uc_benefit_cap_single_claimant_rate (a reg 3(3) single claim is single), is_uc_benefit_cap_exempt_earnings (reg 82), is_uc_benefit_cap_exempt_specified_benefit (reg 83, "a claimant"), uc_benefit_cap_reduction (reg 81, less the childcare costs element). LHA_category / is_lha_shared_accommodation_rate_specified_renter are UC's (Sch 4 paras 27-29). - HB: housing_benefit_benefit_cap (reg 75CA), single claimant per reg 2(1), exceptions 75E (WTC) and 75F (claimant or partner), no cap under the pension-age regulations (reg 5), housing_benefit_benefit_cap_reduction (75D, keeping the reg 75 minimum). housing_benefit_LHA_category, is_housing_benefit_young_individual and housing_benefit_LHA_rate follow reg 13D(2)(a)(i), with names and helpers shared with #2006. - universal_credit and housing_benefit read their own reduction; benefit_cap_reduction is their sum. The claims_legacy_benefits stopgap from #2084 is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions and LHA category Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…B LHA rate; docs and changelog 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>
…case, verbatim reference award - programs.yaml: the Benefit Cap entry named the removed benefit_cap; it now reads benefit_cap_reduction and says each scheme has its own cap. - Armed forces independence payment lifts both caps: UC reg 83(1)(c) (reg 2's "attendance allowance") and HB reg 75F(1)(ea). - The HB-only young person LHA case supplies the two bedrooms both schemes' size criteria give (UC Sch 4 para 10(1); HB reg 13D(3)(b)): C. - reference_universal_credit is the parent formula, protected floor included; the differential excludes AFIP families (an intended change). - Document that uc_benefit_cap_reduction is the reg 81 amount, which can exceed the award; BRMA_LHA_rate is the UC category's weekly rate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Coordination from #2084, which this PR is stacked on. #2084's head moved to
When #2084 is merged into this branch, keep this PR's deletion of the helpers. Then check that the per-scheme variables pass those two YAML cases, since they pin the UC-vs-HB routing the split has to keep. #2084's round-3 review (subfleet 20261003-101137-reg33-review-r3) is reviewing |
|
Overlap with #1978 (Count only claimants' income in Universal Credit), for whichever of the two merges second. #1978 limits two of the things this PR moves to the claimant and partner (
The intended end state is this PR's split, with both new variables summing person-level amounts over #1978's head is |
#2084 has merged, so this branch now targets main. Conflict resolutions: - LHA (#2006, #2134, #2022): main's housing_benefit_LHA_category, housing_benefit_LHA_rate, housing_benefit_severe_disability_premium_applies, LHA_cap, BRMA_LHA_rate, LHA_category and uncapped_BRMA_LHA_rate. This branch's reg 3(3) lines stay in is_lha_shared_accommodation_rate_specified_renter (#2030 is still open, so lha_renter_has_non_dependant and the union responsibility term stay). Its expectations in LHA_claimant_composition.yaml and housing_benefit_lha_order.yaml; the two HB categories this branch added there are now C under main's HB size criteria (reg 13D(3)(b)). - #1944: housing_benefit_pension_age_regulations_apply is main's. Its edit to the deleted is_benefit_cap_exempt_other is dropped. Its tests now read the per-scheme variables; the property file's "before" reform adds the age test to each scheme's exceptions. - #1978: the UC earnings exception and the capped total (benefit_cap_welfare_benefits) sum over is_uc_assessed_claimant. - Carer's Allowance overlap: both specified-benefit exceptions read is_entitled_to_carer_benefit (UC reg 83(1)(i)-(ia), HB reg 75F(1)(h)-(ha)). - #2084 (final head): the stays_on_legacy_benefits helpers stay deleted; its two routing cases are rewritten on the per-scheme variables. - SI 2014/1230 reg 60C: is_uc_benefit_cap_exempt_qualifying_age lifts the UC cap where every claimant has reached the qualifying age for State Pension Credit. That keeps the result #1944 pinned for such awards (inputs only; the model's UC eligibility needs a claimant under that age). - Tests main added that read the removed shared variables now read the per-scheme ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion - housing_benefit_lha_category.yaml: main's severe disability premium counts an adult outside the family only as the liable family's non-dependant (#2006). The case now makes the claimant's family the household head's, so the other adult is a non-dependant (HB reg 3) and defeats Sch 3 para 14(2)(a)(ii), as the case intends. - housing_benefit_on_passporting_benefit.yaml (main): universal_credit reads uc_benefit_cap_reduction, so the case sets that input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oom counts agree The old shared category read the Universal Credit size criteria (LHA_allowed_bedrooms). Since #2006, Housing Benefit has its own count (HB Regs 2006 reg. 13D(3), housing_benefit_LHA_allowed_bedrooms), which gives a bedroom to a young person the Universal Credit count leaves out. The differential now compares Housing Benefit's category with the reference only where the two counts agree, and the examples check that the lone-parent case reaches that difference (HB category C). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p-lha-rules Brings in origin/main 1d3abec: #2119 (UC claims at legacy closure; would_claim_uc -> claims_universal_credit), #2177, #1905, #2376, #2379, #2388 and rollup B's earlier landings. Conflicts: - utils/uc_work_related_requirements.py: keep this PR's deletion of stays_on_legacy_benefits and the two shared-rules helpers. #2119 only switched stays_on_legacy_benefits to claims_universal_credit; on main its sole readers are the shared cap and LHA files this PR replaces. - docs universal-credit.ipynb: main's new claims_universal_credit paragraph, with this PR's per-scheme cap sentences. Main's new tests that read the deleted shared variables now read UC's: - legacy_closure_ineligible_partner.yaml and test_uc_legacy_closure_properties.py: is_benefit_cap_single_claimant_rate -> is_uc_benefit_cap_single_claimant_rate - test_uc_statutory_pay_properties.py: is_benefit_cap_exempt(_earnings) -> is_uc_benefit_cap_exempt(_earnings) (the UC award is what it checks) - legacy_closure_pension_route.yaml and test_data_built_imputations.py pin uc_benefit_cap_reduction, which universal_credit and uc_has_deduction read, instead of the HB+UC total. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UC Regs 2013 reg 83(1)(a) reads "the LCWRA element is included in the award of universal credit", as (j) reads the carer element, which this file already tests as uc_carer_element > 0. The exception now reads uc_LCWRA_element > 0 instead of any non-excluded member's limited-capability flag. uc_LCWRA_element uses the same member mask today (everyone but the other member of a reg 3(3) single claim), so no result changes on main; once #1989 limits the element to the claimants, the exception follows it whichever of the two merges first (PR comment from #1989's review, 2026-10-12). Two YAML cases pin the reading: the flag without the element lifts no cap; the element without the flag does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts commit d7697ed. Independent review (subfleet 20261013-010643, review-delta-d7697edee.md) found that reading uc_LCWRA_element > 0 matches the old flag test at baseline but not under a reform that sets the element's amount to nil (gov.dwp.universal_credit.elements.disabled.amount = 0, or a nil new-claimant health element): main keeps the exception, the element reading drops it. Whether a nil element is "included in the award" is a reading the law leaves open, so this PR keeps main's flag test and stays a pure split. Restricting reg 83(1)(a) to the claimants is #1989's change; the port it needs is noted on #1989 and #2089. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…97ed) From the independent delta review (subfleet 20261013-010643, review-delta-d7697edee.md): - P1: utils/uc_work_related_requirements.py. The 10/9 merge (f4aa92b) kept this branch's whole copy of the file, which undid main's 7a71c86 (#2081 review item 5): couple_members() came back and claimants() stopped reading is_uc_assessed_claimant. The file is now main's, less the three shared-rules helpers this PR removes. No output changes: the two formulas were identical. - P2: is_uc_benefit_cap_exempt_qualifying_age said only a supplied award reaches reg 60C. Since #2119 a reg 60A claim by a Working Tax Credit recipient whose tax credits closed does too. - P3: housing_benefit_pension_age_regulations_apply said its UC-award rule was "right for the cap"; UC's reg 60C exception no longer reads it, HB's cap does. benefit_cap_welfare_benefits now says the benefit-unit amounts are counted whole (#2298). The UC notebook's cap sentence names reg 60C. The legacy closure differential also compares uc_benefit_cap_reduction, which still reads the claim route through the UC award. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Needs changes before merge. The split itself reads the law correctly, but the branch predates #2076 and #2399, which main merged on 13 and 14 October. It now conflicts with main in 13 files and would undo both if resolved in its favour.
@MaxGhenis, findings at head b7e0b2373:
-
must: integrate #2076 and #2399 before this can land.
git merge-treeagainst main714b1438creports 13 conflicts. Five are modify/delete: this PR deletesis_benefit_cap_exempt.py,_earnings.py,_health_disability.py,_other.pyandis_benefit_cap_single_claimant_rate.py, which #2076 rewrote. There is also an add/add conflict inutils/benefit_cap.py(#2076'sbenefit_cap_couple). The per-scheme files here carry the pre-#2076 logic, so taking them as they are would undo main's fixes:universal_credit/benefit_cap/is_uc_benefit_cap_exempt_specified_benefit.py:64andhousing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_specified_benefit.py:51: any income-related ESA (esa_income > 0) lifts the cap. Main requiresesa_includes_support_componentand the claimant or partner as payee. UC reg 83(1)(a) and HB reg 75F(1)(a) both say "an employment and support allowance ... which includes a support component".- The UC LCWRA and carer exceptions read members' circumstances. Main reads whether the element is in the award (reg 83(1)(a), (j)), including entered elements.
benefit_cap_welfare_benefits.py:50counts income-based JSA and income-related ESA whole and scopes people byis_uc_assessed_claimantalone. Main usesbenefit_cap_coupleandaward_of_members, so only the couple's part counts.universal_credit/benefit_cap/is_uc_benefit_cap_exempt_earnings.py:30-38uses gross employment and self-employment income less tax and NI. #2399 moved main touc_individual_earned_income_before_mif, which brings in statutory pay and pension contributions and leaves out MIF income (reg 82(4)).
Fix: merge main, port each of #2076's per-person scopes and #2399's earnings base into the matching UC or HB file, and rerun the split property tests. The Coordination section still lists #2076 as open, so that needs updating too.
-
must:
uc_benefit_cap_reductionclashes with #1819. #1819 addsvariables/gov/dwp/universal_credit/uc_benefit_cap_reduction.py: the cap reduction actually taken off the award, bounded by the award and the protected floor. This PR addsuniversal_credit/benefit_cap/uc_benefit_cap_reduction.pywith the same name and a different meaning: the gross reg 81 amount, which the PR body notes can exceed the award. If both merge, the variable is defined twice. Both also changeuniversal_credit.py,uc_deductions.pyandtest_uc_deductions.py. The two PRs need to agree which name is the gross reg 81 reduction and which is the amount applied. -
should: the "UC or HB, never both" assumption is about to stop holding.
benefit_cap_welfare_benefits.py:3-7adds bothuniversal_credit_pre_benefit_capandhousing_benefit_pre_benefit_capinto one total, and the HB reduction does not check for UC. That is right only while no family gets both. #1910 opens joint HB and UC for specified or temporary accommodation, and main already has thein_specified_or_temporary_accommodationinput (#2123). Once a family gets both, this PR would count HB in the UC cap, against UC reg 80(2A) ("the amount to be used is nil"), and apply the HB cap to someone receiving UC, against HB reg 75F(1)(g). It also leaves out HB reg 75C(2)(a), which counts specified-accommodation HB as nil. Now that the schemes are split, these are a few lines: leave HB out of the UC total, adduniversal_credit_pre_benefit_cap > 0to the HB exception, and read the accommodation input for 75C(2)(a). That would also settle the double-subtraction problem flagged on #1910. If you would rather leave it, the property test that no family gets both needs to fail clearly once #1910 lands. -
should: the reg 82 threshold is a hardcoded £10,152 (
is_uc_benefit_cap_exempt_earnings.py:39). Reg 82(1)(a) sets it at 16 hours a week at the national minimum wage hourly rate, × 52 / 12. From April 2026 the rate is £12.71 (already ingov.hmrc.minimum_wage), so the threshold is about £881.23 a month (£10,575 a year). The £846 figure is from 2025-26. This is pre-existing on main, and #1820 adds a parameter for it. Since this PR creates the new UC file, it is the place to read that parameter (or take #1820's) rather than copy the literal.
What is right: UC reg 80A(2) treats a reg 3(3) claimant as a single claimant, and HB reg 75CA(2) with reg 2(1) treats that couple as a couple. The reg 81 childcare offset and reg 81(2) "no reduction" rule are right, as is the reg 75D(2) 50p minimum. The scheme-specific exceptions are right: earnings and the LCWRA and carer elements for UC only, WTC (75E) for HB only. HB reg 5 for pension-age claims is right, and so is the AFIP treatment (UC reg 2 for reg 83(1)(c) and Sch 4 para 29(5); HB reg 75F(1)(ea)).
What I checked
- Read the full PR body and diff. Read on legislation.gov.uk: UC Regs 2013 regs 80, 80A, 81, 82 and 83, and HB Regs 2006 regs 75C, 75D and 75F.
- Hand-computed
benefit_cap_by_scheme.yaml. Reg 81: 30,000 − 22,020 = 7,980, less 3,000 childcare = 4,980. Reg 75D(2): min(7,980, 5,000 − 0.50 × 52) = 4,974, leaving £26. Both match. - In a fresh environment at
b7e0b2373, 11 changed YAML files and folders all passed (168 cases):benefit_cap_by_scheme,benefit_cap(file and folder),LHA_claimant_composition,housing_benefit_lha_category,housing_benefit,claims_as_single_person,uc_deductions,legacy_award_readers/passports,carers_allowance_overlapping_benefitsandhousing_benefit_on_passporting_benefit. - Mutation: with reg 81's childcare offset removed from
uc_benefit_cap_reduction,benefit_cap_by_scheme.yamlfails 2 of 20 cases. I restored the line afterwards. ruff checkandruff format --checkon the 38 changed or added Python files: clean.git merge-treeagainst main714b1438c: 13 conflicts, listed above.- Compared files with Vahid's open PRs: #1820 changes three of the files this PR deletes and two of its test files, and #1819 is in item 2. #1855, #1864, #2184, #2187, #2189, #2193 and #2382 do not overlap.
- CI at this head: all checks pass. That run predates #2076 and #2399 on main.
- Not checked: I did not run the Hypothesis property files, the full YAML suite, or anything on the Enhanced FRS.
🤖 Generated with Claude Code
…cap-lha-rules Ports #2076's per-person scopes into the per-scheme files instead of keeping the four shared exception files it rewrote (they stay deleted): - UC reg 83 (is_uc_benefit_cap_exempt_specified_benefit): #2076's payee and support-component tests, scoped to "a claimant" (not the reg 3(3) other member), with UC's own child and young-person tests. - HB reg 75F (is_housing_benefit_benefit_cap_exempt_specified_benefit): claimant or partner, ESA via the couple's award and payee, HB's own child and young-person tests. - benefit_cap_welfare_benefits: #2076's benefit_cap_couple and award_of_members scoping of the capped total. - utils/benefit_cap.py and utils/benefit_unit.py: both sides' helpers. - #2076's converted tests (capped total, specified benefits, and its property file) read the per-scheme variables; the #2298 cap hold in the legacy-readers property file is dropped (fixed by #2076). Resolved by the 2026-10-13 landing lane (job 20261013-115007), which lost DNS before committing; snapshot pushed as wip/uc-hb-split-merge-a98f8f084 (3e27c7f). Targeted cap YAML at this tree: 143 passed (merge-2026-10-13b/logs/yaml_cap_targeted.log). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shared-cap-lha-rules #2399 changed the shared earnings exception this PR deletes. Its measure, uc_individual_earned_income_before_mif summed over the assessed claimants (reg. 82(1)(a), excluding MIF income under reg. 82(4)), is carried over unchanged into the UC-only is_uc_benefit_cap_exempt_earnings; the shared file stays deleted. Main's new tests that read the deleted shared names now read the UC ones, with their expected values unchanged: - test_uc_statutory_pay_properties.py (#2399; conflict): main's text and regression case, reading is_uc_benefit_cap_exempt(_earnings). - benefit_cap_earnings_exception_earned_income.yaml (#2399) and uc_trading_loss.yaml (#2085). - test_trading_loss_properties.py (#2085). Targeted YAML (benefit_cap folder, benefit_cap_by_scheme, uc_trading_loss): 121 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only Universal Credit's shared-rate test (is_lha_shared_accommodation_rate_specified_renter) reads it; Housing Benefit's LHA category reads is_housing_benefit_young_individual and housing_benefit_has_non_dependant. P3 from the delta review of 85d9be9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reg 75F(1)(g)) UC Regs 2013 reg 80(2A) counts Housing Benefit as nil in the Universal Credit total, so uc_benefit_cap_reduction takes it out of benefit_cap_welfare_benefits, which keeps it for the Housing Benefit cap (HB Regs 2006 reg 75C(2)(b)). HB reg 75F(1)(g) lifts the Housing Benefit cap where the claimant is receiving Universal Credit: is_housing_benefit_benefit_cap_exempt_specified_benefit now includes a family with Housing Benefit and Universal Credit before the cap. housing_benefit_eligible rules out a family that claims Universal Credit, so no computed family gets both today and no baseline result changes; the rules matter once a family is paid both (specified or temporary accommodation, #1910). Reg 75C(2)(a) (specified-accommodation HB counted as nil) is not applied: in_specified_or_temporary_accommodation does not separate specified from temporary accommodation. Vahid Ahmadi's finding 3 on #2089. Tests: two YAML cases (each fails on the previous formulas), a property file (test_benefit_cap_uc_and_hb_properties.py), and the split property file's reduction invariant now checks reg 80(2A) and 75F(1)(g) in place of asserting that no family gets both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22
left a comment
There was a problem hiding this comment.
Reviewed ea46f7c3ec56bc757e1d1f9ac5ca07e26de50877 against merge base 7e10ccd98326b6c9acd244191bb4636985e710b0 with separate code/test and policy/source roles. The earlier integration and dual-award concerns are addressed; no new blocking formula defect was established. Two points remain to address before treating the review record as complete:
- A1 — Refresh the description. Its opening still names
04ac713fa, says the diff is byte-identical to the old review, and states that UC/HB never coexist. This head deliberately handles supplied dual awards: HB is excluded from UC's cap total and HB is exempted when an established pre-cap UC award exists. Update the scope/invariant sections, distinguish the remaining formula-derived eligibility limitation, and label old-head impact/validation tables. - A2 — Carry forward the dated earnings-threshold correction.
is_uc_benefit_cap_exempt_earnings.pystill uses £10,152. UC reg82(1)(a) ties this to16hours/week at the minimum-wage rate; £12.71 from April2026 implies £10,574.72/year under that conversion. £10,400 of actual earnings therefore lies on opposite sides of the tests. This is Vahid's inherited nonblocking concern, not a newly introduced regression. Coordinate #1820's dated parameter into this renamed file or record the landing dependency.
The split now excludes HB from the UC cap under UC80(2A) and prevents a second HB cap under HB75F(1)(g). The DWP explanatory memorandum, paragraph7.2 supports using established pre-cap UC entitlement even where the cap later reduces UC to nil. That is distinct from unclaimed notional eligibility.
Validation: two independently executed final-income controls passed on Core3.33.0: managed migration preserves the same cap/final award as ordinary take-up; adding £10,000 supplied HB leaves the UC cap unchanged and raises final household income by £10,000. The local broad changed-file rerun hit its540s bound at25%, without a final pass count; it is not claimed as passing. Complete hosted YAML/four Python shards and required checks are green at this exact head. The source review validated28official originals, including the decisive PDF page. Private population impact was not rerun; the combined #1819/#1820 trees were not tested. The substantive Axiom status is present.
Before landing, coordinate #1819's competing uc_benefit_cap_reduction definition and #1820's threshold fix. Current GitHub mergeability was temporarily UNKNOWN after main advanced, so this is not a declaration that every merge gate has passed. Review only; no branch changes or merge performed.
…into uc-hb-split-shared-cap-lha-rules No conflicts. Main's two edits to files this PR also changes (test_benefit_cap_specified_benefits_properties.py: REBALANCED_YEARS from REBALANCING_YEARS; test_legacy_award_readers_properties.py: comment) merged automatically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22
left a comment
There was a problem hiding this comment.
Review status: COMPLETE
Review severity: COMMENT
Reviewed head: 4cfc373c4228c756ab7878ef27cfff91dbd298ab (main a94c65df integrated).
The incremental review found no new critical findings or material evidence gaps. All 86 focused cases passed, including managed migration, supplied UC/HB awards with one cap, and protected/new LCWRA claimants through final UC and household income in 2026, 2029, 2030 and 2032.
Two prior follow-ups remain STILL OPEN at their existing SHOULD ADDRESS severity:
- A1: update the description's old head/equivalence claims and its statement that UC/HB awards never overlap.
- A2: the renamed UC earnings-exemption file still uses £10,152; coordinate the dated threshold from #1820. This remains inherited feedback, with no new critical escalation.
Local checks used Core 3.33.0 / Hypothesis 6.168.2, rather than locked 3.33.2 / 6.169. Hosted Python 1/4 and 2/4 and the required Result were still pending at the final guard; no CI failures were observed. The #1819/#1820 landing coordination remains, and combined dependency trees were not tested.
- P3-1: document that the HB cap rate counts a 16-year-old for the year (has_sixteen_year_old_dependant) while the HB reg 75F exceptions read the model's Child Benefit qualifying young person, which leaves out one outside education; in law one status decides both (CB (General) Regs 2006 regs 5 and 7), and the model has no school-leaver terminal date (#2287). No formula changes: main's #2076 case "A 16-year-old out of education with armed forces independence payment does not lift the cap" pins this reading of reg 75F(1)(ea). - P3-2: the changelog said AFIP "now lifts both caps"; main already lifts them. Against main the UC exception narrows to a claimant's AFIP (UC reg 83(1)(c) with reg 2); HB keeps the young person's (reg 75F(1)(ea)). - P3-3: benefit_cap_welfare_benefits' documentation now says the total includes UC and HB before the cap, which uc_benefit_cap_reduction relies on for reg 80(2A); the capital-income property test's comment no longer names exemption routes the UC exemption stopped reading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No conflicts. #2242 uplifts the UC standard allowance (parameters, the rebalancing uprating, its test); it touches no file this PR changes, and nothing it adds reads a variable this PR removes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing counts The e640e0a delta review's P3: the Child Benefit education or training condition (meets_qualifying_young_person_education_or_training_condition_for_child_benefit) accepts approved training as well as non-advanced education, so "outside education" overstated who the HB exceptions leave out. Docs only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi, thanks for the review of 1. Integrate #2076 and #2399 (must): done.
2.
3. UC and HB both (should): done in
4. The reg 82 threshold is a hardcoded £10,152 (should): answered in writing; the landing dependency is recorded in the description.
Since |
|
@juaristi22, thanks for both rounds, and for the final-income controls. Where your two follow-ups stand at head A1, refresh the description: done.
A2, the reg 82 threshold: recorded as a landing dependency, not changed here.
The #1819 name clash is answered in the Coordination section and in my reply to Vahid: this PR keeps |
juaristi22
left a comment
There was a problem hiding this comment.
Reviewed the replies and revised description at e82c4acacafd516836ca4cc1797ea4536c3e9d63; both independent review roles are complete with no new findings. The previously reviewed code head is unchanged.
A1 is resolved: the description now identifies the current head, distinguishes supplied dual UC/HB awards from formula-derived eligibility, and corrects the reduction invariants and validation provenance.
A2 is resolved through the original request's record the integration dependency alternative. The £10,152 literal remains unchanged; this does not certify the dated threshold as fixed. Keep #1820's parameter/port and #1819's gross-versus-floor-bounded variable naming explicit in landing coordination.
No new model run or private impact validation was performed for this prose-only follow-up; earlier code/source checks and their limitations remain. All current checks are green and GitHub reports a clean merge, but combined dependency integration and Vahid's separate feedback still need coordination before calling this merge ready.
Review r11 (Opus and GPT-6.1 Sol) on 871e46d: main's benefit cap readers (#2076, #2089) called award_of_members on esa_income and jsa_income. Under disable_simulated_benefits the held total matches neither of its readings after the dataset year, so another member's held award was read as the couple's: it raised benefit_cap_welfare_benefits and could exempt a claimant with an ESA support component who receives no ESA. - utils/benefit_unit.py: award_of_couple reads claimant_or_partner_* wherever the rule's couple is the claimant and partner, and award_of_members for any other couple (a UC couple with is_uc_claimant entered). CLAIMANT_OR_PARTNER_AWARDS moves here; the reform imports it. - benefit_cap_welfare_benefits and both specified-benefit exemptions read award_of_couple. - The reform's docstring says it reads its inputs when it is applied, so a region a scenario's after-load modifier or set_input changes later is not followed; applying the reform after the change reads it (r11 Sol finding 2, Opus nit 4). New test for that route. - Changelog: the first bullet no longer claims what #2047 shipped (r11 Opus finding 2); new bullet for the benefit cap. - New test_disable_simulated_benefits_benefit_cap.py: metamorphic (another member's report never changes the cap, reform on), differential against main's replaced reader (reform off), the fallback couple, and an entered award reaching the cap. Each reader's mutant fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base is
main. The head ise82c4acac, which merges main ate7610ac52(#2242). #2084 (UC reg 3(3) single claims), which this PR was stacked on, merged as4ed5b3ce4. Since Vahid Ahmadi's review atb7e0b2373:270bfaabf,85d9be9ff);ea46f7c3e);5f7a7d108,4cfc373c4,e640e0a81);ea048820c,ab514f5b8ande82c4acacanswer review P3s in documentation only.Main has since moved to
14999dc03(#2436, dated-reform backdating; #2440, pension relief in adjusted net income). Neither touches a cap, LHA, UC or HB file. #2440 editstest_trading_loss_properties.py, which this PR also edits (one renamed variable); the hunks are separate, and git merges them without conflict.What was wrong
On main, one set of variables serves both Universal Credit and Housing Benefit, although the two schemes' rules differ:
is_benefit_cap_single_claimant_rate/benefit_capuse one "single claimant" test, with the family rate wherever either scheme's child or young-person test is met. UC Regs 2013 reg 80A(2) and HB Regs 2006 reg 75CA(2) each define it their own way.is_benefit_cap_exempt_health_disability,_otherand_earningsare the union of both schemes' grounds:housing_benefit_pension_age_regulations_apply, HB reg 5) also serves as the UC age test. The UC age exception is SI 2014/1230 reg 60C, which turns on the claimants alone.benefit_cap_reductionserves both schemes. It applies neither UC reg 81 (the childcare costs element comes off) nor HB reg 75D(2) (the 50p weekly minimum stays).universal_creditandhousing_benefitboth subtract the onebenefit_cap_reduction, whose total includes HB. A family given both awards has its HB counted in the UC total, against UC reg 80(2A), and has the reduction taken off both awards. The model's HB eligibility rules out a family that claims UC, so only supplied awards reach this today; Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910 would compute both.is_lha_shared_accommodation_rate_specified_renter, still applies a reg 3(3) single claim only when the family is not staying on legacy benefits, and leaves AFIP out of the para 29(5) list.#2084 gated the reg 3(3) consequences in these shared variables on the family staying on legacy benefits, as a stopgap. That was wrong for any family whose legacy status did not match the scheme being calculated.
The law
legislation.gov.uk data.xml, current text, fetched 2026-10-02 (SI 2014/1230 regs 60A and 60C fetched 2026-10-09).
Who is a single claimant
The welfare benefits counted
Exceptions
Reductions
Shared accommodation
What this PR does
uc_benefit_cap(reg 80A)housing_benefit_benefit_cap(reg 75CA)is_uc_benefit_cap_single_claimant_rate: not joint claimants (a reg 3(3) single claim is single), not responsible for a child or QYP under UC's own testis_housing_benefit_benefit_cap_single_claimant_rate:is_single_person(reg 2(1)); a reg 3(3) couple is a coupleis_uc_benefit_cap_exempt=_earnings(reg 82: #2399's measure, the assessed claimants'uc_individual_earned_income_before_mif) or_specified_benefit(reg 83: the LCWRA or carer element in the award; a claimant's ESA with the support component; a claimant's benefits, AFIP included as attendance allowance (reg 2); UC's own child and QYP tests; the reg 3(3) other member is not a claimant) or_qualifying_age(SI 2014/1230 reg 60C: the single claimant, or both joint claimants, at the qualifying age for State Pension Credit). No WTC exception.is_housing_benefit_benefit_cap_exempt=_working_tax_credit(75E) or_specified_benefit(75F: the claimant's or partner's ESA with the support component and other benefits; AFIP, for a young person too; HB's own child and young-person tests; 75F(1)(g), a family with HB and UC before the cap) orhousing_benefit_pension_age_regulations_apply(reg 5). No earnings, LCWRA or carer-element exception.uc_benefit_cap_reduction: the excess of the welfare benefits, with HB counted as nil (reg 80(2A)), less the childcare costs element, on an award only (regs 78(1), 81)housing_benefit_benefit_cap_reduction: the excess, leaving 50p a week (regs 75, 75D)universal_credit,uc_deductions,uc_has_deductionhousing_benefitLHA_categorywithis_lha_shared_accommodation_rate_specified_renter(Sch 4 paras 27-29): reg 3(3) applied whatever legacy benefits the family reports; AFIP added to the para 29(5) list (reg 2)housing_benefit_LHA_category(#2006), unchangedOther changes:
award_of_members,is_payee_of_couple_award). The LCWRA and carer elements count when they are in the award, including an entered element (main'selement_included).benefit_cap_welfare_benefitsis main's capped total, unchanged:benefit_cap_couple, withaward_of_membersfor income-based JSA and income-related ESA (WRA 2012 s. 96(1); UC reg 80(1)).is_uc_benefit_cap_exempt_earningsis Fix benefit-cap statutory-pay earnings exemption #2399's formula, unchanged, including its threshold of £10,152 a year._qualifying_agereadsis_uc_single_or_joint_claimant(WRA 2012 s. 40). A partner who cannot be a joint claimant is not a claimant, so a 70-year-old claiming as a single person beside a 50-year-old partner excluded by reg 3(3) is not capped; a mixed-age joint claim is. The model's own UC eligibility needs a claimant under the qualifying age, except for a reg 60A claim by a working tax credit recipient whose tax credits closed (claims_uc_at_legacy_closure), so otherwise only a supplied award reaches this exception. The model has no qualifying-claim input: it treats such an award as a reg 60C case, because s. 4(1)(b) bars any other claim by those claimants. This keeps the result Apply the benefit cap to mixed-age couples on Universal Credit, and keep them off pensioner Council Tax Reduction #1944 pinned for these awards inbenefit_cap_pension_age.yaml, which main reaches through the HB reg 5 test.is_entitled_to_carer_benefit(UC reg 83(1)(i)-(ia); HB reg 75F(1)(h)-(ha): "is entitled to"), as main's shared rule does.benefit_cap_welfare_benefits, HB at its amount before the cap under reg 75C(2)(b)) serves both caps.uc_benefit_cap_reductiontakes HB out of it (UC reg 80(2A)). The HB exception includes a family with HB and UC before the cap (HB reg 75F(1)(g)), so its HB reduction is nil. That reads "receiving" as an award of UC before the cap, so a UC award the cap reduces to nil still lifts the HB cap. HB reg 2(3B) treats a person as on UC "whether it is in payment or not", and juaristi22's review cites the DWP explanatory memorandum to SI 2014/771 (para 7.2) for the same reading. Requiring HB before the cap keeps a UC-only family's HB exception flags as they were. Only supplied awards reach this until Abolish working-age Housing Benefit from 1 July 2026 (NI 1 October 2026) #1910 (see "What was wrong").benefit_cap_reductionremains, as the sum of the two schemes' reductions; uk-data's DWP benefit-cap calibration targets read it. A family with neither award now has no reduction. The UC part is the reg 81 amount, which can exceed the award before the cap (universal_creditfloors the award at nil). That predates this PR; bounding the reported amount changes uk-data's monetary inputs, so it is a follow-up.has_sixteen_year_old_dependant), since annual ages cannot place UC reg 5(1)(a)'s 1 September or the Child Benefit terminal dates. UC's exceptions count that 16-year-old too; HB's read the model's Child Benefit qualifying young person, which leaves them out. In law one status decides both under HB. Main's Lift the benefit cap only for the support component and the people HB reg 75F and UC reg 83 name, and cap only the family's benefits #2076 AFIP case pins the HB exceptions' reading, and the fix belongs with Child Benefit qualifying young person omits the school-leaver conditions (Child Benefit (General) Regulations 2006 regs 4, 5 and 7) #2287's school-leaver rules.claims_legacy_benefitsstopgap helpers inutils/uc_work_related_requirements.pyare gone.benefit_cap,is_benefit_cap_exempt,is_benefit_cap_exempt_earnings,is_benefit_cap_exempt_health_disability,is_benefit_cap_exempt_other,is_benefit_cap_single_claimant_rate. The program registry (programs.yaml, served at/uk/metadata) now namesbenefit_cap_reductionfor the Benefit Cap. Three other registry entries name variables that do not exist on main (JSA,ESA,DLA); they are a separate follow-up with a registry test.benefit_cap_reductionas an input (the awards no longer read it), now use the per-scheme ones:benefit_cap/(Lift the benefit cap only for the support component and the people HB reg 75F and UC reg 83 name, and cap only the family's benefits #2076, Fix benefit-cap statutory-pay earnings exemption #2399, Apply the benefit cap to mixed-age couples on Universal Credit, and keep them off pensioner Council Tax Reduction #1944),dependants_income.yaml,carers_allowance_overlapping_benefits.yaml,state_pension_credit_qualifying_age.yaml,council_tax_reduction_pensioner.yaml,housing_benefit_on_passporting_benefit.yaml,legacy_award_readers/passports.yaml,uc_trading_loss.yaml, and the UC claimant and legacy-closure files.test_benefit_cap_and_ctr_pension_age_properties.py,test_benefit_cap_specified_benefits_properties.py,test_data_built_imputations.py,test_legacy_award_readers_properties.py,test_state_pension_credit_qualifying_age.py,test_trading_loss_properties.py,test_uc_claimant_income_properties.py,test_uc_company_owner_properties.py,test_uc_income_from_capital_properties.py,test_uc_legacy_closure_properties.pyandtest_uc_statutory_pay_properties.py.test_benefit_cap_and_ctr_pension_age_properties.py's "before Apply the benefit cap to mixed-age couples on Universal Credit, and keep them off pensioner Council Tax Reduction #1944" reform now adds the old any-member-over-State-Pension-age test to each scheme's exceptions.LHA and #2006/#2030. #2006 merged with the HB LHA names this PR had converged on, so main's
housing_benefit_LHA_category,housing_benefit_LHA_rate,is_housing_benefit_young_individual,housing_benefit_severe_disability_premium_applies,housing_benefit_LHA_allowed_bedroomsandLHA_capstand unchanged. This PR changes only UC's shared-rate test. It keeps the household-composition proxy (lha_renter_has_non_dependant), and the test still reads responsibility under either scheme: a dependant under one scheme only is the other scheme's non-dependant, which the proxy does not identify. #2030 (open) replaces both with each scheme's own non-dependant test.Not modelled (as before): the UC grace period (reg 82(1)(b)), the HB 39-week grace period (75E(3)-(5)), the weekly rounding of HB's relevant amount (75CA(1)), the reg 75 non-payment of HB below 50p, the para 29 exceptions other than disability benefits, and whether a claim is a reg 60C qualifying claim. Also HB reg 75C(2)(a), specified-accommodation HB counted as nil: main's input
in_specified_or_temporary_accommodation(#2123) does not separate specified from temporary accommodation, so that HB counts at its amount before the cap.Impact
Real Enhanced FRS runs (r10), one at a time. Base is main
e7610ac52, the main this head merged; branch is the heade82c4acac. Private copy ofenhanced_frs_2024_25.h5, sha256 e433e532…. Weighted £m a year; records are survey records.benefit_cap_reduction, base → branch4cfc373c4(r9, base maina94c65df9), this PR's UC change is identical in 2025-28 and slightly higher in 2029-30 (+£26.44m and +£28.37m then). Main's UC standard allowance uplift (Apply the Universal Credit Act 2025 standard allowance uplifts to 2029-30 #2242), merged ine640e0a81, raises UC before the cap on both sides from 2027: in 2027-28 that changes main's capped totals but not this PR's effect.benefit_cap_reductionfalls by the UC change. uk-data's DWP benefit-cap calibration targets read it. For 2025 the count of capped benefit units is unchanged and the total falls by £23.0m (3.8%).benefit_cap_reductionwithout an award;LHA_capanduc_LHA_capare unchanged everywhere.Scripts and outputs:
impact/in the handover folder (dataset_impact_r3.py,compare_r3.py,run_r10.sh,table_rows.py,r10/branch_vs_base.json).Tests
gov/dwp/benefit_cap_by_scheme.yaml(22 cases). Each scheme's exceptions:housing_benefit/housing_benefit_lha_category.yaml(9 cases): AFIP excepted under UC Sch 4 para 29(5)(b) with reg 2 and under HB Sch 3 para 14; HB young individual at 30 and at 35; para 14(2)(b) for a couple; an adult non-dependant defeats the premium unless they receive a qualifying benefit (para 14(4)); another family's claimant in the household; an HB-only young person blocks both shared rates.gov/dwp/benefit_cap.yaml(18). The reg 3(3) couple gets UC 14,753 and HB 22,020; so does the HB-only 19-year-old.LHA_claimant_composition.yaml(17). The reg 3(3) couple gets UC A and HB B. Two HB categories this PR had added are C under main's HB size criteria (Give sharers, boarders and lodgers their own rent, tenure and LHA category #2006; HB reg 13D(3)(b)).universal_credit/claims_as_single_person.yaml(33). The other member's PIP lifts the HB cap (75F) but not the UC cap (83); earnings lift the UC cap but not the HB cap; Model a Universal Credit claim by a member of a couple as a single person (UC Regs 2013 reg 3(3)) #2084's "reports HB but claims UC" and "stays on legacy benefits" cases on the per-scheme variables.test_benefit_cap_lha_scheme_split_properties.py(7 tests, derandomised, 1 to 6 families per example, plus eight explicit example families: reg 3(3) on UC and on legacy benefits, reg 81, an HB-only young person, AFIP, a mixed-age couple, LCWRA on HB and reg 75D(2); the other differences are pinned in YAML): differential tests against the shared formulas at Model a Universal Credit claim by a member of a couple as a single person (UC Regs 2013 reg 3(3)) #2084's head (17b9b8a), re-added underreference_names through a structuralReform, so one simulation computes old and new for the same families. The return expressions are verbatim; the copies differ in the names they read, the inlined stopgap helpers and the unused intermediate values they drop. Those reference formulas predate Lift the benefit cap only for the support component and the people HB reg 75F and UC reg 83 name, and cap only the family's benefits #2076 and Fix benefit-cap statutory-pay earnings exemption #2399; the per-person scopes those PRs added are pinned by their own converted YAML and property files (benefit_cap_specified_benefits.yaml,benefit_cap_capped_total.yaml,benefit_cap_earnings_exception_earned_income.yaml,test_benefit_cap_specified_benefits_properties.py,test_uc_statutory_pay_properties.py).LHA_allowed_bedrooms). Since Give sharers, boarders and lodgers their own rent, tenure and LHA category #2006, HB has its own (housing_benefit_LHA_allowed_bedrooms, reg 13D(3)), which gives a bedroom to a young person the UC count leaves out. So HB's category is compared with the reference, and with UC's, only where the two bedroom counts agree.test_examples_reach_the_casespins that the lone-parent example with a 19-year-old reaches that difference (HB category C). Separately, review found that main's UC count also leaves out the renter's own qualifying young person (UC Sch 4 para 10(1)(b)); that is Give benefit-unit members aged 16 or over a bedroom in the LHA size criteria #2008, which open PR Give benefit-unit members aged 16 or over a bedroom in the LHA size criteria #2010 fixes.test_benefit_cap_uc_and_hb_properties.py(1 test, 12 examples, each 1 to 8 families run with their HB and with none; not derandomised, so each run draws new examples): the reg 80(2A) and 75F(1)(g) invariants below.test_uc_single_claim_properties.py(Model a Universal Credit claim by a member of a couple as a single person (UC Regs 2013 reg 3(3)) #2084's structure kept; UC exceptions compared single vs stripped, HB variables single vs joint),housing_benefit/housing_benefit.yaml,universal_credit/uc_deductions.yaml,test_uc_deductions.pyandtest_abolish_benefit_cap_scenario.py(the scenario lifts both caps).e82c4acac: all 15 checks pass (gh pr checksexits 0), including Lint, the four smoke imports, the documentation builds, Fix evidence, andTest / YAMLand the fourTest / Pythonshards (run 38963800030), which run every pytest file above.e82c4acac:policyengine-core test policyengine_uk/tests/policy3,512 passed;test_benefit_cap_lha_scheme_split_properties.py7 passed;test_uc_single_claim_properties.py8 passed;test_housing_benefit_lha_order_properties.py3 passed;test_housing_benefit_pension_age_properties.py4 passed;test_abolish_benefit_cap_scenario.py1 passed;ruff format --check policyengine_ukclean (1,572 files).4cfc373c4(the same formulas; since then only documentation and the merge of Apply the Universal Credit Act 2025 standard allowance uplifts to 2029-30 #2242, which touches no file this PR changes):policyengine-core test policyengine_uk/tests/policy3,512 passed;test_benefit_cap_lha_scheme_split_properties.py7 passed;test_benefit_cap_uc_and_hb_properties.py1 passed;test_benefit_cap_specified_benefits_properties.py26 passed;ruff format --check policyengine_ukclean.LHA_capreading the UC rate; HB reduction ignoring the 50p minimum; UC shared-rate test ignoring reg 3(3); UC exception including WTC; HB cap reaching pension-age claims; HB shared-rate test ignoring the severe disability premium; UC cap rate reading responsibility under either scheme.ea46f7c3e: reverting either of its two formula changes fails both of its YAML cases (2 failed, 20 passed each time).Invariants
For every generated family and every example:
uc_benefit_cap_reduction= max(excess − childcare costs element, 0) on an award, else 0, where the excess counts HB as nil (reg 80(2A)).benefit_cap_reduction= UC + HB reductions.universal_credit/housing_benefitequal the award under the old shared cap.LHA_cap= min(rent,housing_benefit_LHA_rate). That rate equals the UC category'sBRMA_LHA_ratewherever the categories agree.test_benefit_cap_uc_and_hb_properties.py, both awards supplied):benefit_cap_welfare_benefitsat its amount before the cap (reg 75C(2)(b)).benefit_cap_reductionis its UC reduction; with HB, it is exempt from the HB cap.Reviews
Independent reviews through Subfleet, and reviews from Vahid Ahmadi and juaristi22:
claimants(); reading the LCWRA element diverged from main under a reform that sets it to nil; a stale doc). Fixed in 2391f94 (back to main's flag test) and b7e0b23.uc_benefit_cap_reductionname clash with Reconcile UC deduction and benefit cap components under the protected floor #1819; a family paid both; the hardcoded reg 82 threshold. Each is answered on the PR: findings 1 and 3 with their commits, 2 and 4 in writing.ea46f7c3eand the answers to Vahid's findings, with paired single-household probes on main and the head): APPROVE, three P3s, answered in documentation inab514f5b8.ab514f5b8and the merge of Apply the Universal Credit Act 2025 standard allowance uplifts to 2029-30 #2242): APPROVE, one P3 (approved training), answered ine82c4acac.Reviews of 85d9be9 and ea46f7c were cut short (a host shutdown and the six-hour limit); the 4cfc373 delta review covers both heads.
Coordination
270bfaabf; claimant and partner scopes, support-component ESA, the capped total) and Fix benefit-cap statutory-pay earnings exemption #2399 (85d9be9ff; the reg 82 earned-income measure).benefit_cap_earnings_exemption(£881 a month from April 2026, £10,572 a year). Its earnings change follows the move:git merge-treeof Fix benefit cap exemption defects (#1818) #1820's head01e90fe28with this head puts the parameter intois_uc_benefit_cap_exempt_earnings.pywithout a conflict there. Fix benefit cap exemption defects (#1818) #1820 does conflict inis_benefit_cap_exempt_health_disability.py, which this PR deletes, and in two property files; its exception fixes go into the per-scheme files.uc_benefit_cap_reduction). This PR keeps the name for the gross reg 81 amount. Reconcile UC deduction and benefit cap components under the protected floor #1819's floor-bounded amount should take another name (uc_benefit_cap_reduction_after_floorwas suggested on Reconcile UC deduction and benefit cap components under the protected floor #1819 on 10/02) and readuc_benefit_cap_reduction, notbenefit_cap_reduction. policyengine-core refuses a duplicate name at load, and the two PRs edit the same line ofuniversal_credit.py, so the clash cannot land silently.axiom:
uk/regulation/uksi/2013/376/80A(80A.test.yaml:single_not_greater_london_no_child_normal_home,single_not_greater_london_responsible_for_child_no_home);.../81(childcare costs element greater than excess so no reduction applies);.../schedule/4/paragraph/27and/28(renter_meets_all_specified_renter_conditions,renter_not_single_or_claiming_as_single).if eligibletowould_claimvariable descriptions #357. HB Sch 3 para 14: Survey structure, metadata and testing for other legacy benefits #386. HB regs 75A, 75C, 75D, 75E and 75F: Add semver actions #425.if eligibletowould_claimvariable descriptions #357 (reg 3(3) couple: single for UC, couple for HB), Add semver actions #425 (HB family with only a UC element; reg 75D(2) 50p minimum; reg 81 pipeline regressions), Survey structure, metadata and testing for Housing Benefit #384/Addif eligibletowould_claimvariable descriptions #357 (AFIP) and Survey structure, metadata and testing for personal benefits #389 (pension-age HB amounts; reg 60C).🤖 Generated with Claude Code
Reproducer:
policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_claimant_composition.yaml,policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit.yaml::Partially reduced benefit,policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit.yaml::Capped at 0,policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_lha_category.yaml,policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/passports.yaml::The claimant's own income-related ESA with the support component exempts the family from the benefit cap,policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claimant_child_element.yaml::Case 1, an explicitly identified young claimant is not their own child.