PolicyEngine / PolicyEngine/microcosm

UK spine: composition-faithful SPI income draw — bridge predictors and rank-preserving replacement (with-children UC residual)

Open
#840 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
0
Forks
4
Avg merge
1d 3h
Merged PRs (30d)
94

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 a RegimeGatedQRF draw conditioned on age, gender, region only (SPI_SPINE_STAGE1_PREDICTORS; fit_weighted_qrf_stage1 in uk/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) and MCAS (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

  1. 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) ↔ FRS employment_status (+ self_employment_income/PENSION-style precedence) — the mapping is a translate-published-facts adjudication, documented coding by coding;
    • INDUSTRY07sic_industry_division (frs_employment);
    • SPA ↔ age vs state pension age;
    • SEINC_NUM ↔ self-employment indicator;
    • MAINDis_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.
  2. 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_children engine variable + numbered-child rebind, plan D5 of #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

  • #145 — draw-quality workstream (this is its draw-shape increment; María's condition-the-draw suggestion was routed here from #828's review)
  • #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)
  • #832 / PR #835 — the reporter-channel fix whose measurement located this residual (experiments/832-uc-reporter-receipts.md; v17/v18 receipts on uk-publication-stack-835)
  • #828 / #829 — capital coherence; #809 (closed) — M1 draw-quality evidence
  • SPI 2022-23 Public Use Tape documentation, UKDS SN 9422 — Annex A variable list

🤖 Generated with Claude Code

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with stage 18, SPI_SPINE_STAGE1_PREDICTORS and fit_weighted_qrf_stage1 in uk/spec/sources.yaml, then read _gate_draw_with_rng and _draw_target_with_rng in microcosm-fit/qrf.py. Review the stage-1 output assertions and the twin-compare workflow. Done means documented bridge mappings, donor-rank quantiles, preserved determinism, unchanged base-FRS rows, green replay gates, and completed T0–T6 receipt measurements.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, data-engineering
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.