Skip to content

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

Merged
MaxGhenis merged 22 commits into
mainfrom
benefit-cap-75f-claimant-partner
Oct 13, 2026
Merged

MaxGhenis merged 22 commits into
mainfrom
benefit-cap-75f-claimant-partner

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The benefit cap lifted in two cases the law does not, and counted benefits it should not:

  1. ESA without the support component. Any income-related ESA of the claimant or partner, and any member's contributory ESA (here and again in is_benefit_cap_exempt_other), lifted the cap. HB Regs 2006 reg 75F(1)(a) and UC Regs 2013 reg 83(1)(a) need ESA "which includes a support component".
  2. Other members' benefits.
    • The exemption summed every benefit-unit member's attendance allowance, DLA, PIP, industrial injuries benefit, Carer's Allowance and Carer Support Payment.
    • It also counted any member's armed forces compensation (in is_benefit_cap_exempt_other), LCWRA flag or caring.
    • Reg 75F(1) and reg 83(1) name the claimant and partner, plus, for some benefits, a child or young person they are responsible for. A non-dependent adult's attendance allowance lifted the family's cap.
  3. Other members' benefits in the capped total. benefit_cap_reduction summed every member's contributory ESA and JSA, incapacity benefit and SDA, and the whole unit's income-related ESA and income-based JSA. UC Regs 2013 reg 80(1) and HB Regs 2006 reg 75A count the benefits "to which the single person or couple is entitled". With UC of £20,000 before the cap, a non-dependent adult's £5,000 of ESA cut the family's UC from £14,753 to £9,753. Count only the claimant's and partner's legacy awards in means tests and passports #2027's review found this, and Count only the claimant's and partner's legacy awards in means tests and passports #2027 now leaves the whole cap to this PR. Fixes Benefit cap counts another member's own income-based JSA and income-related ESA #2298.

A third defect hid the first. Datasets set is_disabled_for_benefits, the default for uc_limited_capability_for_WRA, from receipt of any of ten benefits, ESA included (policyengine-uk-data disability_benefits.py). So the UC LCWRA exception lifted the cap for every ESA recipient, whatever their ESA group. Universal Credit takes the ESA work capability assessment (UC Regs 2013 reg 40(1)(a)(ii)). So a person whose own ESA lacks the support component is now taken not to have LCWRA, whatever the flag. The work allowance and the reduced minimum age need only limited capability for work, so they get a separate uc_limited_capability_for_work, which such a person keeps.

#2027 merged at bbb9576e8. This branch has merged main up to 1d3abecef (merges 285317c8c, 399da7fc1, d79a189eb and 1c946fafb); the head is 298df66a0.

Law

Fetched verbatim from legislation.gov.uk (/data.xml, current revised text) on 2026-10-02.

HB Regs 2006 regs 75A, 75E(2), 75F; UC Regs 2013 regs 2, 40(1), 79(1), 80(1), 83(1); UC (TP) Regs 2014 reg 19(4); WRA 2007 ss.2(2), 4(4); WRA 2012 s.96(10); HB Regs 2006 regs 2(1), 19, 20(1); UC Regs 2013 regs 4(6), 5(5)

HB Regs 2006 (SI 2006/213) reg 75F:

(1) The benefit cap does not apply where— (a) the claimant or the claimant's partner is receiving an employment and support allowance under Part 1 of the Welfare Reform Act which includes a support component; (b) the claimant or the claimant's partner is receiving an industrial injuries benefit by virtue of Part 5 of the Act; (c) the claimant or the claimant's partner is receiving an attendance allowance; (ca) the claimant or the claimant's partner is receiving pension age disability payment; (d) the claimant or the claimant's partner is receiving a war pension; (e) the claimant, the claimant's partner or a child or young person for whom the claimant or the claimant's partner is responsible, is receiving a disability living allowance; (eza) the claimant, the claimant's partner or a child or young person for whom the claimant or the claimant's partner is responsible, is receiving child disability payment; (ezb) the claimant, the claimant's partner or a young person for whom the claimant or the claimant's partner is responsible, is receiving Scottish adult disability living allowance; (ea) the claimant, the claimant's partner or a young person for whom the claimant or the claimant's partner is responsible, is receiving a personal independence payment, adult disability payment or an armed forces independence payment under the Armed Forces and Reserve Forces (Compensation Scheme) Order 2011; (f) [entitled but not receiving, in hospital or a care home] ... (g) the claimant is receiving universal credit. (h) the claimant, the claimant's partner, or a young person for whom the claimant or the claimant's partner is responsible, is entitled to a carer's allowance under section 70 of the Act; (ha) the claimant, the claimant's partner, or a young person for whom the claimant or the claimant's partner is responsible, is entitled to carer support payment; (i) the claimant, the claimant's partner or a young person for whom the claimant or the claimant's partner is responsible, is entitled to a guardian's allowance under section 77 of the Act.

(2) In this regulation "war pension" has the meaning in regulation 2 (interpretation) and includes— (a) a guaranteed income payment; ...

Reg 75E(2):

This paragraph applies where the claimant is, or the claimant and the claimant's partner are jointly, entitled to working tax credit.

UC Regs 2013 (SI 2013/376) reg 83(1):

The benefit cap does not apply in relation to any assessment period where— (a) the LCWRA element is included in the award of universal credit or the claimant is receiving an employment and support allowance that includes the support component; (b) a claimant is receiving industrial injuries benefit; (c) a claimant is receiving attendance allowance; (ca) a claimant is receiving pension age disability payment; (d) a claimant is receiving a war pension; (e) a claimant is receiving a payment under article 15(1)(c) or article 29(1)(a) of the Armed Forces and Reserve Forces (Compensation Scheme) Order 2011; (f) a claimant, or a child or qualifying young person for whom a claimant is responsible, is receiving disability living allowance; (fa) a claimant, or qualifying young person for whom a claimant is responsible, is receiving Scottish adult disability living allowance; (g) a claimant, or a qualifying young person for whom a claimant is responsible, is receiving personal independence payment; (h) [entitled but not receiving, in hospital or a care home] ...; (ha) a claimant, or a child or qualifying young person for whom a claimant is responsible, is receiving child disability payment; (hb) a claimant, or qualifying young person for whom a claimant is responsible, is receiving adult disability payment; (i) a claimant, or a qualifying young person for whom a claimant is responsible, is entitled to carer's allowance; (ia) a claimant, or a qualifying young person for whom a claimant is responsible, is entitled to carer support payment; (j) the carer element is included in the award of universal credit; (k) a claimant is entitled to guardian's allowance under section 77 of the Contributions and Benefits Act.

UC Regs 2013 reg 2: "attendance allowance" includes "armed forces independence payment under the Armed Forces and Reserve Forces (Compensation Scheme) Order 2011". WRA 2012 s.40: "claimant" means a single claimant or each of joint claimants.

UC Regs 2013 reg 40(1):

A claimant has limited capability for work and work-related activity if— (a) it has been determined that— (i) the claimant has limited capability for work and work-related activity on the basis of an assessment under this Part, or (ii) the claimant has limited capability for work related activity on the basis of an assessment under Part 5 of ESA Regulations; ...

UC (Transitional Provisions) Regs 2014 reg 19(4): where it had been determined that a claimant moving from old style ESA "had limited capability for work-related activity ... (b) the claimant is to be treated as having limited capability for work and work-related activity for the purposes of regulation 27(1)(b) of those Regulations".

Welfare Reform Act 2007 s.2(2) (contributory allowance), and s.4(4) in the same words (income-related allowance):

The conditions of entitlement to the support component are— (a) that the assessment phase has ended, (b) that the claimant has limited capability for work-related activity, and (c) that such other conditions as may be prescribed are satisfied.

UC Regs 2013 reg 79(1) and reg 80(1), the capped total:

79(1) Unless regulation 82 or 83 applies, the benefit cap applies where the welfare benefits to which a single person or couple is entitled during the reference period exceed the relevant amount ...

80(1) ... the amount of a welfare benefit to be used when determining total entitlement to welfare benefits is the amount to which the single person or couple is entitled during the reference period ...

HB Regs 2006 reg 75A:

Unless regulation 75E or 75F applies, a benefit cap applies where the relevant authority makes a determination that during the reference period the total amount of welfare benefits to which— (a) where the claimant is a member of a couple, each member of the couple is or jointly are entitled; or (b) in any other case, the claimant is entitled, exceeds the relevant amount.

Welfare Reform Act 2012 s.96(10): "welfare benefit" means bereavement allowance, child benefit, child tax credit, employment and support allowance (including income-related), housing benefit, incapacity benefit, income support, jobseeker's allowance (including income-based), maternity allowance, severe disablement allowance, universal credit, widow's pension, widowed mother's allowance or widowed parent's allowance.

HB Regs 2006 reg 2(1): "child" means a person under the age of 16. Reg 19(1): a "young person" is a person who falls within the definition of qualifying young person in section 142 of the Act; reg 19(2) excludes a person on income support, income-based JSA or income-related ESA, or entitled to universal credit. Reg 20(1): a person is responsible for a child or young person who is normally living with them. UC Regs 2013 reg 5(5): "A person who is receiving universal credit, an employment and support allowance or a jobseeker's allowance is not a qualifying young person." Reg 4(6): a child or qualifying young person looked after by a local authority is treated as not being anyone's responsibility.

