Skip to content

Add the two-child limit child-poverty benchmarks, recomputed on the certified Microcosm pair - #225

Merged
MaxGhenis merged 3 commits into
mainfrom
uk/two-child-limit-child-poverty
Oct 26, 2026
Merged

MaxGhenis merged 3 commits into
mainfrom
uk/two-child-limit-child-poverty

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Moves the UK two-child-limit child-poverty benchmark comparison out of the hard-coded policyengine-uk docs page (docs/book/validation/child-poverty-tcl.md, corrected by PolicyEngine/policyengine-uk#1724) into scorecard, where it is recomputed on a certified pair and concept-annotated (Max, 2026-10-23: "should that info be in scorecard instead"). PolicyEngine/policyengine-uk#2685 trims that page to the method and links here.

What it adds

  • sources/dwp-two-child-limit-poverty/: DWP's Budget 2025 note, Tables 1-4 (relative and absolute low income, BHC and AHC; children, working-age adults and individuals; FYE 2027-2031), the FYE 2026 figure and the 2 million children in gaining households. Raw GOV.UK content-API captures, adapter, QC against the Child Poverty Strategy note's Tables A1-A5, and annotations. These are 123 claims, all held out.

  • sources/rf-no-half-measures/: the Resolution Foundation's No half measures (30 October 2025), including vendored PDFs, an extract and annotations. Its figures were already uk_ab2025 claims, so the lane attaches results to them one for one and mints none. It also attaches results to HM Treasury's static and post-behavioural costings.

  • pipeline/compute_uk_two_child_limit_poverty.py: the counterparts on policyengine.py 6.11.0's certified Microcosm pair, data/uk/certified_pairs.json, entry pair-pe6.11.0_uk2.132.6_microcosm-uk-2024-25-national-20261019T083323Z-6cba5d71 (policyengine-uk 2.132.6, core 3.33.3). It goes through that registry's preflight and the managed route and covers every concept the sources use:

    • the relative line on each world's own median, and held at the with-limit world's;
    • the absolute line the engine serves (FYE 2025) and a constructed FYE 2011 line, which is DWP's;
    • HBAI dependent children and under-18s;
    • the static Exchequer cost.

    pipeline/modal_uk_two_child_limit_poverty.py ran it in offline Modal containers (block_network), because a host capacity reservation held local national runs. The ingest's check_artifact refuses an artifact that is off its pair.

  • History. The September 2026 Populace answers for the 13 claims this lane took over stay as each claim's earlier history row. pipeline/stage_uk_ab2025.py now skips this lane's claims, and the staged AB2025 files were regenerated over the original cohort; the 13 rows that left them are exactly the history rows.

  • Decomposition (pipeline/decompose_uk_two_child_limit_poverty.py, results/uk/two_child_limit_poverty/DECOMPOSITION.{json,md}): a measured account of how the September figure (~253,000, Populace + policyengine-uk 2.89.2) became October's (~548,000, Microcosm + 2.148.3).

  • Losers diagnostic (pipeline/diagnose_uk_two_child_limit_losers.py, diagnostics/losers.json): who is worse off when the limit goes, and through which variables. It writes aggregates only: the repository is public and the microdata is not.

Results (2029-30 unless stated)

Benchmark Published PolicyEngine Status
DWP: children in relative low income, AHC (Table 1) -450,000 -445,085 comparable
DWP: children in relative low income, BHC (Table 2) -300,000 -449,638 comparable
DWP: children in absolute low income, AHC (Table 3, FYE 2011 line) -250,000 -435,634 constructed
DWP: children in absolute low income, BHC (Table 4, FYE 2011 line) -250,000 -288,572 constructed
DWP: children in gaining households 2.0m 2.07m comparable
DWP: FYE 2026 figure 300,000 211,734 comparable
RF: children lifted 480,000 445,085 comparable
RF: cost £3.5bn £3.19bn comparable
RF: three-child limit 350,000 318,060 comparable
HMT: static Exchequer cost £2.715bn £3.19bn comparable
HMT: post-behavioural cost (Budget 2025 Table 1.2) £3.095bn £3.19bn static concept_mismatch

DWP's count of gaining children in deep material poverty (0.6 million) is pe_gap: policyengine-uk has no material-deprivation variable. Below, a negative DWP value means fewer people in poverty, matching DWP's tables.

Lane totals: 123 claims, 153 results (DWP 123, RF 5, HMT 12, history 13). Statuses: comparable 59, constructed 87, concept_mismatch 6, pe_gap 1.

How the estimate moved from ~253,000 to ~548,000

Measured on 2029-30 AHC children lifted (DECOMPOSITION.md; BHC alongside it there). Each step changes one factor:

Step Children lifted Change
September run (Populace UK 2023, policyengine-uk 2.89.2, under-18s, line held at the with-limit median) 253k
1 the same pair through a bare Microsimulation 253k +0k
2 engine 2.89.2 to 2.132.6 (Populace) 338k +86k
3 data Populace to Microcosm (2.132.6) 441k +103k
4 under-18s to HBAI dependent children 448k +6k
5 fixed line to each world's own median (= this PR's certified value, 445,085) 445k -3k
6 engine 2.132.6 to 2.148.3 (the docs page's run) 548k +102k

Steps 2 and 3 depend on their order. Data first gives +257k for the data, then -68k for the engine.

Step 6 is bisected over every policyengine-uk release from 2.132.6 to 2.148.3, holding policyengine-core at 3.33.3. Every release either ran or sits between two runs with equal counts. Ten releases move a count. The largest:

  • 2.147.1, +116k: HBAI income counts property income after finance costs;
  • 2.148.2, -42k: benefits uprated by the previous September's CPI;
  • 2.146.3, +38k: UC's LCWRA element, carer element and work allowance on the claimants' own circumstances.

Core 3.33.3 to 3.33.12 changes nothing. Each release's changelog summary is in the table; a release with several changes is not split.

Invariants (each tested)

  • Pair identity. The artifact is the registry's pair: identity hash, package versions, packaged bundle manifest, release bundle, dataset revision and sha256, and data years. Each world's dataset bytes are unchanged before and after it runs. Ingest refuses seven kinds of tampering.
  • Differential: wrapper release. policyengine.py 6.10.1 and 6.11.0 certify the same model and data, and their runs agree on every count, line and sum (diagnostics/wrapper_pe6.10.1.json).
  • Differential: managed vs bare route. The managed run and a bare Microsimulation on the same pair agree, to within one weighted child.
  • Bounds. Every count is bounded by its population, and populations do not move between worlds.
  • Fixed lines. Below any line held fixed across the worlds, removing the limit does not raise the count.
  • Before the measure starts, the worlds coincide (2025-26).
  • Absolute lines. The FYE 2011 line sits below the served line, and both rise.
  • Losers. Removing the limit leaves a few households worse off in the model: about 326 weighted households in 2029-30 (2 in the sample), with the largest loss £911 a year. In every world and year, losers.json measures each loss as a Universal Credit award that begins and ends the household's Tax-Free Childcare. policyengine-uk makes a UC award disqualify Tax-Free Childcare, and take-up (would_claim_uc) is a data input. This is a known model limitation, labelled as such. The test holds every loss to that one mechanism, and checks the diagnostic and the compute count the same households. The benefit units whose UC "falls" fall by under a penny a year (float rounding).
  • Decomposition arithmetic. The steps sum to the difference between the end points. The release-to-release changes plus the core change sum to the engine step. Every bisection cell installed the release its name says, on core 3.33.3 and the certified bytes.
  • Status rule. Every lane result has exactly one stated status, from one annotation. comparable requires the same concept, and a measured spread within half the source's rounding unit wherever the source leaves a convention unstated.

Checks

  • uvx ruff format --check be nz scorecard_db tests pipeline
  • The lane's tests locally. The full suite runs in CI, since the host reservation holds heavy local work until 2026-10-27 07:00Z.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
policyengine-scorecard Ready Ready Preview Oct 26, 2026 7:26pm UTC

Request Review

…ertified Microcosm pair

Moves the comparison policyengine-uk's docs page used to hard-code
(docs/book/validation/child-poverty-tcl.md) into scorecard. DWP's Budget
2025 note (Tables 1-4, the FYE 2026 figure, children in gaining
households) becomes 123 held-out claims; the Resolution Foundation's
No half measures figures and HM Treasury's costings were already
uk_ab2025 claims, so this lane attaches results to them and mints none.

PolicyEngine's counterparts run on policyengine.py 6.11.0's pair in
data/uk/certified_pairs.json through the registry's preflight and the
managed route, for every concept the sources use, in offline Modal
containers. Each result's status comes from one annotation; comparable
means the same concept. The September 2026 answers stay as history rows,
and the AB2025 stager skips the claims this lane owns.

DECOMPOSITION.md measures how the September estimate (~253k) became
October's (~548k): an engine x data grid, and a bisection of the engine
step over every policyengine-uk release from 2.132.6 to 2.148.3.
diagnostics/losers.json measures, in aggregate only, the few households
the model makes worse off: in each, a Universal Credit award begins and
ends the household's Tax-Free Childcare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lane's 123 DWP claims opt into display, so populations.json (and its
app/public/data copy) gains them, the build chain gains the lane's step,
and the AB2025 staged outputs this PR regenerates get their new hashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 26, 2026 19:11
…d fiscal-year notes

The staged AB2025 rows plus this lane's 13 history rows, in the stager's
order, now must equal the pre-move staged file byte for byte. The rounding
note no longer says the unstated convention cannot change a rounded figure,
and the fiscal-year notes say 30 April is policyengine-uk's default, with
fiscal_year_blend and preserve_calendar_dates parameters as exceptions.

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

Copy link
Copy Markdown
Contributor Author

Merge audit, head a33d00e5622e7cb0bbaf42b098e6921046aec5e4.

  • Independent review: GPT-6.1 Sol, Subfleet review at the hard tier.
    • Round 2, on 259129dc: REQUEST_CHANGES. Three blocking findings, all integration: the population export, the build-chain pin and the AB2025 snapshot hashes. Two optional wording findings. CI on that head failed on the same five tests.
    • 6f60baa6 and a33d00e5 address all five. They also add an exact test that the staged AB2025 rows plus this lane's 13 history rows rebuild the pre-move file byte for byte.
    • Round 3, on a33d00e5: APPROVE. Every round-2 finding is resolved, there are no new findings, and the reviewer ran 117 tests locally.
  • Checks: gh pr checks exits 0 at this head. Mergeable: CLEAN.

@MaxGhenis
MaxGhenis merged commit eb1bb45 into main Oct 26, 2026
13 checks passed
@MaxGhenis
MaxGhenis deleted the uk/two-child-limit-child-poverty branch October 28, 2026 12:59

This branch was successfully deployed

1 active deployment
Preview — a33d00e5 Deployed Oct 26, 2026 by vercel[bot]
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