Skip to content

Certify policyengine-uk-data 1.58.0 with policyengine-uk 2.123.4 and core 3.32.21 - #555

Draft
MaxGhenis wants to merge 3 commits into
mainfrom
certify-uk-data-1.58.0
Draft

MaxGhenis wants to merge 3 commits into
mainfrom
certify-uk-data-1.58.0

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #554

Certified versions

Version Basis
UK data policyengine-uk-data 1.58.0, enhanced_frs_2024_25 (sha256 5ba399ea…b960b6) release manifest at HF commit c30504aa
UK model policyengine-uk 2.123.4 legacy_compatible_model_package: the publisher's claim in that manifest
Core policyengine-core 3.32.21 the same claim (compatible_core_packages)
US policyengine-us 2.2.1 and populace-us-2024-spm-20260915 unchanged

Before this PR, the bundle certified 1.56.16 with policyengine-uk 2.102.3 and core 3.32.10.

Merging releases this as policyengine 6.2.3: main is at 6.2.2 after #552, and a changed fragment bumps the patch version.

This is the release Max ruled on in d778 (2026-10-05): pin the latest policyengine-uk and core with the uk-data batch release, and certify that bundle. I checked 2.123.4 and 3.32.21 against live PyPI on 2026-10-07; they were uploaded at 16:20Z and 13:49Z. 1.58.0 was built with policyengine-uk 2.122.2 and core 3.32.13, so the newer pair is covered by a publisher compatibility claim, as #536 did for 1.56.16.

Certification

  • Publisher claim. HF commit c30504aa changes only releases/1.58.0/release_manifest.json. It appends policyengine-uk==2.123.4 to compatible_model_packages and policyengine-core==3.32.21 to compatible_core_packages. The build-time claims stay, and tag 1.58.0 still points at 47bbb58d.
    • uk-data's own validate_release_manifest (at 4cbedbec) passes on the new manifest.
    • The finalized-release guard in data_upload.py reads the manifest at the tag, not at main, so later uploads are unaffected.
  • Command. scripts/bundle.py certify-data --country uk --data-producer populace --manifest-uri hf://model/policyengine/policyengine-uk-data-private@c30504aa…/releases/1.58.0/release_manifest.json --model-version 2.123.4.
    • The gate accepts 2.123.4 on the claim. On the same manifest it still refuses an unclaimed version (2.123.3).
  • Core pin. packages.policyengine-core in the bundle manifest moves to 3.32.21. bundle.py generate regenerates the four extras from it, as in Update PolicyEngine Core to 3.32.10 #540.
  • Regenerated. uv.lock (uv 0.12.14, --upgrade-package for the two packages only: 13 lines), both TRO sidecars, docs/households.md's bundle line, and the test constants that pin the certified UK release.

Evidence on the 1.58.0 artifact

All figures below are policyengine_uk.Microsimulation runs on the exact artifact, in clean environments, for 2025, 2026 and 2030. Three environments:

  • (A) policyengine-uk 2.122.2 with core 3.32.13: the build pair.
  • (B) 2.122.2 with core 3.32.21.
  • (C) 2.123.4 with core 3.32.21: the certified pair.
Check Result
Core only (A → B) Every total, caseload and poverty rate is bit-identical in all three years
Finiteness (C) Every aggregate is finite
Unrecognised inputs (A vs C) The 20 input columns the model does not define are identical
Benefits, NI, council tax, household and HBAI net income (A → C) Within 0.26% in every year
Poverty rates, all four measures, by age (A → C) Within 0.03pp

Every model-only move traces to a policyengine-uk 2.122.3–2.123.4 changelog entry:

  • Income tax is +0.20% (+£0.65bn in 2026), from 2.123.0's property allowance fix. The allowance no longer comes off profits already net of expenses, so property allowance deductions fall from £2.5bn to £0.02bn and tax on property income rises 5%.
  • Guarantee Credit is +0.23% (2.123.0).
  • Employer NI is −0.02% (2.123.2: the £5,000 secondary threshold).
  • The High Value Council Tax Surcharge is −2.3% in 2030 (2.123.1: owners only).
  • Child Benefit is −0.18% in 2025 only. I traced this to two benefit units: the property allowance fix raises the higher earner's adjusted net income from £79,576 to £80,576. That is past the end of the HICBC taper, and with opt_out_charge_share at 1 an opted-out claimant stays opted out. child_benefit's formula is identical in both versions.

