Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
00040d9
Lock policyengine-uk 2.127.0 (#1095)
juaristi22 Oct 13, 2026
c2d305f
Cite the locked policyengine-uk 2.127.0 in the stage texts (#1095)
juaristi22 Oct 13, 2026
3e62fa6
Map FRS EMPSTATI 11 to OTHER_INACTIVE and refuse unknown adult codes
MaxGhenis Oct 2, 2026
1113915
Restate the frs_employment stage notes and re-pin the coverage manifest
MaxGhenis Oct 2, 2026
00519cf
Answer review round 1 and the live-tree incumbent scan
MaxGhenis Oct 3, 2026
01c771a
Name the unknown EMPSTATI codes but never a count in the refusal
MaxGhenis Oct 3, 2026
d309afb
Read approved training from the FRS TRAINEE flag on dependants (#1095)
juaristi22 Oct 13, 2026
6c3a0ad
Set statutory paternity pay from the FRS adult table (#1095)
juaristi22 Oct 13, 2026
f19416e
Limit salary sacrifice to the pay it comes out of (#1095)
juaristi22 Oct 13, 2026
2da556a
Split the national 15-19 age bands into 15, 16-17 and 18-19 (#1095)
juaristi22 Oct 13, 2026
0f0bb86
Write UK release weights at the dataset year (#1095)
juaristi22 Oct 14, 2026
e9aa8d3
Keep UK release identifiers inside the engine's 32-bit range (#1095)
juaristi22 Oct 14, 2026
1e88ac5
Name only the column in the identifier refusals (#1095)
juaristi22 Oct 14, 2026
ac6088f
Measure the calibration on the release's identifiers (#1095)
juaristi22 Oct 14, 2026
ae08ba3
Test that the calibration and the release deal the same birth months …
juaristi22 Oct 14, 2026
3623ca5
Lock policyengine-uk 2.130.1 (#1095)
juaristi22 Oct 14, 2026
84ce97a
Store each person's months since their last birthday in the spine (#1…
juaristi22 Oct 14, 2026
e334278
Hold the five-or-more children UC row out of the calibration (#1095)
juaristi22 Oct 14, 2026
0912388
Regenerate the derived surfaces on main after #1256 (#1095)
juaristi22 Oct 14, 2026
fcb6ff2
Record the fourth ports PR's receipts (#1095)
juaristi22 Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1095-age-15-19-band-split.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The two national UK age targets for 15 to 19 by sex are split into ages 15, 16-17 and 18-19 (uk-data#563, microcosm#1095; María's ruling of 2026-10-13). A fit to a five-year band leaves the ages inside it free to move: uk-data#563 found weights that fit the band while holding 35% too many 16-year-olds and about half the 18- and 19-year-olds, and nothing in this contract bound the mix inside the band either. The ages 16 and 18 are where the child and qualifying-young-person rules change. The six rows read the same ONS mid-year estimate by single year of age as the two they replace, so each sex's three rows sum to its old band. The contract has 400 targets (396 before) and 1,283 active references (1,279). In the registry accounting the incumbent's `ons/female_15_19` and `ons/male_15_19` move from mapped to excluded, since no single row has their definition, and the six rows are contract-only declarations; the compile-parity receipt signs the two incumbent rows as fixture-only and the six as ledger-only. Within the `ons_population` family every other row's loss weight falls to 149/153 of what it was.
1 change: 1 addition & 0 deletions changelog.d/1095-approved-training-trainee.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The UK `frs_education` stage sets `is_in_approved_training` from the FRS TRAINEE flag on child-table records, porting uk-data#561 (microcosm#1095). The FRS counts a 16- to 19-year-old as a dependent child only in full-time non-advanced education or unwaged government training, so a child-table record with TRAINEE = 1 is a dependant in approved training. The stage read the adult table's TRAIN2 scheme codes instead (microcosm#1063), which flagged adults on DWP employment schemes and no dependant, so those dependants failed the qualifying-young-person education-or-training condition for Universal Credit, Pension Credit, Child Benefit and child tax credit. No adult-table record is flagged now, a tab without TRAINEE refuses the build, and the entry age and the age-19 terminal-date flag follow the new flag. No engine change is needed. With the flag, fewer than 10 FRS source households separate DWP's count of Universal Credit households with five or more children from its two-child-limit counts; before it, the frame told them apart only through the dependants the missing flag miscounted. So `dwp.uc.households_children_5_or_more` is held out of the calibration until 26 November 2026 (`calibration_measure_exclusions.json`, María's ruling on microcosm#1271), and the two-child-limit rows stay bound.
1 change: 1 addition & 0 deletions changelog.d/1095-birth-month-stored.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The FRS spine's root stage now stores each person's `months_since_last_birthday` (`uk_runtime/birth_month.py`), so every engine read of a build, the stages', the calibration's and the release's, places the same date of birth (microcosm#1095). policyengine-uk places a date of birth from the whole age and this column, and the date decides the two-child limit, the State Pension scheme and the Pension Credit qualifying age. Left unset, the engine spreads each single year of age and sex evenly over the year by weight, in an order set by a hash of `person_id`, so a file's birth months depended on the weights and the identifiers it carried: the release, at the calibrated weights, read a different spread from the one the solve fitted. The stage spreads the months the engine's way once, at the FRS weights, in an order set by an identity-keyed draw on the person's stable identifier; a copy of a person keeps their months. The column is allow-listed on the export surface. An engine test holds the stage's spread equal to the engine's own `stratified_uniform` and shows that stored months do not move with the weights while unset ones do.
1 change: 1 addition & 0 deletions changelog.d/1095-policyengine-uk-2-130.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The lock's UK side moves from policyengine-uk 2.123.0 to 2.130.1, and the UK extras of microcosm-build, microcosm-data and microcosm-frame require >=2.130.1 (microcosm#1095). The branch first moved to 2.127.0 and then, after review, to 2.130.1, the first release published after it (2.128.0 to 2.130.0 never reached PyPI). policyengine-core moves to 3.33.3 on the UK side, which is what policyengine-uk's own floor resolves, and stays at the US side's own 3.32.5 pin (microcosm#1086); a test pins the engine and core that every UK install path resolves. Nothing the UK build writes is renamed, removed or re-typed. What the build's engine reads return does change. Up to 2.127.0: `uc_reported_capital` and `pension_credit_reported_capital` become formulas of the new input `benunit_reported_capital`, a stored value still wins, and a stored `uc_reported_capital` is projected by GDP per head as Pension Credit's already was (pe-uk#2201), which completes microcosm#1200's property rule for Universal Credit; a claimant whose partner cannot claim is assessed as a single person (pe-uk#2084); DWP's announced 2025-26 rates replace CPI projections (pe-uk#1925); the Housing Benefit Guarantee Credit passport needs receipt and each family's Council Tax Reduction uses its own scheme; a unit's own claimant or partner no longer counts as its child; Carer's Allowance stops above the earnings limit for reported recipients too; savings and dividends are banded above property income; the employer Class 1 secondary threshold is £5,000 a year; and the road-fuel litre proxies are re-based on DESNZ pump prices. From 2.128.0 to 2.130.1: families move onto Universal Credit when DWP closes a legacy benefit they receive, tax credits from 2025-26 (pe-uk#2119); statutory sick, maternity and paternity pay count as Universal Credit earned income, and paternity pay in household benefits and HBAI income (pe-uk#2365); Gift Aid extends the basic rate band instead of reducing allowances (pe-uk#2271); a child on the working parent childcare entitlement keeps the universal hours (pe-uk#2177); the benefit cap's ESA and disability exceptions follow the claimant and partner (2.129.0) and its earnings exception counts statutory pay (pe-uk#2399, 2.130.1); a reported income-related ESA or income-based JSA award ends when the claimant or partner is in remunerative work (2.129.1); and the Pension Age Winter Heating Payment counts in household income. Six parts of the build follow. The CGT taxable-income measure is net income less the allowances, as the engine now stacks gains on it, and no longer takes Gift Aid off the allowances. The salary-sacrifice relief by rate class extends a Gift Aid donor's bands by the grossed-up gift, as the engine's `earned_income_tax` now does (ITA 2007 s.414(2)(b)), so the classes still add up to the engine's tax; without it the national calibration refused, and the three relief rows declare the extension in the contract. The universal-only childcare row counts children on the universal hours who are not on the extended entitlement, as the published difference does. The UC take-up lockstep test names a third case the population does not mirror: a pension-age family moved onto Universal Credit at the legacy closure (SI 2014/1230 reg 60A). The concept mapping declares `would_claim_uc_at_legacy_closure` left at the engine's default, so every family moved off a legacy benefit claims Universal Credit; uk-data#534 draws it at DWP's Move to UC claim rates, which are not yet a Chronicle fact, so the calibration's 2025 Universal Credit rows rest on that default. And the liquid-asset reason names the three company-capital inputs of Universal Credit's regulation 77 (2.130.0), which default to no company and which the build sets none of. Re-derived with no change to data: the uprating pins (the version stamp; every pinned value, the ONS population growth included, is unchanged), the concept coverage (288 inputs to 299: eleven owned-company inputs and `would_claim_uc_at_legacy_closure` join, `uc_has_limited_capability_for_work` leaves; the build writes none of them), the stage texts that cite the locked engine, the H2 fixture's dependency, the coverage manifest's source digests and `APPROVED_UV_LOCK_SHA256`. One engine test changed its expectation at 2.127.0: the single-claim rule gives a 24-year-old with a 17-year-old partner the single young allowance.
1 change: 1 addition & 0 deletions changelog.d/1095-release-identifiers-32-bit.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A UK release's identifiers stay inside the range policyengine-uk stores (microcosm#1194, microcosm#1095; uk-data#558 is the incumbent's side). The clone stages number each copy by adding a power of ten to every identifier, so a built frame's identifiers run far past 2,147,483,647, and policyengine-uk keeps its five identifier variables as 32-bit integers: the stored values wrapped, so `calculate("household_id")` did not return the file's identifiers and every engine draw keyed on one read the wrapped number. The release boundary now renumbers (`national_frame.uk_release_identifier_tables`, called by `uk_release_export_frame` and by the local export): per entity the identifier is one plus the rank of the build identifier, the person table's two memberships map through the same arrays, and row order does not change. The build identifiers stay in the file as `person_build_id`, `benunit_build_id` and `household_build_id`, which the existing lineage columns describe, and are allow-listed on the export surface. The build and its checkpoints keep the build identifiers, because every draw in the build is keyed on them. The engine file the calibration measures on carries the release's identifiers (a per-clone block takes the ranks its rows have in the whole frame), so the solve fits the engine outputs the released file gives. Both readbacks refuse a written identifier that repeats, is below 1 or is above the 32-bit range, naming the column only. The release assembler pairs a release with its spine on the build identifiers, and the size evaluation and `tools/verify_uk_identity_stability.py` read them too. Engine tests hold both halves against the installed engine: the renumbered file's identifiers read back as written, and the build's own do not. Why the calibration follows: policyengine-uk spreads each person's birth month over the year by a hash of `person_id` (`months_since_last_birthday`), and the date of birth decides the two-child limit, the State Pension scheme and the Pension Credit qualifying age. Measured on a calibrated file, with the solve left on the build identifiers, the renumbered file moved two-child-limit and Universal Credit award rows by up to a tenth or more against the solve's own estimates; the receipts give the comparison. The Universal Credit deduction draws do not move: the build stores them. The spine now stores the birth months too (`1095-birth-month-stored`), so neither the identifiers nor the weights move them; the calibration keeps the release's identifiers so that any engine read keyed on one agrees with the release. The spine stages still read the engine on the build identifiers, as before.
1 change: 1 addition & 0 deletions changelog.d/1095-salary-sacrifice-within-pay.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The UK `salary_sacrifice` stage limits every salary-sacrifice amount to the pay it comes out of, porting the final rule of uk-data#518 (microcosm#1095). A sacrifice is pay given up for a pension contribution, so no amount, reported or predicted, now exceeds the pay the person still receives, and a person without pay has none; microcosm#1089 had done this only for people not asked the question. The limit bites above all on SPI-channel rows, which keep their FRS twin's answer beside pay redrawn from the SPI. The forest trains only on respondents with pay. The headcount conversion moves a contribution into salary sacrifice up to half the pay and leaves the rest as an employee contribution; it moved the whole contribution before and floored pay at zero, which could leave a converted record with a sacrifice and no pay. The stage refuses if any sacrifice ends above the pay that remains. policyengine-uk 2.126.2 ignores a sacrifice with no pay behind it (PolicyEngine/policyengine-uk#2063), so the stored column now agrees with what the engine relieves. The stage's manifest gains `clamp_maximum` and `move_pay_share_cap`, the evidence records how many amounts the limit and the half-pay cap touched, and the identity tool restores a converted record's contribution as what it kept plus what moved.
1 change: 1 addition & 0 deletions changelog.d/1095-statutory-paternity-pay.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The UK root stage sets `statutory_paternity_pay` from the FRS adult table's SPPADJ ("SPP amount", a weekly amount for the adult's main job), annualised like `statutory_sick_pay` and `statutory_maternity_pay` beside it (uk-data#575, microcosm#1095). Nothing set it before, so every adult had none. policyengine-uk reads it in Class 1 National Insurance earnings and in the Universal Credit and Tax-Free Childcare paid-work tests, and from 2.128.0 also as Universal Credit earned income and in household benefits and HBAI income (pe-uk#2365). On an engine before 2.128.0 the column was only a National Insurance base, so it lowered the net income of the households that report it; this PR locks 2.130.1. Whether SPPADJ times the weeks in a year (365.25/7) is the right annual amount for a payment that lasts at most two weeks is still open (uk-data#575): it matches sick and maternity pay, and a grossed weekly amount from a reference week is the FRS's usual annual estimate in aggregate, but not per person. A tab without SPPADJ refuses the build. The column is new on the export, so it joins the reviewed extra columns of the export-surface gate; few adults report it, so its signed entry gives survey support as a band only.
1 change: 1 addition & 0 deletions changelog.d/1095-weights-at-dataset-year.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A UK release writes its household weights at the dataset's own year (microcosm#796 A19, microcosm#1095; uk-data#538 is the incumbent's side of the same mechanism). policyengine-uk projects a single-year dataset forward: from the dataset's year it multiplies `household_weight` by one plus the ONS population growth of each later year. Weights solved at 2025 and stored on the 2024-25 dataset therefore read 0.72% high at 2025, on every weighted count and total. The release write now divides the solved weights by the engine's own factor (`uk_runtime.weight_year`), so the engine returns the solved weights at the solve year. The division is the last record of the file's mass log, and the national and local readbacks expect exactly it. The solve, its gates, its diagnostics and the engine scratch file keep the solved weights. The growth is vendored in a new `dataset_projection` block of `uk/hmrc_uprating_engine_pins.json` and a test holds it against the installed engine, with a second test that `household_weight` is the only weight the engine projects and a third that loads a written file and reads the solved weights back at 2025. Readers of a written file follow the same projection: the rule-1 scorer reads each side with the weights the engine gives it at the scored year and its score block gains `weight_year`, so scores are not comparable with earlier ones; the release-cut certifier runs its gates on the solved weights and refuses a file divided for another year; `tools/evaluate_uk_incumbent_surface.py` reads the candidate the same way; and the assembler's build manifest says which year the weights NPZ stands at (it pairs with the H5 row by row, so it carries the file's own weights).
1 change: 1 addition & 0 deletions changelog.d/uk-frs-empstati-other-inactive.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The UK `frs_employment` stage maps FRS EMPSTATI code 11 ("Other Inactive" in the UKDS FRS 2024-25 data dictionary) to `employment_status` OTHER_INACTIVE instead of LONG_TERM_DISABLED, matching the incumbent's uk-data#526, so other-inactive adults no longer enter the ESA health-condition and support-group proxies. Codes 1-11 are mapped one data-dictionary label each; people outside `adult.tab` stay CHILD, and an adult with a blank or unknown code now refuses the build instead of defaulting to LONG_TERM_DISABLED.
Loading
Loading