Skip to content

UK spine: refuse malformed FRS claimant roles directly; check every supplied role reaches the engine - #1205

Draft
MaxGhenis wants to merge 2 commits into
mainfrom
uk-frs-claimant-or-partner
Draft

MaxGhenis wants to merge 2 commits into
mainfrom
uk-frs-claimant-or-partner

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

WIP (draft): the H2 parity fixture regeneration, the full test run and an independent review are still to come.

Follow-up to #1095's person types (6900190), which supplies is_claimant_or_partner as FRS adult-table membership (uk-data#524).

What was wrong

The spine and the stage spec both said that the UC claimant mask "already refuses a benefit unit without one or two adult records". It does not. The mask counts head, or adult with dependants, or any member of a unit whose DEPCHLDB is 0. Where DEPCHLDB is 0 beside child-table records:

  • A unit with no adult record builds. Its child becomes the claimant, and is_claimant_or_partner is false for everyone in the unit.
  • A lone adult's child becomes a UC claimant while is_claimant_or_partner says dependant. policyengine-uk 2.107.0 and later derive is_uc_claimant from is_claimant_or_partner, so with both supplied, UC would see a couple and every other programme a single adult.

Both cases were executed against main's own spine on the fabricated test fixture before the fix: both built without error.

What this changes

  • frs_claimant_or_partner (in frs_spine.py) refuses a benefit unit with zero or three or more adult-table records itself, mirroring uk-data#524's derive_is_claimant_or_partner_from_frs_microdata.
  • The spine refuses any person whose supplied is_uc_claimant differs from is_claimant_or_partner.
  • The stage spec's text is corrected, and the source pins in the coverage manifest are regenerated.
  • The policyengine-uk adapter now checks that is_claimant_or_partner and is_hbai_dependent_child reach the model as unchanged inputs, as it already did for is_uc_claimant. microcosm locks policyengine-uk 2.122.2, which reads both, so a loader that dropped either would silently bring back age inference.

Impact

None on the pinned FRS 2024-25. The aggregate-only measurement on the raw tabs (34,966 people in 18,850 benefit units) found:

  • 9,986 units with one adult-table record and 8,864 with two;
  • DEPCHLDB equal to the child-record count in every unit;
  • is_uc_claimant equal to adult-table membership for every person.

So no built column changes. The refusals guard the next vintage.

Invariants (tested)

  • Range: every benefit unit has one or two claimant-or-partner roles. Refused otherwise.
  • Children: a child-table record never holds the role. The role does not come from UPERSON: a dependant can be UPERSON 2 under a single head.
  • Adults: everyone aged 20 or over holds the role (FRS dependants are 19 at most), and so does every head.
  • Determinism: record order does not change anyone's role.
  • Differential: where the dependant count equals the child records, the UC mask and adult-table membership name the same people (Hypothesis).
  • Agreement: the supplied is_uc_claimant equals is_claimant_or_partner, enforced at build time.

axiom: n/a: microsim data-pipeline change.

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 11, 2026 11:24
…le reaches the engine

The spine took is_claimant_or_partner as adult-table membership and relied on
the UC claimant mask to refuse a benefit unit without one or two adult
records. That mask reads DEPCHLDB, so where DEPCHLDB is 0 beside child
records it lets through a unit with no adult record (its child becomes the
claimant) and a lone adult whose child it counts as a partner, leaving
is_uc_claimant and is_claimant_or_partner disagreeing. policyengine-uk
derives is_uc_claimant from is_claimant_or_partner from 2.107.0, so UC and
every other programme would see different families.

frs_claimant_or_partner now refuses a unit without one or two adult records
itself (mirroring uk-data#524's derivation), and the spine refuses any person
whose two supplied roles differ. The stage spec's claim that the claimant
derivation already refused such units is corrected; source pins regenerated.

The policyengine-uk adapter checks that is_claimant_or_partner and
is_hbai_dependent_child, like is_uc_claimant, reach the model as unchanged
inputs: microcosm now locks policyengine-uk 2.122.2, which reads both.

Tests: example refusals, a UPERSON-2 dependant, and Hypothesis properties
(one or two roles per unit, never a child record, everyone 20+ and every head,
record-order invariance) plus a differential test against the UC mask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the mirrored map_columns scope and the graph JSON that hashes it move;
the fixture's person, benunit and household tables are unchanged.

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

This branch has not been deployed

No deployments
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.

1 participant