US. Core also moves for the US bundle, so I ran policyengine-us 2.2.1 on the certified populace_us_2024 (sha256 matches) with core 3.32.10 and with 3.32.21, for 2026. All 18 totals (income, state income and payroll taxes, EITC, CTC, SNAP, SSI, TANF, Social Security, WIC, school meals, the ACA PTC, household and SPM unit net income) and the SPM poverty rate are bit-identical. I did not run the ACS-local dataset that #552 now uses for US state and district regions. It is 9.8 GB, and this machine had 41 GB free. It runs the same policyengine-us 2.2.1 code on different rows.

What changes for users

UK results change, mainly because of the new data release. These come from policyengine.py's own API (ensure_datasets, Simulation, calculate_uk_poverty_rates and _by_age) for 2026. The left column is policyengine 6.2.1 from PyPI, the right is this branch, and both use the bundle's certified default dataset. 6.2.1 reproduces the figures in #536.

Poverty rate, 2026-27 6.2.1 (1.56.16, uk 2.102.3) This PR (1.58.0, uk 2.123.4)
Absolute BHC, everyone (the headline) 14.7% 15.2%
Absolute AHC, everyone 19.0% 20.3%
Relative BHC, everyone 18.8% 16.6%
Relative AHC, everyone 23.1% 21.1%
Absolute BHC, children (child) 16.3% 20.0%
Absolute AHC, children 23.7% 29.2%
Absolute BHC, 65 and over (senior) 15.2% 15.7%
Relative BHC, 65 and over 21.5% 17.5%
Total, 2026-27 (£bn) 6.2.1 This PR
Universal Credit 79.3 83.5
Housing Benefit 13.8 9.2
Child Benefit 17.7 16.9
Pension Credit 7.5 7.4
State Pension 127.5 124.4
Income tax 312.4 319.9
Household net income 1,760 1,709

For comparison, #536 cites DWP's FYE 2027 projection for children (DWP counts dependent children) as 17% BHC and 24% AHC. On the new data, both absolute child rates are above those.

Why absolute child poverty rises: it corrects an understatement. Every release from 1.56.16 to 1.57.4, run on policyengine-uk 2.123.4, gives 16.3–16.7% child absolute BHC poverty for 2026-27. The rise happens entirely at 1.57.4 → 1.58.0, where it reaches 20.0%.

  • Where it sits. The extra 0.51m poor children are all in SPI-synthetic households. Poor children there go from 0.05m to 1.20m, out of 4.25m children in those rows. Poor children in FRS rows fall from 2.49m to 1.85m.
  • The cause is Add the weighted-module design note #529's fix. Before it, SPI-synthetic rows gave employment income to 99.7% of their children and 46% of out-of-work adults (Add the weighted-module design note #529's own table). That inflated those families' incomes: the newly poor ones had £79k of household earnings on 1.57.4 and have £12.7k on 1.58.0.
  • Take-up is not the cause. Forcing every family to claim Universal Credit takes only 0.06m of the 1.19m back out of poverty.
  • Checked against HBAI. In FYE 2025 the absolute line is 60% of HBAI's own median, so the model's median should match HBAI's £719.48 a week. It was £779.82 on 1.57.4 (8.4% high) and is £736.01 on 1.58.0 (2.3% high). The model's FYE 2025 rates now sit near HBAI's (HBAI FYE 2025: children 21% BHC and 27% AHC; everyone 16% BHC):
FYE 2025, policyengine-uk 2.123.4 1.57.4 1.58.0 HBAI
Children, absolute BHC 18.2% 21.8% 21%
Children, absolute AHC 24.9% 29.8% 27%
Everyone, absolute BHC 15.4% 15.7% 16%

HBAI counts dependent children; this table uses ages 0–17. Child AHC now overshoots by 2.8pp. One candidate cause, being followed up separately: the data does not fill uc_reported_capital. Without it, Universal Credit uses a household wealth proxy, and 34% of poor FRS families with children have UC-assessable capital above £16,000. Relative poverty falls because the lower median lowers the relative line.

Model versus data. Separating the two needs one more run: policyengine-uk 2.123.4 with core 3.32.21 on the 1.56.16 artifact. These are direct-Microsimulation totals, 2026:

