Skip to content

Calibrate Microcosm UK to CenTax's parent income tabulations - #1399

Draft
MaxGhenis wants to merge 5 commits into
uk-pair-release-2151from
uk-centax-childcare-parent-targets
Draft

MaxGhenis wants to merge 5 commits into
uk-pair-release-2151from
uk-centax-childcare-parent-targets

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

WIP (draft). Stacked on #1396 (uk-pair-release-2151). Don't merge before #1396.

Adds CenTax's HMRC tabulations of parents' incomes as Microcosm UK national calibration targets (Advani, Flew, Pepin-Hall and Summers 2026, Removing the childcare cliff-edge). The targets set the income distribution of parents of 3- and 4-year-olds in England around the £100k childcare cliff, which the £100k childcare dashboard reads.

What it adds

  • Derived facts. uk/centax_childcare_parent_facts.json holds 9 England cells built from Figures 3.1, 3.3, 3.6 and 3.7. Each cell is CenTax's published share times the baseline build's own parent count. tools/build_uk_centax_childcare_parent_facts.py builds the file, and the workbook's sha256 is pinned (8c238557…).
  • Nine contract targets. They are microcosm.centax_childcare_parents.*, bound on the national line only, with registry-parity accounting:
    • four ANI bands partitioning (0, £200k] in 2021-22 pounds: £0–50k, £50–80k, £80–120k, £120–200k;
    • five splits of (£80k, £150k]: women, main income source other than employment, and London / South excl. London / North and Midlands.
  • Two person measures. These are centax_parent_of_eligible_3_4_year_old and centax_employment_main_income_source. The second weighs the year's pay against every component of the engine's total_income (ITA 2007 s. 23), taking social_security_income benefit by benefit; a tie goes to pay. The resolver also computes adjusted_net_income_annual on the annual-income simulation.
  • One loader for the derived facts. uk_runtime/derived_facts.py merges the high-value home stock and the CenTax cells at every national compile site. Those sites are the full and national target inputs, the release-cut certifier, the compile-parity receipts, the UC matrix diagnostic and the incumbent evaluation. The receipts name both resources.
  • Diagnostic. tools/diagnose_uk_centax_parents.py reports each cell's fit and support on any national H5: records, Kish effective households and the top-ten household share.

Never targets (d2014): CenTax's model outputs. That means the bunching estimates (Tables 3.1, 3.2 and 4.1), frictions, elasticities, marginal bunchers and every cost. Figure 2.1 (childcare-account shares), Figure 3.12 (non-working partners) and Table A.1's levels stay diagnostics inside the resource.

Design calls

  • Shares, not CenTax's levels. Microcosm targets are sums, so each share becomes a count: the share times the baseline's own parents in the band's range at the calibration year. The cells set the shape of the income distribution and leave its level where the DfE caseloads put it. CenTax's level is an identified sample (1,648,632 parents of 3- and 4-year-olds in Table A.1), not a population count.
  • Wide bands around £100k. In 2021-22, with the cliff at £100k, the (£80k, £120k] band held both the parents who bunched below it and those they came from above it. The FRS also has too few effective households above £80k to bind narrower bins (see support below). The five splits cover (£80k, £150k], the widest band that every split figure covers, and the three region groups partition it.
    • Not bunching-neutral after ageing (review r1). Aged to 2025, the band and the split band start at £99,817 while the cliff stays at £100k. So a parent who bunches that year to below £99,817 leaves them for the band below. Their counts assume no bunching under the frozen cliff, and they lean high by about the parents who bunch below that edge. CenTax estimates that 2,610 parents of 3- and 4-year-olds bunched in 2022 (p. 22). A lower middle edge, about £75k in 2021-22 pounds, would hold both years' cliffs. That's a follow-up: it needs another national build.
  • Ageing. The edges are CenTax's 2021-22 pounds aged to the calibration year (2025) by the OBR average earnings index that policyengine-uk carries (×1.2477), rounded to whole pounds. I chose earnings because the spine's incomes are aged that way, so each cell keeps its place in the distribution. CPI would move the edges against the incomes. The shares themselves aren't aged: the shape of 2021-22 is assumed to hold in 2025.
  • Parent identification. CenTax finds parents in three ways: childcare accounts (the 2018–2025 panel), PAYE parental leave (from 2014-15), and address (UPRN) matching of partners. See pp. 12–13 and Appendix A (Figure A.1). So it misses couples with none of these. Those are mostly self-employed or company-owning parents over £100k, where neither partner takes recorded leave or ever opened an account. The report says this overstates parents below £100k relative to those above (p. 13). The upper cells may therefore lean low, and the non-employee split especially.
  • Child definition. A parent is an adult (not an HBAI dependent child) in a benefit unit with a dependent child in England whose DfE age is 3, 4 or 5 and whose early_years_term_share_before_school is above a sixth. DfE age is the model year less birth_year, keyed as uk_runtime/dfe_spring_term.py keys it. The term-share rule means the child is age-eligible for the universal hours in at least one of the year's three terms. CenTax counts it the same way: "eligible for free childcare hours in 2022 for a positive number of months" (Table A.1 notes). The DfE caseload rows read the same age and term share but count the spring term only (a share above one half).
  • Income. HMRC's data count what each parent received in the tax year. So the cells read adjusted_net_income_annual on the annual-income simulation, not the point-in-time ANI.
  • England only. CenTax tabulates England's free hours, so the targets use England's geography pin (TARGET_PREFIX_GEOGRAPHY_PINS).
  • National line only. calibration_lines: ["national"]; the local line drops the rows.

