Conversation
A lone parent flagged is_parent who lives with an adult son or daughter was read as a couple. Below a flagged parent the under-20 limit no longer applies. Unflagged inputs keep it: survey childless couples carry no flags and include large age gaps. No enhanced FRS 2024-25 benefit unit changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mant-partner-adult-child
…rties - an unflagged member 16+ years below a claimant flagged as a parent is never their partner, at any age; - flagging the claimant as a parent never adds a partner (metamorphic); - FRS-shaped units (one or two adults, all flagged when there are dependants) keep the earlier under-20 rule exactly (differential). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…for #2039 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… add boundary cases Review round 1: a couple with children where only the claimant is flagged and the partner is 16+ years younger is now assessed as a lone parent. The docstring now says flags follow the FRS convention (both partners flagged) and recommends supplying is_claimant_or_partner when relationships are known. New YAML: the unflagged under-20 boundary (19 child, 20 partner); the 60/42/10 couple flagged once (lone parent, UC earned income 0), flagged twice, and with explicit roles (couple, UC earned income 31,563.60). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Response to the round-1 independent review (REQUEST_CHANGES at 8baea5a)Fixes are in
Also added: an unflagged age-20 boundary case (19 is a child, 20 the partner), so the preserved limit no longer depends on sampling. "Adult" is clarified as "raw FRS adult record" in the neutrality explanation. Tests on 15354fa: full policy YAML 1,569 passed; the property file 12 passed. |
Codex review — input-contract decision requiredReviewed commit code-1 — [P2] Resolve the effect of partial parent flags before adopting the new default
The PR documents this trade-off and tests it, so this is not an undisclosed coding accident. Nor do the ages alone establish whether the42-year-old is a partner or an adult child. A maintainer/product decision is needed on whether the complete-parent-flags convention is enforced for every supported household-input path. If partial flags remain supported, prefer explicit relationship/role data or validation rather than silently choosing a materially different family. Existing tests establish the designed inference, not the truth of the relationship. Confidence: high for the executed sensitivity; actual relationship and acceptable input contract require a decision. The policy review agrees: Welfare Reform Act 2012 section 39 defines a couple by relationship, but does not choose a default for ambiguous inputs. FRS array equality establishes compatibility with that dataset convention, not the correct relationship for a calculator submission. The two reviewers' overlapping finding is counted once. This is a relevant model-default decision, not grounds to close the PR as irrelevant. The current documentation and tests acknowledge the trade-off; explicit acceptance or input validation is still needed. Outstanding evidence/decision:
Validation: 36 focused tests passed. An independent final-award diagnostic measured UC of £8,869.68 with the single parent flag versus £0 with both parent flags; ages and incomes were unchanged. One official source original was inspected and its cached bytes verified. No relationship truth was inferred from person names or age gaps. Focused tests used an existing cached Python 3.13 environment (Core 3.32.9, NumPy 2.1.3, pandas 2.3.1, microdf-python 1.2.1, Hypothesis 6.168.2, pytest 8.4.2) with this PR’s isolated source snapshot. No dependencies were installed; this was not a freshly synced lock environment or a full-suite/population run. This is a review comment, not a formal GitHub review vote. Live check before posting (2026-10-02T10:39:11.307643+00:00): same commit, CLEAN; 0 failed checks; 0 pending checks. |
|
Review pass (Claude Code, on Vahid's behalf) at Short version: the rule does what the body says, the FRS neutrality argument holds, and the tests pass. On the input-contract decision raised in the Codex review, I'd take a narrower version of the rule. It keeps the fix for the flagged lone parent with an adult child and removes the 60/42/10 couple regression, so every probe I ran either improves on #1896 or matches it. I've also listed two ambiguous inputs that neither branch handles, as candidates for this PR or a follow-up. Probes (
1. Suggestion: only drop the age limit when the parent flag is otherwise unexplained. A flagged claimant who already has a child under 20 in the unit has a flag that child explains, so the flag says nothing about an older unflagged member. Lift the under-20 limit only when no such child is present: large_gap = claimant_age - age >= p.minimum_age_gap
young_child = (age < p.age_limit) & large_gap
flag_unexplained = claimant_is_parent & ~person.benunit.any(young_child)
presumed_child = ((age < p.age_limit) | flag_unexplained) & large_gap
2. Question (pre-existing, in the spirit of "ambiguous family inputs"): when the claimant is unflagged and exactly one other member is flagged, the flagged member becomes the partner whatever the age gap.
The two-flagged-parents branch already handles the grandparent case when both parents are present. A symmetric rule would fix the single-parent version: a flagged member at least 16 years older than the claimant is presumed their parent, not their partner. Alternatively, when exactly one non-claimant member is flagged and the claimant isn't, make that member the claimant. Fine as a follow-up if you'd rather keep this PR narrow; worth a line in the docstring either way. 3. Nit: the docstring paragraph on partial flags could also say the result depends on whether the flag is "explained" by a younger child, if you adopt item 1. Checked and correct
|
…ow the docstring example Review round 2: a margin of 1 let every true/false expectation in those three cases pass whatever the model returned. A margin of 0.01 enforces them (and still accepts UC earned income 31,563.598). The docstring's lone-parent example now states its conditions: the parent is the claimant, and the child is unflagged and at least 16 years younger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Response to the round-2 independent review (REQUEST_CHANGES at 15354fa)Fixes are in
|
…flag Review (Vahid, and the Codex input-contract finding): a flagged claimant with a child under 20 who is 16+ years younger has a flag that child explains, so the flag says nothing about an older unflagged member. Lifting the limit only when the flag is unexplained keeps the #2039 fix (flagged 50 with a son of 25 is single) and restores the large-gap couple with a young child (60/42/10) to a couple, so no probed household is worse than before #1896's follow-up. Documented limitations, unchanged from main: a flagged lone parent with an adult and a younger child, and a flagged grandparent with a daughter and grandchild, are read as couples; an unflagged head with exactly one flagged member pairs them whatever the gap. Supply is_claimant_or_partner for these. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, both. Adopted Vahid's narrower rule in c18c836. The under-20 limit is now lifted only when the claimant is flagged and no member under 20 and at least 16 years younger explains the flag: large_gap = claimant_age - age >= p.minimum_age_gap
young_child = (age < p.age_limit) & large_gap
flag_unexplained = claimant_is_parent & ~person.benunit.any(young_child)
presumed_child = ((age < p.age_limit) | flag_unexplained) & large_gapCodex input-contract finding. The 60/42/10 household flagged once is a couple again, with UC earned income £31,563.60 and UC £0, the same as when both are flagged or roles are supplied. The YAML now asserts that with a 0.01 margin, so its true/false outputs are enforced. Of Vahid's probes, only the #2039 case changes from main. The one new trade-off is a childless pair 16+ years apart where only the elder is flagged, which becomes single. With no child in the unit, that flag marks the younger member as the adult child. It has its own YAML case. Vahid's question 2: unflagged head with one flagged member (grandparent with a flagged daughter, or a 25-year-old head with a flagged mother). This is unchanged and now documented in the docstring as a limitation, with the advice to supply App follow-up: draft policyengine-app-v2#1240 sends explicit roles from the UK builder. Data follow-up: draft policyengine-uk-data#524 supplies Re-measured on c18c836:
The PR body is rewritten to match. |
Fixes #2039
Follow-up to #1896, now merged. The base is
main.What changes
is_claimant_or_partnerpresumes that a member is the claimant's child rather than their partner when they are at least 16 years younger (presumed_child.minimum_age_gap) and either:presumed_child.age_limit, as before), oris_parent) whose flag no young child explains. A young child here means a member under 20 and at least 16 years younger.So a lone parent aged 50, flagged
is_parent, living only with an unflagged son aged 25 is now single. The son is neither claimant nor partner. When a young child does explain the flag, the under-20 limit stays, as before.This narrower form follows Vahid's review. The first version lifted the limit for any flagged claimant, which turned a large-gap couple with one partner flagged into a lone parent. That was the case the Codex review flagged: ages 60, 42 and 10, with the 42-year-old earning £50,000, gave UC of £8,869.68 instead of £0. That household is a couple again.
is_parent)The docstring states the remaining limitations, all unchanged from main. For each, it recommends supplying
is_claimant_or_partner(true for the claimant and any partner, false for everyone else):The one new trade-off: a parent flag on one of a childless pair 16 or more years apart makes them single. With no child in the unit, the flag marks the younger member as the adult child.
Scope: this PR fixes who the claimant and partner are. It does not charge an adult child in the claimant's own benefit unit a non-dependant deduction. Here, the UC/HB/CTR non-dependant deductions count only members of other benefit units (household minus benefit unit). In-unit non-dependants are #2017 (issue #2014). Until that lands, enter an adult non-dependant as a separate benefit unit when housing entitlement matters.
Why this presumption
Measured on the enhanced FRS 2024-25 (2025 policy year). Figures are aggregates only; cells from fewer than 10 survey households are suppressed.
The flag rule moves no FRS benefit unit. policyengine-uk-data flags every raw FRS adult record (the head and any partner) in a unit with dependants as a parent (
derive_is_parent_from_frs_microdata, from the FRS dependent-child count). A flagged claimant's partner is therefore always found among the flagged parents, before the presumption is reached.Unflagged HBAI adults in flagged units do exist. They are raw child records that the HBAI fallback calls adults, and all are under 20, so they were already presumed children.
A gap-only rule (no age limit, flagged or not) would split 220 FRS couples, about 203k weighted households. These are childless couples with large age gaps, and they carry no flags. Adding an unflagged gap rule would still split real FRS couples:
How often a large gap means a couple. The table covers co-resident pairs of HBAI adults where the younger is 20 or over. "Couple share" is the share the FRS puts in one benefit unit, as a share of weighted pairs (each pair takes its household's weight).
Most large-gap pairs are in separate benefit units. The FRS benefit-unit IDs do not record kinship, so reading those pairs as parents and adult children is an inference, not a measurement.
An unflagged rule needs the dataset to supply
is_claimant_or_partnerfirst. That is policyengine-uk-data#524 (draft), with a stacked PE-UK PR for an unflagged gap rule. Both are queued as a methodology decision.How the calculators build households
age(andemployment_income) and supplies no relationship roles. A user can addis_parentthrough the builder's custom-variable search, but heads andis_claimant_or_partnerare formula variables that search does not offer. Its composition logic (getBuilderPartnerKey) treats any other adult aged 18+ as the partner once no "your partner" exists. Draft policyengine-app-v2#1240 sends explicit roles and stops reading an adult dependant as the partner.uk.calculate_householdpeople, plus only the variables the caller supplies.Impact (real base vs branch runs)
The enhanced FRS 2024-25 was run on
mainat 3c48247 (which includes #1896) and on this branch at c18c836, for 2025 and 2026. Every array compared is identical in both years:relation_type;is_claimant_or_partner, income tax, NI, and BHC/AHC poverty.The first version of the rule was also identical (verified independently by review round 1).
Invariants
Each is executed as a Hypothesis property over the domain stated:
The reference
claimants_or_partnersintest_child_and_adult_definitions_properties.pyfollows the new rule. The exhaustive age × education × flag grid checks it against the model.Mutation check: against main's formula, 3 of the new YAML cases fail. They are the flagged 50/25 single, the 16-year boundary, and the childless flagged 60/42. Five of the 12 tests in the property file fail:
test_entry_terminal_and_wtc_disability_boundaries;Property 4 passes on both formulas, as intended. Log:
mutation_vs_main_r2.log.Tests
benefit_unit_structure.yaml:test_child_and_adult_definitions_properties.py: 12 passed.ruff formatandruff checkare clean on 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 (WRA 2012 s.39), not by age.
Follow-ups
is_claimant_or_partnerfrom FRS adult records, plus a stacked PE-UK PR for an unflagged 20-year gap rule. These are queued for Max as a methodology call.🤖 Generated with Claude Code