Total Model: 2.102.3 → 2.123.4 on 1.56.16 Data: 1.56.16 → 1.58.0 on 2.123.4 Combined
income_tax +0.1% +2.1% +2.3%
national_insurance +0.2% +2.7% +2.9%
universal_credit +5.4% −0.1% +5.3%
child_benefit −2.4% −2.1% −4.5%
pension_credit +2.3% −1.4% +0.9%
state_pension −2.7% −1.4% −4.0%
housing_benefit +1.2% −34.1% −33.3%
council_tax_reduction −0.4% −7.5% −7.9%
household_net_income −0.0% −3.0% −3.0%
hbai_household_net_income −0.0% −2.7% −2.7%

The model step moves every poverty rate by 0.5pp or less. Almost all of the poverty change comes from the data: absolute child poverty is +3.7pp BHC and +5.7pp AHC, and relative poverty is −2.3pp overall. The Housing Benefit caseload falls from 1.71m to 1.30m and Pension Credit's from 1.78m to 1.48m on the data step.

guarantee_credit is left out of these tables. In 2.102.3 it had no defined_for = "is_pension_credit_eligible", so its total there (£113bn) is not the same quantity. pension_credit is comparable.

Household snapshots (refreshed with PE_UPDATE_SNAPSHOTS=1; nothing else changed):

  • Employer NI falls by £1.20 per employee in the three earning households. That is 2.123.2's statutory £5,000 threshold instead of 52 × £96.
  • In uk_couple_two_kids, the three-year-old now gets the extended 30-hour childcare entitlement, £7,104.38 in place of the universal 15 hours (£3,552.19). Household benefits and net income rise by £3,552.19. I bisected this to policyengine-uk 2.114.2, which rewrote the extended entitlement's qualifying-child and income tests (SI 2022/1134 regs 13, 14(3)(c) and 18). Both parents work and each earns under £100,000, so 30 hours applies.
  • The model surface grows from about 2,600 to 3,200 parameters and from about 900 to 1,000 variables.
  • personal_rent is now a formula variable with no defined_for, so test_uk_model_exposes_selected_variable_metadata checks child_benefit's defined_for (would_claim_child_benefit) instead.

Invariants

This PR changes pins and certification metadata, not logic. The checks above that hold for every input are:

  • the core bump is bit-identical on both certified datasets (a differential test across two core versions);
  • every aggregate is finite;
  • the set of unrecognised inputs does not change;
  • the certification gate refuses any model version the manifest does not claim.

Checks run locally

  • make format and make lint pass.
  • scripts/bundle.py check --published-spm --include-tros --strict-tros and scripts/release_lock.py pass.
  • uv lock --check with uv 0.12.14 passes.
  • scripts/check_release_credentials.py fails locally by design: it accepts only a read-role token, and mine is fine-grained. CI's secret is read-role.
  • Merge with main. Use ACS-local data for US regional simulations #552 (ACS-local US regions, released as 6.2.2) landed after this branch was cut. cdad33ae merges it. The bundle manifest and both TROs are resolved by regenerating them on main's: certify-data, the core pin, then bundle.py generate with TROs. Against main, the manifest differs only in the UK entry and the core package. At the merged head, the bundle, certification, release-manifest, region, model, metadata, snapshot and release-lock tests give 226 passed. The strict TRO check, release_lock.py and uv lock --check pass.
  • Full suite on Python 3.13 with the HF token, before the merge:
    • At the first commit: 1,185 passed and 5 failed. The failures were the snapshot and metadata drift described above, fixed in the second commit.
    • At the head: 1,174 passed, 9 skipped and 16 setup errors, all in tests/test_release_lock.py. That fixture runs a live uv lock against PyPI, and it failed while the machine was heavily loaded. Re-run alone, the file gives 25 passed.

axiom: n/a: a wrapper pin and data certification; no policy is encoded here.

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 7, 2026 16:52
…core 3.32.21

Certify enhanced_frs_2024_25 from policyengine-uk-data 1.58.0 as the UK
default, on the publisher's compatibility claim added to the 1.58.0 release
manifest at HF commit c30504aa. Pin the latest policyengine-uk (2.123.4) and
policyengine-core (3.32.21), regenerate the extras, lock and TRO sidecars,
and move the tests that pin the certified UK release.

