Repository navigation
UK spine: composition-faithful SPI income draw — bridge predictors and rank-preserving replacement (with-children UC residual) #840
Description
Problem
PR #835 (#832) made the SPI reporter channel truthful — reporter→positive-award conversion is now ≈100% by construction and the 1.24m dead-reporter mass is gone — but the four deferred UC with-children receipts survive it essentially unchanged (v18: single_with_children −34.2%, children_1 −27.9%, children_2 −34.4%, children_5_or_more −29.1%). Decomposing the single_with_children gap on the v18 calibrated weights (target 2,213,760; support 1,460k; gap 754k):
| Component | Weighted | Owner |
|---|---|---|
| Dead reporters (reported, award-killed) | 0 | closed by #835 |
Take-up residue (award > 0, would_claim_uc false) |
59k | exhausted — 0.55 stands |
| Income/eligibility shape: lone parents with no pre-take-up award | 400k, of which 195k SPI-drawn and 205k FRS-observed | this issue (the SPI-drawn part) |
| Structural mass shortfall: only 1.919m lone-parent benefit units exist in the spine | ≥295k | separate item (concealed-family representation) |
The income-shape signature is stark: SPI-channel lone parents pass the pre-take-up screen (max(0, uc_maximum_amount − uc_income_reduction) > 0) at 35% (0.351m of 1.004m) versus 69.5% on the base FRS channel (0.768m of 1.105m). The SPI channel hands lone-parent benefit units incomes that a lone parent rarely earns, so #835 correctly refuses to draw them as reporters — the defect moved from a hidden contradiction to a sized shortfall.
Why this happens
- Stage 17 (
spi_support_channel) samples FRS donor households uniformly within (region, capital-gains-clone, clone) strata; composition is not a stratum, and the donor keeps its benefit-unit structure. - Stage 18 (
hmrc_spi_income_spine) stage 1 then overwrites every SPI-channel adult's 18 income components with aRegimeGatedQRFdraw conditioned on age, gender, region only (SPI_SPINE_STAGE1_PREDICTORS;fit_weighted_qrf_stage1inuk/spec/sources.yaml). Each row's regime is sampled from the gate's probabilities with an RNG uniform (_gate_draw_with_rng,microcosm-fit/qrf.py:950-962) and its magnitude at an RNG quantile (_draw_target_with_rng,qrf.py:965-989:quantiles = rng.random(n)). The donor's own observed income — and with it the composition↔income relationship the FRS carries — is discarded. A lone parent receives the income of a random same-age/gender/region taxpayer, and the taxpayer population is far richer than lone parents are. - Composition cannot be conditioned from the SPI side: the SPI 2022-23 public-use tape carries no child, dependant, or family-composition variable (Annex A, UKDS SN 9422 — the word "child" appears only in a footnote on the abolished additional personal allowance). The only relationship signals are
MAIND(marriage-allowance claimant) andMCAS(married couple's allowance — partner born before 1935). The signal has to come from the FRS side of the pairing.
Proposed fix — one increment, two mechanisms
- Bridge predictors that exist on both sides. Extend the stage-1 predictor list with fields the SPI tape and the FRS spine share:
MAINSRCE(1 pay / 2 occupational pension / 3 sole trader / 4 partnership / 5 other / 6 claims case) ↔ FRSemployment_status(+self_employment_income/PENSION-style precedence) — the mapping is a translate-published-facts adjudication, documented coding by coding;INDUSTRY07↔sic_industry_division(frs_employment);SPA↔ age vs state pension age;SEINC_NUM↔ self-employment indicator;MAIND↔is_married— a documented approximation (claimants are a subset of married couples with one low-earning partner), included as a weak couple signal, never as a lone-parent signal.
A part-time retail employee then draws from part-time-retail-employee taxpayers, not from every 35–44 woman in the region; most of what composition does to income rides on these fields.
- Rank-preserving replacement. Supply the per-row draw quantile from the donor's observed FRS income rank within the predictor cell instead of
rng.random(n)(and the gate's uniform likewise), so the FRS lone parent at her cell's 30th percentile receives the SPI cell's 30th-percentile income. This preserves the FRS composition↔income-rank joint while importing SPI's marginal — the fat top tail the channel exists for. Ranks are robust to the FRS's under-reporting of top-tail levels. To adjudicate: rank on the donor's FRS total income vs per component; tie/zero handling within the regime gate; whether the gate keeps its own stream.
Both mechanisms live in stage 18, so they move the pinned stage-1 output surface (_assert_income_stage_parameters), re-draw every SPI income column and, through the stage-2 chain (whose predictors are those incomes), every FRS-only fill on SPI rows — a whole-SPI-surface signed-difference campaign. The twin compare must show the diff confined to SPI-channel rows; the base channel stays byte-unchanged.
Companion (separate, calibration-side, not this increment): a joint target on high-income × has-children from HMRC's High Income Child Benefit Charge / Child Benefit statistics, so the solver cannot leave lone parents rich regardless of how the draw got there.
Explicitly out of scope
- No take-up changes (the residue is 59k; 0.55 stands).
- No composition-aware donor selection at stage 17 — it needs taxpayer-composition evidence the SPI tape does not carry.
- Not the ≥295k structural lone-parent mass shortfall (the ONS lone-parent-household target fits exactly at 1.835m; the missing units are benefit units embedded in larger households) — its own item.
- Not the B-lane (
num_uc_childrenengine variable + numbered-child rebind, plan D5 of UK spine: condition SPI UC receipt on benefit-unit composition and add a UC-specific child count #832). - No SPI/WAS tail suppression; the 208-fact HMRC replay and its fences stay in force.
Acceptance criteria
- Stage-1 predictors carry the bridge fields with adjudicated, documented mappings from the published SPI codings to the FRS columns.
- Draw quantiles come from the donor's within-cell income rank; declared in the manifest; twin determinism preserved.
- Before/after receipt by family type and channel: screened-eligible share of SPI lone parents (35% → toward the 69.5% base rate) and the no-award SPI lone-parent mass (195k → ?).
- Twin compare: diff confined to SPI-channel person income columns and their stage-2 / reporter / coherence dependents; base-FRS rows byte-unchanged.
- The stage's HMRC income-band replay and distributional-mass gates stay green (the SPI marginals are still the SPI marginals).
- Rebuilt candidate: recalibration + T0–T6; re-measure the four with-children receipts with the childless cells, housing benefit, pension bands and CGT on the sibling watch; solver-pressure movement reported from
calibration_diagnostics.json. - Every moved column signed with receipts before any exclusion changes.
References
- Port UK compact source stages and 2024-25 refresh from uk-data #145 — draw-quality workstream (this is its draw-shape increment; María's condition-the-draw suggestion was routed here from UK spine: preserve FRS benefit-unit capital for UC while retaining WAS household wealth #828's review)
- WS-E spine deferrals: known bugs and signed inconsistencies awaiting their increment #796 — WS-E spine-deferrals tracker: register this as the fix arm for the four UC with-children receipts (renewal-or-fix, window to 2026-09-30)
- UK spine: condition SPI UC receipt on benefit-unit composition and add a UC-specific child count #832 / PR UK spine: benefit-unit-grain SPI UC reporter draw with pre-take-up screen (#832); exact capital sentinel (#833) #835 — the reporter-channel fix whose measurement located this residual (
experiments/832-uc-reporter-receipts.md; v17/v18 receipts onuk-publication-stack-835) - UK spine: preserve FRS benefit-unit capital for UC while retaining WAS household wealth #828 / UK spine: preserve FRS benefit-unit capital for UC, re-anchor take-up after SPI (#828) #829 — capital coherence; UC residual after the pension-wealth split: support 4.24m vs 6.76m — re-draw undershoot, the 0.55 take-up freeze, and composition shape #809 (closed) — M1 draw-quality evidence
- SPI 2022-23 Public Use Tape documentation, UKDS SN 9422 — Annex A variable list
🤖 Generated with Claude Code
Activity
juaristi22 commentedon Sep 2, 2026
B-lane hand-off from #832:
num_uc_childrenand the numbered-child rebind#832 closes with PR #835 (the reporter-channel fix); its second half — the UC-specific child count for the five DWP numbered-child cells — was split off as a separate lane (plan #832 D5 / A4) and is recorded here so it does not live only in a closed issue. It is a target-semantics correction, not a gap-closer: on spine-k about 78k weighted positive-UC benefit units change count band under the UC definition (one-child ≈ +1k support, two-child ≈ −5k, five-plus ≈ −3k).
Why it has to be an engine variable. The target resolver (
uk_runtime/measure_simulation.py,UKMeasureResolver.knows/compute) resolves filter variables only throughtax_benefit_system.variables, and a person-grain boolean routed to benefit-unit grain collapses to ANY (bool_any_collapse_person_to_benunit), never a count. So there is no binding-layer escape hatch (value_expressioncovers+-joined same-grain columns onvalue_variableonly, not filters).The change, in order.
num_uc_children—BenUnit,int,YEAR,benunit.sum(benunit.members("is_child_or_qualifying_young_person_for_universal_credit", period)). The flag already excludes looked-after children and is the one the engine's own child element, child-limit and disabled-child logic use (uc_child_index,uc_is_child_limit_affected, …), so the count matches "children on the claim" as DWP counts them, including 16–19 qualifying young persons.packages/microcosm-build/pyproject.tomlukextra,uv.lock), with the UK WAS stage: split private pension wealth out of corporate_wealth (uk-data#452 M2) #750 lesson applied: check whether the new version newly recognizes loader columns (unknown_export_columns_excludedin the parity extraction) and, if so, re-record the licensed coverage/parity evidence from the same pinned artifacts (theae9457a3precedent).uk/uk_population_targets.json: the five cellsdwp.uc.households_children_{1,2,3,4,5_or_more}change their filter{"variable": "num_children", …}→{"variable": "num_uc_children", …}(operators unchanged:== 1/2/3/4,>= 5); update eachnotes— the "16–19 qualifying young persons … declared residue" sentence is discharged for these cells. Regeneratetarget_references.jsonand the membership report withtools/generate_uk_target_references.py --contract … --ledger-facts … --period 2025 --output … --membership-report ….test_uk_population_targets.py(allowed_filter_variables, currently{universal_credit, num_children, family_type}), thetest_uk_ledger_targets.pyfixture benunit table.TargetSpechash (registry.py), unlike Re-bind the DWP UC composition and TCL disability-children targets to the publisher's grain (#808, #809) #813's entity/measure flip — verifyTargetRegistry.versionis unmoved;verify_register_coverage.py's family counts and the headline range are unaffected.Declared residue, on purpose. The four family-type cells keep their binding:
family_typederivesLONE_PARENT/COUPLE_WITH_CHILDRENfrom the same age<18is_child, so they carry the QYP omission this lane fixes for the numbered cells. #832 kept them out of scope; whoever takes this lane should say so in the cell notes rather than widen the change.Synergy with #835: the reporter-redraw stage already computes its UC child band from the same engine flag (summed in-stage), so once the engine variable exists there is one definition of "children on the claim" across the draw and the targets.
Owner per the #832 plan: María.
juaristi22 commentedon Sep 7, 2026
PR #879 is an incremental input and measurement correction; it does not complete #840 or close the UC support gap. The latest results below supersede the earlier comparison that used the wrong national VOA geography and generic UC family categories.
What #879 covers
What changed in the matched comparison
Both runs use UK 2.94.0/Core 3.31.0, Chronicle
6fb700e, the same 371 active targets, 1,500 epochs, seed 0 and maximum weight ratio 10. Measurement code is117860ef; the committed comparison is at4ccaabba.Loss falls 39.8%, but interest contributes 91.5% of the net objective gain. This does not establish that the joint income-tax/state-pension/UC problem is solved. All nine England VOA measures are now within 0.22%; the earlier Band A miss came from comparing a wider geography against England targets.
Why the UC gap remains
The main composition-preserving mechanisms proposed in #840—adjudicated shared SPI/FRS bridge predictors and donor-rank-preserving income replacement—are not implemented by this PR. Pension-receipt conditioning is narrower. The PR also does not add the missing benefit-unit composition support identified in this issue, or establish a new channel-by-family decomposition of that shortfall. The original v18 decomposition should therefore not be treated as a measurement of this candidate.
The high-payment childless-couple problem is a separate support and modeling defect. After correcting family classification, neither spine contains childless-couple support from £26.4k annual UC upwards. The old £27.6–28.8k fit depended on two copies of one lone-parent family whose older child was counted as a partner. Dividend rebasing moved its award across a narrow boundary; it was never a sound childless-couple donor. Reweighting cannot populate an empty cell. The calibration correction also leaves the native model's standard-allowance relationship error unresolved.
The £26.4–27.6k band was already excluded from calibration. The next two bands remain active and both miss by −100%. Across all families there are 100 payment-band references, of which 84 are active and 16 were already excluded. The highest labeled band's existing open-ended implementation is documented and held fixed in this experiment; its payment-boundary semantics still need correction.
Exclusion and release status
No new target exclusions have been signed or added in #879. All 44 measurement exclusions, existing reviewed fit exclusions, target values, take-up and gate thresholds were preserved for the controlled comparison. A fit deferral would change release acceptance, not repair support or improve the measured fit.
Both runs remain release-blocked and export no calibrated H5. Treatment has four unreviewed fit failures: the two empty childless-couple bands (−100% each), self-employment income in the £50–70k income band (+34.485%), and state-pension income in the £20–30k band (+27.941%). The existing private-pension-count exclusion is stale in both runs; the two-child UC exclusion is also stale in treatment because those targets now fall inside the bound.
Keep #840 open for the remaining income/composition work, with the wider build deferrals tracked in #796. Separate CGT work remains in #878 and #875.
Corrected comparison · 371-target paired receipt and support audit · All UC bands and activation statuses.
juaristi22 commentedon Sep 16, 2026
A finding from the #882 repairs work that belongs on this issue's SPI-channel lane (branch
uk-uc-repairs-882, receipts inexperiments/882-uc-repairs-receipts.md, tooltools/diagnose_uk_legacy_benefits.py).The A16 legacy-benefit misses (#867 Housing Benefit, #869 JSA, and the ESA rows) are mostly the SPI support channel, not FRS misreporting. FRS 2024-25 at design weights reports 1,559,991 Housing Benefit benefit units, 99.9 percent in rented households, against a DWP GB caseload of 1.94m in January 2025 falling to 1.46m by December; and 63k JSA benefit units (27 records) against DWP's 71,000 New Style claimants. Spine-q carries 1.21m GB Housing Benefit reporters: the
frschannel's 758k are all renters, but thespichannel's 451k include 363k in owner-occupied synthetic households, which the engine can never pay. JSA: 161k of the spine's 181k contributory reporters arespi-channel units. ESA runs the other way: 1.06m raw against 791k on the spine, the channel under-carrying them. Household mass is split half and half between the channels (14.66m / 14.58m).The mechanism is the FRS-only QRF fill on SPI support rows (
spi_support.py,FRS_ONLY_SPI_FILL_PERSON_COLUMNS): it re-imputes the reported benefit columns onto synthetic households from age, gender, region and the six income predictors, with no tenure or eligibility coherence, so a reporter's Housing Benefit lands in an owner-occupied household and JSA receipt is over-drawn. On the Q50f candidate the engine pays Housing Benefit to 235k units (£2.13bn against the OBR £12.10bn) and the JSA row measures 34k against 71,000 after the solve pares the reporters to fit the OBR JSA spend line.What the #882 PR does about it: gates the
would_claim_ucdraw to benefit units with an adult under State Pension age (a second, separable mechanism: 377k pension-age Housing Benefit reporters on Q50f were zeroed by a Universal Credit claim they cannot make), re-adjudicates the two A16 rows to 2026-12-08 on these receipts, and binds the Stat-Xplore Housing Benefit caseload by tenure as measured diagnostics. What it does not do: touch the SPI fill. The repair candidates for this lane are a tenure-coherent fill (zero the housing-cost benefits where the synthetic household does not rent, or add tenure and a means-test proxy to the fill's predictors) and a check of the JSA/ESA incidence the fill produces against the raw FRS shares.Declare the recipient flag among the landlord node's reads (#840)
Draw SPI-channel incomes at the FRS twin's conditional rank (#840)
Measure the downstream moves of the twin-conditional SPI draw (#840)
Integrate the twin-conditional draw with the landlord and ports stages (
Declare the recipient flag among the landlord node's reads (#840)
Measure the twins on #1200's base and regenerate the coverage manifest (
Draw SPI-channel incomes at the FRS twin's conditional rank (#840)
Measure the downstream moves of the twin-conditional SPI draw (#840)
Integrate the twin-conditional draw with the landlord and ports stages (
Declare the recipient flag among the landlord node's reads (#840)
Measure the twins on #1200's base and regenerate the coverage manifest (
Address the review of the twin-conditional draw (#840)