Changes

ESA with the support component

  • New has_own_esa_award (Person): the person claims ESA themselves. That means reported contributory ESA or a reported income-related award. When the family's income-related award is entered directly, the award goes to the claimant or partner who heads the benefit unit (else the eldest), so a couple's award has one recipient.
  • New esa_includes_support_component (Person): whether the person's ESA includes the support component (WRA 2007 ss.2(2), 4(4)).
    • Datasets and users can supply it.
    • Otherwise it is assumed for anyone with an ESA award of their own.
    • FRS 2024-25 supports the default: of the ESA recipients in FRS-sourced records who answered the ESA group question, 584 of 629 (93%) are in the support group.
  • The exception needs a claimant or partner who receives ESA with the support component. Receiving means their own contributory ESA, or the couple's income-related award (is_on_income_related_esa, from Count only the claimant's and partner's legacy awards in means tests and passports #2027).
  • The esa_contrib > 0 term leaves is_benefit_cap_exempt_other.

On the default: policyengine-uk-data also builds esa_support_group_proxy, from labour-market status (working age, reported status long-term sick or disabled, no hours of work, and its ESA health-condition proxy). It agrees with the survey's own answer for 524 of those 629 records (83%). Assuming the support component agrees for 584 (93%). So the model uses the constant default and does not read the proxy. The survey's own answer is the better source; see the follow-ups.

Who counts

