Architectural gaps vs US¶
1. _reported consulted at runtime in some would_claim_* formulas¶
US pattern: _reported columns are consulted once, in the
data-build, to anchor stochastic take-up flags (reported recipient →
takeup = True with certainty; rest filled probabilistically to a target
rate). Runtime formulas only read the pre-computed
takes_up_X_if_eligible flag, so reforms that change eligibility flow
through cleanly to take-up.
UK pattern (partial): some UK would_claim_* variables already
match: would_claim_uc and would_claim_pc are input-only with
default_value = True, populated by the data build (now Microcosm
UK’s frs_take_up stage). But several still derive from
_reported at runtime:
would_claim_housing_benefitwould_claim_council_tax_reductionwould_claim_ISwould_claim_WTCwould_claim_CTC
In hand-entered situations each is claims_all_entitled_benefits | (foo_reported > 0)
(for Income Support only the claimant’s or partner’s report counts), and
claims_all_entitled_benefits is true for a benefit unit that reports under
£1 across seven means-tested benefits. In data-built simulations
claims_all_entitled_benefits is false for every benefit unit, and Housing
Benefit and Council Tax Reduction ignore it, so a reform expanding
eligibility only reaches existing _reported > 0 claimants. Setting
claims_all_entitled_benefits to true on a data-built simulation does not
change this: Housing Benefit and Council Tax Reduction ignore it there, and
the eligibility tests for Child Tax Credit, Working Tax Credit, Income
Support and working-age Housing Benefit (is_CTC_eligible,
is_WTC_eligible, income_support_eligible, housing_benefit_eligible)
also require a reported award. In years when tax credits are active it does
turn on would_claim_CTC for families that report no Child Tax Credit.
Unlike the family and disabled child elements, CTC_child_element does not
check is_CTC_eligible, so a family with children that claims Child Tax
Credit without being eligible has tax_credits (the child element, less the
income taper) but no child_tax_credit. That amount enters its Housing
Benefit and Council Tax Reduction means tests, and the tax credit award that
the 2024 Winter Fuel Payment and Pension Age Winter Heating Payment tests
read. This already happens on main in tax-credit years with nothing
overridden. In hand-entered situations the flag is true for every family
that reports none of the seven benefits, so each such family with children
claims Child Tax Credit. In both kinds of simulation, a family that reports
Child Tax Credit but claims Universal Credit still claims it although
ineligible. So does a family whose legacy benefits have closed, but on
main that happens only once tax credits are no longer active, so its
tax_credits is zero unless a reform leaves tax credits active after a
legacy benefit has closed. Otherwise, the same eligibility
tests mean that a hand-entered family reporting none of the seven benefits
can receive, of these five benefits, only Council Tax Reduction and
pension-age Housing Benefit.
Fix: convert each to input-only default_value = True (matching
would_claim_uc), and add each to Microcosm UK’s reported-anchored
take-up stages. Because of those eligibility tests, the only new awards
this gives families that report none of these benefits are Council Tax
Reduction and pension-age Housing Benefit (the model’s pension-age route,
which includes mixed-age couples that can still claim on it). For years when
tax credits are active, CTC_child_element needs an is_CTC_eligible gate.
The fix would spread the problem but does not create it: without the gate,
a family with children drawn as a Child Tax Credit claimant without a report
gets the child element, less the income taper, as tax_credits but no Child
Tax Credit, and that amount counts as income in its Council Tax Reduction
and any Housing Benefit means test.
Tax credits stopped paying ongoing awards on 5 April
2025, and gov.dwp.tax_credits.active is false from 2025-04-06 (see
tax-credits.md), so the WTC and CTC
fix matters only for simulations of earlier years.
2. Take-up assignment ignores reported data¶
Done in Microcosm UK. Its frs_take_up, pension_credit_take_up
and child_benefit_take_up stages keep every benefit unit that reports
receipt as a claimant and draw the rest to the target rate
(source_stages.json at 75167a6).
The description below is the Enhanced FRS behaviour before
policyenginewould_claim_uc, would_claim_pc and
would_claim_child_benefit on reported receipt, the same three flags
Microcosm UK anchors.
Until then the Enhanced FRS take-up assignment was a pure random draw:
pe_benunit["would_claim_uc"] = generator.random(len(pe_benunit)) < universal_credit_rateThis discards information: respondents who reported receiving UC
clearly took it up, and should have would_claim_uc = True with
certainty.
Fix (done in policyengine-uk-data#359): port
policyengine_us_data/utils/takeup.py::assign_takeup_with_reported_anchors
into policyengine-uk-data and apply it to each take-up variable where
a _reported column exists (would_claim_uc, would_claim_pc,
would_claim_child_benefit). This tightens per-variable calibration
without changing the target rates.
3. Second-stage imputation gap on SPI donors¶
Done in Microcosm UK. hmrc_spi_income_spine draws gift_aid and
charitable_investment_gifts from the SPI jointly with the new incomes, and
an FRS-only second stage, trained on the FRS rows, predicts pension
contributions, tax-free savings income and the *_reported benefits for the
SPI rows from those incomes. spi_housing_shell redraws tenure,
dwelling and council tax band for the SPI households, and the wealth,
spending and VAT stages now run after the SPI block, so they are drawn
for the new incomes rather than inherited from the FRS parent. The
description below is the Enhanced FRS behaviour before April 2026:
policyengine-uk-data#347 then added gift_aid and
charitable_investment_gifts to the SPI imputation, and #362 added a
second-stage QRF (datasets/imputations/frs_only.py) for pension
contributions, tax-free savings income and the *_reported benefits; rent
and wealth on SPI rows still come from the FRS parent there.
policyengine_uk_data/datasets/imputations/income.py::impute_income
trained a QRF that replaced only six income variables on SPI donor
rows; everything else (rent, wealth, pension contributions, gift aid,
benefits-reported flags) stayed as whatever middle-income FRS donor
happened to be sampled. So a synthetic SPI donor ending up with £2M
imputed self-employment income still carried an unchanged middle-FRS-
donor’s savings balance and zero gift aid.
The US equivalent (policyengine-us-data#589)
fixed this by adding a second-stage QRF: train on CPS with predictors =
demographics + newly-imputed income vars, outputs = the ~60 CPS-only
variables; for PUF-clone prediction, substitute imputed PUF incomes as
predictors so CPS-only variables come out consistent with imputed
income.
Fix: add the analogous second-stage QRF in
policyengine_uk_data/datasets/imputations/, training on FRS with
predictors = demographics + 6 income vars, outputs = a wide set of
“FRS-only” consequential variables including rent,
mortgage_interest_repayment, gift_aid, covenanted_payments,
charitable_investment_gifts, other_deductions,
employee_pension_contributions*, employer_pension_contributions,
total wealth components, capital_allowances, deficiency_relief,
and the full list of *_reported benefits. policyengine-uk-data#362 (April
2026) added this stage with a narrower output list: pension contributions,
tax-free savings income and the *_reported benefits. Microcosm UK’s second
stage had the same outputs at 75167a6, less the incapacity benefit and
maternity allowance reports, which its base data lacks; it has since added
trading losses, six owner-manager company variables and the War Widow’s
Pension report
(source_stages.json at 54b514f).
Specific aggregate residuals¶
After the formula-side fixes already merged (BASIC/NEW classification in PR #1618, new State Pension pro-rating + Protected Payment in PR #1634), the residual benefit-aggregate gaps against OBR on the Enhanced FRS (for the rows other than State Pension, the figures #1621 reported for 2025 on Enhanced FRS 1.50.3 in April 2026, which predates policyengine-uk-data#347, #359 and #362; the State Pension row is taken from the State Pension page; none re-measured on Microcosm UK) were:
| Variable | Model | Target | Gap | Likely cause |
|---|---|---|---|---|
income_support | ~£1.07 bn | ~£0.2 bn | +£0.9 bn | Legacy benefit near-fully migrated; same reported > 0 retention issue as the (pre-fix) tax credits. |
esa_contrib | ~£5.56 bn | ~£2.5 bn | +£3.1 bn | Large. Likely reported-based take-up plus no phase-out for migrated cases. |
attendance_allowance | ~£8.76 bn | ~£7.0 bn | +£1.8 bn | Cap-rate or take-up issue. |
tax_free_childcare | ~£0.41 bn | ~£0.9 bn | -£0.5 bn | Understated by about half: under-imputed eligible families. |
state_pension | £119.0 bn | £131.3 bn | -£12.2 bn | 2024-25, against DWP’s Spring Forecast 2026 less payments abroad, on policyengine-uk-data-private 1.57.4, as documented in state-pension.md. Most of the shortfall arises in building the Enhanced FRS (policyengine |
Each warrants a scoped PR pair (model + data) once the architectural fixes above land.
Small items from #1621¶
The issue body flagged three small items; two are resolved:
tax_credits/min_benefit.yamlunit wascurrency-USD; it is nowcurrency-GBP(#1627).benunit_weekly_hourslabel said “Average” but the formula sums; label now reads “Total weekly hours worked by adults in the benefit unit” (#1627).Some
~operations on Python bools raisedDeprecationWarning: Bitwise inversion '~' on bool, which becomes an error in Python 3.16; no fix is tracked.
Separately, the state_pension_type BASIC/NEW classification was rewritten in PR #1618.
This page reflects the current gap rather than the issue body, so the next reviewer working through the queue doesn’t waste time on already-fixed items.
Candidate PR sequence¶
Convert remaining
would_claim_*formulas to input-only (model side) + reported-anchored assignment in Microcosm UK’sfrs_take_upstage. Still open: the five formulas above read_reportedat runtime onmain.~~Port
assign_takeup_with_reported_anchors~~: done in policyengine-uk-data#359 and in Microcosm UK (gap 2).~~Add second-stage QRF for FRS-only variables on SPI donors~~: done in policyengine-uk-data#362 and in Microcosm UK (gap 3), both with a narrower output list than proposed above; Microcosm UK also redraws housing and runs the wealth, spending and VAT stages after the SPI block.
~~Add
gift_aidtoIMPUTATIONS~~: done in policyengine-uk-data#347 and in Microcosm UK, wheregift_aidis a stage-1 SPI output.Residual benefit-aggregate follow-ups: re-measure on the Microcosm release first, then separate small PRs for ESA contrib investigation, AA calibration, TFC under-imputation, the State Pension data-side fix (#1632, policyengine-uk-data#493). The IS phase-out landed in #1631 (
gov.dwp.income_support.activeis false from 2026-04-01).
References¶
Tracking issue: #1621.
US pattern reference:
policyengine-us-data#589.Related state pension docs: state-pension.md, #1632.
Related calibration scope: non
-uk -benefit -receipt -plan .md (#842), separate from this pipeline alignment but interacting with it on the calibration target side.