diff --git a/changelog.d/1621-pipeline-alignment-page-corrections.changed.md b/changelog.d/1621-pipeline-alignment-page-corrections.changed.md new file mode 100644 index 0000000000..80deba2632 --- /dev/null +++ b/changelog.d/1621-pipeline-alignment-page-corrections.changed.md @@ -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`. diff --git a/docs/book/assumptions/uk-pipeline-alignment-plan.md b/docs/book/assumptions/uk-pipeline-alignment-plan.md index 34def30814..50d2f87982 100644 --- a/docs/book/assumptions/uk-pipeline-alignment-plan.md +++ b/docs/book/assumptions/uk-pipeline-alignment-plan.md @@ -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 @@ -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. @@ -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 @@ -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)) @@ -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 @@ -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 | @@ -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 @@ -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