Repository navigation
Conversation
…lags Unflagged inputs (calculators, policyengine.py, the APIs) kept the under-20 limit because survey childless couples carry no flags. Once the dataset supplies is_claimant_or_partner from the FRS adult table (policyengine-uk-data), the presumption only applies to inputs without roles, so it can follow the FRS base rate: among co-resident HBAI adults (younger 20+), pairs 16-19 years apart are mostly couples (61%) and pairs 20+ apart mostly not (12% at 20-24, under 1% at 25+). - New parameter presumed_child.minimum_age_gap_at_any_age (20): a member at least that much younger is presumed the claimant's child at any age. A member flagged as a parent can still be the partner. - YAML: 50 + 25 unflagged is now single; the 20-year boundary (19 vs 20, and at 90/70); a flagged partner 25 years younger stays the partner; an unflagged 60/38 couple with a child is a lone parent (documented trade-off) unless roles are supplied. The under-20 boundary case moves to an 18-year gap so it still tests the age limit. - Properties: the reference rule follows; the FRS-shape differential now says the change touches only childless couples 20+ years apart (intended), and supplied roles are always kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…imant-partner-unflagged-gap The any-age 20-year gap is OR'ed onto #2040's presumption. It changes two of #2040's documented limitations where the adult is 20+ years younger (a flagged lone parent with an adult child and a younger child; a flagged grandparent with a daughter and grandchild): both are now read correctly. A 16-19-year case keeps the documented limitation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exhaustive over two unflagged adults aged 20-90 (head the elder): the younger is the partner exactly when under 20 years younger. The Hypothesis properties rarely draw an exact 20-year adult pair, so threshold mutants (21, 19, strict >) were caught only by one YAML boundary case; this test kills all three on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riants - The 70/45 Lifetime ISA and 79/54 Pension Credit couples supply is_claimant_or_partner and assert it, keeping their capital and eligibility expectations (the new presumption would otherwise split them). - The valid-family property now checks membership against the reference rule exactly and states the one intended exception (an unflagged couple whose head is 20+ years older); module docstring updated. - Deterministic test: a 70/45 couple with each head order, inferred and with supplied roles. YAML: a younger head 20 years below the other adult keeps the couple. - Docstring and differential comment: the split applies when the elder is the claimant. - Reference HBAI helper: an identified parent must be 16 or over, as in is_hbai_dependent_child; the under-16-parent regression now also checks the reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI on d8fc744: two property tests build couples with random ages and no roles, so the 20-year presumption split the wide-gap ones (30/67, 78/49, 93/35) and their independent expectations failed. Their generators now supply is_claimant_or_partner (true for the adults, false for children in the UC test), since the units are couples by construction. The strict xfail for #1942 keeps failing only for its own reason. Review r2: the changelog, test comments and YAML header no longer claim survey datasets supply the role. Only datasets that carry it (the enhanced FRS once policyengine-uk-data#524 ships) are unaffected; the bundle's populace_uk_2023 and 2023-24 FRS overlays carry no role. The FRS-shape differential exempts only units whose head is 20+ years older, and the head-order test checks every person. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to claimant-partner-unflagged-gap
#2040's docstring example (a flagged 50 with a 25-year-old becomes a couple when a 12-year-old is added) does not hold with the any-age gap: a member 20+ years younger stays the claimant's child either way. The example now uses a 16-19-year gap (38 and 20), checked against the model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merging main brought ten YAML cases (benefit cap for pension-age claimants, CTR pensioner status, the HB earnings disregard) that enter a mixed-age couple aged 70 and 50 without roles. Exactly 20 years apart, the younger partner is presumed the claimant's child, so each case now supplies is_claimant_or_partner for the two partners. Expectations are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Full YAML suite on the result: 1,935 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_mixed_age_couple_properties and the P6 test in test_benefit_cap_and_ctr_pension_age_properties build couples with random ages (for example 67 and 25) and no roles, so the 20-year presumption split them. Both now supply is_claimant_or_partner for the two partners. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lagged-gap One conflict: main also supplies claimant/partner roles in the UC State Pension property generator; kept main's code and widened its comment to mention partners 20 or more years younger. Full YAML suite: 1,985 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Full YAML suite on the result: 2,006 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's new test_council_tax_reduction_pensioner_earnings_disregard_properties builds two-adult families with independently drawn ages and no roles; the 20-year presumption splits the wide-gap ones. The adults now supply is_claimant_or_partner (children false). 7 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR CI on 73a7b90: test_scottish_carer_supplement_is_not_means_tested_income drew a pension-age couple 25 years apart (67 and 92) with no roles, so the 20-year presumption split it. Every member of these units is an adult of the couple, so each now supplies is_claimant_or_partner. 3 passed; it was the only failure among 2,693 Python tests and 2,006 YAML cases in that run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Thanks Max. The rule itself is right and tightly scoped. At 12962b0 I checked it against the docstring and the YAML: gap >= 20 ORed onto #2040's rule (is_claimant_or_partner.py:99-108), with a flagged younger member still taking the partner slot through other_parent. Ages 50/31 stay a couple, 50/30 and 90/70 split, and the boundary uses >=. At the head, the six changed YAML files pass (32 + 24 + 13 + 11 + 8 + 30 cases), and so do the seven changed property files (36 passed, 1 xfail), each run in its own process. WRA 2012 s.39(1) and SSCBA 1992 s.137(1) define a couple by marriage, civil partnership or living together "as if they were a married couple or civil partners", with no age condition, so this is purely a presumption for role-less input. The parameter files say that, and they don't cite statute.
I compared calculator-style situations for 2026 (ages only, one benefit unit and one household) on main b5b0923 and on this branch with main merged in. In every case below, results are identical to the penny once the adults are flagged is_parent, roles are supplied, or the younger adult is the head. With ages only, they move as follows:
| Ages (no roles) | main | this PR |
|---|---|---|
| 50, 30 | couple, UC £8,003.64 | single, UC £5,098.80; net income −£2,904.85 |
| 50, 30, younger earns £20k | couple, UC £0 | single, UC £5,098.80; net income +£4,846.80 |
| 60, 38, child 10 | couple, UC £12,226.20 | lone parent, UC £9,321.36 |
| 45, 24, child 2 | couple, UC £11,650.92 | lone parent, UC £8,746.08; net income −£2,904.84 |
| 70, 48 | mixed-age couple on UC £8,003.64 | single pensioner on Pension Credit £12,376.00; net income +£4,372.36 |
| 67, 45, rent £9,600 | UC £17,003.64 | PC £12,376.00 + HB £9,600.00; net income +£4,972.36 |
| 67, 45, rent £9,600, younger earns £15k | UC £8,989.26 | PC £12,376.00 + HB £9,600.00; net income +£12,734.74 |
-
Blocking: main has added role-less mixed-age couples that this rule now splits. I merged origin/main (b5b0923) into the head locally. The full YAML suite then gives 2,444 passed and 4 failed. Each failure is a fixture added on main after your last merge (325d585) that enters a 20+ year-gap couple without roles:
finance/benefit/family/housing_benefit/housing_benefit_universal_credit_passport.yaml:240(70/40)finance/benefit/family/housing_benefit/pension_age_premiums.yaml:281(50/70)finance/benefit/family/universal_credit/income/uc_earnings_deductions.yaml:62(70/45;uc_earned_incomecomes out 0 against an expected £12,879.60)gov/dwp/pension_credit/state_pension_credit_qualifying_age.yaml:544(Case 23, 50/70;pension_creditcomes out £12,376 against an expected 0)
Alsotests/test_council_tax_reduction_carer_income_properties.py:155-165, a generator added on main that draws working-age couples aged 25 to 60 without roles. Hypothesis fails it on a 25/57 pair (full pytest on the merged tree: 2,725 passed, 1 failed).
All five pass on main. Fix: merge main and supplyis_claimant_or_partner: truefor both partners in these four YAML cases and in that generator, as you did for the others. That merge also clears the current red Test job: the failure iscode_health/test_cached_arrays_not_written_in_place.py(core 3.32.16'sput(..., derived=...)), which main fixed at 4ac62cb and which passes after the merge (35 passed). The only textual conflict is a comment intests/test_uc_state_pension_properties.py(about line 114 after the merge). Resolve just that hunk and don't take--oursfor the whole file: main's #1947 rework of invariants 2 and 4 auto-merges into the same file, and taking your side whole makestest_tax_on_state_pension_does_not_reduce_partners_earned_incomefail. With the hunk-only resolution, the file passes (5 passed, 1 xfail).
-
Blocking (merge gate): don't merge until policyengine-app-v2#1240 is merged and deployed. Today the app sends "you" and a declared partner with ages only, and #1240 is still a draft. A merge to main publishes to PyPI by itself, so as soon as the API picks the release up, a real mixed-age couple entered in the calculator (67 and 45) moves from UC to Pension Credit plus full HB, and the partner's earnings drop out of the means test (+£12,735 above). The body already says #1240 "should land before or with this PR"; I'd make that a hard gate in the PR description and hold the merge until #1240 is live. Households saved earlier with ages only also need re-saving (or a backfill) before they pick up roles.
-
Should fix: the split reading leaves the "adult child" uncharged. Once the 45-year-old is presumed the claimant's child, they are an adult in the claimant's benefit unit but not a claimant or partner, and main charges no non-dependant deduction for that. For the 67/45/£15k case,
housing_benefit_non_dep_deductionsis £0 and HB is the full £9,600. The same people in separate benefit units get a £3,346.20 deduction and HB of £6,253.80. So the PR's intended reading (parent plus adult child) is costed more generously than the law would cost it, until in-unit non-dependant deductions land (#2017, and #2077 for local CTR). Either sequence this after #2017, or add a sentence to the docstring (is_claimant_or_partner.py:39-46) and the changelog saying the presumed adult child is not yet charged as a non-dependant. -
Nit: pin the young-child case.
45, 24, child 2with ages only now reads as a lone parent with an adult child, and UC drops by £2,904.84. #2040's young-child exception only fires when the claimant is flagged, so it doesn't help unflagged input. The body's evidence is for adult pairs without conditioning on a young child in the unit, so I'm not claiming this is wrong. But it's a common calculator shape, so it deserves a YAML case inbenefit_unit_structure.yaml(next to the 60/38/10 trade-off case at line 502) so the behaviour is explicit. -
Nit: overlaps. Once main is in,
test_uc_state_pension_properties.pyis also touched by #2081, #1963, #1999 and #2131, andtest_child_and_adult_definitions_properties.pyby #2030 and #2016. Whichever lands second should keep the supplied roles in the generators.
The PR body is clean on disclosure: survey-household counts suppressed under 10 and weighted totals only, with no record ids, weights or record-level amounts.
The review of 94041e9 found that holding every family's own awards still let property 4 fail through the same channel, and hid cross-family reads of awards: - In Newham, when the head's family stops claiming Universal Credit, the non-dependant moves into the sharer's claim and raises its uc_maximum_amount, which the four _legacy councils use as a UC family's applicable amount. The sharer's council award moved from 241.67 to 417.02 with its awards held. - Holding every family's awards also gives a perturbed 85-year-old head the award of the 47-year-old it replaced, and a mutant that read another family's UC survived. Property 4 now holds, with each family's CTR means test, the two household allocations through which other families reach a family's own awards: uc_non_dependants_counted (UC Regs 2013 Sch 4 para 9(2)) and has_non_dependant_for_severe_disability_premium. Each family's awards are computed. The body passes on CI's population, its minimal household and the reviewer's counterexample, and catches all five mutants the reviewer and author built. The generator also sets is_claimant_or_partner on every family's adults, so wide-gap couples stay couples once #2082 presumes a member 20 or more years younger to be a child. build() keeps each held value's own type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and exemption (#2015) * Assess each Council Tax Reduction claim under its own family's scheme and exemption In a shared-rent household every family liable for the rent claims, but the pensioner scheme and the non-dependant exemption were picked once per household from the head's family. SI 2012/2885 reg 3 (and SI 2012/2886 Sch para 3, SI 2013/3029 reg 3, SSI 2021/249 reg 3, SSI 2012/319 reg 12) define "pensioner" per person and partner, and the exemption applies where "the applicant or his partner" is blind or gets a listed benefit (SI 2012/2885 Sch 1 para 8(6) and equivalents). - council_tax_reduction_pensioner (BenUnit) replaces the household flag; the name and entity match #1944 and #1966, which extend the same definition. - council_tax_reduction_applicant_has_non_dep_exemption (BenUnit) replaces the household exemption; the councils' schemes apply it per claim. - council_tax_reduction_scheme_supported is per family, so each family falls back to its reported reduction only where its own scheme is unmodelled. - Councils' person-level non-dependant deductions no longer depend on the head's family; each claimant's own scheme decides whether to use them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reconcile reported and simulated reductions within a household With the scheme chosen per family, one household can hold a simulated claim and a family whose scheme is not simulated. Review r1 found that a family that cannot claim then kept its reported reduction beside a head whose simulated award already covered the whole bill: £3,000 against £1,800. - A family that cannot claim keeps a reported reduction only where no claim in its household is simulated (new household flag council_tax_reduction_household_has_simulated_claim). Without sharers this is the base behaviour. - A jointly liable claimant's reported reduction is limited to its own share of the council tax (SI 2012/2885 Sch 1 para 7(3)-(4)), the most any scheme could give it. Tests: three YAML cases; the property tests now generate reported reductions, check the fallback and the household bound, and toggle one claimant's exempting benefit to show it leaves the other claim unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Set a claim's scheme and exemption from the applicant and their partner #2009 now names the liable people directly. A household head can be liable without being the claimant or partner of their family (a grandparent heading a family formed around a young couple), so "the applicant or his partner" (SI 2012/2885 reg 3, Sch 1 para 8(6)) is not always the family's claimant and partner. New person-level council_tax_reduction_applicant_or_partner: the liable member, and the other member of the couple where the liable member is the claimant or partner, whatever the partner's age; in a family that cannot claim, its claimant and partner. The pensioner flag and the applicant exemption read it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Use #2078's CTR applicant-or-partner variable #2078 (also stacked on #2009) adds is_council_tax_reduction_applicant_or_partner and council_tax_reduction_head_applies_alone for the CTR means test. They define the same people as this PR's council_tax_reduction_applicant_or_partner: the claimant and partner where the liable person is one of them, the liable head alone otherwise, and the claimant and partner in a family that cannot claim. Take #2078's two files byte for byte, so whichever PR lands second merges without conflict, and point the pensioner flag and the applicant exemption at it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cap a reported reduction at the claimant's share only beside a simulated claim Review r2 found that capping every jointly liable claimant's reported reduction at its share could discard a lawful report: the model's divisor (#2006's joint-liability share) counts students that SI 2012/2885 Sch 1 para 7(5) leaves out. The cap exists to stop a reported claim and a simulated claim together exceeding the bill, so it now applies only where another claim in the household is simulated, on the share that claim uses. Where nothing is simulated, reports are kept as reported, as on the base. Tests: - YAML: a jointly liable claimant keeps its whole report where nothing is simulated; a pension-age grandmother in working-age applicants' family does not make their claim a pensioner's (an intended change under reg 3(1): the base paid £1,800 on the pensioner scheme). - Properties: the fallback oracle follows the narrowed cap, and property 4 compares council_tax_benefit wherever the household's reconciliation is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Apply the reported-reduction cap only beside a simulated claim that pays On the payer build, the only remaining change came from heads whose reported reduction was capped beside a sharer whose simulated claim paid nothing. With no simulated reduction competing for the bill there is nothing to reconcile, so the cap now applies only where the simulated claims in the household pay something. The household bound still holds: each simulated award is within its share, and a capped report within its own. Tests: a YAML case where the simulated claim beside a report pays nothing; the fallback oracle, the bound and property 4's comparison follow the paid condition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test the per-family pensioner flag against #1944's reg 3 conditions council_tax_reduction_pensioner is now #1944's SI 2012/2885 reg 3 test on this PR's members: the qualifying age for State Pension Credit, and no Income Support, income-based JSA, income-related ESA or Universal Credit award. Property 1's oracle computes that directly. A family's own Universal Credit award depends on the rent it pays, which changes when it lives alone, so property 2 compares the families whose own awards are the same either way (and checks there are some). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Address the after-merge review: student share, head's own awards, P5 - council_tax_benefit: the share leaves out only people in higher education, so where a jointly liable person it counts is in other education (who may be a student SI 2012/2885 Sch 1 para 7(5) leaves out), the share may be too small; a report is then kept rather than capped. - council_tax_reduction_pensioner: where the household head applies alone, the benefit unit's Income Support, income-based JSA, income-related ESA and Universal Credit are its claimant and partner's, not the applicant's (as #2078 does for council_tax_reduction_relevant_income_based_benefit). - Two YAML regressions: a report beside a paying simulated claim where a further-education student is counted in the share, and a grandmother head who stays a pensioner when the young parents get Income Support. - Property test: the alone simulation is given each family's own awards instead of filtering; some generated adults are further-education students, and properties 5 and 6 follow the new rule. - test_benefit_cap_and_ctr_pension_age_properties P5 compares against a reform of the cap exception alone, so the CTR pensioner test (which also sets the applicable amount) is held fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hold the means test fixed in property 4 CI found that swapping the other claiming families' ages and disability benefits can move a family's simulated award through its applicable amount: the model's severe disability premium counts the other adults in the household (has_non_dependant_for_severe_disability_premium), joint occupiers included. That is main's means test, not the scheme or the exemption, so the perturbed run is given each family's applicable amount and income from the unperturbed one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read the student test and in_FE for the cap's student guard The re-review of 38364ca found that the guard missed in_FE, and that current_education's age default (tertiary at 18 and 19) turns it on with no education input. The guard now uses the model's student test for non-dependants (_legacy.is_full_time_student_non_dep) or in_FE, for a jointly liable person the share counts. The age default is kept deliberately: in the enhanced FRS current_education is an observed input, and the same test already treats an 18- or 19-year-old non-dependant as a student. The documentation says so, including that part-time students and students para 75(2) brings back in are treated the same way. Regressions: the student shown by in_FE (fails on 38364ca), and a jointly liable sharer with no education input at 19 (report kept) and 20 (capped). The property generator makes students by current_education or by in_FE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hold each family's own awards fixed in property 4 CI on d988725 (run 37383946508) failed property 4 on a Newham household: a working-age head (47) claiming Universal Credit, a sharer couple aged 100 and 63 with earnings, and a non-dependant. Perturbing the head to 85 moves the non-dependant into the sharer's claim (uc_non_dependants_counted: the household's non-dependants count in one claim only, UC Regs 2013 Sch 4 para 9(2), the head family's where it claims). The sharer's size criteria rise from one bedroom to two, its LHA cap from 9,000 to 10,860 and its award from 0 to 1,288.29, and reg 3(1)(a)(ii) then makes it not a pensioner. That is the family's own award, which the pensioner test is meant to follow, so the change is intended. Property 4 now holds each family's own UC, IS, JSA and ESA awards fixed, as property 2 already does, along with its means test. The same population fails on a9466b8, so this predates the merge of main; CI on a9466b8 did not generate it. With the awards held, a household-level pensioner flag still fails property 4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hold the household allocations, not the awards, in property 4 The review of 94041e9 found that holding every family's own awards still let property 4 fail through the same channel, and hid cross-family reads of awards: - In Newham, when the head's family stops claiming Universal Credit, the non-dependant moves into the sharer's claim and raises its uc_maximum_amount, which the four _legacy councils use as a UC family's applicable amount. The sharer's council award moved from 241.67 to 417.02 with its awards held. - Holding every family's awards also gives a perturbed 85-year-old head the award of the 47-year-old it replaced, and a mutant that read another family's UC survived. Property 4 now holds, with each family's CTR means test, the two household allocations through which other families reach a family's own awards: uc_non_dependants_counted (UC Regs 2013 Sch 4 para 9(2)) and has_non_dependant_for_severe_disability_premium. Each family's awards are computed. The body passes on CI's population, its minimal household and the reviewer's counterexample, and catches all five mutants the reviewer and author built. The generator also sets is_claimant_or_partner on every family's adults, so wide-gap couples stay couples once #2082 presumes a member 20 or more years younger to be a child. build() keeps each held value's own type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hold the CTR capital in property 4 and pin its counterexamples Pension Credit's guarantee credit sets a family's CTR capital to nil, and the model lets other families reach that guarantee credit twice: the severe disability addition's residence condition counts every other adult in the household, and pension_credit_assessable_capital divides the household's savings among its pension-age adults. Property 4 held the CTR applicable amount and income but not the capital, so a head reaching pension age, or gaining Attendance Allowance, could move a pension-age sharer's award from nil to its £900 share of an £1,800 bill (review r5, finding 1). Hold council_tax_reduction_assessable_capital with the rest of the means test, name the two Pension Credit routes in the docstring, and pin as Hypothesis examples the four populations that broke earlier versions of the property: CI's Universal Credit non-dependant move, the uc_maximum_amount move, and the two Pension Credit routes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Refs #2039. Follow-up to #2040 (merged 3 October), kept separate because it changes calculator results for unflagged inputs, which is a methodology call (Max ruled d842: adopt).
What changes
is_claimant_or_partnergains one more presumption for households entered without relationships. A member at least 20 years younger than the claimant is presumed to be their child at any age, flagged or not (new parameterhousehold.demographic.benefit_unit.presumed_child.minimum_age_gap_at_any_age= 20). It is OR'ed onto #2040's rule (now on main), which is unchanged: 16+ years younger and either under 20, or below a flagged claimant whose flag no young child explains.A member flagged
is_parentstill takes priority as the partner, so a flagged couple 25 years apart stays a couple. With flags, it also fixes two cases #2040 documents as limitations, provided the adult is 20+ years younger:At 16-19 years apart the limitation stays (38/20/12 is still read as a couple, YAML case).
Why 20 years
Once a dataset supplies
is_claimant_or_partner(PolicyEngine/policyengine-uk-data#524 does, from the FRS adult table), the presumption no longer touches the microsimulation. It only applies to inputs without roles, so it can follow how often a large age gap means a couple. Measured on the enhanced FRS 2024-25 with the survey's own roles (couple = both in one benefit unit as claimant and partner), co-resident HBAI adults with the younger aged 20+:Shares are of weighted pairs (each pair takes its household's weight). #2040's evidence cited 61% / 12% / 0.6% / 0.2% from the released eFRS snapshot using benefit-unit membership alone; the 20-year boundary is the same either way.
Below 20 years apart, a couple is the more likely reading; from 20, a parent and adult child is. The table describes co-resident adults in the population, not calculator submissions.
Trade-off and who it reaches
An unflagged couple 20+ years apart entered without roles, whose elder is the claimant (the head, or the eldest adult when no adult head is given), is now read as a single claimant plus a non-member adult. A younger explicit head keeps the couple: the gap is measured below the claimant.
is_claimant_or_partnerfor "you" and a declared partner, once the API runs a policyengine-uk release containing Replace generic child and adult flags with each programme's legal definitions #1896. With it, a declared partner keeps the couple whatever the gap. Until it lands, a declared partner 20+ years younger would be read as a non-member adult, so Add automatic function for decomposing variable outputs #1240 should land before or with this PR.uk.calculate_household, policyengine-api, household-api: pass households through. Callers that send ages only get the presumption. Supplyingis_claimant_or_partner(or flagging both partnersis_parent) keeps the couple. The docstring says so.Impact (real microsimulation runs)
Seeded build of policyengine-uk-data#524's branch (enhanced FRS 2024-25 with
is_claimant_or_partnerfrom the adult table), simulated for 2025 and 2026. Latest run: main at 493f0d2 (which includes #2040) against this branch at 34c83a9. Two earlier runs, at earlier heads of both, gave the same figures. Cells under 10 survey households are suppressed; SPI-synthetic and donor copies don't count toward the 10.Role supplied (the data this PR should ship with): base (main) vs branch identical. Every compared array is identical in 2025 and 2026 across the versions run. The arrays are: person
is_claimant_or_partner, income tax and NI; benefit-unit UC, HB, Pension Credit, Child Benefit, CTC, WTC, IS, benefit cap andrelation_type; household CTR, net income, HBAI net income BHC/AHC, benefits, tax and BHC/AHC poverty.Role dropped (any enhanced FRS without the column):
relation_typechanges in those 33 households. UC, Pension Credit, CTR, household net income and income tax change in fewer than 10 survey households (suppressed).Calculator-style input: each eFRS household entered as one benefit unit with no roles, flags or heads (2025). Truth is the survey claimant and partner of the eldest member's own benefit unit, because the model's claimant is the eldest adult.
Where the rules disagree, this PR is right in 612 survey households (382k weighted) and main in 72 (146k weighted).
Sequencing (Max ruled d842: adopt; merge on gates, ship with uk-data#524)
Simulation()needs one explicitly or viaPOLICYENGINE_UK_DEFAULT_DATASET). policyengine.py's bundle manifest pins model and data together. On policyengine.py main (6a9c878) the UK default isenhanced_frs_2024_25from policyengine-uk-data 1.56.16, the dataset policyengine-uk-data#524 changes.is_claimant_or_partner:populace_uk_2023(policyengine/populace-uk-private),enhanced_frs_2023_24andfrs_2023_24(policyengine-uk-data at 655dd07e). Real runs, Presume a much younger member is a flagged parent's child at any age #2040's head (c18c836) vs this branch (f43e5ec), 2025. Cells under 10 source households are suppressed; populace's ten OA clones count as one source household, and inenhanced_frs_2023_24only households whose ids are raw FRS 2023-24 serial numbers count (the file doesn't flag its copies).populace_uk_2023: roles change in 18 source households (about 25k weighted households). Benefit and income amounts move in fewer than 10 (suppressed).enhanced_frs_2023_24: roles change in 18 survey households (64 households in the file, counting its unflagged synthetic and donor copies; about 13k weighted). Amounts move in fewer than 10 (suppressed).is_claimant_or_partner. A separate task adds the role to microcosm's populace UK build.Invariants
Executed as Hypothesis properties in
test_child_and_adult_definitions_properties.py, plus the exhaustive age × education × flag grid against the updated referenceclaimants_or_partners:is_claimant_or_partnerroles are always kept, andis_couplefollows them.Mutation check (local log; run at 0448203, 31 YAML cases and 13 properties):
>, fails the 20-year YAML boundary case and the new exhaustive pair test.Tests
benefit_unit_structure.yaml, 32 cases):is_claimant_or_partnerfor the two partners, with expectations unchanged. YAML: Lifetime ISA capital (70/45), Pension Credit mixed-age (79/54), and ten 70/50 mixed-age cases (benefit cap for pension-age claimants, CTR pensioner status, HB earnings disregard). Hypothesis generators: Pension Credit reported capital, the mixed-age couple invariants, the benefit cap pension-age partner test and the CTR pensioner earnings disregard (main already supplies roles in the UC State Pension generator). Without roles the presumption splits these couples, as intended.test_unflagged_adult_pairs_follow_the_20_year_gap(2,556 unflagged pairs aged 20-90), and a 70/45 couple under each head order, inferred and with supplied roles. The reference HBAI helper now needs an identified parent aged 16+, asis_hbai_dependent_childdoes; the under-16-parent regression also checks the reference.ruff formatandruff checkon the changed Python files.axiom: n/a: a PolicyEngine presumption for households entered without relationships, not a statutory rule. Benefit law defines the claimant and partner by relationship, not age.
🤖 Generated with Claude Code