Skip to content

Add the UK national calibration step over ledger-backed target references - #729

Merged
juaristi22 merged 4 commits into
mainfrom
uk-national-calibrate
Aug 21, 2026
Merged

Add the UK national calibration step over ledger-backed target references#729
juaristi22 merged 4 commits into
mainfrom
uk-national-calibrate

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

Summary

The national calibrate step scoped in #623, stacked on #727 (base branch; the first commit here is tree-identical to its head). Part of #623 and #701 — the post-calibration aggregate-scaler adjudication (2026-08-14 comment) stays open and out of scope: no incumbent scalers are ported.

  • uk_runtime/national_calibration.py: resolves activated references from uk/target_references.json against the pinned chronicle facts; an activated reference that cannot compile aborts the build (the policyengine-uk-data#456 silent-drop class is impossible by construction). Builds the benefit-unit-grain target matrix per the reference declarations and calibrates household weights via microcosm.calibrate.calibrate() (public API only).
  • Activation: dwp.uc.households (caseload, sum of benefit units with universal_credit.amount > 0) and obr.universal_credit_in_cap (expenditure), both period 2025 under the resolver's per-reference resolve-at-or-before semantics. Facts: chronicle#184 (fixture-backed here; live resolution needs the released chronicle bundle carrying Add proximal L1 solver path #184 — stated dependency).
  • Diagnostics: per-target estimate/target/rel-error plus loss/ESS/weight-ratio in the step manifest, following the US build's calibration-diagnostics pattern.
  • Gate: release-blocking activated-vs-entered count check, per the UK gate adjudication: reference registry, spec-armed thresholds, weight-ratio re-baseline (#630) #706 registry pattern (uk/gates.json).
  • Driver: tools/build_uk_national_dataset.py grows an opt-in calibration flag + diagnostics JSON output; the staging seam's behaviour without the flag is unchanged.

No dataset was built; running this on the real staging H5 is a later, gated act. Refs: #707, #727, chronicle#184, policyengine-uk-data#452.

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits August 20, 2026 16:23
…nces

Part of #623 and #701: a named national stage that resolves activated
references against pinned chronicle facts (any unresolvable activated
reference aborts the build), builds the benefit-unit target matrix, and
calibrates household weights through microcosm-calibrate's public front
door. Activates the UC caseload and UC-in-cap expenditure references at
period 2025. Post-calibration aggregate scalers stay out of scope per the
open adjudication on #623.
Rebasing over merged #727 (and #730 beneath it): main's
TestUnevidencedArms now enumerates the closed set of gates that report
evidence_absent when the battery runs unarmed, so the new
uk_calibration_reference_coverage gate joins that set. Also squares the
indentation of the earlier enumeration update in the national-build
terminal-batch test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juaristi22
juaristi22 force-pushed the uk-national-calibrate branch from e077f5d to 9dcc83b Compare August 20, 2026 14:52
@juaristi22
juaristi22 changed the base branch from uk-uc-contract-refresh to main August 20, 2026 14:52
juaristi22 and others added 2 commits August 20, 2026 17:55
Adding uk_calibration_reference_coverage to uk/gates.json moves the
three vintage pins (policy sha256, gates-manifest sha256, spec
fingerprint) and the entry-gates mirror in the data contract, plus the
schema-3-style local copies in the data shard's contract tests. The new
entry carries no legacy detail schema and contributes no evidence
digest, so the legacy-name and evidence-id mirrors are unchanged; the
all-passing fixture mirrors the evaluator's activated/resolved/matrix
detail block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The calibration stage exposed checkpoint_metadata but no
resume_from_checkpoint, so a resumed build left manifest None, the
evidence getter handed the coverage gate an empty mapping, and the
release-blocking uk_calibration_reference_coverage entry failed closed
on a KeyError despite a completed calibration checkpoint (the driver
sidecar wrote null diagnostics). Mirror the retained-leaves and SPI
restoration precedents: record the output frame's content identity in
the checkpoint record, and rehydrate manifest/diagnostics on resume
with fail-closed validation of the three coverage counts and a
drifted-record refusal.

Tests cover the JSON round-trip and its refusals, the resumed evidence
feeding the real coverage evaluator, and an end-to-end crash-resume
build asserting the gate passes without re-executing calibration. The
e2e cleanup stage drops the stage's prepared scratch column before the
staging write — the pandas staging writer refuses "/" in column names,
a latent issue for real armed builds recorded on the PR.

Implemented via Codex (gpt-5.4) from the reviewed plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juaristi22

Copy link
Copy Markdown
Collaborator

Adversarial review of the rebased branch: findings and dispositions

A Codex adversarial review ran against this branch after the rebase onto post-#727 main (all findings independently reproduced before being accepted). Disposition of each:

Fixed in this PR (6c8a95a)

3. Checkpoint resume dropped calibration evidence. UKNationalCalibrationStage exposed checkpoint_metadata() but no resume_from_checkpoint, so a build resumed past a completed calibration left manifest = None; _stage_calibration_evidence handed the coverage gate {}, and the release-blocking uk_calibration_reference_coverage entry failed closed on a swallowed KeyError (the driver sidecar wrote {"targets": [], "calibration": null}). Fixed following the retained-leaves/SPI-restoration precedents: the checkpoint record now carries the output frame's content identity, and resume rehydrates manifest/diagnostics with fail-closed validation of the three coverage counts plus a drifted-record refusal. Tests: JSON round-trip + refusals; resumed evidence through the real evaluator; end-to-end crash-resume build asserting the gate passes without re-executing calibration.

Arriving with the stacked #622-completion PR

2. Benunit-grain references cannot compile against household weights. matrix.py's only cross-entity path is person-grain collapse (_entity_row, no entity check): on any frame where benunits ≠ households the stage aborts (array is not broadcastable to correct shapeNo targets compiled into the constraint system; repro: 6-person/4-benunit/3-household frame). Nastier: when n(benunit) == n(person) coincidentally — the shape of this PR's fixtures — the scatter silently produces a garbage row that happens to be numerically right, so the existing tests pass by accident. Disposition (adjudicated): fixed entity-general in microcosm-calibrate by the #622-completion PR, whose regenerated surface carries 112 benunit-grain references and a non-1:1:1 regression frame (which must also cover the silent-coincidence case). Until it lands, armed builds (--ledger-facts) abort fail-closed; no silent path exists.

1. The 2025 UC caseload activation binds the December stock, not the intended basis. With chronicle#184's monthly facts now landed, dwp.uc.households at period: 2025 under identity-only operations resolves latest-at-or-before → December 2025 (7.17m) instead of the calendar-year-2025 average (~6.76m) named in the #701 thread — about 6% high, and the resolved/declared count checks cannot catch it. Disposition: the #622 PR ships the calendar_year_average (and latest_plateau) value operations and the regenerated declaration carrying the basis. Until then the 2025 activation resolves December by default — recorded here as a known interim state, not a decision.

Discovered while implementing the resume fix (needs an owner)

4. Prepared scratch columns crash the real staging writer. The stage's prepared measure columns are slash-named (dwp/uc/households) and persist on the returned frame; the staging writer is pandas-HDFStore-based with data_columns=True, which refuses / in column names — so a real armed build would crash at the staging write after a successful calibration. The new e2e test works around it with an explicit cleanup stage (marked as such). Candidate fixes: the stage restores pristine entity tables after the solve (keeping only calibrated weights + mass record), or the shared #622 materializer owns scratch-column lifecycle. Flagging for adjudication rather than fixing here.

Rebase housekeeping already on the branch

🤖 Generated with Claude Code

@juaristi22
juaristi22 merged commit 2c7a721 into main Aug 21, 2026
4 checks passed
juaristi22 added a commit that referenced this pull request Aug 21, 2026
…ise channel-blind scope note

UK_REFERENCE_DATASET_NAME drops the _recalibrated suffix (adjudicated
2026-08-20): the pinned reference is the published enhanced_frs_2024_25.h5
itself and no recalibrated variant exists at this vintage, so the gate-report
label now names the artifact exactly (June report strings keep their own
label). The registry scope notes stop claiming the incumbent "structurally
lacks" the SPI clone channel - the 2024-25 artifact carries the synthetic
rows structurally but no admin-restored mass in the channel-exclusive
columns, which is the fact the reviewed exclusions rest on; the approved
exclusion reasons are untouched. Gate digests re-cut over the post-#729
union in the same change.

Part of #723.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Aug 21, 2026
…ation seam

Two release-blocking preflight gates prove the Ledger-backed compile against
the frozen fixtures (production 2023 and incumbent 12a1e028 at 2025) with
signed-difference enumeration in both directions. The national build tool
adopts the shared ledger-artifact pin surface (one resolved artifact feeds
the pin, the gates, and the #729 calibration stage, which is adapted to the
387-reference surface). Gate-battery digests are recomputed from the merged
gates.json; the spec-fingerprint doc now matches the code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Aug 22, 2026
…ise channel-blind scope note

UK_REFERENCE_DATASET_NAME drops the _recalibrated suffix (adjudicated
2026-08-20): the pinned reference is the published enhanced_frs_2024_25.h5
itself and no recalibrated variant exists at this vintage, so the gate-report
label now names the artifact exactly (June report strings keep their own
label). The registry scope notes stop claiming the incumbent "structurally
lacks" the SPI clone channel - the 2024-25 artifact carries the synthetic
rows structurally but no admin-restored mass in the channel-exclusive
columns, which is the fact the reviewed exclusions rest on; the approved
exclusion reasons are untouched. Gate digests re-cut over the post-#729
union in the same change.

Part of #723.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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