Invariants (tested)

  • The four band shares partition (0, £200k], and so do the three region shares within the split band.
  • Each count is its share times the baseline's parent count (rounded to 0.001), and the four band counts add back to that total.
  • The aged edges stay contiguous and rise with the uprating index.
  • The parent measure marks whole benefit units of adults and is invariant to row order.
  • The main-source measure is monotone in pay and scale-invariant. Every other source can outrank pay. The compared sources are exactly the installed engine's total_income leaves (an engine test).
  • Every source in that comparison is the year's amount. A claimant paid contributory ESA for part of the year, whose current ESA is above the year's pay, counts as an employee (a resolver test that fails on the previous resolver).
  • The diagnostic's support over FRS source households never exceeds its support over Microcosm households. It has fewer units, a lower Kish count and a larger top-ten share, and equals it when each household is its own source (Hypothesis, plus a worked example).
  • The contract binds exactly the resource's cells, on the national line.
  • Every national compile reads the merged rows.
  • The build's materialized target column equals the diagnostic count on all nine cells (a differential check on a6299504; see below).

Hypothesis properties cover the edges, both measures, the share partition on any positive workbook, and the order-independent rows digest.

Status

  • Derived facts, measures, diagnostic
  • 9 contract targets, merged loader, tests
  • Regenerate target_references.json, the membership report and the signed compile-parity receipt. These are additions only: nine references, nine membership entries, nine ledger_only receipt rows. Each feed read peaked at 27 GB and took 11–13 min. They're committed locally and not pushed yet.
  • Independent review r1 (GPT-6.1 Sol): request changes. The major finding was that other investment and miscellaneous income were left out of the main-source comparison; it's fixed in 85181c8. The minor finding, the bunching claim after ageing, is corrected above and in the selector notes.
  • Independent review r2 (GPT-6.1 Sol): approve. It raised one unconfirmed concern: the main-source measure read the contributory benefits at their current amounts, not the year's. That is fixed in ea2e3d6ed (local). On both nationals, the nine cells and every parent aggregate are identical under the old and new measure.
  • Local national build with the new rows (1,277 targets). It is never staged or certified, and its gate report is signed with a throwaway key.
    • All nine rows fit within 0.5% of their targets.
    • Of the 1,199 published shared targets, the same ones are within 10% as on a6299504, and none moved by more than a point. Effective sample size fell 1.2%.
    • The before/after also carries a locally rebuilt spine and a newer Python. The noise baseline that would separate those runs after 10/29.
  • The diagnostic now counts support over FRS source households (c35cd4eb5, local). The high-income cells rest on few source households. The non-employee cell rests on fewer than ten, so the build withholds its figures; it stays bound under the 2026-10-21 rule.
  • Dashboard static cost before and after, local only. The numbers are for Max's review and are not published here; the live dashboard is unchanged (d2013).
  • Independent review r3 (GPT-6.1 Sol) of ea2e3d6ed and c35cd4eb5: approve with nits. It found no production-code defect. Its one P3, that the top-ten source share was not pinned apart from the household share, is fixed in ad0b2ee24 (local), and the new test fails on that mutant.
  • Push 3b9941603, ea2e3d6ed, c35cd4eb5 and ad0b2ee24. They are held until a ruling on where the derived workbook values live.
  • María's sign-off; land after Build the national + local-area pair on policyengine-uk 2.151.0, and drop UK model-figure withholding #1396