Fixes #554

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Employer NI falls by GBP 1.20 per employee: policyengine-uk 2.123.2 stores
  the Class 1 secondary threshold as the statutory GBP 5,000 a year rather
  than 52 x GBP 96.
- The couple's three-year-old now gets the extended 30-hour childcare
  entitlement (GBP 7,104.38) instead of the universal 15 hours
  (GBP 3,552.19). Bisected to policyengine-uk 2.114.2, which rewrote the
  extended entitlement's qualifying-child and income tests (SI 2022/1134).
- The model surface grows to about 3,200 parameters and 1,000 variables.
- personal_rent became a formula variable with no defined_for, so the
  metadata test checks child_benefit's defined_for instead.

Fixes #554

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the bundle manifest and both TRO sidecars by regenerating them on
main's: re-run certify-data against the 1.58.0 manifest at HF c30504aa,
re-apply the policyengine-core 3.32.21 package pin, then bundle.py generate
(with TROs). The UK entry keeps main's 6.2.2 stamps.

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

Copy link
Copy Markdown
Contributor Author

Pin policyengine-uk ≥ 2.123.6, not 2.123.4. The certified bundle needs PolicyEngine/policyengine-uk#1927 ("Key the Housing Benefit Guarantee Credit passport on receipt"). It merged on 2026-10-08 at 12:07Z as bd13f3d1, and it ships in 2.123.6 and 2.123.7 but not 2.123.4.

On earlier releases the HB passport keys on Guarantee Credit entitlement, so pensioners who don't claim Pension Credit still get passported to maximum Housing Benefit. In the uk-triple-lock model-v2 pilot, that overstates the Burnham plan's HB offset by about £3.3bn on an affected path. The review on PolicyEngine/uk-triple-lock#24 makes #1927 a hard precondition for the certified bundle the rebuild uses (d778), and the rebuild will refuse a bundle whose HB passport doesn't read in_receipt_of_guarantee_credit.

So please certify 2.123.7, or 2.123.6, with whatever core it needs, and extend the 1.58.0 manifest's publisher claim to cover it. If that claim can't be extended without a new data build, say so here.

@MaxGhenis

MaxGhenis commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Heads-up for the UK tables in this PR body: the policyengine.py-API columns ("6.2.1" and "This PR") carry a wrapper bug, #556, fixed in #557. A UK year-file run took the projected year as policyengine-uk's data year, so the State Pension followed CPI instead of the triple lock. The direct-Microsimulation "model versus data" table is unaffected.

The same pepy_poverty.py on this branch's head with #557 applied (uk 2.123.4, data 1.58.0, 2026). These now equal the direct policyengine-uk run on every aggregate and overall poverty rate:

This PR body With #557
State Pension, £bn 124.4 128.3
Pension Credit, £bn 7.4 7.1
Income tax, £bn 319.9 320.5
Housing Benefit, £bn 9.2 9.1
Household net income, £bn 1,709 1,711
Absolute BHC, everyone 15.2% 15.0%
Absolute AHC, everyone 20.3% 20.2%
Relative BHC, everyone 16.6% 16.6%
Relative AHC, everyone 21.1% 21.0%
Absolute BHC, 65 and over 15.7% 15.0%
Relative BHC, 65 and over 17.5% 16.8%
Absolute BHC / AHC, children 20.0% / 29.2% 20.0% / 29.2%

The 6.2.1 column moves too: State Pension £127.5bn → £133.7bn, absolute BHC everyone 14.7% → 14.4%, and 65+ absolute BHC 15.2% → 13.5%. #557 has the full table and the record-level checks.

Reforms are affected too, which matters for the triple-lock rebuild. Take a State Pension rate reform (new £260/wk, basic £200/wk from 2026) through Simulation(policy=...):

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

#557 is merged into main (e080ce00), so this branch can merge main and pick up the fix. The UK tables in this PR body should then come from a re-run of pepy_poverty.py on the merged head. The corrected figures in my comment above came from this branch's cdad33a with #557 cherry-picked. Any triple-lock or State Pension rate run on this bundle needs the fix: without it, the reform above was understated by about 3% on this branch.

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.

Certify policyengine-uk-data 1.58.0 with the latest policyengine-uk and core

1 participant