Skip to content

UK: take up #1095's actionable items on policyengine-uk 2.165.1 - #1422

Open
juaristi22 wants to merge 38 commits into
mainfrom
uk-1095-next
Open

juaristi22 wants to merge 38 commits into
mainfrom
uk-1095-next

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR takes the engine-free items of #1095 that María ruled on 29 October (Phase A), and the first re-bump past #1421 (Phase C): policyengine-uk 2.165.1. Main is merged in after #1420 and #1421, and the national and local target surfaces are regenerated on the pinned feed (Chronicle 53815b2). The spine and calibration items (Phase B) move to #1426, each measured in its own arm.

policyengine-uk 2.165.1

The lock moves from 2.164.2, which #1421 brought, to the newest release.

  • Other investment income in market, HBAI, net and capital income (2.164.3, Count other investment income in market and HBAI income policyengine-uk#2736; 2.164.4, Count other investment income in capital income, and test it in every income measure policyengine-uk#2746). The build writes it from National Savings and gilt interest on FRS rows and from the SPI's other investment income on SPI rows. The engine already taxed it, and now also counts it in market income, HBAI income, household net income and capital_income. The stages that read those incomes:
    • The LCFS, NEED and NTS stages band recipients on the engine's HBAI income with its deductions added back, so their gross income now counts it, as the surveys' gross incomes do.
    • was_wealth, and was_lisa through its predictors, read household net income against WAS's DVTotinc_bhcR8, whose investment income covers every account and investment WAS asks about, National Savings bonds and gilts included. was_wealth also reads capital_income as a predictor, which now matches the donor's gross income from all financial investments (DVGIINVR8).
    • etb_services reads the engine's HBAI net income, and etb_vat its HBAI gross income less the ETB's direct taxes, against the ETB's disinc. The ETB's original income includes investment income, and its records come from the Household Finances Survey from 2018. Whether that survey's investment income counts National Savings and gilt interest is still to check against its documentation.
  • Company car and car fuel benefits (2.165.0, Model the company car and car fuel benefits, with Class 1A NICs policyengine-uk#2750). The build writes none of the nine person inputs, so no one has a company car and both benefits and their Class 1A NICs are zero, as before. The inputs are signed at their default in UK_ENGINE_DEFAULT_INPUTS, for María's signature. The company car PR for the Autumn Budget dashboard supplies six of them.
  • Tax-Free Childcare over a smaller UC award (2.165.1, Keep Tax-Free Childcare over a smaller UC award, and take no deductions from awards only a reform creates policyengine-uk#2754). A family that would claim UC keeps Tax-Free Childcare instead when the top-up is worth more than the award. No debt deductions come out of an award only a reform creates. Every new variable is a formula.
  • No economic assumption moves. The vendored uprating pins and the bonus-sacrifice anchor change only their version stamps, and no gate digest moves.

Regeneration on feed 53815b2

The national and local references, membership and compile-parity receipts are regenerated on the merged tree, on main's feed pin (Chronicle 53815b2, #1420's re-pin). The vendored resources check against the full artifact unchanged. The local census, the local references, the local parity receipt and the coverage manifest regenerate unchanged.

  • National surface: 1,409 active references, up from 1,403, and no row left without a fact (four before). The new rows are the three OBR council tax nation rows (D14e) and SLC's three nation rows (R4). The DfE rows, the R7 windows and obr.council_tax move as described below. SLC's three nation rows differ from the incumbent's devolved metrics only by their uprating to 2025.
  • SPI uprating holds catch up with Budget 2026 ISA data, PR B of #1418: ISA subscriptions per adult, the Lifetime ISA anchor, additive ISA income, policyengine-uk 2.164.2 #1421: main's surfaces were generated before Budget 2026 ISA data, PR B of #1418: ISA subscriptions per adult, the Lifetime ISA anchor, additive ISA income, policyengine-uk 2.164.2 #1421 refreshed the HMRC uprating pins with the October EFO, so the regeneration moves 21 SPI holds. The eight self-employment amount band holds go from 1.1176 to 1.1115, as per-capita mixed income from 2023 to 2025 falls from 1.0384 to 1.0327. The 13 dividend holds by income band go from 1.088147 to 1.088151 (OBR per-capita GDP).
  • Local surface: 27,168 active references, every status count unchanged. The signed deferrals fall from 94 to 77, as the support-floor deferrals now follow each target's nations.
  • A window fix the regeneration found: R7's calendar-2025 window on dfi_ni.bus.passenger_receipts failed to compile, because its selector pins two services and the window counted series only from list-valued source keys. A dimension pinned to several values now names one series per value. The window of the sum is the sum of the windowed series, and a service short of a year, or with no facts at all, refuses the window. No other UK, US or NZ window reference pins several dimension values.

Targets

  • OBR council tax by nation (D14e): the three nation rows are pinned to the UK and held as measured diagnostics.
  • Declared geography wins (R8, R9): a selector's declared geography_id is returned before the inferred pin, and the authoring refuses a disagreeing pin.
  • SLC devolved repayments (R4): slc.repayments.devolved_total becomes one row per nation. Wales binds. Scotland and Northern Ireland are held out by María's ruling, as their plan thresholds are not modelled. The three rows carry SLC's 2024-25 outturn to 2025 by policyengine-uk's OBR average earnings index, since repayments are a share of earnings above the plan thresholds (María's ruling of 30 October). This replaces the matched-period hold of 29 October, which declared the frame's own year: the solve honours a declared period only at the calibration year, or at the frame's year for a stored column, so it would have refused the Welsh row.
  • Calendar-2025 window (R7): Scottish Child Payment, the TFC top-up, Northern Ireland bus receipts and the four SLC England repayment rows.
  • Ledger observation_period: a reference can select its facts at a declared period while the target keeps its own.
  • DfE (D5): the eight rows bind the January 2026 census at model year 2025. The three in_year_snapshot holds are retired.
  • Window-ratio complement: a numerator can be its denominator less the listed cells.
  • OBR council tax (D14f): binds the main-residence share: England's chargeable dwellings less second homes and empty homes, over chargeable dwellings (MHCLG, October 2025, about 0.968). Scotland and Wales take England's share, as a stated approximation.
  • Signed exclusions: D3 keeps Scotland's three-plus-children rows out with a corrected reason. D18a signs the MSOA income deferral. D18c keeps Northern Ireland's UC area cells out.

Spine and stage records

Gates

  • Zero-weight strata (Adjudicate the UK spine grain at the ingest/calibration seam #688): both SPI-synthetic strata are capped at 0. The June caps of 100,000 counted the retired ten-clone grain, and the national seam ships no zero-weight row.
  • Export surface (WS-E spine deferrals: known bugs and signed inconsistencies awaiting their increment #796): the all-zero incapacity_benefit_reported exclusion never matched the pinned reference, so it is dropped. The release boundary still drops the column.
  • macro_realism (Swap the microcosm-built UK spine into the national build and retire the incumbent feed #757): not declared, and the battery's policy text records why. The solve binds the income components directly, and the national accounts' household disposable income differs in scope. The release contract refuses a not_applicable entry in this spec vintage.
  • Take-up flags at their stages (María's ruling, "do both"): frs_take_up, frs_person_draws and frs_household_draws record their flags' shares at the stage's weights. Three assembled-phase gates hold them to their contract targets, at the terminal gate's bounds until a spine receipt measures them. The terminal uk_take_up_signal gate stays release-blocking.
  • The battery, spine-part, release-cut and dense digests in microcosm-data's contract move with the manifest.

Disclosure floor of 3 (A1)

María ruled on 29 October to move every UK disclosure floor from ten source households to three, CD171's absolute floor (recorded on #1095). One constant, receipt_counts.UK_DISCLOSURE_FLOOR, now sets it for the stage receipts, the stage-health gates and the report tools. A withheld figure reads "<3", and counts from three to nine read "3-9", rounded record counts included (Vahid's review). The SPI tape's two SN 9422 conditions are unchanged. A guard test fails on any "<10" label left in the UK runtime, the UK spec, the gate notes or the tools. The statistical floors stay: the zero-support floor, the private-rent tool's ten-row group rule, and the SPI coherence tool's floor for medians and quantiles. The signed rules in local_small_cell_rule.json defer published cells, not survey cells, so they are unchanged.

Text

Stale tracker text in notes, docstrings and the vendored bus resources' purposes. The Incapacity Benefit leaf note now names the FRS 2024-25. The charter-H2 fixture and the release input coverage manifest follow the stage contract digests.

Not in this PR

Tests

  • Engine tier on policyengine-uk 2.165.1: every UK engine test passes, run in full locally because CI stops at the first failure. Three needed updates: Tax-Free Childcare's top-up now lives in a shared helper, which still reads the spend the build routes; the company car's list price and capital contributions are person-level stocks that are no one's capital; and the graph build's stage records list each stage's rewrites beside their outputs (Carry manifest rewrites into the shared Stage/StageRecord producer graph #721). CI passed in full at 02df9df2b.
  • Full engine-free suite at b46be0044 (673 files, in 30-file chunks): 22,969 tests, 233 skipped, counted by --junitxml. Sixteen fail:
    • ten fixed since: the SLC nation rows' declared period (four test_uk_measurement_period tests) and the pins and fixture rows that follow the regenerated surface (six). At 652835c1f, the 60 files that read the regenerated surfaces pass: 3,920 tests, 62 skipped;
    • six only on this machine: four Orrery tests (a missing Node package), test_engine_is_reported_unavailable_without_the_uk_extra (the uk extra is installed here) and test_spawned_workers_score_identically_to_sequential_cli (needs Python 3.14.7; this machine has 3.14.6).
  • The "3-9" band (254be6d41): the tests of every rounded_count caller pass: 39 engine-free files (1,384 tests) and 8 engine-tier files (72 tests).
  • After the Promotion for the constant-id national line: pointer and certified-loader extension #823 revert (1f46653b6): the 18 files that resolve or load a UK dataset pass, bar the live Hub pointer test, which needs private credentials (CI skips it). Main's version of that test expects the old release naming; UK: make the promoted national line the default load (#823) #1432 fixes it.
  • Feed-reading tests on 53815b2 (the 15 feed-gated files, one process each, which CI skips without the feed): 235 tests, two skipped because the Scotland support and the authenticated national artifact are not on this machine. Two tests in test_uk_target_references.py still expected values from before D5 and R7; 02df9df2b moves them to DfE's January 2026 census and the calendar-2025 windows, each value checked against the feed's facts and the committed membership. The two drift checks that regenerate the vendored resources and the target surfaces from the feed were left out: the regeneration wrote those files, and the vendoring check passed in it.
  • After Vahid's second review: f83d7a3c2 links the ruling's record from receipt_counts and the changelog, and its tests pass. ddd82aa2f adds a test that a pinned service with no facts refuses the window; the 460 tests of test_ledger_targets.py pass.
  • Checks: tools/ci_test_plan.py verify, tools/spec_engine_coverage.py --check, the vendoring check against the full artifact, and ruff on the edited files.

🤖 Generated with Claude Code

juaristi22 and others added 21 commits October 29, 2026 12:59
OBR's table 4.1 is UK-wide, so Chronicle stamps its England, Scotland and
Wales council tax rows K02000001, and the substring rule's nation pins
compiled no fact. The rows now pin to the UK, as obr.council_tax and
obr.domestic_rates do; each binding still scopes the model column to its
nation. obr.council_tax binds their sum, so measure exclusions hold the three
as diagnostics, measured on every evaluation as evidence for CT-C (María's
ruling of 2026-10-29, microcosm#1095 D14e). The references and membership
regenerate with the PR's shared regeneration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 R8 and R9: a wrong geography pin reads as "no fact", and the
generator's inference (one-by-one pins, prefix pins, then the substring rule)
silently overrode any geography a target's selector declared, because the
authoring merged the pin over the selector. María ruled on 2026-10-29 that a
declared geography wins. The UK generator now returns a selector's declared
geography_id first and refuses a one-by-one pin that disagrees with it, and
the shared authoring refuses a pin that differs from a declared selector key
instead of overwriting it. No committed selector declares a geography yet, so
no reference changes; the devolved SLC rows are the first to use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 R4. slc.repayments.devolved_total took one fact per devolved
nation, but the "slc." rule pinned it to Great Britain, where no fact sits, so
it never compiled. It becomes slc.repayments.wales_total, .scotland_total and
.northern_ireland_total, each declaring its nation's geography in its selector
(the R8 rule), measuring the frame's own year (measurement_period 2024)
against SLC's 2024-25 outturn, and filtering the model to its nation. The
incumbent registry's three per-nation metrics now map one to one.

María ruled on 2026-10-29: bind Wales, and hold Scotland and Northern Ireland
until their borrowers' plan thresholds are modelled (plan_4_imputation is
False; policyengine-uk#2636 closed as not planned). Measure exclusions hold
the two. The matched-period hold for the three rows waits for her signature,
so the membership assertion in test_uk_target_references fails until the
shared regeneration, which needs that hold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follows the split of slc.repayments.devolved_total (microcosm#1095 R4): the
contract has 453 targets, the registry scope 393, and the incumbent's three
devolved metrics map one to one, so 185 mapped targets. geography_id joins
the national selector vocabulary, as the R8 rule lets a selector declare its
fact's geography.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 R7: Scottish Child Payment spending, HMRC's Tax-Free Childcare
top-up, Northern Ireland's Translink bus passenger receipts and SLC's four
England repayment rows took their 2025-26 fact alone, outside María's rule of
2026-09-22 that fiscal-year facts take only the months to the end of the
calibration calendar year. She extended the rule to them on 2026-10-29, so
each declares calendar_year_window: three twelfths of 2024-25 and nine of
2025-26. The pinned feed has both years for all seven (Scottish Child Payment
has 2026-27 too). No hold or measurement period names them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 D5: a target is measured at the model's period, but some
publications observe a period with another label. DfE's January census for
model year Y is January Y + 1, the spring term of the year's entitlements, so
the rows could only bind the census a year out: every selection path refused
a fact after the target period.

LedgerTargetReference gains observation_period. Fact eligibility, the
latest-not-after choice, the exact-period match and the resolved-fact guard
read it in place of the target period; the TargetSpec keeps the target period,
and its metadata records ledger_observation_period. Operations that compose or
pin periods themselves (the calendar-year windows and average, latest_plateau,
the two ratio translations) and the monthly windows refuse it, as does an
unparseable period. The authoring passes a target's observation_period into
its reference rows and checks eligibility at it. Nothing declares one yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 D5. The eight dfe.funded_childcare rows bound the January 2025
census, but model year 2025's entitlement year runs from April 2025 to March
2026: its spring term starts on 1 January 2026, and the spring-term measures
count children by their age on 31 December 2025, the basis DfE's January 2026
census uses. The January 2025 census was a year out, held as an in-year
snapshot. María ruled on 2026-10-29 to bind January 2026.

Each row declares observation_period 2026-01, so its fact is selected there
(the universal-only difference's two operands too) while the target stays at
2025. A fact after the target period carries no uprating hold, so the three
in_year_snapshot holds retire. The reference and membership assertions wait
for the shared regeneration; the childcare take-up refit follows on the arm's
spine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 D14f needs OBR's council tax receipts restated for the main
residences households hold: England's chargeable dwellings less its second
homes and empty dwellings, over the chargeable dwellings. No published cell
is that remainder, and a window-ratio operand can only sum cells.

A calendar_year_window_scaled_by_ratio numerator may now declare complement:
true. Its cells are taken off the denominator before the quotient, which
keeps the (0, 1] subset-share check. complement is operand metadata, never a
selector field; only a numerator may declare it, and only as true. The spec
records ledger_value_numerator_rule, and the formula reads
"(denominator - numerator) / denominator".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 D14f. obr.council_tax bound OBR's Great Britain receipts,
which include council tax on second homes and empty dwellings. Households do
pay it on property they own, but the frame's council_tax is each household's
main-residence bill only and the engine has no input for council tax on
other property, so the solve met the whole line by pushing main-residence
bills up. María ruled on 2026-10-29 to bind the main-residence share.

The row now scales its calendar-2025 window by England's chargeable dwellings
less its second homes and empty dwellings, over its chargeable dwellings
(MHCLG Council Taxbase, October 2025), through the window ratio's new
complement numerator. A private compile of the row against the pinned feed
resolves the window and the three taxbase cells once each. The count-based
share and England standing for Scotland and Wales are stated in the notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095, María's rulings of 2026-10-29:
- D3: ons.population.scotland_households_3plus_children stays signed out,
  but its reason was wrong: a fact exists, Chronicle's Scotland cut, and it
  counts couple households only, so binding it would fix part of the group
  and leave lone parents free. The register entry takes the corrected reason
  (couples_only_composition_fact), her approval of 2026-10-29 and an
  adjudication line; it tracks #1095, as #736 closed.
- D18a: ONS's small-area income rows (BHC and AHC means, housing costs) were
  deferred "pending the signed mean-aggregation design". ONS models them
  from the FRS, so binding them would partly fit the FRS to itself: they are
  kept out as circular (frs_model_based_circularity).
- D18c: Northern Ireland's UC area cells stay out; the reason now records
  that DfC publishes claimants, counting a joint claim twice (uk-data#590).

The membership and local surfaces carry the new texts at the shared
regeneration; test_uk_target_references pins the D3 text to the new reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095's "stale register and gate text" items that need no signature:
- weight_year's docstring quoted 0.72% growth from 2024 to 2025, the figure
  before policyengine-uk 2.155.0's population; it now points at the pinned
  dataset_projection block, which every lock re-pins.
- The certifier and the calibration run said local-candidate gates stay out
  of national certification "until microcosm#146"; #146 closed when the local
  variant shipped, so they now say the local line certifies them.
- was_wealth's bedrooms note pointed at the closed #145; the question is
  #1095's "num_bedrooms as a WAS predictor" (uk-data#463).
- dwp.uc.households_lcwra_element's note quoted a September baseline and an
  open #882 question; it now records pe-uk#1989's proxy and María's NS6
  ruling of 2026-10-29 to keep it.
- obr.universal_credit's note said both "UK incl. Northern Ireland" and
  "Scoped to Great Britain" (CF1); it now says Great Britain.
- obr.sdlt's note called pe-uk#2322 unreleased; it has been in the lock since
  #1400, and the note now states how its projection compares with the row's.

Signed register text (the JSA and ORR reasons, tracking fields of closed
issues) waits for the renewals PR, so each entry is re-signed once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#721 (tracked in #1095): a stage can declare rewrites, columns an
earlier stage produced that it replaces, but StageRecord kept only the
declared outputs, so provenance credited a rewritten column to its first
producer, and the UK spine's final nonzero-share list kept a hard-coded
FRS_EDUCATION_GRANT_REWRITES carve-out (redundant, as frs_spine already
produces education_grants).

StageRecord gains rewrote, the stage's declared rewrites, and its
nonzero_share covers them as they stand after the stage. A rewrite absent
after the stage is refused, like a missing output. The spine manifest's
stage_records carry rewrote, and the final list reads every record's
outputs and rewrites, so the carve-out and its import go. #721 can close
once this lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 (from #796): the support-floor loop deferred E06000053 and
E09000001 for every local-authority target outside the bound families,
including the nine Welsh and eight Scottish families whose area_scope holds
no English authority, so their membership listed deferrals for cells they
never have. The loop now reads each target's local_authority gss_prefixes,
keeps only the areas in scope, and skips a target with none. Thirty-two
targets take the deferral (59 area entries, E06000053 in 27 and E09000001 in
32), down from 49 (93; 44 and 49). The local references and membership
regenerate with the shared regeneration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 items from #688, #796 and #757.

- uk_zero_weight_strata: the two strata kept June's caps of 100,000 each,
  10,000 SPI-synthetic households times ten output-area clones, a grain the
  national seam no longer builds. Its calibrated control of 2026-10-28
  (4374e0bf) holds 78,093 households and no zero-weight row in any of the
  four SPI-synthetic and capital-gains-clone strata, because the SPI stage
  gives the stacked donors their prior mass before calibration. Both strata
  are re-declared at a cap of 0 under current names; they keep their
  selectors so that a zero-weight row there is named by stratum.
- uk_export_surface: the reviewed exclusion for
  person.incapacity_benefit_reported never matched. The pinned reference
  (efrs_parity_reference.json) lists the all-zero column among its zero-share
  inputs, not on its export surface, so the gate reported the exclusion as
  unused. The release boundary still drops the column, and the shared check
  refuses it as an unlisted extra, so the guard holds without it.
- macro_realism: María ruled on 2026-10-29 to drop it and record why. The
  plan declared it not_applicable, but the release contract's verifier
  refuses an excused entry in this spec vintage (test_gate_battery_contract_
  pins holds every UK entry to not_applicable None). So the battery's policy
  text records the reason: the solve binds the household income components
  directly, and the national accounts' household disposable income differs
  in scope (imputed rent and, in the headline series, non-profit
  institutions serving households).

The battery, part and dense digests in microcosm-data's contract move with
the manifest, as in a094f5d. The take-up stage-time checks (her ruling of
2026-10-29) follow in their own commit. The Carer's Allowance stage-health
gate waits on her: the take-up contract records her 2026-10-20 ruling of no
such gate until a caseload target binds the rate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d2a1f9c gave StageRecord its rewrote field and read the spine's final
nonzero-share list from every record's outputs and rewrites, but only the
legacy plan path filled it. The graph build, the production path, projects
its records from node artifacts and left rewrote empty, so its records
dropped the rewrite provenance microcosm#721 asks for, and
test_driver_writes_spine_h5_sidecars_and_logbook lost education_grants from
the final list (the real roster still lists it through frs_spine's outputs).

A declared rewrite is an owned cell of its node, as an output is, so the
graph projection now reads its share from the node's artifact and records
the stage's declared rewrites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's ruling of 2026-10-29 on microcosm#1095 ("do both"): keep the
terminal uk_take_up_signal gate release-blocking on the calibrated shares,
and check the same flags at stage time.

- The twelve flags the terminal gate measures are drawn by three stages
  before the assembled boundary: frs_take_up (the four childcare flags, the
  UC childcare element and the under-2 usage), frs_person_draws (Marriage
  Allowance, Scottish Child Payment) and frs_household_draws (TV ownership,
  licence evasion, the first-home and purchase flags).
  take_up_signal_outputs selects each stage's flags, and a test holds the
  three sets to a partition of the terminal gate's.
- The terminal gate's per-flag measurement moves into
  _measure_take_up_signal, unchanged, so that uk_take_up_signal_receipt
  measures the same thing at the stage's weights. The receipt is
  aggregate-only: support is a band of distinct source households, and a
  share or target resting on fewer than ten is withheld.
- Each stage records the receipt in its checkpoint evidence, and the graph
  hashes frs_take_up and receipt_counts with the two draw stages.
- stage_health gains a take_up_signal check: the receipt must carry
  exactly the declared outputs; each must vary, sit within the declared
  bound of its target, and keep the universal hours nested; a withheld
  share fails, as it cannot be measured.
- gates.json declares uk_stage_frs_take_up_signal,
  uk_stage_frs_person_draws_take_up_signal and
  uk_stage_frs_household_draws_take_up_signal (assembled, release-blocking,
  evidence_absent_blocks, population_fact_check) at the terminal gate's
  bounds, 0.05 and 0.10 for the under-2 usage, until a spine build's
  receipt measures the stage-time deviations. The spine scope, the data
  contract's entry, evidence and spine-part mirrors and the battery, spine
  and release-cut digests follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#823 (tracked in #1095). The registry kept the UK 2024-25 national
line off the default variant until its first certified cut existed on the
Hub, so that resolve("uk") never chased an absent pointer; its comment named
the flip a one-line follow-up after promotion (review of #966, Max's #823
ruling). Checked on 2026-10-29: latest-national.json on
policyengine/populace-uk-private names
microcosm-uk-2024-25-national-20261025T091414Z-a6299504 (updated
2026-10-26), with its release certification, built on policyengine-uk
2.151.0, the version #1095 names for the flip.

The entry's variant becomes DEFAULT_VARIANT, so resolve("uk") and
load("uk", 2025) follow latest-national.json, and load("uk", 2023) stays on
latest.json. This changes the default UK load for every consumer of
microcosm.data; no other local repository names the retired national
variant. The registry, loader and assembler tests and the release runbook
follow; the live pointer test now branches on the line pointer, not the
variant, and resolves the promoted cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two items from microcosm#1095's stale-text list, both without a signature:

- The three vendored bus resources said that the LCFS and ETB bus-fare and
  bus-support rakes consumed them. microcosm#930 and María's ruling of
  2026-09-23 replaced those rakes with the bus fare and bus support pricing.
  The purposes in the selection register and in the vendored files now name
  the pricing, and dft_bus_value_anchors.json lists bus_support_pricing,
  which reads its net support, among its consumers.
- The Incapacity Benefit SPI leaf (FRS benefit code 17) was "a structural
  zero in the audited 2023-24 FRS". A private check of the FRS 2024-25
  benefits table found no code-17 row, so the note now names both vintages.
  The text is in sources.yaml, its source_stages.json projection and the
  contract's expected string.

Both move stage contract digests, of frs_hmrc_spine_leaves, lcfs_consumption
and etb_services. So tools/graph_uk_spine_fixture.py regenerated the
charter-H2 fixture (only those three digests and the leaf note change), and
the release input coverage manifest takes the new source manifest digest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8e3394b gave LedgerTargetReference an observation_period field. The
monetary binding serialises the reference with asdict, so every activated
and source reference receipt now carries the key (None unless declared), and
microcosm-calibrate's integrity check refused it as an unknown key: the full
engine-free run failed test_monetary_targets' two binding tests. The
verifier's reference keys gain observation_period, as #834 added the operand
keys, and the calibrate fixture's hand-built receipt carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fff37ea held the three OBR council tax nation rows (ruling D14e) and
ae1edf7 held SLC's Scottish and Northern Irish repayment rows (ruling R4),
which takes the packaged calibration measure exclusions from 40 to 45 and
the census's slc. marker from 3 to 5. test_uk_measure_simulation still
said 40 and 3; the full engine-free run caught it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm#1095 R4. ae1edf7 split SLC's devolved repayments into one row
per nation; each resolves SLC's 2024-25 outturn, the latest each nation
publishes, at the 2025 target period and measures the frame's own year
(measurement_period 2024). The compile needs a signed matched_period hold
for that, and test_every_committed_hold_is_signed refuses a proposed one,
so the hold waited for María's signature. She signed it on 2026-10-29
(approved_by juaristi22, expires 2027-01-29). The shared regeneration can
now compile the three rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
María's ruling of 2026-10-29 on microcosm#1095 (A1): "move everything to 3".
CD171's absolute floor is three; the build used ten, CD171's working
minimum against secondary disclosure across outputs. Her ~/.claude/CLAUDE.md
now sets three for what people read too.

- receipt_counts gains UK_DISCLOSURE_FLOOR (3), UK_WITHHELD ("<3") and
  UK_BELOW_TEN_BAND ("3-9"). small_cell_share withholds one or two records;
  support_band and extreme_count print "<3" and "3-9" below ten;
  rounded_count prints three to nine exactly.
- Every stage receipt that withheld on fewer than ten sources now withholds
  on fewer than three, through the shared names: annual income, the
  apprentice draw, Carer's Allowance, the Move to UC and UC take-up draws,
  pension coherence, property purchases, the SPI band mean, the NTS fare
  mix, the gambling fits, the high-value home gates and the single years of
  age (diagnostics.UK_DIAGNOSTIC_MIN_SOURCE_HOUSEHOLDS). stage_health reads
  the new label and bands, and its detail key becomes
  regions_rewritten_under_floor.
- Tools: the private-rent receipt (its output key becomes
  cells_under_floor_source_households), the childcare realised take-up
  report and, through the diagnostics constant, the council tax liability
  comparison.
- Kept on purpose: the zero-support floor (1); local_small_cell_rule.json,
  whose three signed rules defer published cells too small to fit, not
  survey cells, so they need no re-signing; the private-rent tool's
  ten-row group rule (no summary describes one district); and the SPI
  coherence tool's cell floor for correlations, medians and quantiles,
  since a median of a few records is one record's own value.
- Six gate notes and four stage notes state the new floor; the battery and
  spine-part digests, the coverage manifest's source digest and the
  charter-H2 fixture's data bundle follow (its graph is unchanged).
- About 86 test expectations across 21 files: a withholding test keeps its
  purpose on one or two sources and expects "<3"; an incidental "<10" now
  reads the exact figure, "<3" or "3-9".

1,270 tests across the touched files, the gate and contract pins and the
coverage manifest pass; the charter-H2 parity test passes.

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

Copy link
Copy Markdown
Contributor

@juaristi22 this is an early look at 5a944e449, not a full review, since the body says it isn't ready. It covers must and should items only, and nothing on your own pending list. I'll do a full pass after main merges in and the references regenerate.

  1. must: an engine-uk failure the body doesn't list. tests/engine/uk/test_uk_graph.py:84 still asserts set(nonzero_share) == set(stage.outputs). But _graph_stage_records now adds stage.rewrites to shares (Carry manifest rewrites into the shared Stage/StageRecord producer graph #721). So age_tail fails in engine-uk on both Pythons (run 40917095981, at this head). Regenerating won't fix it. The assertion needs set(stage.outputs) | set(stage.rewrites). The body's engine-tier line covers only the charter-H2 parity test, so this one was missed.
  2. should: rounded_count now prints exact counts from three to nine (receipt_counts.py:103). The module docstring still says "no exact count that ... narrows a small group by difference". extreme_count and support_band print "3-9" there instead. Printing exact counts just above the floor is where differencing bites. That matters more now that the floor is three rather than ten, CD171's working minimum against secondary disclosure across outputs, as the docstring says. I'd keep "3-9" in rounded_count too. I'm not reopening the ruling. Please link the UK: every open data item: uk-data parity, engine locks, the local surface, consumption and high-value homes #1095 comment that records it from the module and the changelog, and say there that the SN 9422 conditions (HMRC sees SPI-based material ten working days ahead) are unchanged.
  3. should: UK #1095 A/B fixes: the SPI age band (uk-data#628), households' HRAD from Table 8a receipts, and the SPI pay floor (uk-data#629) #1426 adds new "<10" sites. The SPI pay-floor receipt in UK #1095 A/B fixes: the SPI age band (uk-data#628), households' HRAD from Table 8a receipts, and the SPI pay floor (uk-data#629) #1426 hardcodes ten in several places. Whichever of the two lands second should move them onto UK_DISCLOSURE_FLOOR. Otherwise one receipt stays on the old floor without anyone noticing. I've raised it on UK #1095 A/B fixes: the SPI age band (uk-data#628), households' HRAD from Table 8a receipts, and the SPI pay floor (uk-data#629) #1426 too.
  4. should: ship the Promotion for the constant-id national line: pointer and certified-loader extension #823 default-load flip on its own. Pointing load("uk") at latest-national.json and retiring variant="national" affects every consumer. It doesn't depend on Budget 2026 slower data fixes, PR C of #1418: electricity at the ONS level, pension income by area #1420, Budget 2026 ISA data, PR B of #1418: ISA subscriptions per adult, the Lifetime ISA anchor, additive ISA income, policyengine-uk 2.164.2 #1421 or the regeneration. As its own small PR it could land now, and it wouldn't be held behind this one. The old 823-national-variant-until-promoted.changed.md entry, which describes the opposite state, should go in the same change.

The D14f main-residence share (0.968) is covered on #1423: #1423 (comment).

What I ran. I read the diff at 5a944e449 against its base fddbc7073 and the failing CI logs. I didn't run anything locally. I didn't review the target, gate or ledger changes in detail, and I'll leave those for the full pass.

🤖 Generated with Claude Code

juaristi22 and others added 3 commits October 30, 2026 15:44
Main now carries #1420 (electricity at the ONS level, pension income by
area, Rural Fuel Duty Relief areas, the balanced Carer's Allowance draw)
and #1421 (policyengine-uk 2.164.2, ISA subscriptions, the Lifetime ISA
anchor, the additive ISA contract), with the feed at Chronicle 53815b2.

Hand-merged:
- uprating_holds.json: main's holds, re-pointed to microcosm#1095, less
  the three DfE in_year_snapshot holds this branch retires (D5), plus the
  signed SLC nation hold (R4) and main's ONS electricity hold.
- gates.json: the export surface keeps main's tax_free_savings_income
  exclusion (microcosm#1360) and drops the all-zero incapacity benefit
  layer as this branch does; its notes name both.
- sources.yaml: main's Carer's Allowance stage note, with its receipt
  floor restated at three source households.
- weight_year.py keeps the docstring that names no lock's growth figure.
- calibration_run.py, test_country_spec.py, contract.py and
  test_contract.py list both sides' new gates in gates.json's order.
- The tests take the merged counts: 458 targets (registry 396, profile
  62; mapped 185, unmapped 211), 44 measure exclusions, and 34 support
  floor rows over 61 area entries ({E06000053: 27, E09000001: 34}).
- The Carer's Allowance receipt test takes main's balanced placement at
  the floor of three; two stale comments restate that floor.

Regenerated on the merged tree with policyengine-uk 2.164.2:
source_stages.json, the release input coverage manifest (143 required,
2 reviewed exclusions), the gate battery, spine-part and release-cut
digests, and the charter-H2 fixture. The national and local references,
membership and compile-parity receipts regenerate next, on feed 53815b2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UK lock moves from 2.164.2, which #1421 brought, to 2.165.1, the
newest release (microcosm#1095, Phase C's first re-bump):

- 2.164.3 (policyengine-uk#2736) counts other investment income in
  market income and in HBAI household net income, before and after the
  reset to zero. The LCFS, NEED and NTS stages band recipients on that
  HBAI income with its deductions added back, so their gross income now
  counts it, as the surveys' gross incomes do.
- 2.164.4 (policyengine-uk#2746) counts it in capital_income too. Its
  changelog says nothing reads capital_income; the WAS stage does, as a
  predictor, and the recipients' figure now matches the donor's
  DVGIINVR8, WAS's gross income from all financial investments.
- 2.165.0 (policyengine-uk#2750) adds the company car and car fuel
  benefits in kind and the Class 1A NICs on them, read from nine person
  inputs. The build writes none, so no one has a company car and both
  benefits are zero, as before. The inputs are signed at their default
  in UK_ENGINE_DEFAULT_INPUTS for María's signature; the company car PR
  for the Autumn Budget dashboard's #316 supplies six of them.
- 2.165.1 (policyengine-uk#2754) lets a family that would claim UC keep
  Tax-Free Childcare instead when the top-up is worth more than the
  award (forgoes_universal_credit_for_tax_free_childcare), and takes no
  debt deductions from an award only a reform creates. Every new
  variable is a formula.

Two lockstep tests follow the engine: Tax-Free Childcare's top-up is
one helper from 2.165.1, which reads the spend the build routes; and the
person-level GBP stocks gain the company car's list price and the
capital contributions towards it, which are no one's capital.

No economic assumption moves: the vendored uprating pins and the
bonus-sacrifice anchor change only their version stamps, and no gate
digest moves. The three pyproject floors, uv.lock (policyengine-uk
alone), the approved lock digest, the engine-core pin test, the
concept coverage report, the version mentions in code, notes and
tests, source_stages.json, the coverage manifest and the charter-H2
fixture follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
610a1bc records a stage's declared rewrites beside its outputs in the
graph build's stage records, as d2a1f9c does in the plan build
(microcosm#721). The engine-lane test of the record projection still
expected the outputs alone, so it failed on age_tail, which rewrites
age. It now expects both, and the record's rewrote field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 and others added 2 commits October 30, 2026 17:40
R7 puts dfi_ni.bus.passenger_receipts on the calendar-2025 window. Its
selector pins two services (Ulsterbus, and Metro and Glider), so each
year carries two facts of one measure. The window counted series only
from list-valued source concepts, measures and tables, so it read the
two services as an ambiguous selector, and the row fell out of the
references on the regeneration (a signed-deferral compile error).

A dimension the selector pins to several values now names one series per
value, as a list-valued source key does: the window of the sum is the
sum of the windowed series, and a service short of a year refuses the
whole window. A member's label in the window's metadata names the
pinned dimension's value, so the two services stay apart. No other UK,
US or NZ window reference pins several dimension values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one regeneration this PR owed after merging main: the national and
local references, membership and compile-parity receipts on main's feed
pin, Chronicle 53815b2 (#1420's re-pin), with policyengine-uk 2.165.1.
The vendored resources check against the full artifact unchanged, and
the local census, the local references, the local parity receipt and
the coverage manifest regenerate unchanged.

National surface (1,439 candidates, 396 contract targets):
- 1,409 active references, up from 1,403, and no row without a fact (4
  before): the three OBR council tax nation rows pinned to the UK (D14e,
  held as measured diagnostics) and SLC's three nation rows (R4; Wales
  binds, Scotland and Northern Ireland are held).
- The eight DfE rows select the January 2026 census (observation_period
  2026-01) at model year 2025, with no uprating (D5).
- Scottish Child Payment, the Tax-Free Childcare top-up, Northern
  Ireland's bus receipts and SLC's four England repayment rows take the
  calendar-2025 window (R7).
- obr.council_tax binds the main-residence share of OBR's receipts
  (D14f).
- The national compile-parity receipt: 1,409 compiled, 1,417
  differences (calibration drift 561, fixture only 42, ledger only 814).
  The incumbent's three devolved SLC metrics now match the nation rows
  exactly.

Local surface: 27,168 active references, every status count unchanged.
The membership's signed deferrals fall from 94 to 77: a target scoped to
Wales or Scotland no longer defers the two English micro-authorities
(microcosm#796, via #1095). The MSOA income deferral carries D18a's
signed reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22 juaristi22 changed the title UK: take up #1095's actionable items on the current policyengine-uk (draft, waits for #1420 and #1421) UK: take up #1095's actionable items on policyengine-uk 2.165.1 (draft) Oct 30, 2026
@juaristi22
juaristi22 marked this pull request as ready for review October 30, 2026 18:25
@vahid-ahmadi

Copy link
Copy Markdown
Contributor

@juaristi22 review of b46be0044, round 2. The title and body still say draft, but GitHub shows the PR as ready for review. Worth marking it draft, or dropping "(draft)" from the title, so the two agree.

Earlier findings (round 1, at 5a944e449)

  1. must, engine-uk graph records test: fixed. e40eabd1b expects outputs and rewrites (tests/engine/uk/test_uk_graph.py:84-89) and checks rewrote. engine-uk passes on both Pythons.
  2. should, rounded_count exact from three to nine: still open. receipt_counts.py:102-103 still returns the exact count, and the module docstring (receipt_counts.py:5-7) still rules out an exact count that narrows a small group by difference. The changelog now says it plainly, which helps. It still doesn't link the UK: every open data item: uk-data parity, engine locks, the local surface, consumption and high-value homes #1095 comment that records the ruling.
  3. should, UK #1095 A/B fixes: the SPI age band (uk-data#628), households' HRAD from Table 8a receipts, and the SPI pay floor (uk-data#629) #1426's hardcoded "<10": still open. UK #1095 A/B fixes: the SPI age band (uk-data#628), households' HRAD from Table 8a receipts, and the SPI pay floor (uk-data#629) #1426 hasn't moved (457af0274, e.g. spi_income.py:936 < 10, and its tests assert "<10"). The body's Overlaps paragraph names only the shared generated files. One line there would cover it: whichever lands second moves those sites onto UK_DISCLOSURE_FLOOR.
  4. should, the Promotion for the constant-id national line: pointer and certified-loader extension #823 default-load flip: still in this PR. Two older fragments still describe the opposite state, and they would ship in the same release notes as 823-national-line-default-load.changed.md. They are changelog.d/823-national-variant-until-promoted.changed.md and the last sentence of changelog.d/823-uk-2025-registry.changed.md.

D14f's main-residence share: there's no reply on #1423 yet and the PR is unchanged, so that point stays there: #1423 (comment)

New findings

  1. must: engine-free is red on both Pythons, with five failures and two causes. CI shows only one failure because it runs with --maxfail=1. I reproduced all five locally.

    • test_uk_target_references.py:126 still pins ACTIVE_REFERENCE_COUNT = 1403. This is probably the test the body says waited for the regeneration, but b46be0044 didn't update it. The regenerated surface has 1,409, so :345 and :1116 fail. The status counts at :1119 also need no_fact_at_or_before_period gone.
    • The second cause is new. The regeneration puts slc.repayments.england_plan_2 on the calendar-2025 window. The fixture feed tests/fixtures/uk_target_reference_feed_rows.jsonl has its two series (full-time and part-time) only for FY2024, so the compile refuses: "needs the years opening in [2024, 2025] from each of 2 series; found [2024]". That fails test_uk_ledger_targets.py:599 and two tests in test_uk_target_references.py: compile_from_real_staged_feed_rows and constrain_a_frame_with_prepared_columns. With 4962bc3f3's surfaces the fixture test passes. Adding FY2025 rows for both plan 2 series to the fixture should fix all three.
  2. should: the regeneration moves 21 SPI uprating holds that the body doesn't mention. In target_reference_membership.json, the eight self-employment band holds go from 1.11763 to 1.11152 (about -0.55%), and the 13 dividend band holds go from 1.088147 to 1.088151. The new values are right. Budget 2026 ISA data, PR B of #1418: ISA subscriptions per adult, the Lifetime ISA anchor, additive ISA income, policyengine-uk 2.164.2 #1421's 2.164.2 lock refreshed hmrc_uprating_engine_pins.json with the October EFO: per-capita mixed income, 2023 to 2025, falls from 1.0384 to 1.0327. But main's surfaces were last generated before that (ead8943d6), so this regeneration catches them up. The lock's "no economic assumption moves" holds for c5d40e4c8. But the regeneration section lists what moves (DfE, the R7 windows, obr.council_tax) and leaves these out. Please add the 21 holds there, so nobody reads the SPI rows as unchanged.

  3. should: 2.164.3 reaches more stages than the body lists. pe-uk's own test (other_investment_income_in_household_income.yaml) shows household_net_income gains other_investment_income too, not only market, HBAI and capital income. These stages also read the moved incomes:

    • was_wealth reads household_net_income against DVTotinc_bhcR8 (was_wealth.py:64, :350).
    • was_lisa reads it as a predictor and for its tertiles (was_lisa.py:164, :1405).
    • etb_services reads hbai_household_net_income against ETB's disinc (etb_services.py:62, :322), and etb_vat reads etb_disposable_income, which is built from the engine's HBAI income (household_income.py:110-125).

    The body names LCFS, NEED, NTS and WAS's capital_income only. Worth listing these too, and saying whether each donor's income measure already counts this income.

  4. nit: the selector windowing (4962bc3f3) looks correct. Each pinned value partitions by the fact's dimensions, a service short of a year refuses, and a missing service fails len(complete) != expected_series. The regeneration drops expected_member_count: 2 from dfi_ni.bus.passenger_receipts, and the window's series count now carries that check. One test where a service is absent entirely, not just short a year, would pin that.

Interactions. The feed pin is right: chronicle_feed.json and all 57 vendored source_commit values are on 53815b2, the same pin #1420 and #1421 used, with no stale pins. #1430 is stated on both sides: it locks 2.165.0 and this PR locks 2.165.1, the newer lock is kept, and the second PR removes only the six company car inputs its stage writes. test_uk_terminal_gates.py:514 would catch a written input left among UK_ENGINE_DEFAULT_INPUTS. pe-uk 2.164.2 to 2.165.1 changes no economic_assumptions parameter; uprating_indices.yaml gains only the three company car inputs under CPI.

Verdict: not ready to approve. Finding 5 has to go green first. Findings 2 to 4, 6 and 7 are small body or fragment edits. The body still says Phase B follows on this branch, so I'll look again when that lands.

What I ran. A fresh clone at b46be0044 with uv sync --all-packages --locked. These engine-free files: shared/test_ledger_targets.py, shared/test_engine_core_pins.py, uk/test_uk_receipt_counts.py, uk/test_uk_terminal_gates.py, uk/test_uk_chronicle_feed.py, uk/test_uk_ledger_targets.py, uk/test_uk_target_references.py. Only the five failures above failed. I also ran the fixture test against 4962bc3f3's surfaces. I diffed the national and local surfaces against main, and diffed pe-uk 2.164.2...2.165.1. I didn't run the engine tier, a build or the full engine-free suite, and I didn't check the 2.165.1 Tax-Free Childcare claim (fewer than three source households) or the local surface beyond the deferral and status counts. engine-us was still running when I checked.

🤖 Generated with Claude Code

juaristi22 and others added 2 commits October 31, 2026 00:49
… surface

CI's engine-free lane failed on the regenerated surface:

- R4's three SLC nation rows declared the frame's own year
  (measurement_period 2024) under a matched-period hold. The solve
  honours a declared period only at the calibration year, or at the
  frame's year for a value the frame stores, and student_loan_repayment
  is computed by the engine (#1420's period check). So the national
  solve would refuse slc.repayments.wales_total, the one that binds.
  María's ruling of 2026-10-30: carry SLC's 2024-25 outturn to 2025 by
  policyengine-uk's OBR average earnings index, since repayments are a
  share of earnings above the plan thresholds. The three rows drop the
  declared period and declare the index, and their matched-period hold
  retires. Scotland and Northern Ireland stay held out of the solve by
  their own signed exclusions (their plan thresholds are not modelled).
- The fixture subset compile read SLC Plan 2 with 2024-25 alone, so R7's
  window refused it. The four Plan 2 rows (full-time and part-time,
  2024-25 and 2025-26) now come verbatim from the pinned feed, and the
  staged-rows test pins the windowed value.
- The pins follow the regenerated surface: 1,409 active references, no
  row without a fact, and 34 support-floor rows over 61 area entries.

The national references, membership and compile-parity receipt are
regenerated for the three uprated rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vahid's review of #1422 (its second and third points).

- Since the move to a floor of three, rounded_count printed three to nine
  exactly, while extreme_count and support_band print "3-9" and the
  module's own rule is that no exact count narrows a small group by
  difference. It now reads "3-9" there too. Two tests that read an
  incidental exact count follow.
- The module docstring and the changelog say that the SPI Public Use
  Tape's two conditions (SN 9422) are unchanged by the move to three.
- A guard test fails when a UK runtime module, the UK spec, the gate notes
  or a tool still writes the old "<10" label, so that a receipt left on
  the floor of ten cannot land unnoticed. #1426's SPI pay-floor receipt
  writes it today; whichever of the two lands second moves it onto
  UK_DISCLOSURE_FLOOR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 and others added 4 commits October 31, 2026 01:01
Vahid's review of #1422 (its fourth point): pointing load("uk") at
latest-national.json and retiring the national variant changes the default
load for every consumer and depends on nothing in this PR. It moves to
#1432, which can land now and also removes #966's unreleased changelog
entry that described the opposite state.

This reverts commit 5c7a40d.

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

Two tests in test_uk_target_references.py read the pinned feed, so CI skips
them, and they still expected values from before D5 and R7. On feed 53815b2:

- the DfE rows bind the January 2026 census (D5), so the compiled caseloads
  and the signed-out age-4 cells move to DfE's January 2026 headline figures;
- the Tax-Free Childcare top-up and Northern Ireland's bus receipts take the
  calendar-2025 window (R7), three twelfths of FY2024-25 and nine twelfths of
  FY2025-26, which the test now writes out;
- the extended sum's refusal reads the January 2024 census through the target
  period, as it did before D5, since the row itself now selects January 2026.

Every expected value matches the pinned feed's facts and the committed
membership. The 15 feed-gated test files pass on 53815b2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The module docstring of receipt_counts and the changelog fragment now link
the microcosm#1095 comment that records María's ruling of 2026-10-29, as
Vahid's review asked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A selector that pins two services, one of which has no fact at all,
refuses the calendar-year window, as one short of a year already does
(Vahid's review, round 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 31, 2026
823-uk-2025-registry.changed.md ended by saying the UK default stays on the
June 2023 line until the national line's promotion and the flip of its
entry. This PR makes that flip, and both fragments ship in the same release
notes (Vahid's review of #1422, round 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22 juaristi22 changed the title UK: take up #1095's actionable items on policyengine-uk 2.165.1 (draft) UK: take up #1095's actionable items on policyengine-uk 2.165.1 Oct 31, 2026
@juaristi22

Copy link
Copy Markdown
Collaborator Author

@vahid-ahmadi thanks for both rounds. Below are your second review's points in its numbering, as of ddd82aa2f. The title no longer says draft.

  1. engine-uk graph records test: fixed in e40eabd1b, as you found.
  2. rounded_count from three to nine: it reads "3-9", as extreme_count and support_band do (254be6d41), and two tests that read an incidental exact count follow. The module docstring and the changelog now link the ruling's record on UK: every open data item: uk-data parity, engine locks, the local surface, consumption and high-value homes #1095 (comment, f83d7a3c2). Both say that the SPI tape's two SN 9422 conditions are unchanged.
  3. UK #1095 A/B fixes: the SPI age band (uk-data#628), households' HRAD from Table 8a receipts, and the SPI pay floor (uk-data#629) #1426's "<10" sites: a guard test in test_uk_receipt_counts.py fails when a UK runtime module, the UK spec, gates.json or a tool writes the old "<10" label. The Overlaps paragraph now says that whichever of UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422 and UK #1095 A/B fixes: the SPI age band (uk-data#628), households' HRAD from Table 8a receipts, and the SPI pay floor (uk-data#629) #1426 lands second moves the SPI pay-floor receipt onto UK_DISCLOSURE_FLOOR.
  4. Promotion for the constant-id national line: pointer and certified-loader extension #823: moved to its own PR, UK: make the promoted national line the default load (#823) #1432, and UK: take up #1095's actionable items on policyengine-uk 2.165.1 #1422 reverts it in 1f46653b6. UK: make the promoted national line the default load (#823) #1432 removes 823-national-variant-until-promoted.changed.md and drops the last sentence of 823-uk-2025-registry.changed.md (45df16363).
  5. engine-free: fixed in 652835c1f. The pins follow the regenerated surface (1,409 active references, no row without a fact), and the fixture feed gains FY2025 rows for both plan 2 series. The same commit takes SLC's three nation rows off the frame's own year, 2024, which the solve refuses for a value the engine computes. By María's ruling of 30 October they carry SLC's 2024-25 outturn to 2025 by policyengine-uk's OBR average earnings index, and their matched-period hold retires. CI passed in full at 02df9df2b.
  6. The 21 SPI holds: the regeneration section now lists them and says why they move: main's surfaces predate Budget 2026 ISA data, PR B of #1418: ISA subscriptions per adult, the Lifetime ISA anchor, additive ISA income, policyengine-uk 2.164.2 #1421's pins.
  7. 2.164.3's other stages: the body now lists was_wealth, was_lisa, etb_services and etb_vat beside the LCFS, NEED and NTS stages. WAS's DVTotinc_bhcR8 already counts this income: its investment income covers every account and investment WAS asks about, National Savings bonds and gilts included. The ETB's original income includes investment income, but its records come from the Household Finances Survey from 2018, and whether that survey counts National Savings and gilt interest is still to check against its documentation. was_lisa's tertiles at :1405 only report holder shares; no model reads them.
  8. A pinned service with no facts: ddd82aa2f adds the test. The window refuses that case, as it refuses a service short of a year.

The 15 feed-reading test files, which CI skips without the feed, pass on 53815b2. 02df9df2b moved two stale expectations in test_uk_target_references.py onto D5 and R7.

The D14f main-residence share stays open on #1423. The body now lists Phase B under "Not in this PR".

The LCFS excise-quantities and gambling-participation gate notes and two
stage-health docstrings still described withholding at ten, while their
receipts withhold at UK_DISCLOSURE_FLOOR (three). The guard test reads the
"<10" label, not the prose, so it missed them. The gate battery and
spine-part digests are re-pinned.

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

Copy link
Copy Markdown
Collaborator Author

Independent review of #1422 at 0a69ef4b5

This is an independent adversarial review. Four reviewers each took one area:

A verifier then reproduced each finding on synthetic data, and a last pass looked for gaps. Vahid's two rounds aren't repeated. Paths are relative to packages/microcosm-build/src/microcosm/build/ unless they start with packages/, tools/ or changelog.d/.

Verdict:

Fix before merge

1. The regional home values gate crashes on the new "3-9" band.

  • uk_runtime/stage_health.py:4557 handles only "<3" and calls int() on every other count, as does int(rows) at :4564. But since 254be6d, regional_home_values.py:199 and :228 print "3-9" for counts of 3 to 9.
  • With the packaged parameters:
    • a region with 2 rewritten rows is flagged and passes;
    • 3 or 9 rows fail the gate closed with ValueError: invalid literal for int() with base 10: '3-9';
    • 10 rows pass.
  • The gate is release-blocking, and only the synthetic smoke is exempt, so a licensed sampled rung is blocked.
  • The test fixture (test_uk_stage_health.py:2782) uses an exact 5, which the producer can no longer emit.
  • Suggestion: read any support band at its lower bound and flag the region; do the same for rows.

2. The pay-ratio clamp kept its floor of ten.

  • uk_runtime/frs_annual_income.py:811 still clamps the DV-to-usual ratio into [0.95, 1.10] when support < 10. The receipt, however, now prints the ratio from 3 households (:939).
  • With a true ratio of 1.5 on 5 or 9 households, the receipt shows 1.1, the gate passes, and no pay_concept_ratio_measured flag is set.
  • The comment at :814 ("it stays flagged") is now false.
  • Suggestion: move the clamp onto UK_DISCLOSURE_FLOOR, or flag the ratio below 10.

Periods and windows

3. observation_period survives every restamp.

  • ledger_targets.py:4626 returns observation_period whatever the reference's period is. The restamps (uk_runtime/ledger_targets.py:492-494 and tools/fit_uk_childcare_takeup.py:189-190) change only period. So the 8 DfE rows select January 2026 at any period:
    • fit_uk_childcare_takeup.py --target-period 2024 fits 2024 to the 2026 census;
    • --vintage-override silently does nothing, though its receipt still records it;
    • any period before 2026-01 binds a later census with no hold;
    • the 2026 calibration move fails as an undeclared hold.
  • The PR's own test has to null the field (test_uk_target_references.py:726).
  • It lives in the shared ledger, so it is unbounded for the US and NZ too.

4. On #1430's feed, the three OBR council tax nation rows won't compile.

  • They are calendar-year windows with no source_table_by_opening_year map. On chronicle 08962f1, March's EFO carries only FY2024-25, so each row has two incomplete partitions and refuses.
  • The merged tree then has 22 OBR window rows, 3 of them unmapped, and the build refuses: full_targets.py:239-244 and :417-421 raise, and the three exclusions raise "matched zero registry specs".
  • obr.council_tax itself auto-merges into window × main-residence share + map, and its value moves to October's outturn. The D14e and D14f values pinned here are 53815b2 numbers.
  • Suggestion: whichever PR lands second adds the three rows to the map.

5. The guard for engine defaults can't see stage outputs.

6. The multi-value window counts series, not one per pinned value (ledger_targets.py:4301).

  • Suppose one pinned service has two series that differ only in record_set_id, and the other has no facts. The window still compiles.
  • With the test helpers, two ulsterbus series and no metro_glider give twice the ulsterbus value, and the member labels collide.
  • The ratio-operand guard at :4236 already checks one-to-one; the window should too.

7. The council tax nation diagnostics sit on a different basis from the GB row.

  • D14f binds obr.council_tax at 0.968 × row 15 (£47.197bn). But D14e's three nation rows measure main-residence council tax against OBR's unscaled nation receipts, which sum to £48.774bn.
  • So a frame that fits the GB row reads about 3.2% low on every nation, from the basis alone.
  • These texts still say the GB row binds the nations' sum:
    • the notes at uk/uk_population_targets.json:2597, :2640 and :2683;
    • the exclusion reasons at uk/calibration_measure_exclusions.json:401, :410 and :419;
    • the changelog.
  • Suggestion: scale the nation rows by the same share, or state the gap.

Gates and tests

8. The new take-up stage gates fail on withheld cells (uk_runtime/stage_health.py:1171).

  • Every sibling gate flags such cells instead, under the 26 October ruling (:1280, :1481, :3880, :4551, :4700).
  • On a licensed sampled rung, the under-2 usage row stops the build at the assembled boundary.
  • The notes say this is deliberate, but it departs from the ruling the other gates follow.

9. childcare_copy_take_up has no stage gate. It redraws five of the twelve take-up flags, while the changelog says all twelve are checked at their drawing stages.

10. Test gaps:

  • nothing runs frs_take_up and reads its take_up_signal evidence (the person and household stages are tested);
  • no spine battery evaluates the three new assembled-phase gates;
  • nothing covers "3-9" in the regional home values gate.

11. The floor guard matches only the "<10" label (tests/engine_free/uk/test_uk_receipt_counts.py:92).

Texts

12. Floors of ten are neither moved nor listed as kept:

  • uk/gates.json:1803 (minimum_distinct_sources: 10, "the UKDS disclosure floor") and high_value_home_gates.py:634;
  • DEFAULT_SDC_MINIMUM_COUNT = 10 in three tools;
  • spi_rank_transport.py:92 and sources.yaml:1717;
  • stale prose at stage_health.py:3877 and gambling_participation.py:670.

All of these are stricter than the new floor, but the body's "one constant … sets it" doesn't hold. List them as kept or move them.

13. Texts that describe replaced designs:

  • the R4 changelog's "signed matched-period hold", retired in 652835c;
  • the parity tool's DfE rationale ("January 2025 census") and council tax rationale (no main-residence share), both copied into the regenerated receipt;
  • uk_population_targets.json:933;
  • local_target_census.py:543;
  • generate_uk_target_references.py:519-521.

14. The complement's metadata contradicts its formula. ledger_value_numerator records denominator − cells, while ledger_value_formula reads (denominator − numerator)/denominator. So the recorded pieces give the cells' share instead: 0.25 for 0.75 in the PR's own test. The value itself is right.

Checked and found true

  • The lock. uv.lock matches APPROVED_UV_LOCK_SHA256, the floors are at 2.165.1, and concept coverage adds the 9 company car inputs. The uprating pins and the bonus-sacrifice anchor change only version stamps.
  • The national surface moves exactly as described, and nothing else moves:
    • 1,409 active references;
    • SE holds go from 1.117625 to 1.111520;
    • dividends are 1.0881514;
    • SLC rows move ×1.041198;
    • DfE rows read 2026-01;
    • obr.council_tax is 0.96767 × the window.
  • Local deferrals go from 94 to 77: the 9 Welsh and 8 Scottish stock families.
  • The take-up stage receipts share the terminal gate's measure, keys and bounds, fail closed on a missing receipt, and run before the assembled gate.
  • Other changes: Carry manifest rewrites into the shared Stage/StageRecord producer graph #721's rewrote, the zero-weight caps, the export-surface change, and the contract digests and fixtures check out, and their tests pass.
  • Windows elsewhere: no other UK, US or NZ window pins several dimension values.

Disclosure-control items went to the author separately.

🤖 Generated with Claude Code

juaristi22 and others added 3 commits October 31, 2026 17:02
- Regional home values: the gate reads a banded count at its bounds, so a
  region of three to nine rows ("3-9") no longer crashes it, and the stage's
  own banded row count is read at its top (finding 1).
- Pay concept ratio: the stage still holds a ratio on fewer than ten
  households to its bounds, now PAY_CONCEPT_RATIO_MINIMUM_SUPPORT, and the
  gate reads a ratio whose support band is under ten as unmeasured (2).
- Take-up stage gates: a withheld share or target is flagged unchecked,
  never failed, as every sibling gate does under María's ruling of
  2026-10-26; the terminal gate still measures it (8).
- childcare_copy_take_up records the five flags it re-draws and a fourth
  assembled gate checks them, as María's "do both" ruling asks (9).
- Tests: frs_take_up's receipt, the spine battery's stage_health binding on
  the take-up gates, the copy stage's gate, and banded home values counts
  (10). The engine-defaults guard also reads every stage's declared outputs
  and rewrites (5).
- Floors of ten: the high-value-homes support gate and the three tools'
  SDC defaults move to three; the gambling prose follows; the statistical
  floors that stay say so. A second guard fails on any comparison,
  constant, default or prose that keeps a floor of ten outside the named
  statistical ones, across tools/** and microcosm-data too (11, 12).

The gate battery and spine-part digests are re-pinned.

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

- restamp_reference_period moves a declared observation_period by the same
  years as the target period, so the eight DfE rows read the January census
  after whatever model year they are restamped to: the calibration compile,
  the local compile and the childcare fit tool, whose --target-period and
  --vintage-override now select the matching census (finding 3).
- A calendar-year window over several pinned members needs one complete
  series per member: two series of one service and none of the other no
  longer compile (6).
- The complement's metadata gives its value back: the recorded numerator is
  the denominator less the cells, and the formula reads numerator /
  denominator (14).

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

- The three OBR council tax nation rows take obr.council_tax's main-residence
  share, so they sum to the bound row and each reads its nation's fit on
  the same basis; their notes, exclusion reasons and changelog say so
  (finding 7).
- Texts that described replaced designs: the R4 changelog's matched-period
  hold, the parity tool's DfE census and council tax rationales, the DfE
  rows' contract note, the local census's MSOA income entry (D18a's
  circularity reason) and the generator's SLC comment (13).

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

Copy link
Copy Markdown
Collaborator Author

Thanks for the independent review of 0a69ef4b5. Every finding is addressed at c0dd6eae2, except finding 4, which belongs to whichever of #1422 and #1430 lands second.

Fix before merge

  1. Regional home values and "3-9": the gate reads any banded count at its bounds. A region of three to nine rows counts as three, the stage's own banded row count is read at its top, and only a region printed "<3" is flagged, as before. The fixture's counts are now ones the receipt can print, and a new test covers "3-9" (7a95f37a7).
  2. The pay-ratio clamp: it stays at ten, now PAY_CONCEPT_RATIO_MINIMUM_SUPPORT, as a statistical floor: on fewer households a ratio past its bounds is noise, and stopping the build on it is what Vahid's second review of Build the national + local-area pair on policyengine-uk 2.151.0, and drop UK model-figure withholding #1396 ruled out. The gate now reads a ratio whose support band is under ten as unmeasured, so a held ratio is flagged rather than judged (7a95f37a7).

Periods and windows

  1. observation_period across restamps: restamp_reference_period moves a declared observation period by the same number of years as the target period. The calibration compile, the local compile and the childcare fit tool restamp through it. So the DfE rows read the January census after their model year at any period, and --target-period and --vintage-override select the matching census. The test now restamps the row instead of nulling the field (42913ab5e).
  2. UK: company cars from the FRS job table, and policyengine-uk 2.165.0 (dashboard #316) #1430's feed: right; this falls to the second PR to land. The UK: company cars from the FRS job table, and policyengine-uk 2.165.0 (dashboard #316) #1430 session has the three nation rows to add to source_table_by_opening_year when it merges main.
  3. The engine-defaults guard: it also reads every stage's declared outputs and rewrites from source_stages.json. UK: company cars from the FRS job table, and policyengine-uk 2.165.0 (dashboard #316) #1430's company_cars stage will fail it until its six written inputs leave UK_ENGINE_DEFAULT_INPUTS (7a95f37a7).
  4. Series per pinned value: a window over several pinned members now needs exactly one complete series per member. Your case of two ulsterbus series and no metro_glider refuses, with a test (42913ab5e).
  5. The nation diagnostics' basis: the three nation rows now take obr.council_tax's main-residence share, so they sum to the bound row and each reads its nation on the same basis. The notes, exclusion reasons and changelog say so (c0dd6eae2).

Gates and tests

  1. Withheld cells in the take-up stage gates: they are flagged unchecked, never failed, as the sibling gates do under María's ruling of 26 October. The terminal gate still measures every flag at the calibrated weights, so nothing goes unmeasured (7a95f37a7).
  2. childcare_copy_take_up: it now records the five flags it re-draws, and a fourth assembled gate holds them to their targets. The changelog says so (7a95f37a7).
  3. Test gaps: frs_take_up's receipt is read in its stage test. A new test runs each take-up stage gate through the battery's stage_health binding, as the spine battery does. The copy stage's gate is tested, and so is "3-9" in the home values gate (7a95f37a7).
  4. The floor guard: it now scans tools/** and microcosm-data too. A second guard fails on any comparison, constant, default or prose that keeps a floor of ten outside the statistical floors it names (7a95f37a7).

Texts

  1. Floors of ten: moved to three are the high-value-homes support gate and its default, and the three tools' SDC defaults. Kept as statistical floors, each now saying so, are the pay ratio's ten, the SPI transport's ten rows, the regional renter minimum, the SPI coherence tool's cells and the private-rent tool's groups. The stale prose is restated.
  2. Replaced designs: restated are the R4 changelog, the parity tool's DfE and council tax rationales, the DfE contract note, the local census's MSOA entry (now D18a's reason) and the generator's SLC comment (c0dd6eae2). The regenerated receipts carry them.
  3. The complement's metadata: the formula now reads numerator / denominator, matching the recorded numerator, and the test checks that the recorded pieces give the value back (42913ab5e).

The national and local surfaces, both parity receipts and the local census are regenerated on Chronicle 53815b2 in a follow-up commit. Until it lands, CI's checks of the generated surfaces will fail.

The national references, membership and compile-parity receipt move only
for the three OBR council tax nation rows, now scaled by obr.council_tax's
main-residence share, and for the restated DfE and council tax rationales.
The local census carries D18a's circularity reason for ONS's small-area
income rows. The local references, the local parity receipt, the coverage
manifest and the H2 fixture regenerate unchanged.

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.

2 participants