axiom: n/a: microsim calibration targets only, no policy rule changes.

🤖 Generated with Claude Code

MaxGhenis and others added 5 commits October 25, 2026 13:45
CenTax's HMRC tabulations of English parents of 3- and 4-year-olds
(Advani, Flew, Pepin-Hall and Summers 2026, Removing the childcare
cliff-edge, Figures 3.1, 3.3, 3.6 and 3.7, tax year 2021-22) become nine
derived facts in uk/centax_childcare_parent_facts.json. Four are ANI band
shares of the parents in (0, £200k]: (0, 50k], (50k, 80k], (80k, 120k]
and (120k, 200k]. Five split the parents in (£80k, £150k]: women, a main
income source other than employment, London, the South excluding London,
and the North and Midlands. Each share is multiplied by the baseline
build's own parent count, so the targets set the shape and leave the
level to the DfE caseloads.

uk_runtime/centax_childcare_parents.py holds the cells, the edges (the
2021-22 edges aged by policyengine-uk's OBR average earnings index, 2025
over 2021, which is 1.24772), the loader's checks, and two measures:
- centax_parent_of_eligible_3_4_year_old: an adult of a benefit unit with
  a dependent child in England, DfE age 3 to 5, before school in at
  least one term;
- centax_employment_main_income_source: employment is the year's largest
  income source.
The measure resolver computes both and refuses a persisted column of
either. adjusted_net_income_annual joins the annual income measures,
because HMRC's data count the tax year's income.

tools/build_uk_centax_childcare_parent_facts.py reads the pinned
workbook (sha256 8c238557…). tools/diagnose_uk_centax_parents.py
reports each cell's fit and household support on any national H5. The
parent count, 2,165,344, comes from its run on the a6299504 staged
national.

Never targets: CenTax's model outputs, which are the bunching tables,
frictions, elasticities, marginal bunchers and costs. The Table A.1
levels and the Figure 2.1 and 3.12 shares are kept as diagnostics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nine targets (microcosm.centax_childcare_parents.*) bind the derived
CenTax facts on the national line only, at England grain: four adjusted
net income bands and five splits of (£80k, £150k]. One loader,
uk_runtime/derived_facts.py, merges them with the high-value home stock
at every national compile site, and the target-input receipts name both
resources. The reference generator reads the new resource, pins the
family to England and streams the feed line by line. The compile-parity
receipt tool marks the nine rows ledger_only with a stated rationale.

Tests cover the resource, the two person measures, the contract and the
merged compile sites, with Hypothesis properties for the edges, the
measures and the share partition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w CenTax finds parents

The plain-column review test lists every variable a contract binding reads as
a filter or band. The two CenTax measures are new there. Neither is a stored
read: the resolver refuses them as frame inputs, computes the parent flag from
the engine's term shares at the calibration period, and computes the main
income source from the annual measures. So they join the reviewed set.

Each CenTax target's selector note now says how CenTax identifies parents
(childcare accounts, PAYE parental leave, address matching of partners) and
that this leaves out some parents above £100k, so the upper cells may lean low.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… correct the bunching claim

Review r1 (GPT-6.1 Sol) found the main-source measure left out other
investment income and taxable miscellaneous income. A parent with £30,000 of
pay and £100,000 of other investment income counted as an employee, so the
non-employee cell undercounted. The measure now weighs pay against every
component of the engine's total_income (ITA 2007 s. 23), with
social_security_income taken benefit by benefit. An engine test pins the
compared set to the installed engine's own total_income leaves. A parametrized
test checks that each source can outrank pay, and a resolver regression covers
other investment income.

The selector notes said the (£80k, £120k] band's count does not depend on
bunching. That held in 2021-22, when the cliff sat inside the band. Aged to
2025, the band and the split band start at £99,817 while the cliff stays at
£100k, so a parent who bunches below that edge leaves them. The notes now say
so, and size the lean with CenTax's 2,610 bunching parents of 3- and
4-year-olds in 2022 (p. 22).

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

This branch has not been deployed

No deployments
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