Skip to content

Use the FRS benefit-unit capital for Pension Credit's capital test - #513

Merged
MaxGhenis merged 5 commits into
mainfrom
frs-pc-reported-capital
Oct 7, 2026
Merged

MaxGhenis merged 5 commits into
mainfrom
frs-pc-reported-capital

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Based on main, independent of #490 and #510. It needs policyengine-uk 2.107.0, the first release defining pension_credit_reported_capital (#2018), and this PR bumps the floor and lock to it. Like every uk-data merge, it lands with the batched release on Max's go (d833); the every-unit vs reporters-only choice is d800.

Why

About a third of FRS Pension Credit reporters get no modelled entitlement, and most of them fail on deemed income from imputed capital. Measured on #510's build D (2024-25 survey, FY2025-26, GB), original FRS households at survey weights, policyengine-uk main:

  • 1,173k benefit units report Pension Credit; 759k are entitled in the model and 415k are not.
  • 266k of the 415k fail only because of deemed income from capital.

For those 415k, the household wealth imputation and the survey's own benefit-unit capital (TOTCAPB3) disagree:

capital under £6,000 under £16,000 over £100,000 weighted median
imputed (WAS QRF, household, pension-age share) 13% 18% 43% £74,300
FRS TOTCAPB3 83% 96.5% 0.3% £300