Benefit or circumstance Law Lifts the cap when held by
ESA with the support component 75F(1)(a); 83(1)(a) claimant or partner
Attendance allowance, industrial injuries benefit, armed forces compensation (guaranteed income payments) 75F(1)(b)-(d), (2)(a); 83(1)(b)-(e) claimant or partner
DLA 75F(1)(e); 83(1)(f) claimant, partner, or a child or young person they are responsible for
PIP, Carer's Allowance, Carer Support Payment 75F(1)(ea), (h), (ha); 83(1)(g), (i), (ia) claimant, partner, or a young person they are responsible for
Armed forces independence payment 75F(1)(ea); UC reg 2 (counts as attendance allowance) claimant, partner, or a Housing Benefit young person
LCWRA element, carer element 83(1)(a), (j); regs 27(1), 29(1) the award includes the element: one entered directly, or given by a reform, when positive; the model's own element when positive, unless only members who are not claimants have limited capability or caring responsibilities
Working tax credit 75E(2) the claimant or couple (unchanged)
  • One cap, two schemes. The model applies one cap to Universal Credit and Housing Benefit, so an exception in either scheme counts. It already does this for the family rate (is_responsible_for_child_or_young_person_for_uc_or_housing_benefit).
    • A child or young person is anyone is_child_or_young_person_for_legacy_benefits (HB regs 2, 19) or is_child_or_qualifying_young_person_for_universal_credit (UC regs 4-5) counts.
    • A 16-year-old also counts (UC reg 5(1)(a), the same annual approximation the family rate uses), unless they receive benefits in their own right (UC reg 5(5); HB reg 19(2)) or are looked after by a local authority (UC reg 4(6)).
    • The UC LCWRA and carer exceptions also reach Housing Benefit families, as before, but now only through a claimant.
  • LCWRA inferred from ESA, in one direction. uc_limited_capability_for_WRA still defaults to is_disabled_for_benefits. A person whose own ESA award lacks the support component is now taken not to have LCWRA, whatever the flag. A determination of limited capability for work-related activity is what gives ESA its support component, and Universal Credit takes it (UC Regs 2013 reg 40(1)(a)(ii); UC (TP) Regs 2014 reg 19(4)).
  • Limited capability for work. New uc_limited_capability_for_work = uc_limited_capability_for_WRA or is_disabled_for_benefits.
    • The work allowance (reg 22(1)(b)) and the reduced minimum age (reg 8(1)(a)) read it. So an ESA recipient outside the support group, who loses LCWRA, keeps both. This is wider than main for one kind of input: a person with uc_limited_capability_for_WRA entered as false and the disability flag now has limited capability for work. They get the work allowance and fall in the work preparation group, where main gave neither (a YAML case pins it: work allowance £0 to £4,932, UC £7,381.68 to £10,094.28).
    • Reg 39(1)(a) would also give limited capability for work to any ESA award of the person's own. The model does not infer that yet, for the same every-member reason.
  • UC elements. The exception asks whether the award includes the LCWRA or carer element (reg 83(1)(a), (j)):
    • an element entered for the award decides alone, positive or nil;
    • an element a reform's replacement formula gives counts when positive (calculated_by matches the simulation's formula with the model's by code location, since the tax-benefit system loads each variable file as its own module);
    • the model's own formula still gives the element for any member's circumstance, so its element counts when positive unless only members who are not claimants have the circumstance (regs 27(1), 29(1)). A family-level benunit_has_carer entry names no member and counts; a nil element never does.
    • Where reg 29(4) gives a carer with limited capability only the LCWRA element, that element lifts the cap instead. The model does not have the reg 28 waiting period, reg 29(5)-(6) or reg 30(3).
  • The 2026-2029 rebalancing. scenarios/uc_reform.py rescales uc_LCWRA_element for 2026-2029 in every simulation, and stored it with set_input, which supplied_input read as an entry. So, before this head, another member's limited capability lifted the cap in 2026-2029 but not in 2025 or 2030. The scenario now stores the element with utils/supplied_inputs.set_model_value. That is still set_input, so reforms keep the value (core's apply_reform replays only recorded inputs), but supplied_input does not return it unless the period was supplied before, so a user's entry stays an entry. The rebalanced amounts are unchanged (test_uc_rebalancing.py; every Enhanced FRS array other than the cap's is identical to main's).
    • The record that a value is the model's lasts only while that value is the one stored. Simulation forgets it when set_input stores a value for the period (even an equal one), when delete_arrays removes the value, and when the populations are rebuilt (build_from_situation, build_from_dataframe). set_input detects a store by policyengine-core's record of the period's input (Holder._user_input_storage), which core replaces on every store. An input for a longer period (year:2026:2) or a month consistent with the year stores nothing for the year, so it leaves the model's value the model's (review r6). Each clone and branch keeps its own copy. So an entry equal to the rebalanced amount is still an entry, however it is made (review r5). apply_reform replays inputs through storage, so the model's value stays the model's across a reform.
  • AFIP. Armed forces independence payment now lifts the cap (Benefit cap applies to 2x too many households (5.3% of UC vs 1.6% administrative) #1818 defect 1; Fix benefit cap exemption defects (#1818) #1820 defect 3). It is a pure input that no dataset populates.
  • Clean-up. is_benefit_cap_exempt_earnings and is_benefit_cap_exempt_other lose the benefit sums they computed and never used. The earnings test and the State Pension age test return what they returned before. The labels that named the wrong test are corrected.

The capped total

benefit_cap_reduction now counts:

For a family on Universal Credit the couple is the award's claimants (is_uc_assessed_claimant, including the other member of a reg 3(3) single claim, reg 78(2)); otherwise Housing Benefit's claimant and partner. The two are the same unless is_uc_claimant is entered.

Another member's own award no longer reduces the family's Universal Credit or Housing Benefit. esa, jsa, household_benefits and household income keep the full awards. Maternity allowance and the bereavement benefits are welfare benefits too (s.96(10)); they were not in the model's total and are not added here.

The rate. is_benefit_cap_single_claimant_rate keeps main's is_couple test (which reads an entered relation_type), unless the entered claimants of a family on Universal Credit (is_uc_claimant) are not Housing Benefit's claimant and partner; then the award's claimants decide (UC Regs 2013 reg 80A). For every input that leaves is_uc_claimant unset it is main's formula.

Review of the other exceptions

Invariants (property-tested)

test_benefit_cap_specified_benefits_properties.py draws up to six families per simulation.

  • Members: a claimant and an optional partner. Up to three dependants, each a child (0-15), a looked-after child, a young person (16-18 in non-advanced education), a 16-year-old out of education, or a 16-year-old receiving benefits in their own right. One non-dependent adult (aged 17-60 or 67-80, not in education, earning nothing or £25,000).
  • Circumstances: every person starts with none. Each family gets up to two of sixteen circumstances on random members, and the non-dependant always gets one or two. The sixteen are AA, IIB, AFCS, DLA, PIP, AFIP, CA, CSP, contributory ESA, income-related ESA, contributory JSA, income-based JSA, incapacity benefit, SDA, the disability flag, and 35 hours of caring.
  • Support component: each simulation either supplies it for every person or for none. Policyengine-core gives a partly supplied input its default for the rest.
  • Coverage: hypothesis.event records every role and circumstance drawn.
  1. Adding a non-dependent adult never changes is_benefit_cap_exempt_health_disability, is_benefit_cap_exempt_other, is_benefit_cap_exempt, benefit_cap, benefit_cap_reduction or universal_credit (with £30,000 of UC before the cap), whatever disability or carer benefits, ESA, JSA, incapacity benefit, SDA, disability flag or caring they have.
  2. Differential. The exemption equals an independent reading of regs 75E(2), 75F(1), 83(1) and 40(1)(a)(ii), with the roles fixed by construction.
  3. An exempt family has no cap and no reduction. Any other family has the cap its roles imply: £14,753 for a single claimant with no child or young person, £22,020 otherwise (2025-26, outside London). Its reduction is at least UC less the cap. For the rate, any 16-year-old counts, as the model's family-rate helper still does (follow-up).
  4. Each head alone, exhaustively. For every role × every circumstance, one family whose only circumstance is that one, held by that member, matches the reference. There are 8 roles (claimant, partner, child, looked-after child, young person, 16-year-old, 16-year-old with own-right benefits, non-dependant) and 16 circumstances, so 128 families. The table runs once for each support mode: supplied true, false, and unset.

Properties 1-3 run 40 random examples each. Property 4 is a deterministic table. #2027's legacy-award invariance test reads the cap exemption again (#2027 had dropped it when it handed the cap over). It now calculates the cap reduction too: #2294 had held it at zero because the cap counted another member's own JSA and ESA (#2298), and its pinned family (a couple on income-based JSA with a lodger, adding a member with £3,000 of JSA) now leaves the cap, UC and the council tax reduction income unchanged. Further tests pin an element a reform gives (including one equal to the model's own amount), and that the rebalancing adds no entry and keeps a user's.

  1. Entries and the model's values. Over random sequences of up to six operations, the element is an entry exactly when its value was last set as an input, or adjusted by the model after an input. The operations are the model storing the element, an input of it, its deletion, a rebuild of the populations and a clone. The amounts include the model's own, so an entry equal to it is drawn. A clone's stores never change what the simulation it came from reads. Example tests enter an element equal to the rebalanced amount by set_input, after deleting it, and through a rebuild from a situation or an input frame; each lifts the cap, as in a simulation built with the entry.

See Runs for the results.

No known violations. Main's age test (#1944, #1907) and earnings test read only the claimant and partner, so the draws include a non-dependant over State Pension age and one with earnings.

On the base (3803dda0d), in the single-head table with the support component left to the default, 60 of the 128 families disagree with the reference. For example:

  • a non-dependant's or a looked-after child's anything lifts the cap;
  • a child's attendance allowance lifts it;
  • the claimant's AFIP does not.

On #2027's earlier head 200a1cdc4, Hypothesis's minimal counterexample for properties 1 and 2 was a 25-year-old claimant whose benefit unit adds a 17-year-old non-dependant receiving attendance allowance: the family became exempt.

Tests

YAML: gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml has 67 cases, each hand-computed from the regulation and the 2025-26 caps (UC reg 80A(2), HB reg 75CA). Every head has a case where a non-dependent adult holds the benefit.

  • ESA, claimant and partner, with and without the support component:
    • contributory ESA on UC: no reduction against £10,247 (18,000 + 7,000 − 14,753);
    • income-related ESA on Housing Benefit: HB of £9,000 against £8,753 (reduction £247, reg 75D(1));
    • couple: no reduction against £2,980.
  • ESA, other cases:
    • income-related ESA stopped by the capital test;
    • the default for a person's own ESA;
    • no ESA;
    • a directly entered award, for a single claimant and for a couple (one recipient);
    • a partner who reports the couple's award.
  • LCWRA inferred from ESA:
    • an ESA recipient flagged disabled but without the support component has no LCWRA and no element, and is capped (£10,247);
    • with the support component, LCWRA and the £5,079.24 element (423.27 × 12, 2025-26) without a flag;
    • the support component alone does not add LCWRA (the ESA head still lifts the cap);
    • a directly entered award with the flag gives the claimant the element, and without the support component no LCWRA and a reduction of £247.
  • Limited capability for work: an adult ESA recipient outside the support group, flagged disabled, keeps the work allowance, and a 17-year-old one meets the reduced minimum age. A claimant with LCWRA entered as false and the disability flag has limited capability for work, the work allowance (£4,932) and the work preparation group (UC £10,094.28).
  • IIB: for the partner, a young person and a non-dependant.
  • DLA:
    • for a child, a young person and a 16-year-old out of education (exempt);
    • for a 16-year-old receiving benefits in their own right, a 19-year-old out of education, a looked-after child, and a non-dependant (reduction £3,247) (not exempt).
  • PIP, Carer's Allowance and CSP: for a young person, the claimant and a non-dependant.
  • AFIP: for the claimant, a young person in education, a 16-year-old out of education, and a non-dependant.
  • LCWRA flag: for the claimant, a non-dependant and a child.
  • Other:
    • caring without a Carer's Allowance claim;
    • a carer or LCWRA element entered directly (reduction £0 with UC £18,000), also beside a non-dependant's own circumstance;
    • an element entered as nil, though the claimant has limited capability (reduction £3,247);
    • a non-dependant's limited capability with no element entered, in 2025 and in 2026, when the rebalancing rescales the element (reduction £3,247), and an element entered for 2026 (exempt);
    • a carer element given by an entered benunit_has_carer with no member flagged (exempt), and a nil one though the claimant cares (reduction £3,247);
    • a non-dependant with every benefit at once;
    • working tax credit;
    • the unchanged earnings and age tests.

YAML, the capped total: gov/dwp/benefit_cap/benefit_cap_capped_total.yaml has 10 cases on UC and Housing Benefit amounts.

  • A non-dependant's £5,000 of income-related ESA with UC of £20,000: reduction £5,247 and UC £14,753.
  • A non-dependant's contributory ESA, JSA, incapacity benefit and SDA with UC of £18,000: reduction £3,247.
  • The claimant's own contributory JSA of £3,000 with UC of £15,000 counts: reduction £3,247 and UC £11,753.
  • A non-dependant's £6,000 of income-based JSA leaves the family's £9,000 of Housing Benefit alone.
  • The claimant's own £6,000 of income-based JSA counts: reduction £247 and HB £8,753.
  • Entered joint UC claimants who are not Housing Benefit's couple: an adult's own contributory or income-based JSA counts on UC (couple limit £22,020, reduction £4,980), and not on Housing Benefit alone (single limit, reduction £247).
  • A couple entered with relation_type: COUPLE whose younger member the model would presume a child keeps the couple limit (£22,020), on Housing Benefit alone and on UC.

Other YAML:

  • benefit_cap.yaml: a 16-year-old out of education gives the family rate (£22,020). This is unchanged; it pins the reg 5(1)(a) approximation. The family-rate helper does not yet apply reg 5(5), because the LHA non-dependant test shares the rule and must change with it (follow-up).
  • legacy_award_readers/passports.yaml (from Count only the claimant's and partner's legacy awards in means tests and passports #2027): the two benefit cap cases now state the support component.

On the original base (3803dda0d, when the files had 56 and 5 cases):

  • benefit_cap_specified_benefits.yaml fails 36 of 56 cases.
    • 21 fail because the new variables do not exist there.
    • 15 fail on value:
      • every case where a non-dependent adult, a dependant with a claimant-only benefit, a 19-year-old, a looked-after child, a 16-year-old with own-right benefits or a disabled child without DLA lifted the cap;
      • AFIP for the claimant and for a young person;
      • an LCWRA element entered directly.
  • benefit_cap_capped_total.yaml fails 3 of 5 cases, all on value. A non-dependant's ESA exempts the whole family there, and a non-dependant's income-based JSA takes £247 off the family's Housing Benefit.

Runs on the head 1c946fafb, with policyengine-core 3.33.2 as main's uv.lock now pins:

  • gov/dwp and finance/benefit YAML: 1,635 passed.
  • The cap, legacy-award-reader and child/adult-definition property files: 50 passed.
  • The UC rebalancing, input-record, code-health, single-claim and pension-age cap tests: 2,314 passed and 1 failed. The failure, test_uc_rebalancing.py::test_section_4_floor_properties, was Hypothesis's slow-input health check on this loaded host (one draw took 367 s), not a counterexample; it passed on a rerun (27 s) and in CI.
  • ruff format --check: clean.
  • CI: every check passed on 1c946fafb (run 38346827099).

On the head 298df66a0, which adds only tests to 1c946fafb: the cap YAML file, 67 passed; test_other_members_awards_never_change_the_readers without the hold, 1 passed (on main 1d3abecef's code it fails on its pinned family, at the council tax reduction income, as #2298 describes). CI: every check passes (run 38360222263).

Enhanced FRS impact

Real Microsimulation runs, 2025-2030, on a private copy of the Enhanced FRS 2024-25 (sha256 e433e532…), with policyengine-core 3.33.2: main 1d3abecef (the commit merged here) against this branch's head 1c946fafb. Both trees were clean. The head 298df66a0 adds only tests to 1c946fafb, so these runs stand for it. Aggregates only; nothing below rests on fewer than 10 survey records.

The previous runs were main 6d89338ab against 399da7fc1. Every change this PR makes, below, matches them, with one exception: 2025's UC before the cap on the survey ESAGroup copy, −£0.128bn then, is now −£0.129bn, as one more record changes in that year. Main's own cap count, given for scale at the end, moved from about 302,000 to 293,000. Both moves come from main's intervening merges, which include policyengine-core 3.33.2.

The dataset as it stands: £0 in every year 2025-2030.

  • Universal Credit, Housing Benefit, household net income (including HBAI and gov_spending) and the poverty rates do not change in any year. Some families' amounts differ only in float32 rounding, every one by under 5p.
  • The capped total moves the weighted cap reduction by under £200 in any year, and no family's reduction moves by 5p or more.
  • Fewer than 10 records lose the overall exemption in each year, and none of them is above the cap. In each, the only holder is a member the regulations do not name for that head: a child under 16 with a young person's benefit, or a dependant's or another adult's limited capability or caring.
  • is_benefit_cap_exempt_other loses its armed forces and contributory ESA terms, which flips it for 920-947 records (about 778,000-796,000 benefit units weighted); they stay exempt through the scoped is_benefit_cap_exempt_health_disability or earnings. is_benefit_cap_exempt_health_disability changes for 69 records (about 27,000 weighted) each year; all but the few above stay exempt another way.
  • The ESA rule changes nothing because the dataset does not report the ESA group: the support component takes its default for every ESA recipient, and every ESA recipient is flagged disabled.
  • 2026-2029 behave as 2025 and 2030 do: the rebalancing's element is the model's, not an entry.

Sensitivity: the survey's own ESA group. The same copy, with esa_includes_support_component taken from FRS 2024-25 ESAGroup for the 629 ESA recipients in FRS-sourced records who answered it (584 in the support group). The stacked SPI-synthetic and capital-gains copies do not link back to FRS records and keep the default. Same two package states:

Year Lose the exemption (k) LCWRA element (£bn) UC before cap (£bn) UC (£bn) Household net income (£bn) Cap reduction (£bn)
2025 11.0 −0.194 −0.129 −0.149 −0.150 not shown
2026 11.0 −0.176 −0.121 −0.146 −0.147 not shown
2027 11.1 −0.180 −0.124 −0.151 −0.152 not shown
2028 10.5 −0.178 −0.124 −0.153 −0.155 not shown
2029 10.6 −0.182 −0.126 −0.158 −0.160 +0.035
2030 10.6 −0.110 −0.073 −0.108 −0.111 +0.038
  • About 38,000 benefit units lose the UC LCWRA element, because a claimant's own ESA is outside the support group.
  • UC before the cap falls by less than the element, because some awards are already tapered to nil.
  • The cap takes more from the families that lose the exemption. In 2025-2028 fewer than 10 records' reductions change, so that column is not shown for those years.
  • Since only about 62% of weighted ESA recipients link to an answer, this understates what a complete survey column would do.
  • No year's change comes near £1bn.
  • For scale: on this copy main already caps about 293,000 UC families in 2026, against DWP's administrative count of 111,000 for November 2025 (Benefit cap applies to 2x too many households (5.3% of UC vs 1.6% administrative) #1818).

Review

Independent reviews of the heads after #2027 merged:

  • r1 (a6100a7fc), r2 (a6a3073a5) and r3 (e756d1b91) requested changes, and each was answered in the next commit.
  • r4 approved 2f341de77, with three nits (follow-ups below). The reviewer was an Opus 5.5 agent, run in the landing job's own session because the Subfleet daemon was offline.
  • r5 (GPT-6.1 Sol) reviewed 399da7fc1, the merge of main 6d89338ab, and requested changes. Its one should-fix: an entry equal to the rebalanced amount was read as the model's after a rebuild through build_from_dataframe. 5d543f169 fixes it, and also r4's nit 3 (clones shared the record).
  • r6 (GPT-6.1 Sol) reviewed e249a42c7 and requested changes. Its one should-fix: set_input forgot the model's value for every period overlapping the one passed, even where it stored nothing, so an input for year:2026:2 or a consistent month made the rebalancing's element an entry. d4984fb34 forgets it only where the call stores a value.
  • r7, for d4984fb34, was lost with its lane before it wrote a verdict.
  • r8 (Opus 5.5) approved 1c946fafb, the merge of main 1d3abecef under policyengine-core 3.33.2, with one nit (follow-ups below). It found:
    • r6's should-fix resolved: r6's script leaves every rebalanced year the model's;
    • the write detection sound on every path core 3.33.2 stores an input by;
    • main's new simulation.py code (_start_new_population, parameter rebuilds) composing with this PR's overrides;
    • main's legacy-closure claims (Move families onto Universal Credit when a legacy benefit they receive closes #2119) and reported Carer's Allowance limb still reading the cap's people correctly;
    • the new tests failing on e249a42c7's code (7 of 16) and passing on this head.
  • Vahid Ahmadi reviewed e648b2db5 (10/5) and d4984fb34 (10/12). 298df66a0 answers the 10/12 points: a YAML case pins LCWRA entered as false, and this body no longer says such results are unchanged or treats Apply the benefit cap to mixed-age couples on Universal Credit, and keep them off pensioner Council Tax Reduction #1944 and Reduce Carer's Allowance and Carer Support Payment by overlapping benefits #1994 as pending.
  • r9 (GPT-6.1 Sol) approved 298df66a0 with no findings. It derived the new YAML case by hand and ran it on main, and found that the property without the hold passes here and fails on main's cap (a lodger's £3,000 of JSA gives a reduction of £1,047.52).

Axiom

axiom: TheAxiomFoundation/rulespec-uk#425 queued

None of these provisions is in the corpus: HB Regs 2006 regs 75A, 75E and 75F; UC Regs 2013 regs 79, 80 and 83; WRA 2007 ss.2 and 4; WRA 2012 s.96. rulespec-uk encodes UC regs 80A, 81 and 82, and its reg 81 takes the capped total as an input. The issue holds the verbatim law, the required outputs, a pasteable review_finding and companion tests taken from this PR's YAML. It extends #389 (regs 79/83, the age question) and #416 (reg 75F(1)(a), from #2027).

Coordination

Follow-ups

  • ESA group from the survey. FRS 2024-25 asks every ESA recipient which group they are in (ESAGroup, benefits table VAR3 for BENEFIT 16: 1 work-related activity group, 2 support group, 3 assessment phase). policyengine-uk-data can supply a complete esa_includes_support_component column from it. Until then, no dataset result depends on the support component.
  • Children with adult benefits in the data. 89 children under 16 in SPI-synthetic households report enhanced PIP and contributory ESA (policyengine-uk-data).
  • Reg 5(5) for the family rate and LHA. is_responsible_for_child_or_young_person_for_uc_or_housing_benefit and lha_renter_has_non_dependant should stop counting a 16-year-old who receives benefits in their own right or is looked after. Both have to change together.
  • Split the exceptions by scheme: Give Universal Credit and Housing Benefit their own benefit cap and LHA shared-accommodation rules #2089.
  • Reforms of uc_LCWRA_element in 2026-2029 (on main). The UC rebalancing stores the element for 2026-2029 before a reform= is applied, and core's apply_reform replays it, so a reform that replaces the element's formula has no effect in those years. The cap then credits such a reform with the rebalanced value (r4 nit 1).
  • An entry equal to the model's value, on a branch (r8 nit). On a branch whose cap is already calculated, set_input of exactly the rebalanced uc_LCWRA_element makes it an entry, but core keeps the cap's cached values, because the stored array did not change. Only a caller entering that amount on a branch reaches this; situations, frames and datasets set inputs first. A fix: in set_input, when the call replaces a model value on a branch, drop the values that depend on it (_drop_values_that_may_depend_on).
  • Contradictory entries (r4 nit 2). Three people entered as claimant or partner on UC, with relation_type: SINGLE also entered, get the couple limit where main gives the single one.

🤖 Generated with Claude Code

Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/passports.yaml::Another member's income-related ESA does not exempt the family from the benefit cap, 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/childcare_element/uc_childcare_work_condition_reg_32.yaml::Reg. 32(1)(b)(i) - a partner whose own ESA lacks the support component still has limited capability for work, policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/income_floor/uc_MIF_applies.yaml::Limited capability for work (section 21), policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/income_floor/uc_MIF_applies.yaml::Limited capability for work with an ESA award lacking the support component (section 21), policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/income_floor/uc_minimum_income_floor.yaml::A claimant aged 19 in the work preparation group has a 16-hour threshold at the rate for their age

MaxGhenis and others added 8 commits October 2, 2026 05:54
HB Regs 2006 reg 75F(1)(a) and UC Regs 2013 reg 83(1)(a) lift the cap where
"the claimant or the claimant's partner is receiving an employment and
support allowance ... which includes a support component". The model lifted
it for any income-related ESA of the claimant or partner and for any
member's contributory ESA, in both is_benefit_cap_exempt_health_disability
and is_benefit_cap_exempt_other.

Add has_own_esa_award (Person): the person claims ESA themselves (a reported
contributory or income-related award, or a directly entered income-related
award attributed to the claimant who heads the benefit unit). Add
esa_includes_support_component (Person): whether that ESA includes the
support component (WRA 2007 ss.2(2), 4(4)). Datasets or users can supply it;
otherwise it is assumed for anyone with an ESA award of their own, as for
about nine in ten ESA recipients in the Family Resources Survey 2024-25.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HB Regs 2006 reg 75F(1) and UC Regs 2013 reg 83(1) name, for each benefit,
whose receipt lifts the cap. is_benefit_cap_exempt_health_disability summed
every benefit-unit member's attendance allowance, DLA, PIP, industrial
injuries benefit, Carer's Allowance and Carer Support Payment, and
is_benefit_cap_exempt_other exempted any member's armed forces compensation.
Any member with the LCWRA flag or caring responsibilities also lifted it.

Now:
- the claimant or partner only: attendance allowance, industrial injuries
  benefit and armed forces compensation (75F(1)(b)-(d); 83(1)(b)-(e));
- also a child or young person they are responsible for: DLA (75F(1)(e);
  83(1)(f));
- also a young person: PIP, Carer's Allowance and Carer Support Payment
  (75F(1)(ea), (h), (ha); 83(1)(g), (i), (ia)); armed forces independence
  payment for a Housing Benefit young person (75F(1)(ea); UC reg 2 counts
  it as attendance allowance);
- the UC LCWRA and carer elements: a claimant with limited capability or
  caring responsibilities (83(1)(a), (j); regs 27(1), 29(1)), or an element
  entered directly.

The cap serves both schemes, so a child or young person under either
scheme's definition counts. A 16-year-old who receives benefits in their own
right or is looked after counts for neither (UC regs 4(6), 5(5); HB reg
19(2)).

Datasets flag every ESA recipient as disabled, so the LCWRA exception lifted
the cap for every ESA recipient whatever their ESA group. Universal Credit
takes the ESA assessment (UC Regs 2013 reg 40(1)(a)(ii)), so for a person
with an ESA award of their own uc_limited_capability_for_WRA is now inferred
from the support component. The work allowance and the reduced minimum age
need only limited capability for work (regs 8(1)(a), 22, 39), which an ESA
award carries, so they read the new uc_limited_capability_for_work.

is_benefit_cap_exempt_earnings and is_benefit_cap_exempt_other lose their
unused benefit sums and keep their results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Inferring limited capability for work-related activity from the ESA support
component in both directions gave a non-dependant's own ESA an LCWRA element
in the family's Universal Credit, because uc_LCWRA_element and the work
allowance still read every member of the benefit unit (#2027's legacy-award
invariance test caught it). Now a person whose own ESA lacks the support
component is taken not to have LCWRA (UC Regs 2013 reg 40(1)(a)(ii)), and
otherwise uc_limited_capability_for_WRA keeps its is_disabled_for_benefits
default. uc_limited_capability_for_work is LCWRA or the disability flag, so
the work allowance and reduced minimum age are as before for every input
that does not set the new variables. The cap property's family-rate
reference now counts any 16-year-old, as the family-rate helper does.

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

The Universal Credit reform hook calculates uc_LCWRA_element while the
simulation is being built, and uc_limited_capability_for_WRA now reads
has_own_esa_award. That went through claimant_or_partner_esa_income and so
entered_directly, which read Simulation._user_input_keys before any input
had created it (test_child_and_adult_definitions_properties builds its
situations without inputs). has_own_esa_award now takes an income-related
award with no member's report behind it as entered for the family, and
entered_directly treats a simulation with no recorded inputs as having none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efit-cap-75f-claimant-partner

#2027 now leaves the benefit cap to this branch and deletes utils/inputs.py
(entered_directly), deciding a directly entered award by value. Conflicts:
- is_benefit_cap_exempt_health_disability.py: keep this branch's version;
  an entered UC LCWRA or carer element is now recognised by value (positive
  although no member's circumstances give it) instead of entered_directly.
- passports.yaml: keep this branch's support-component cap cases.
- utils/inputs.py: deleted, as on #2027.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UC Regs 2013 reg 80(1) caps "the amount to which the single person or
couple is entitled", and HB Regs 2006 reg 75A the welfare benefits to which
the claimant or couple is entitled. benefit_cap_reduction summed every
benefit-unit member's contributory ESA and JSA, incapacity benefit and SDA,
and the whole unit's income-related ESA and income-based JSA, so another
member's own award reduced the family's Universal Credit or Housing Benefit:
with UC of 20,000 before the cap, a non-dependent adult's 5,000 of ESA cut
the family's UC from 14,753 to 9,753 (#2027 review r2).

The capped total now takes the claimant's and partner's person-level awards,
claimant_or_partner_esa_income, claimant_or_partner_jsa_income, and the
family's Child Benefit, CTC, Income Support, UC and Housing Benefit. Household
income keeps the full awards. YAML tests check UC and Housing Benefit
amounts; the non-dependant invariance property now also covers
benefit_cap_reduction and universal_credit, with JSA, incapacity benefit and
SDA among the circumstances. #2027's legacy-award invariance reads the
benefit cap exemption again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The non-dependent adult's support component cannot lift the family's cap, so
the two cases do not need the input and now also run on the base package,
where they fail on value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efit-cap-75f-claimant-partner

#2027 makes "on" a legacy award payee-only: of the claimant and partner,
only the one the award is payable to (who reports it, or the claimant who
heads the family when it is entered directly) is on it (HB Regs 2006 reg
2(1), (3), (3A)). The cap's ESA head reads is_on_income_related_esa for
receipt, so a partner who does not hold the couple's award no longer
receives it. Two YAML cases now expect the payee only, and the property
reference counts an income-related award as received by its reporter. The
direct-entry attribution in has_own_esa_award already picks the same person.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efit-cap-75f-claimant-partner

#2027 now puts a member outside the couple on an income-related award only
when the award on their own report alone is positive. The cap's ESA head
reads is_on_income_related_esa only for the claimant and partner, so the
exemption is unchanged; the cap YAML and the single-head table pass.

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

Copy link
Copy Markdown
Collaborator Author

Coordination with #2084 (UC claim by a member of a couple as a single person, UC Regs 2013 reg 3(3)).

This PR counts is_claimant_or_partner as the people UC reg 83(1) names ("a claimant"). Under reg 3(3), the other member of a couple claiming as a single person is a partner but not a claimant (WRA 2012 s.40). Their PIP, DLA, AA, IIDB, AFCS, carer's allowance, contributory ESA or LCWRA does not lift the UC cap. HB reg 75F names the claimant's partner, but Housing Benefit has no single claim by a member of a couple, so the UC rule decides here.

That PR edits main's versions of is_benefit_cap_exempt_health_disability.py and is_benefit_cap_exempt_other.py to exclude that member through other_member_of_single_claim_in_shared_rules(person, period) (in utils/uc_work_related_requirements.py). That helper is false unless a single claim actually exists, and false for a family claiming legacy benefits, because the cap is shared with Housing Benefit, which has no such single claim.

Whichever PR lands second should make the claimant set in the rewritten file is_claimant_or_partner & ~other_member_of_single_claim_in_shared_rules(person, period) for the reg 83 limbs. It should also keep the YAML cases "Reg. 83(1)(g) ..." and the legacy-family case in claims_as_single_person.yaml passing.

A separate point from that PR's review, not caused by either PR: has_pensioner (any member over State Pension age lifts the cap) has no basis in UC regs 82-83. ADM E5054 has a pension-age rule only for managed migration. HB reg 75C(2)(a) does have one, so it may belong to the Housing Benefit side only.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Coordination with #2088, which models the UC childcare work condition's partner exceptions (UC Regs 2013 reg 32(1)(b)). Limb (i) is "has limited capability for work". On main the only LCW proxy is uc_limited_capability_for_WRA, so #2088's new uc_unable_to_provide_childcare reads that, just as the work allowance and the reg 8 minimum age do on main.

What changes once both merge. Under this PR, an ESA award without the support component removes LCWRA but not LCW. So whichever of the two merges second should switch limb (i) in policyengine_uk/variables/gov/dwp/universal_credit/childcare_element/uc_unable_to_provide_childcare.py from uc_limited_capability_for_WRA to uc_limited_capability_for_work. The documentation string there names the proxy, so update it too.

If this PR merges first, I'll make the switch in #2088. If #2088 merges first, please switch it here when you merge main.

Tests to add with the switch. A working claimant whose partner has has_own_esa_award: true and esa_includes_support_component: false, plus is_disabled_for_benefits: true:

  • uc_childcare_work_condition should stay true (LCW holds);
  • uc_limited_capability_for_WRA is false.

🤖 Generated with Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Coordination with #2089 (each scheme its own benefit cap and LHA shared-accommodation rules), which is stacked on #2084.

#2089 deletes the shared exception files this PR rewrites, is_benefit_cap_exempt_health_disability.py, _other.py, _earnings.py and is_benefit_cap_exempt.py, and replaces them with one set per scheme:

  • UC (UC Regs 2013 regs 79, 82, 83): is_uc_benefit_cap_exempt = is_uc_benefit_cap_exempt_earnings (reg 82) | is_uc_benefit_cap_exempt_specified_benefit (reg 83).
  • HB (HB Regs 2006 regs 75A, 75E, 75F, 5): is_housing_benefit_benefit_cap_exempt = is_housing_benefit_benefit_cap_exempt_working_tax_credit (75E) | is_housing_benefit_benefit_cap_exempt_specified_benefit (75F) | housing_benefit_pension_age_regulations_apply (reg 5).
  • Capped total: benefit_cap_reduction is now uc_benefit_cap_reduction + housing_benefit_benefit_cap_reduction. The total of welfare benefits moved to benefit_cap_welfare_benefits.

Porting this PR onto the split (whichever of the two merges second):

  1. Person scopes.
    • UC reg 83(1) "a claimant": is_claimant_or_partner & ~other_member_of_single_claim(...). Its child-or-QYP tests use the UC definitions (reg 5, including the 16-year-old window).
    • HB reg 75F(1) "the claimant or the claimant's partner": is_claimant_or_partner. Its child-or-young-person tests use is_child_or_young_person_for_legacy_benefits and the Child Benefit qualifying young person.
    • The union of both schemes' person scopes that this PR builds is no longer needed: each scheme's file takes its own scope.
  2. ESA support component (esa_includes_support_component, has_own_esa_award) goes into both specified-benefit files: UC reg 83(1)(a) and HB reg 75F(1)(a).
  3. LCWRA and carer elements: UC file only (reg 83(1)(a), (j)). Working tax credit: HB file only (75E). AFIP: UC via reg 83(1)(c) (reg 2 "attendance allowance"); HB via 75F(1)(ea), which also covers a young person.
  4. is_benefit_cap_exempt_other (any member at State Pension age) is gone. UC has no age exception, and HB reads reg 5 (the variable from Apply the benefit cap to mixed-age couples on Universal Credit, and keep them off pensioner Council Tax Reduction #1944).
  5. Claimant/partner-scoped capped total: goes into benefit_cap_welfare_benefits (one total for both schemes; the model never pays a family both).

I will push a pre-resolved composition of the two branches, uc-hb-split-on-2076, and link it here. Its per-scheme exception files carry this PR's scopes, so the second to land can take them as they are.

MaxGhenis added a commit that referenced this pull request Oct 3, 2026
…late example cases

From the independent review of 17b9b8a (no P1s; two P2s, two P3s):
- P2: the shared-rules gate read only claims_legacy_benefits, so a family
  that reports Housing Benefit but is calculated on Universal Credit
  (would_claim_uc) lost the UC rules in the LHA category, the UC housing
  costs element and the cap. The gate is now stays_on_legacy_benefits =
  claims_legacy_benefits & ~would_claim_uc, the route housing_benefit_eligible
  gives a continuing working-age HB award. YAML pairs: reports HB but claims
  UC (single cap, no exception for the partner's PIP, shared rate) and stays
  on legacy (couple rate, partner's PIP lifts the cap per HB reg 75F(1)(ea),
  no shared rate).
- P2: the examples gave LCWRA and caring to the same person and asserted a
  carer element, against reg 29(4). LCWRA and caring are now in separate
  example families.
- P3: AFCS and contributory ESA examples isolated, with positive joint and
  claimant controls on both cap exceptions; the capital property is now a
  differential against each member calculated alone; docstring notes the
  model's ESA-amount proxy for the support component and the under-16
  scope of the cap-rate oracle.
- P3: the changelog no longer says every cap exception is gone for the
  other member (an older partner still lifts it, pre-existing; #2076).

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

Copy link
Copy Markdown
Collaborator Author

The pre-resolved composition of this PR with #2089 is pushed: branch uc-hb-split-on-2076, head 31fb9dc.

  • a6f071d merges this branch (at e648b2d) into Give Universal Credit and Housing Benefit their own benefit cap and LHA shared-accommodation rules #2089. It ports this PR's person scopes into each scheme's file:

    • UC reg 83: is_uc_benefit_cap_exempt_specified_benefit. "A claimant" is the claimant or partner minus the reg 3(3) other member. A UC child or qualifying young person counts for DLA; a UC qualifying young person counts for PIP, CA and CSP. AFIP counts as attendance allowance (reg 2), claimant only. Also the ESA support component, and the LCWRA and carer elements via a claimant or an element entered directly.
    • HB reg 75F: is_housing_benefit_benefit_cap_exempt_specified_benefit. The claimant or partner. An HB child or young person counts for DLA; an HB young person counts for PIP, AFIP, CA and CSP. Also the ESA support component. No UC elements.
    • Elsewhere: WTC stays in HB's 75E file, the claimant-scoped capped total moves to benefit_cap_welfare_benefits, and is_benefit_cap_exempt_other (any member at State Pension age) is gone. UC has no age exception, and HB reads reg 5.
  • 15c5815 converts this PR's and Count only the claimant's and partner's legacy awards in means tests and passports #2027's tests to the per-scheme variables. Every changed expectation names its regulation:

    • the LCWRA and carer elements are UC only;
    • WTC is HB only;
    • a 16-year-old outside education is a UC qualifying young person but not an HB young person;
    • AFIP is a young person's exception only in HB;
    • a claimant aged 70 gets HB's reg 5 and no UC age exception.

    The capped-total amounts are unchanged.

  • 31fb9dc merges Give Universal Credit and Housing Benefit their own benefit cap and LHA shared-accommodation rules #2089's review fixes.

Checked on this branch, one process at a time:

  • policyengine-core test policyengine_uk/tests/policy: 1,943 passed.
  • test_benefit_cap_specified_benefits_properties.py: 6 passed.
  • test_benefit_cap_lha_scheme_split_properties.py: 7 passed.
  • test_legacy_award_readers_properties.py: 10 passed.
  • test_uc_single_claim_properties.py: 8 passed.

Whichever of #2076 and #2089 merges second can take these files as they are. git rerere in the local clone has the content resolutions recorded.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

#2088 has merged to main (99d0448), ahead of this PR. As my earlier comment said, the switch now falls to this PR: when you merge main, change limb (i) in policyengine_uk/variables/gov/dwp/universal_credit/childcare_element/uc_unable_to_provide_childcare.py from uc_limited_capability_for_WRA to uc_limited_capability_for_work, and update its documentation string to match.

The test to add with the switch: a working claimant whose partner has has_own_esa_award: true, esa_includes_support_component: false and is_disabled_for_benefits: true. It should give uc_childcare_work_condition: true and uc_limited_capability_for_WRA false.

🤖 Generated with Claude Code

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks Max. The scoping is right, and the tests support it. I reviewed head e648b2db5 against #2027 (b8b4d7b50).

What I checked:

  • Law. I pulled HB Regs 2006 regs 75A, 75E and 75F, UC Regs 2013 regs 39, 40, 79, 80, 80A and 83, and WRA 2007 s.2(2) from legislation.gov.uk. Your quotes match the current text. The person scopes match the 75F/83 wording head by head, including AFIP: UC treats it as attendance allowance (claimant only), and HB 75F(1)(ea) also covers a young person.
  • Tests. All passed:
    • YAML (303): gov/dwp/benefit_cap/, benefit_cap.yaml, legacy_award_readers/ and finance/benefit/family/universal_credit/.
    • test_benefit_cap_specified_benefits_properties.py: 6 passed.
    • test_legacy_award_readers_properties.py: 10 passed.
  • Probes (2025, North East), main / #2027 base / this PR:
    • A non-dependant with £5,000 of income-related ESA, and UC of £20,000 before the cap: UC goes from £20,000 (exempt) to £14,753, a reduction of £5,247. That matches the PR body.
    • The claimant's own contributory ESA without the support component, flagged disabled, rent £9,000, earnings £3,000: no LCWRA element, the £4,932 work allowance is kept, and UC is capped at £9,753. With the support component, the LCWRA element is £5,079.24 and the family is exempt.
    • Cases that are exempt on all three: the claimant's LCWRA flag without ESA, a 10-year-old's DLA, and the claimant's attendance allowance on HB only.
    • A 17-year-old in FE with PIP: capped on main and the base (reduction £4,334.60), exempt here. That is a real fix.
    • Breaking-change check. A single ESA claimant entered as esa_contrib_reported + is_disabled_for_benefits with no support-component input gives the same result here as on the base: UC £18,880.92, LCWRA element kept, exempt. Calculator users who don't set the new variables keep the LCWRA element.

Findings:

  1. Should fix. uc_limited_capability_for_work.py:29-30 changes results for inputs that set none of the new variables. The body says the opposite ("results are as before for every input that does not set the new variables"). Take someone with uc_limited_capability_for_WRA: false set explicitly, is_disabled_for_benefits: true and £6,000 of earnings, rent £9,000. They now get the work allowance: £0 → £4,932, and UC goes from £10,501.68 to £13,214.28. Before, setting LCWRA to false removed the work allowance. Fix: only add back what the ESA inference took away, e.g. uc_limited_capability_for_WRA | (is_disabled_for_benefits & has_own_esa_award & ~esa_includes_support_component) (reg 39(1)(a): ESA's own LCW determination). Then add a YAML case for the explicit-false input. If you'd rather keep the wider rule, say so in the changelog.

  2. Should fix (when you resolve against main). Main has changed all three cap files since this branch's base:

    • The carer test reads is_entitled_to_carer_benefit (378a248, 607a815).
    • benefit_cap_reduction sums over is_uc_assessed_claimant (de51949, c8f3902).
    • is_benefit_cap_exempt_other is the pension-age HB / reg 60C test (a2bd3c4, efd9dd7).

    The body's "Review of the other exceptions" and "Coordination" sections still describe #1994 and #1944 as pending. When you resolve:

    • is_benefit_cap_exempt_health_disability.py:29-31 should list is_entitled_to_carer_benefit, not the carers_allowance / carer_support_payment amounts. Otherwise a claimant whose Carer's Allowance is overlapped to nil stops lifting the cap again, a regression against main. Add a YAML case for it.
    • benefit_cap_reduction.py:46 should combine your claimant-or-partner lists with is_uc_assessed_claimant.
    • Keep main's age test and drop only the afcs / esa_contrib terms.

    Please also refresh those two body sections.

  3. Should fix (PR body). The Enhanced FRS section describes a cell of fewer than 10 records together with its members' characteristics. Please change it to "fewer than 10", as #2082 does. The other counts are ≥10 and are aggregates.

  4. Nit. is_benefit_cap_exempt_health_disability.py:138-141: take a non-dependant flagged disabled. They still add the £5,079.24 LCWRA element to the family's UC, because uc_LCWRA_element reads every member, but the family is now capped (UC £18,880.92 → £14,753). Capping is the right direction in law. Until #1989 makes the element claimant-only, though, the award "includes the LCWRA element" (reg 83(1)(a)) and is capped at the same time. A one-line note in the docstring would help, or a YAML case pinning it as a known limitation.

  5. Nit. benefit_cap_reduction.py:22 cites UC reg 80 while counting housing_benefit_pre_benefit_cap. Reg 80(2A) counts HB as nil for the UC cap, and HB reg 75F(1)(g) switches the HB cap off when the claimant gets UC. This is pre-existing and belongs in your "split the exceptions by scheme" follow-up. It would be worth naming 80(2A) there.

Mergeability: the branch merges cleanly into #2027's current head (fc1ff55d1, one commit ahead of the version merged here). As part of the stack it conflicts with main in the cap files above. Other open PRs editing the same files: #2089, #2084, #1989, #1820, #1999, #1965, #2066, #2085.

MaxGhenis added a commit that referenced this pull request Oct 11, 2026
The previous commit put this PR's set_input override beside
delete_arrays expecting git to conflict with #2076's override there. It
does not: git merges the two (`git merge-tree` exits 0), and the class
then defines set_input twice. The later definition, this PR's, replaces
#2076's, so its forget_model_value call would be lost without an error.
CI checks formatting only, not `ruff check`, whose F811 reports this.

This code-health test fails when Simulation or Microsimulation defines a
method more than once (property setters, deleters and overload stubs
aside). It fails on the tree merged with #2076, so whichever of the two
lands second must combine the overrides into one: call core's set_input,
then forget_model_value, then hold_identifiers_in.

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

MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Merge hazard with #2354 (UK hub, 2026-10-11 21:05Z): this PR and #2354 ("Fix PE-UK identifiers for inputs set before the data year", stacked on #2349) both override Simulation.set_input in policyengine_uk/simulation.py. Git merges them with no conflict, but the class then defines set_input twice and the later one wins. If #2354's definition comes later, it drops this PR's forget_model_value without any error. ruff check reports F811, but CI runs only ruff format --check.

#2354 adds policyengine_uk/tests/code_health/test_simulation_methods_defined_once.py, which fails on the merged tree. Whichever of the two lands second must merge main and combine the overrides into one method:

super().set_input(variable_name, period, value)
forget_model_value(self, variable_name, period)
hold_identifiers_in(self, variable_name, period)

Then re-run its CI after the first one merges. A run from before that merge won't see the duplicate.

Edited 2026-10-11 22:40Z: corrected the recipe. Since #2354's d2f48c8, hold_identifiers_in takes the variable name; the earlier line hold_identifiers_in(self, period) would raise TypeError.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PE-UK main fix landed (UK hub, 2026-10-11 21:42Z): #2349 merged as ed7b42f9. It fixes test_dataframe_dataset.py::test_an_exported_frame_loads_back_and_exports_itself, which failed on every PR after #2251. Merge main into this branch and push once; don't re-run an old run, because re-runs use the original commit. If this PR's only Test failure was that test, the next run should be green.

MaxGhenis and others added 2 commits October 11, 2026 22:18
…f-claimant-partner

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

`Simulation.set_input` forgot every model value whose period contained, or
was contained in, the period passed, whether or not the call stored
anything there. An input for `year:2026:2` (stored under its own key) or a
month consistent with the year (which stores nothing) then left the
rebalancing's 2026-2029 element in place but read it as an entry, so a
non-claimant's limited capability lifted the cap in those years (r6).

The override now compares, for each model value of the variable on the
branch, policyengine-core's entry for the stored input
(`Holder._user_input_storage`) before and after the call. Core replaces that
entry each time it stores an input for the period, even an equal value, so
only a period the call stored into is forgotten. It is forgotten even if the
call then raises.

Tests: r6's five periods and three controls as a parametrized test over
2026-2029, an inconsistent month (core's error, record kept), the engine's
own array stored back as an entry route, and longer-period and month inputs
in the Hypothesis sequence property.

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

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks Max. Reviewed at d4984fb34, against main 3381500f4.

The law reading still holds, and the resolutions in the main merges are right. Below: two body corrections and two notes on merging with other PRs. Nothing here blocks.

Earlier points:

  1. Not fixed: explicit uc_limited_capability_for_WRA: false.
    • The changelog now states the wider rule, which was the alternative I offered.
    • But the body ("Changes > Limited capability for work") still says results "are as before for every input that does not set the new variables". They aren't.
    • My case: a single 35-year-old in the North East in 2025, is_disabled_for_benefits: true, uc_limited_capability_for_WRA: false set explicitly, £6,000 earnings and rent £9,000. Main against this head:
      • work allowance: £0 → £4,932;
      • UC: £7,381.68 → £10,094.28.
    • With uc_has_limited_capability_for_work gone, the same person now also falls in the work preparation group (uc_limited_capability_for_work.py:32-35).
    • Please correct that sentence, and add a YAML case pinning the explicit-false input under whichever rule you keep.
  2. Code fixed, body not.
    • The carer test reads is_entitled_to_carer_benefit (is_benefit_cap_exempt_health_disability.py:49-52), and main's overlap cases in carers_allowance_overlapping_benefits.yaml pass.
    • benefit_cap_reduction reads benefit_cap_couple, which uses is_uc_assessed_claimant for a family on UC (utils/benefit_cap.py:18-24).
    • is_benefit_cap_exempt_other is main's reg 5 test, with the AFCS and ESA terms dropped.
    • The body still treats #1944 and #1994 as pending, in two places. Both merged in c2a8c35fc.
      • "Review of the other exceptions": Age "still exempts a family with any member over State Pension age"; "#1994 switches the carer test".
      • "Known violations".
    • "The head is 5d543f169" is also out of date.
  3. Fixed. The impact section no longer reports any cell below 10.
  4. Fixed. The comment at is_benefit_cap_exempt_health_disability.py:196-212 and element_included cover it.
  5. Not taken. Reg 80(2A) still isn't named. That's fine as part of #2089.

New:

A. Merging with #1820 (4a5174c8c).

  • I merged #1820 into this head, keeping this PR's _health_disability file and #1820's _earnings file. 532 of 534 gov/dwp YAML cases pass.
  • The two failures are #1820's expectations in benefit_cap/is_benefit_cap_exempt.yaml, and on both this PR has the law right:
    • "A child's Armed Forces Independence Payment excepts the benefit unit" (a 10-year-old): HB reg 75F(1)(ea) names a young person, not a child, and UC counts AFIP as attendance allowance, which only a claimant's lifts the cap. The expectation should be false.
    • "Armed Forces Compensation Scheme payments except from the benefit cap" expects is_benefit_cap_exempt_other: true. AFCS has moved to _health_disability, which is still true, so the _other line should go.
  • Whichever lands second updates those two cases. The rest of the merge is mechanical:
    • this PR's _health_disability, which already has AFIP and no duplicate earnings test;
    • #1820's _earnings;
    • either label in is_benefit_cap_exempt.py.

B. The #2354 recipe is out of date.

  • Your 21:02 comment says to combine the two with forget_model_value(self, variable_name, period). This head has no forget_model_value.
  • set_input (simulation.py:268-277) now takes model_value_input_records before super().set_input, and calls forget_replaced_model_values in a finally.
  • Merging #2354 (11b806732) gives no conflict but two set_input definitions. The combined method should be:
records = model_value_input_records(self, variable_name)
try:
    super().set_input(variable_name, period, value)
finally:
    forget_replaced_model_values(self, variable_name, records)
hold_identifiers_in(self, variable_name, period)

Please update the recipe on #2354 as well.

What I checked:

  • Law. I re-read HB Regs 2006 reg 75F and UC Regs 2013 reg 83 on legislation.gov.uk. The person scopes match head by head, including:
    • the support component for "the claimant or the claimant's partner" (HB) and "the claimant" (UC);
    • AFIP under 75F(1)(ea);
    • "entitled to" for carer's allowance and carer support payment.
  • Tests at d4984fb34, in a fresh venv on policyengine-core 3.33.2:
    • YAML in gov/dwp and finance/benefit/family: 1,522 passed.
    • 2,187 passed across the cap specified-benefits, legacy award readers, UC single claim, UC work-related groups and NI class 4 property tests, plus test_uc_rebalancing.py, test_dataframe_dataset.py and code_health.
  • Merged with main 3381500f4: no conflicts. The same YAML (1,539) and the cap property, rebalancing, dataframe and code_health tests (2,394) all pass.
  • Impact. No variable file has changed since 399da7fc1 apart from main's merge, so the Enhanced FRS runs in the body still apply to this head.
  • Leftovers. uc_has_limited_capability_for_work is no longer referenced anywhere except an old CHANGELOG entry. uc_unable_to_provide_childcare now reads limited capability for work, as agreed after #2088.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

For whoever merges main into this PR after #1820 (UK hub, 2026-10-12). An independent review of #1820 (issuecomment on #1820) found that this PR's current earnings rewrite in is_benefit_cap_exempt_earnings.py brings back the hard-coded £10,152 and total income tax. When you merge main after #1820 lands:

  • keep Fix benefit cap exemption defects (#1818) #1820's dated threshold (benefit_cap_earnings_exemption.yaml: £846 a month in 2025, £881 in 2026) and its corrected earnings calculation, including the fixes Fix benefit cap exemption defects (#1818) #1820 is still owed (per-person floors, no pension relief after 75);
  • keep AFIP and the reg 3(3) claimant masking, while tightening recipient scopes;
  • move AFCS/ESA out of _other;
  • re-run the benefit cap and single-claim property tests.

vahid-ahmadi added a commit that referenced this pull request Oct 12, 2026
A child's Armed Forces Independence Payment excepted the whole benefit
unit, because AFIP was summed over every member. HB Regs 2006 reg
75F(1)(ea) names the claimant, partner or a young person they are
responsible for, not a child under 16; for Universal Credit AFIP is
within "attendance allowance" (UC Regs 2013 reg 2), which excepts only
where a claimant receives it (reg 83(1)(c)). AFIP now counts for the
claimant, partner or a Housing Benefit young person.

The child test now expects false, with a new young-person test. The
AFCS test drops its is_benefit_cap_exempt_other assertion, which #2076
removes when it moves AFCS into is_benefit_cap_exempt_health_disability,
and asserts is_benefit_cap_exempt instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 12, 2026
Main now has #1978 (is_uc_assessed_claimant), #1896, #1944 and #2084 (the
reg 3(3) single claim, is_uc_single_or_joint_claimant). Resolution:

- uc_LCWRA_element and uc_carer_element read is_uc_single_or_joint_claimant,
  which keeps #2084's rule that the other member of a single claim is not a
  claimant, and this branch's regs 27(4), 29(2) and 29(4).
- is_uc_work_allowance_eligible reads is_uc_assessed_claimant, which keeps the
  other member of a single claim as reg. 22(3) requires, and main's
  is_responsible_for_child_or_qualifying_young_person_for_universal_credit.
- is_benefit_cap_exempt_health_disability and is_benefit_cap_exempt_other
  take main's version. The cap exceptions are now #2076's (reg 83/75F
  person scopes) and #2089's (separate UC and HB caps); this branch's cap
  test file and cap properties are removed with them.
- disability_basic_income reads is_uc_single_or_joint_claimant, like the
  element it pays on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 12, 2026
The cap exceptions are now #2076's and #2089's.

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

Copy link
Copy Markdown
Collaborator Author

Overlap with #1989, for whichever of the two merges main second.

Both PRs rewrite the limited-capability line of is_uc_work_allowance_eligible:

Reg 22(1)(b)(i) needs both changes: "limited capability for work" of "a single claimant ... or ... joint claimants". Reg 22(3) treats a single claim by a member of a couple as joint for the deduction, so the other member of a single claim counts; is_uc_assessed_claimant keeps them. The combined line is:

has_LCW = benunit.any(
    person("is_uc_assessed_claimant", period)
    & person("uc_limited_capability_for_work", period)
)

The docstrings here say the LCWRA element and work allowance "still read every member of the benefit unit". After #1989 they don't.

#1989 no longer touches any benefit cap file; the cap exceptions are this PR's and #2089's.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

More for whoever merges second, #2076 or #1989 (follow-up to the comment above).

These checks were found in #1989's independent review and read here at d4984fb. After #1989, uc_carer_element and uc_LCWRA_element count only claimants (is_uc_single_or_joint_claimant), and uc_carer_element no longer reads benunit_has_carer at all. Its per-claimant flag is the new uc_carer_element_included_for_claimant. These test premises in #2076 then no longer hold:

  • tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml
    • "A carer element the family's entered carer flag gives lifts the cap, though no member is flagged" (benunit_has_carer: true): the element becomes £0, so the expected exemption fails. To test an entered award, supply uc_carer_element directly.
    • The next case, "A nil carer element is not included in the award, though the claimant cares" (benunit_has_carer: false with a claimant carer): the claimant's caring now gives a positive element, so the expected non-exemption fails. To test a nil award, supply uc_carer_element: 0.
  • tests/test_benefit_cap_specified_benefits_properties.py at about lines 540, 629 and 699 asserts uc_LCWRA_element > 0 when only a non-claimant has uc_limited_capability_for_WRA. That becomes 0. The "a non-claimant's LCWRA does not lift the cap" conclusions still hold, but now because no element is included, not through the only_other_members guard. Tests that need a stored model amount will need a claimant with LCWRA, or an entered element.

Nothing here needs #2076 to change before #1989 lands. It is a heads-up for the merge.

MaxGhenis and others added 2 commits October 13, 2026 00:10
Brings in #2119 (UC claims at legacy closure), #2373, #1905, #2177,
#2305, #2306, #2325, #2376, #2379, #2380, #2388 and #2364.

One conflict, in the UC docs notebook: kept main's new paragraph on
claims at legacy closure and this branch's benefit cap sentence.

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

Vahid Ahmadi's review of d4984fb asked for a YAML case pinning an entry of
false for `uc_limited_capability_for_WRA`. `uc_limited_capability_for_work` is
LCWRA or the disability flag, so a claimant flagged disabled with LCWRA
entered as false keeps limited capability for work: the work allowance
(4,932 in 2025-26), the work preparation group and UC of 10,094.28, where
main gives no work allowance and UC of 7,381.68.

#2294 held `benefit_cap_reduction` at zero in
`test_other_members_awards_never_change_the_readers` because the cap counted
another member's own income-based JSA and income-related ESA (#2298). This
branch counts only the couple's, so the hold goes. With it gone the test
passes here, and on main 1d3abec it fails on its pinned family (the
council tax reduction income moves when a member with 3,000 of JSA joins).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit e3c27c7 Oct 13, 2026
16 checks passed
@MaxGhenis
MaxGhenis deleted the benefit-cap-75f-claimant-partner branch October 13, 2026 10:38
MaxGhenis added a commit that referenced this pull request Oct 13, 2026
#2261 said the benefit cap still counts every member's award, so a binding
cap could carry another member's award to a reader through Universal Credit.
#2076 merged right after it, in the same merge-queue group:
benefit_cap_reduction now counts only the couple's own awards and the
family's. The note now says so, and points to JSA_COUPLE_WITH_LODGER, which
the first property checks: it is £1,952.48 below the cap in 2025, so an added
member's £3,000 of JSA would take it over if the cap counted it. Comment only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vahid-ahmadi added a commit that referenced this pull request Oct 14, 2026
Resolve conflicts with #2399, #2076 and #1965:

- is_benefit_cap_exempt_earnings: keep main's per-claimant
  uc_individual_earned_income_before_mif test and documentation; keep this
  PR's dated threshold parameter in place of 10_152 and its reg 57 and
  FA 2004 s.188 references.
- is_benefit_cap_exempt_health_disability: take main's version (#2076) and
  add the UC route for AFIP: on a Universal Credit award only a claimant's
  counts; otherwise Housing Benefit's young person counts too.
- is_benefit_cap_exempt: take main's label.
- test_uc_single_claim_properties: keep main's AFCS assertions and this
  PR's partner-only AFIP example.

Pin main's young-person AFIP YAML case to the Housing Benefit route, and
give the specified-benefits property oracle the family's scheme. Rewrite
the changelog fragment to what the PR still changes relative to main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 15, 2026
…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>
MaxGhenis added a commit that referenced this pull request Oct 16, 2026
- 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>
MaxGhenis added a commit that referenced this pull request Oct 17, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Benefit cap counts another member's own income-based JSA and income-related ESA

2 participants