Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Corrected the UK pipeline alignment planning page (`docs/book/assumptions/uk-pipeline-alignment-plan.md`): five `would_claim_*` formulas read reported receipt (Council Tax Reduction was missing); in data-built simulations `claims_all_entitled_benefits` is false and Housing Benefit and Council Tax Reduction ignore it, and eligibility for tax credits, Income Support and working-age Housing Benefit needs a reported award; policyengine-uk-data anchored take-up (#359), imputed gift aid (#347) and added a second-stage imputation (#362) in April 2026; the residual aggregates are #1621's April 2026 figures; the Income Support phase-out landed in #1631; `CTC_child_element` is not gated on `is_CTC_eligible`, so in tax-credit years an ineligible Child Tax Credit claimant's `tax_credits` enters its Housing Benefit and Council Tax Reduction means tests; and the third small item in #1621 was a `~`-on-bool warning, not `state_pension_type`.
145 changes: 106 additions & 39 deletions docs/book/assumptions/uk-pipeline-alignment-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ The target data pipeline is now
[Microcosm UK](https://github.com/PolicyEngine/microcosm), which replaced
the Enhanced FRS build in `policyengine-uk-data`; its UK national line is
built on the Family Resources Survey 2024-25 (`microcosm_uk_2024_25`).
Gaps 2 and 3 below, and PR 4 of the sequence, are implemented there;
gap 1 is still open on the model side. The aggregate residuals are the
May 2026 Enhanced FRS measurements and have not been re-measured on the
Microcosm release.
Gaps 2 and 3 below, and PR 4 of the sequence, are implemented there (and
were in policyengine-uk-data in April 2026, gap 3 in narrower form);
gap 1 is still open on the model side. The aggregate residuals other than
State Pension are the figures #1621 reported for 2025 on Enhanced FRS 1.50.3
in April 2026, and none has been re-measured on the Microcosm release.
```

## Architectural gaps vs US
Expand All @@ -39,19 +40,57 @@ UK's `frs_take_up` stage). But several still derive from
`_reported` at runtime:

- `would_claim_housing_benefit`
- `would_claim_council_tax_reduction`
- `would_claim_IS`
- `would_claim_WTC`
- `would_claim_CTC`

The runtime pattern is `claims_all_entitled_benefits | (foo_reported > 0)`,
which means a reform expanding eligibility only reaches existing
`_reported > 0` claimants. Non-claimants who newly qualify are
invisible unless the user manually sets
`claims_all_entitled_benefits = True` everywhere.
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. Tax credits stopped paying ongoing awards on 5 April
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](../programs/gov/dwp/tax-credits.md)), so the WTC and CTC
fix matters only for simulations of earlier years.
Expand All @@ -62,9 +101,13 @@ fix matters only for simulations of earlier years.
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`](https://github.com/PolicyEngine/microcosm/blob/75167a68/packages/microcosm-build/src/microcosm/build/uk/source_stages.json)).
The description below is the Enhanced FRS behaviour this replaced.
The description below is the Enhanced FRS behaviour before
[policyengine-uk-data#359](https://github.com/PolicyEngine/policyengine-uk-data/pull/359)
(April 2026), which anchored `would_claim_uc`, `would_claim_pc` and
`would_claim_child_benefit` on reported receipt, the same three flags
Microcosm UK anchors.

The Enhanced FRS take-up assignment was a pure random draw:
Until then the Enhanced FRS take-up assignment was a pure random draw:

```python
pe_benunit["would_claim_uc"] = generator.random(len(pe_benunit)) < universal_credit_rate
Expand All @@ -74,27 +117,36 @@ This discards information: respondents who reported receiving UC
clearly took it up, and should have `would_claim_uc = True` with
certainty.

**Fix**: port `policyengine_us_data/utils/takeup.py::assign_takeup_with_reported_anchors`
**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. This tightens per-variable calibration
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` trains an FRS-only
second stage on the new SPI incomes (its outputs include `gift_aid` and
`charitable_investment_gifts`), `spi_housing_shell` redraws tenure,
**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 this replaced.
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`
trains a QRF that replaces only six income variables on SPI donor
trained a QRF that replaced only six income variables on SPI donor
rows; everything else (rent, wealth, pension contributions, gift aid,
benefits-reported flags) stays as whatever middle-income FRS donor
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 carries an unchanged middle-FRS-
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`](https://github.com/PolicyEngine/policyengine-us-data/pull/589))
Expand All @@ -112,7 +164,14 @@ predictors = demographics + 6 income vars, outputs = a wide set of
`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.
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`](https://github.com/PolicyEngine/microcosm/blob/54b514f9/packages/microcosm-build/src/microcosm/build/uk/source_stages.json)).

## Specific aggregate residuals

Expand All @@ -121,7 +180,9 @@ in [PR #1618](https://github.com/PolicyEngine/policyengine-uk/pull/1618),
new State Pension pro-rating + Protected Payment in
[PR #1634](https://github.com/PolicyEngine/policyengine-uk/pull/1634)),
the residual benefit-aggregate gaps against OBR on the Enhanced FRS
(measured May 2026, except the State Pension row, which is taken from
(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 |
Expand All @@ -130,18 +191,20 @@ the State Pension page; none re-measured on Microcosm UK) were:
| `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](../programs/gov/dwp/state-pension.md). The shortfall arises in building the Enhanced FRS ([policyengine-uk-data#493](https://github.com/PolicyEngine/policyengine-uk-data/issues/493)). |
| `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](../programs/gov/dwp/state-pension.md). Most of the shortfall arises in building the Enhanced FRS ([policyengine-uk-data#493](https://github.com/PolicyEngine/policyengine-uk-data/issues/493)). |

Each warrants a scoped PR pair (model + data) once the architectural
fixes above land.

## Items already fixed since #1621 was filed
## Small items from #1621

The issue body flagged three small bugs; all are now resolved:
The issue body flagged three small items; two are resolved:

- `tax_credits/min_benefit.yaml` unit was `currency-USD`; it is now `currency-GBP`.
- `benunit_weekly_hours` label said "Average" but the formula sums; label now reads "Total weekly hours worked by adults in the benefit unit".
- `state_pension_type` BASIC/NEW classification (the `is_SP_age` heuristic in the issue) was rewritten in PR #1618.
- `tax_credits/min_benefit.yaml` unit was `currency-USD`; it is now `currency-GBP` (#1627).
- `benunit_weekly_hours` label 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 raised `DeprecationWarning: 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
Expand All @@ -151,19 +214,23 @@ already-fixed items.

1. **Convert remaining `would_claim_*` formulas to input-only** (model
side) + reported-anchored assignment in Microcosm UK's `frs_take_up`
stage. Still open: the four formulas above read `_reported` at
stage. Still open: the five formulas above read `_reported` at
runtime on `main`.
2. ~~Port `assign_takeup_with_reported_anchors`~~: done in Microcosm UK
(gap 2).
2. ~~Port `assign_takeup_with_reported_anchors`~~: done in
policyengine-uk-data#359 and in Microcosm UK (gap 2).
3. ~~Add second-stage QRF for FRS-only variables on SPI donors~~: done
in Microcosm UK (gap 3).
4. ~~Add `gift_aid` to `IMPUTATIONS`~~: done in Microcosm UK, where
`gift_aid` is a stage-2 output.
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.
4. ~~Add `gift_aid` to `IMPUTATIONS`~~: done in policyengine-uk-data#347
and in Microcosm UK, where `gift_aid` is a stage-1 SPI output.
5. **Residual benefit-aggregate follow-ups**: re-measure on the
Microcosm release first, then separate small PRs for
IS phase-out (analogous to the WTC/CTC reactive scheme close-out),
ESA contrib investigation, AA calibration, TFC under-imputation,
the State Pension data-side fix (#1632, policyengine-uk-data#493).
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.active` is false from
2026-04-01).

## References

Expand Down
Loading