Conversation
claimant_or_partner_esa_income and claimant_or_partner_jsa_income are the income-related awards on the claimant's and partner's reported amounts, after the same capital test as esa_income and jsa_income (the income_related_esa_award and income_related_jsa_award helpers, byte-identical to the copies on #2013 and #2025). A directly entered esa_income or jsa_income is taken to be the claimant's or partner's. is_on_income_related_esa, is_on_income_based_jsa and is_on_income_support say whether the award is payable to a person (HB Regs 2006 reg 2(1), (3), (3A)): the claimant and partner through their couple's award, anyone else through an award they report themselves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and passports
esa_income and jsa_income sum every benefit-unit member's reported award, so
a member who is neither the claimant, the partner nor their dependant (for
example a non-dependent adult) fed means tests and passports that the law
ties to the claimant and partner:
- HB and CTR income (SSCBA s.136(1), HB Regs 2006 reg 25(1); CTR (PR)
(England) Regs 2012 Sch 1 para 11) and the working-age CTR passport;
- the tax credit income test (TCA 2002 s.7(2), SI 2002/2008 reg 4);
- Scottish Child Payment (SSI 2020/351 reg 18(e)-(f));
- the benefit cap health exemption (HB Regs 2006 reg 75F(1)(a));
- targeted childcare (SI 2014/2147 reg 2(1));
- would_claim_IS.
The CTR non-dependant exemption ("who is on") and the maintenance loan
benefits proxy now read the person's own award. Household totals (esa,
household_benefits, HBAI, gov_spending, benefit_cap_reduction) are
unchanged: another member's award is still household income.
Tests: 53 YAML cases computed by hand from the regulations, and Hypothesis
properties (adding such a member never changes any reader; the scoped awards
are bounded by, and without other awards equal to, the benefit-unit awards;
they match a direct reference; the person-level flags follow their
definition; Merton's non-dependant deductions are unchanged).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-readers-claimant-partner
…was input claimant_or_partner_esa_income and claimant_or_partner_jsa_income took esa_income / jsa_income as the claimant's or partner's award whenever the name was in Simulation.input_variables. That list is fixed at construction and ignores the period: an input for 2024 alone made 2025 read the benefit-unit total, so another member's 2025 award counted, and an award set later with set_input or on a branch was ignored. Both now use entered_directly (policyengine_uk/utils/inputs.py, byte-identical to #2013 and #2025), which reads core's record of each input's variable, branch and period. jsa_income.py is re-copied from #2025 (6337f31): the formula returns early while income-based JSA is inactive, before reading reports. Tests: YAML cases for an award entered for an earlier year only, and Python tests for a late set_input, a set_input after the value was cached, a branch and a late zero entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Takes policyengine_uk/simulation.py and policyengine_uk/utils/inputs.py byte-identical from 48abd88 on #2025 (is-remunerative-work-jsa-conditions). In policyengine-core 3.32.9, delete_arrays keeps the deleted arrays' _user_input_keys entries, and clone shares the record. A formula value could then look like a direct input, after a deletion or an input on a clone or on a parent after a branch. The UK Simulation now gives each clone its own copy of the record and drops the entries for deleted arrays, and entered_directly asks about the stored value the simulation actually reads. test_claimant_or_partner_awards_entered_directly.py mirrors #2025's lifecycle cases on claimant_or_partner_esa_income and claimant_or_partner_jsa_income. 6 of its 12 cases fail without the Simulation changes. #2025's own test file asserts its s.124(1)(f) JSA bar, which this branch does not have, so it is not copied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2013 and #2025 replaced entered_directly and the Simulation overrides with a value rule, after #2025's round-3 review found the record of direct inputs unsound in policyengine-core 3.32.9: - a Simulation without inputs has no record; - the YAML runner and SimulationBuilder bypass the UK Simulation; - disk-backed clones share files; - branch names and ETERNITY periods diverge; - delete_arrays becomes O(keys). This merge takes their removal of policyengine_uk/utils/inputs.py and their simulation.py (identical to 63b633c's). claimant_or_partner_esa_income and claimant_or_partner_jsa_income now mirror the gate. Where esa_income / jsa_income holds what the reported awards give, either after the capital test (the formula) or as their plain total (disable_simulated_benefits), they use the claimant's and partner's reported awards. Otherwise they take the stored value to be the claimant's or partner's: an award entered directly, or a reform that replaces or removes it. An entered award equal to either amount is read through the reports. test_claimant_or_partner_awards_entered_directly.py gains cases for an entered award equal to the reports, the raw total with capital (partner £200, other adult £3,000, savings £10,000: £0), and a neutralised award. The lifecycle cases (deletion, clone, branches) keep their expectations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…acy-award-readers-claimant-partner
…t cap left unscoped Review r2 of #2027 (subfleet job 20261002-044711) and #2013/#2025's latest value-rule refinements (0953c8a, 081ec14): - claimant_or_partner_{esa,jsa}_income: a stored zero is zero. References are compared at the stored float32 precision. When the stored award is the plain total of the reports (the disable_simulated_benefits reform), the claimant's and partner's plain reported total passes through unscreened, so a claimant's own £200 at £10,000 capital is no longer zeroed. Entering these variables directly is the explicit way to state ownership. - is_on_income_related_esa / _based_jsa: another member's own report counts only while the benefit unit's modelled award is positive, so a reform that removes the benefit takes the status with it. is_on_income_support: likewise only while Income Support is active and not neutralised. - is_benefit_cap_exempt_health_disability is reverted to the benefit unit's esa_income. benefit_cap_reduction (out of scope) still caps every member's ESA and JSA, so scoping the exemption alone would cap the claimant with another adult's own award. Both go to the benefit-cap follow-up. - The property generator fixes claims_all_entitled_benefits per benefit unit (it sums reports across the simulation). SCP comments name the kinship route. The childcare citation is reg 1(2), not 2(1). Changelog wording matches the paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-readers-claimant-partner
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>
Response to review r2 of #2027 (job 20261002-044711)Reviewed head 57b37fd. The fixes are in 3f31347, and the merge of #2013 0953c8a is 3803dda.
Also adopted from #2013 and #2025's latest refinements: a stored zero is zero ( |
…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>
|
Cross-reference from #2000, the HB passport for IS, income-based JSA and income-related ESA (fixes #1993). #2000 meets its merge gates and is on the merge train. #2000 adds (benunit("income_support", period) > 0)
| (benunit("jsa_income", period) > 0)
| (benunit("esa_income", period) > 0)It zeroes When this PR meets #2000 on main, the receipt test is one more reader of the whole-benefit-unit 🤖 Generated with Claude Code |
A person is on income-based JSA or income-related ESA on a day it "is payable to him" (reg 2(3), (3A)), and a person on income support is one "in receipt of" it (reg 2(1)). A couple's award is paid to the claimant, not the partner. is_on_income_related_esa, is_on_income_based_jsa and is_on_income_support now hold, of the claimant and partner, only for the payee: the one who reports the award, or the claimant where neither does (is_payee_of_couple_award). Another member's report never moves the payee. Anyone else is unchanged (their own report while the award is modelled as paid). Agreed with #2007, whose round-2 review raised the same point and which applies the same reading through its is_award_payee helper; the codebase now reads "who is on" one way. Effects: in the local working-age CTR schemes a non-dependant couple on one of these benefits pays the partner's deduction (one deduction per couple, the higher: Default Scheme Sch para 30(3)); the maintenance-loan ESA proxy follows the student's own award, not their partner's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
is_on_income_related_esa and is_on_income_based_jsa gated a member outside the couple on the benefit unit's award over every report. Another member's report could then lift that award above the tariff income and put them on it: an excluded student's loan schedule or a Merton non-dependant deduction changed when another adult was added (review r3's probes on 3803dda). They now ask whether the award on that member's own report alone is positive: the report exceeds the tariff income from the benefit unit's capital, within the capital limit, while the benefit unit's award is positive. Once the member reports, the capital test reads the household's capital whoever else reports, so no other report can change the result. The property generators now give qualifying young persons and an existing outside adult reports of their own, and the Merton property has savings and an existing outside adult; both failed on the old code. Two YAML cases are hand-computed from ESA Regs 2008 reg 118. The maintenance-loan proxy cites SI 2011/1986 reg 71(1)(h)(iii), reg 61(2)(b) and HB Regs 2006 reg 56(2)(a). 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>
Fixes #2026
Summary
esa_incomeandjsa_incomesum every benefit-unit member's reported award. That is right for household income. But a member who is neither the claimant or partner nor a child or young person they are responsible for, such as a non-dependent adult, also had their award read by means tests and passports that the law ties to the claimant and partner. This PR audits each reader against the governing regulation. Readers that name the claimant and partner now read a new claimant-and-partner award; person-level readers read the person's own award. The aggregation definitions of household totals are unchanged, and the measured change on the Enhanced FRS is £0.Stacked on #2013 (
is-eligibility-claimant-partner-gates, merged in at0953c8aa6), which is stacked on #2001 and then #1896.New variables
claimant_or_partner_esa_incomeandclaimant_or_partner_jsa_income(BenUnit): the income-related award of the claimant and partner. They use theincome_related_esa_awardandincome_related_jsa_awardhelpers, which are byte-identical to the copies on Apply the Income Support conditions to the claimant and partner only #2013 and Apply the Income Support remunerative work and JSA conditions (s.124(1)(c), (f)) #2025. Whatesa_income/jsa_incomeholds is decided by value, as Apply the Income Support conditions to the claimant and partner only #2013's Income Support gate does:disable_simulated_benefitsreform sets), this is the plain total of the claimant's and partner's reports;An entered
esa_incomeequal to either reported amount is read through the reports. To state the claimant's or partner's award explicitly, whatever other members report, enterclaimant_or_partner_esa_income/_jsa_incomedirectly. Two earlier versions detected direct entry throughSimulation.input_variables, then through core's private input record (_user_input_keys). Reviews on Apply the Income Support remunerative work and JSA conditions (s.124(1)(c), (f)) #2025 showed both were unsound in policyengine-core 3.32.9; Apply the Income Support conditions to the claimant and partner only #2013, Apply the Income Support remunerative work and JSA conditions (s.124(1)(c), (f)) #2025 and this PR now share the value rule.is_on_income_related_esa,is_on_income_based_jsaandis_on_income_support(Person): whether the award is "payable to" the person (HB Regs 2006 reg 2(1), (3), (3A); the CTR schemes define it the same way). A couple's award covers both partners, so the claimant and partner are both on it when their award is positive; this keeps the model's existing couple convention. Anyone else is on an award only through their own report, and only while the benefit unit's modelled award is positive (for IS: while IS is active and not removed by a reform). A reform that removes the benefit therefore takes the status with it.Reader by reader
housing_benefit_applicable_incomeclaimant_or_partner_*council_tax_reduction_applicable_incomeclaimant_or_partner_*council_tax_reduction_relevant_income_based_benefitclaimant_or_partner_*_legacy.py)is_on_*tax_credits_applicable_income(exempt benefits)claimant_or_partner_*is_scp_eligibleclaimant_or_partner_*qualifying_benefitsclaimant_or_partner_*maintenance_loan_entitled_to_benefitsis_on_income_related_esa, alongside the existing parent and disability testswould_claim_IS(and soincome_support_assessable_capital)claims_all_entitled_benefitsis_benefit_cap_exempt_health_disabilitybenefit_cap_reduction, out of this PR's scope, caps every member's ESA and JSA. Scoping the exemption on its own would cap the claimant with another adult's own award in the capped sum (review r2). Both halves go to the benefit-cap follow-upis_benefit_cap_exempt_earnings,is_benefit_cap_exempt_otherincome_supportreadersincome_supportalready needs the claimant's or partner's own award (#2013)Aggregation definitions not changed:
esa,jsa,household_benefits, HBAI,gov_spending,benefit_cap_reduction, and the ESA/JSA capital allocation (*_assessable_capital), which is part of the award's own definition.Behaviour beyond the other member's own award
Person-level "on" is now each person's own award:
maintenance_loan_entitled_to_benefits. None is in higher education, so no maintenance loan changes; see the impact section.Invariants (property-tested)
Adding a member who is neither the claimant, the partner nor a dependant, whatever Income Support, income-based JSA or income-related ESA they report, never changes:
would_claim_ISandincome_support_eligible;is_scp_eligible,maintenance_loan_entitled_to_benefitsoris_on_*.This holds when the awards are formula-computed, with the take-up mode fixed per benefit unit:
claims_all_entitled_benefitssums reports across the whole simulation. An enteredesa_incomethat happens to equal the reports is read through them; entering the scoped variable states ownership.0 <= claimant_or_partner_esa_income <= esa_income, likewise for JSA, with equality when no other member reports the award.The scoped awards equal a direct reading of the regulations: the claimant's and partner's reports less tariff income of £1 a week for each £250 or part above £6,000, nothing above £16,000 (ESA Regs 2008 regs 110, 118; JSA Regs 1996 regs 107, 116). This is a differential test against an independent Python reference.
is_on_*is the couple's award for the claimant and partner, and for anyone else their own report while the benefit unit's modelled award is positive.Adding such a member to a non-dependant benefit unit never changes the existing members' Merton non-dependant deductions.
Tests
tests/policy/baseline/finance/benefit/family/legacy_award_readers/, each computed by hand from the regulation and parameter values. Every changed reader has a case with an excluded adult. On the base (0953c8aa6), 42 cases fail: 17 on value (every excluded-member case for an existing reader) and 25 only because the new variables do not exist there.test_legacy_award_readers_properties.py: the invariants above, an exact-difference check that the claimant's and partner's own awards count in full in HB and CTR income, and 4 direct-entry tests.test_claimant_or_partner_awards_entered_directly.py: 25 cases across ESA and JSA. They cover deletion, clones, branches, an entered award equal to the reports, the raw total with capital, a neutralised award, a stored zero, float32 precision, explicit ownership through the scoped variable, and a removed award taking an excluded student's status with it.3803dda0d: YAML 1,572 passed; pytest 2,077 passed, 45 skipped.ruff formatandruff checkare clean.Enhanced FRS impact
Real
Microsimulationruns on a private copy of the Enhanced FRS 2024-25 (sha256e433e532…), for two package states: base0953c8aa6(#2013) and this branch at3803dda0d. Earlier pairs of runs at each intermediate head, back tob1977d936againsta135639b2, gave the same results. Every year 2025-2030 changes by £0, for every benefit, household total (includinggov_spendingandhbai_household_net_income) and poverty rate. 107 of the 108 arrays compared are identical.The one array that changes is
maintenance_loan_entitled_to_benefits, on 64 dataset records (about 20,000 people weighted). All are children or qualifying young persons aged 19 or under whose parent is on income-related ESA. None has a maintenance loan, andmaintenance_loanis identical.Why the effect is zero: the dataset has 74 records (about 18,000 people weighted) of members who are neither claimant, partner nor dependant. None of them reports income-related ESA, income-based JSA or Income Support. None is in a benefit unit whose claimant or partner reports any of them, so the non-dependant and maintenance-loan cases cannot arise either. The H5 inputs and per-record NPZ arrays stay private; the summaries are aggregates.
Review
3f3134755; the response is in the PR thread. A follow-up review covers the delta.Axiom
axiom: TheAxiomFoundation/rulespec-uk#416 queued
Of these provisions only SSI 2020/351 reg 18 is in the corpus, and its module encodes only 18(b). HB reg 25, Sch 5 and reg 75F, SSCBA s.136, TCA 2002 s.7, SI 2002/2008 reg 4, SI 2014/2147 reg 1, SI 2012/2885 Sch 1 paras 8 and 11, SI 2012/2886 and SI 2011/1986 reg 61 are not ingested. The issue holds the verbatim law, the required outputs, a pasteable
review_findingand companion tests taken from this PR's YAML.Coordination and follow-ups
income_related_jsa_award. Once Apply the Income Support conditions to the claimant and partner only #2013, Apply the Income Support remunerative work and JSA conditions (s.124(1)(c), (f)) #2025 and this PR have all landed,income_support_eligiblecan readclaimant_or_partner_esa_incomeandclaimant_or_partner_jsa_incomeinstead of calling the helpers.in_receipt_of_income_support_jsa_ib_or_esa_ir, the HB passport;housing_benefit_non_dep_deduction_exempt;benefit_cap_reduction(UC Regs 2013 reg 80(1)). These are handed over with review r2's reproduction.disable_simulated_benefitsreform: it crashes onattendance_allowance_reported, and its later years copy unuprated reports. It needs per-year claimant/partner routing shared with the IS gate limbs.🤖 Generated with Claude Code