The WAS imputation predicts household wealth from income, composition, tenure and region, and knows nothing about means-tested receipt. It also carries pension wealth in corporate_wealth (#501 removes that). TOTCAPB3 (used for this comparison) and its successor TOTCAPB4 are DWP's derived benefit-unit totals of savings and investments; DWP's below-average-resources statistics use TOTCAPB4 from 2019/20. In 2023-24 DWP also assigned Pension Credit to eligible FRS non-reporters using eligibility "sourced from survey data, combined with administrative data" (FRS background information and methodology, section 8.3).

What changes

  • create_frs writes pension_credit_reported_capital = FRS TOTCAPB4 for every benefit unit. Earlier survey years fall back to TOTCAPB3, and -1 is written where both are missing or negative (none are in 2024-25). policyengine-uk (#2018) then uses it in place of the household proxy in Pension Credit assessable capital.
    • Why B4: DWP's below-average-resources statistics replaced TOTCAPB3 with TOTCAPB4 "from when it became available in 2019/20" (BAR FYE2023, changes since last publication).
    • B3 vs B4 makes no material difference here. On build D plus the column, with the policyengine-uk fixes: 87.7k non-entitled reporters with either; GB entitled units 2,080.7k vs 2,083.5k; gate claims 1,290.0k vs 1,291.8k.
    • It is an approximation of Pension Credit capital, not the assessed figure. It covers financial assets only (second homes and land are not in it), and no Sch V disregard or reg 19 valuation is applied.
  • SPI-synthetic copies (impute_income) get -1, so they fall back to the household proxy. Their incomes are SPI-imputed, and the FRS donor's capital does not belong with them. In build E's saved dataset there are 96 SPI-synthetic PC reporter units: 14 are entitled with the donor's capital and 3 with it cleared (calibrated weight 4.2k vs 2.1k). Capital-gains copies keep their FRS incomes, so they keep the value.
  • uprating_factors.csv and uprating_growth_factors.csv get the column, with rows computed from policyengine-uk 2.107.0's per-capita GDP index (2024 1.204, 2025 1.256). The committed savings row (1.190, 1.223) is stale against that model. Copying it would have grown the capital differently in calibration than at runtime, enough to move a unit across the £10,000 deemed-income disregard. A test checks the rows against the model.
  • policyengine-uk >= 2.107.0 in pyproject.toml and uv.lock, with policyengine-core 3.32.13 (which cuts per-simulation memory overhead against 3.32.12).
  • Unchanged: savings and the other wealth columns, so wealth analyses still use the WAS imputation.

TOTCAPB4 and TOTCAPB3 cover financial assets only. Second homes and land, which Pension Credit also counts, are not in them, and no Sch V disregard or reg 19 valuation is applied.

Choice for review: every benefit unit (this PR) or reporters only.

  • Filling every benefit unit gives entitled reporters and non-reporters the same capital definition.
  • It matches the population DWP's take-up rate is measured against: 62% caseload take-up of 1,382k claims implies about 2.2m entitled GB benefit units.
  • Filling reporters only fixes the reporter gap. Non-reporters stay on imputed capital, which understates the entitled pool.

Measured (real runs; build D plus this column, before any rebuild)

These use #510's own solver at 62% take-up, solved over GB entitled units as in #510 f09497f, original FRS households at survey weights, GB, FY2025-26. "All PRs" = policyengine-uk #2018, #2019, #1899, #1940 and #1925.

main, build D #2018, reporters only #2018, every unit all PRs, every unit
reporters not entitled 415k 159k 159k 88k (7.5%)
GB entitled benefit units 1,344k 1,600k 1,907k 2,084k
fill probability (GB solve) 0.128 0.000 0.188 0.206
claims / spending after the fill 833k / £3.57bn 1,015k / £4.69bn 1,182k / £5.16bn 1,292k / £6.00bn
scale calibration needs to reach DWP's 1,382k / £6.14bn 1.66x / 1.72x 1.36x / 1.31x 1.17x / 1.19x 1.07x / 1.02x

So with these changes calibration no longer has to inflate reporters to reach DWP's caseload. Build D took GB reporters from 1,199k (survey weights) to 1,736k.

Totals on build D's calibrated weights overshoot, because those weights already inflate reporters (GB Pension Credit is £6.24bn on main and £8.17bn with #2018 and every unit filled, without recalibration). On the same static basis (2025-26, calibrated weights, main against #2018 with every unit filled), other outputs that read the Pension Credit assessment also move:

  • UK Housing Benefit £8.33bn → £8.59bn (guarantee credit passport);
  • UK Council Tax Reduction £1.84bn → £1.98bn (pension-age CTR takes PC capital and the GC passport);
  • pensioner relative poverty BHC 21.78% → 21.62%.

Build E, below, gives the rebuilt figures.

Rebuild: build E vs D′ (real production builds, 1 OA clone, 512 epochs; GB, FY2025-26)

The #510 session ran both builds:

E D′ DWP
Before calibration (survey weights)
GB reporters / of whom entitled 1,192k / 1,105k (93%) 1,196k / 771k (64%)
GB entitled benefit units 2,129k 1,368k ~2.2m implied
fill probability 0.209 0.129
GB claims / spending after the fill 1,341k / £6.17bn 859k / £3.64bn 1,382k / £6.14bn
After calibration
GB claims / spending 1,403k / £6.27bn 1,428k / £6.22bn 1,382k / £6.14bn
survey reporters (excluding SPI-synthetic copies), calibrated vs survey weights 1,251k vs 1,192k (+5%) 1,687k vs 1,196k (+41%)
SPI-synthetic reporters' calibrated weight (not anchored in #510) 58.6k 48.9k
take-up among entitled 60.2% 73.4% 62%
national fit: median abs(ratio-1) / share within 10% 4.0% / 83.9% 4.0% / 84.5%
Minimum guarantee +10% (same seeded flags)
newly entitled / share claiming 529k / 25.2% 214k / 20.4%
added spending (to the newly entitled) £1.89bn (£0.081bn) £1.89bn (£0.028bn)

Pensioner poverty rises in E. Relative AHC goes from 17.5% to 18.4% (all-person AHC 23.6% to 23.7%). I split it by running both datasets under both policyengine-uk trees (2025, calibrated weights):

pensioner relative poverty AHC main code combined code
D′ data and weights 17.39% 17.30%
E data and weights 19.76% 18.43%
  • Code alone (D′ weights): −0.09pp.
  • E's data and weights (combined code): +1.13pp.

Interpretation (inferred; the 2×2 separates code from data-and-weights together, not calibration weights alone): the rise comes from calibration no longer inflating reporters. In D′ the +41% reporter upweight stood in for entitlement the model missed, and it crowded out entitled pensioners who don't claim. In E those people get their weight back. Pensioners in entitled, non-claiming benefit units rise from 0.70m to 1.29m, and those of them in AHC poverty from 0.16m to 0.32m. Take-up among the entitled falls to 60%, against DWP's 62%. The poverty figure moves away from HBAI, so it is called out here for review.

Max's questions on d800

~/reviews/uk-pc-reporter-gap-2026-10-01/d800/D800_ANSWER.md (local) answers them. The recommendation is unchanged: FRS financial capital for every benefit unit.

At household level the WAS imputation barely tracks the FRS's own capital. The rank correlation between FRS TOTCAPB4 and WAS-imputed savings is 0.29 across households and 0.17 among means-tested reporters. 26% of households the FRS puts under £6k are imputed over £16k, yet national totals agree (£1.50trn vs £1.57trn).

How the US builds combine sources:

  • policyengine-us-data splits SIPP and SCF by variable.
  • Microcosm draws one source per household at random (0.5). That costs nothing in the US, because the CPS observes no asset stocks. In the UK the FRS observes financial capital, so a random mix would overwrite observed values.

Follow-ups:

  • UC, HB and the legacy tests on the same input.
  • Non-home property added back where the FRS shows rental income.
  • A WAS draw conditioned on FRS capital, for wealth analysis.

Remaining gap (all PRs, every unit filled; survey weights, 88k)

class k
income more than 10% above the appropriate amount (misreporting or income measurement) 43.5
income within 10% of the appropriate amount 21.3
capital (FRS capital itself over the disregard) 8.6
mixed-age couples 5.7
Savings Credit age test at birthday boundaries 4.6
earnings 4.1

Most of this is survey reporting that the model should not chase.

Tests

tests/test_pension_credit_reported_capital.py:

  1. A finite, non-negative TOTCAPB4 is carried over unchanged. A valid TOTCAPB3 is the fallback where TOTCAPB4 is absent or invalid, including with nullable (pd.NA) columns.
  2. Missing, non-numeric or negative values in both become -1.
  3. A table with neither column gives -1 throughout.
  4. Over randomised inputs, the output is always -1 or non-negative, one per benefit unit.
  5. The uprating rows equal what create_policyengine_uprating_factors_table derives from the locked policyengine-uk, for 2020-2034.
  6. SPI-synthetic copies are cleared to -1.

tests/test_policybench_transfer.py: the family-structure test now compares is_married with the claimant-and-partner count, because policyengine-uk 2.107 (#1896) defines a couple that way. Its family_type check is unchanged.

Before merge

  1. policyengine-uk#2018 released; bump the lock: done (2.107.0).
  2. Integration build E: done; see the section above.
  3. Decide whether to accept the pensioner-poverty move (+0.9pp relative AHC). It comes from the data and weights, not the code. Attributing it to take-up weighting is an inference. This is part of d800.
  4. Build E predates the SPI -1 change. In build E's saved dataset, clearing the donor's capital on the 96 SPI-synthetic reporter units leaves 3 entitled instead of 14 (calibrated weight 2.1k vs 4.2k). Solve Pension Credit take-up over entitled benefit units #510 already draws these copies rather than anchoring them. The batch build will show the combined effect.

axiom: n/a: data change

🤖 Generated with Claude Code

TOTCAPB3 (DWP's derived benefit-unit total of savings and investments) for
every FRS benefit unit; -1 where missing or negative. Pension Credit counts
the claimant's and partner's capital (SPCA 2002 s.5); the WAS household
imputation gives FRS Pension Credit reporters a weighted median of 74,300
pounds against the survey's own 300 for those the model then finds not
entitled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…K 2.107.0

- SPI-synthetic copies get pension_credit_reported_capital = -1: the FRS
  donor's capital does not belong with SPI-imputed incomes (24 of 152 SPI
  PC reporters were entitled in build E only because of it).
- uprating_factors.csv and uprating_growth_factors.csv get the column with
  the savings row (policyengine-uk uprates it with the same per-capita GDP
  index), so uprate_dataset keeps the two in step.
- policyengine-uk >= 2.107.0, the first release defining the variable
  (#2018); core 3.32.12 comes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 3, 2026 17:22
…513)

- TOTCAPB4 (DWP's below-average-resources measure since 2019/20) in place of
  TOTCAPB3, which stays as the fallback for earlier survey years. On #510
  build D plus the column: the same 87.7k non-entitled reporters as with
  TOTCAPB3, GB entitled units 2,080.7k vs 2,083.5k, gate claims 1,290.0k vs
  1,291.8k. The docstring now calls it an approximation (financial assets
  only; no Sch V disregard or reg 19 valuation).
- uprating_factors.csv and uprating_growth_factors.csv rows recomputed from
  policyengine-uk 2.107.0's per-capita GDP index (2024 1.204, 2025 1.256),
  not copied from the stale savings row, so calibration and runtime grow
  the capital alike; the test now checks the rows against the model.
- policyengine-core 3.32.13 in uv.lock (3.32.12 leaks memory per simulation).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uk 2.107)

policyengine-uk 2.107 (#1896) presumes a couple married when the dataset
does not say, and its couple is the claimant and partner rather than any two
adults: a member under 20 and 16+ years younger than the claimant is
presumed to be their child. The family-structure test compared is_married
with the adult count, which the lock bump to 2.107.0 made fail; it now
compares with the claimant-and-partner count. family_type still counts
is_adult, so that check is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With pandas nullable Float64 columns, a missing TOTCAPB4 (pd.NA) made the
validity mask NA and wiped a valid TOTCAPB3. Convert each column to plain
float64 first so the mask has no missing entries. The FRS reader uses plain
NumPy dtypes, so this release's input is unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Queued in release PR #544 for the 10/8 uk-data batch. It lands only on Max's go (d833).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant