Skip to content

UK local release: nation-grain doctrine at K=15 and 2,000 epochs, a 100,000-household release path, line-scoped gate exclusions, and the QRF tail surface - #1242

Merged
juaristi22 merged 4 commits into
mainfrom
uk-local-doctrine-k25-ngfes
Oct 14, 2026
Merged

juaristi22 merged 4 commits into
mainfrom
uk-local-doctrine-k25-ngfes

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Part of #1124. This PR prepares the UK local release line, built from main, for a rebuild that can be published. It carries over the parts of #1163 that this needs, without #1163's experiment harness and results:

  • the row-metadata target-weight rules and the size-stage chi-square L2 penalty from Add UK target-weight rules, size-stage L2 and a selection experiment harness (#1124) #1163 (uk_runtime/target_weights.py and its wiring through the problem, solve, holdout, CLI, diagnostics schema and scorer);
  • the doctrine: 2,000 epochs and nation_grain_family_equal_sqrt_count, K stays 15, with defaults for sized builds;
  • a release path for the 100,000-household file, under the dense line's publication requirements;
  • gate-policy fixes for three release-blocking gates that failed the first K=25 candidate on policy rather than fit;
  • national certification unblocked on the QRF tail gate: the columns spi_benefit_coherence supersedes leave the surface, and two row-counted columns get time-boxed national exclusions;
  • the release scorer reads graph-built candidates on the UK local build (K=25, 2026-10-06): data-quality defects on the target surface and the spine #1123 surface again;
  • the dwp.uc.households_housing_element measure exclusion renewed to 2026-11-14;
  • the telemetry emitter's startup window, and a candidate-only --inspection-export.

Main is merged in. #1163 can then keep only the experiment harness, the pre-registration and the results, or close.

The doctrine change

uk_runtime/local_doctrine.py. Each constant's note cites its evidence.

Before (#762 rulings) After
Clone count K 15 15 (unchanged)
Solve epochs 1,500 2,000
Target-weight rule grain_equal nation_grain_family_equal_sqrt_count
Stretch bound, loss cap 10, 10 unchanged
Release size whole pool whole pool, or 100,000 households at the size doctrine

Sized builds (--dataset-households), when the request leaves the setting unset:

Explicit values still win, and --refit-l2-lambda 0 turns the penalty off.

Why K stays 15 (review item 2)

An earlier revision of this PR moved K to 25. Across the dense pools in the uk-dataset-evaluation reports (55k-candidates and 55k-rebuild at K=15, 55k-k20 at K=20, k25-60k and k25-100k at K=25), more clones did not lift the weakest seat's support, and the 100,000-household draw's support gain on the K=25 pool came from the size settings and the size, not from K. A K=15 / 100,000-household build from this branch re-measures it, with support collapsed by source household (review item 3). Seat-level support figures stay out of this thread (#1241).

Epochs (review item 4)

The 2026-10-11 build stores every stage's loss trajectory. The losses swing about ±10 % from epoch to epoch.

  • Dense solve (keeps its best iterate): its best loss by epoch 1,500 is 3.5 % above its best by 2,000.
  • Size search: still falling at 2,000. It is 7.5 % above at 1,500 and 2.6 % above at 1,750.
  • Refit: within 0.2 % of its best by epoch 500, but it returned its closing iterate, about 11 % above its own best. It now keeps its best epoch on the objective it minimizes (round 2, item 5).

The K=15 build reports the same per-stage figures, and a release candidate's rotated holdout measures the rule on held-out local rows.

A release path for the 100,000-household file

Ruling of 2026-10-13: the local release ships the 100,000-household selection instead of the whole pool, under the dense line's publication requirements. The pool is too large for one engine and for the consumers who load the file.

  • Release candidate: may request --dataset-households 100000 when it carries the size doctrine (local_doctrine.uk_local_size_doctrine): those settings, no selection L2 penalty, drawn with the solve seed and selected afresh rather than resumed from a size checkpoint. rowwise_cli.size_doctrine_drift names any departure, and a sized release candidate refuses one. Any other size stays candidate-only.
  • Releasable: a doctrine-sized build is releasable on the same verdict as the dense line (full rung, exact engine population, every release-blocking gate). The posture records size_doctrine. No matched-size scorecard is required; the UK small national default: L0-selected ~50k-household file from the 535k pool #355 promotion protocol stays the route for any other size.
  • Holdout: a sized fold reruns the whole size selection on its training rows. Each fold's budget search now starts from the main search's --selection-initial-lambda, so five folds do not each search from zero.
  • Pre-flight: checks the size doctrine, the size receipt (requested and realized 100,000), release_posture.size_doctrine, no selection penalty and no resumed selection. An unsized run must carry no refit penalty. The incumbent-surface evaluation is unchanged: one engine over the released file.
  • Refit iterate: under this path the refit's weights are the released weights. The refit now keeps its best epoch on the objective it minimizes, the target loss plus the L2 penalty, rather than the closing state. This is the new retain_best_objective option of calibrate and refit_l0_selection, off by default, recorded as best_feasible_objective.
  • Assembly: the assembler and the data contract have no size-specific checks, so a sized candidate that passes the pre-flight assembles on the same contract. No sized release has been assembled end to end yet; the K=15 build will be the first. The release identity is unchanged.

Gate-policy fixes

The first K=25 candidate failed three release-blocking gates on policy, not fit.

  • Line-scoped exclusions. A schema-2 reviewed exclusion may name release_lines (national, local), the lines whose evidence earned it. A gate context declares its line: the full build declares local, and the national gate kernel and the release-cut certifier declare national. On any other line, and wherever a context declares no line, a scoped entry stands aside: it fails closed, neither suppressing a failure nor reading as stale, and the gate details name it. Unscoped entries behave as before everywhere, and unscoped receipts keep their policy payload. All four schema-2 exclusion gates honour it.
  • uk_qrf_tail_concentration. The charitable_investment_gifts exclusion is scoped to the national line, where it was earned. The gate counts records, and on the K-clone pool each spine record is a clone row, so the column fell below the threshold and the entry read as stale. Re-arming the gate on independent draws, which would also give the local line its own thresholds, is Re-arm the UK QRF tail-concentration gate on independent draws #1277.
  • uk_degenerate_release_surface. household.atomic_area_basis is constant at assigned by construction. It gets a local-line reviewed exclusion and stays in the export as assignment provenance, per UK geography step 2: identity-keyed atomic-area assignment on rebuilt supports (#931) #932. The data contract's degenerate evidence pin moves with the register.
  • One engine block per clone. The pre-flight accepts an engine resolved per clone when the build recorded the pool representation exact, as release_verdict and the runbook already did. The record now states what exact covers (exact_scope, below).
  • uk_input_mass_parity. The runbook now passes the national line's licensed --input-mass-reference. Re-emitting the totals under policyengine-uk 2.122.2 does not reproduce the pinned totals_sha256, so the operator's file is required.

QRF tail surface and national certification. Two problems blocked national release-cut certification on this gate.

Measure exclusion renewal. dwp.uc.households_housing_element (#882) expired on 2026-10-15. Its structural bias is unchanged, since the model has no other/unknown tenure, so it is renewed for one month to 2026-11-14, in the register's renewal form.

Per-clone engine blocks: what "exact" covers. I compared per-clone and single-block resolution with the real engine on a slice of the K=25 pool at design weights (every clone of a random set of source households).

  • Every household column and every constituency metric matched exactly.
  • About 0.1–0.3 % of person records differed: state pension type, pension credit and SLC repayment.
  • That moved 78 of 1,196 national rows on the slice, and some local-authority metrics.

So the "exact" record certifies the weight-share scaling, not engine draws that depend on a record's position. The cause is unconfirmed and a follow-up.

  • The measures record now says so: engine_population_representation.exact_scope.
  • The released file's fit is gated under one engine regardless. The incumbent-surface evaluation re-measures the shipped file on one engine, which the data contract requires. It fails release for any national or local row more than 25 % from its target, or any family of five or more rows with fewer than half within 25 %, and signed deferrals do not exempt a row.
  • The runbook now states this beside the evaluation step.

Other fixes the K=25 build needed

  • Telemetry emitter startup window, 3 s → 15 s. The service imports microcosm.build (torch, pandas, the calibrate stack), which takes about 4 s on an M1 Max even warm. Every build there fell back to local-only telemetry.
  • --inspection-export (candidate-only; refused with --release-candidate and by the national role). When structural gates block the artifact, the build still runs its export descriptor, H5 readback, package inventory and staging, so a blocked candidate can be studied. The bundle stays unreleasable and the exit status still reports the block. The swapped export kernel lives in its own module, so graph_terminal's source hash is unaffected.

The f1 smoke of the release path

On licensed data, from this branch with #1257 merged, at a 1 % sample: K=15, 2,000 epochs, the nation-grain rule, 15 engine blocks, the 5-fold holdout, local-only staging and the national input-mass reference. The run then went through scoring, the incumbent-surface evaluation on one engine, the pre-flight and an assembly attempt.

  • It worked as intended:
    • the doctrine came from the constants, with no overrides;
    • the per-clone engine was recorded exact, and the pre-flight accepted it;
    • the atomic_area_basis exclusion applied, and the QRF-tail gate ran as local and set the charitable_investment_gifts entry aside;
    • the input-mass gate evaluated against the reference, and the holdout, export, readback and evaluation all ran.
  • Expected at f1: the 19 pre-flight failures are posture (f1 cannot be a release candidate), the sample's gate failures and the smoke's throwaway signing key. A sized selection refuses the 266 national rows a 1 % sample cannot carry, so the size stage was smoked on the synthetic graph instead.
  • Fixed here: the scorer. tools/score_uk_local_candidate.py read only a registry artifact, and a graph build records its registry inside target_selection.json. Its frozen count, 20,885, also predated the UK local build (K=25, 2026-10-06): data-quality defects on the target surface and the spine #1123 surface. No current candidate could be scored, and assembly needs the score receipt. It now reads the target selection's local rows and pins 21,804, UK local build (K=25, 2026-10-06): data-quality defects on the target surface and the spine #1123's compiled local reference count. At full scale the diagnostics cover every local row (the K=25 build covered all of its surface); at f1 they cannot, so scoring needs the full rung.
  • Fixed by Carry the compile's unresolved references to the UK compile-parity gates, and retire the 2023 gate #1257: uk_ledger_compile_parity_production_2023 failed at main's Chronicle pin.

Before a release from this branch

  1. Build K=15 / 100,000 households from this branch's merge commit, with no flag overrides: the spine rebuilt on main, Chronicle at the committed 942a1fa pin, the national input-mass reference and the stable signing key.
  2. Renew the measure exclusions that lapse before publication: ons.savings_interest_income expires 2026-10-22, two SLC entries on 2026-11-03 and about 40 entries on 2026-11-25/26. dwp.uc.households_housing_element is renewed here.
  3. Re-measure the gates that fail on fit (uk_target_fit, uk_local_area_support, uk_local_target_fit) on that build.

Testing

  • Engine-free suites for build (UK and shared), data, calibrate and diagnostics: all pass locally, except one CGT test that asserts the UK engine is absent (it holds in the engine-free CI job).

  • New tests cover:

    • the QRF surface reading the coherence stage's declaration, and the new receipts;
    • the scorer's target-selection input and its re-pinned count;
    • the refit's best-objective retention, and the solver's opt-in for it;
    • the size-doctrine refusals of a selection penalty and a resumed selection;
    • fail-closed scoping where no line is declared.

    Earlier additions cover:

    • the doctrine pin, the sized defaults and the size doctrine (its refusals by name, and the bare request carrying it);
    • the pre-flight's sized checks and per-clone acceptance;
    • the holdout's warm start;
    • release_lines (validation, unchanged payloads, standing aside off-line, refusing an unknown line);
    • the inspection export.
  • tools/ci_test_plan.py verify passes; ruff check and ruff format --check pass on the changed files.

🤖 Generated with Claude Code

@juaristi22 juaristi22 changed the title Move the UK local doctrine to K=25, 2,000 epochs and the nation-grain rule, with #1163's rules and size-stage L2 UK local release: nation-grain doctrine at K=15 and 2,000 epochs, a 100,000-household release path, and line-scoped gate exclusions Oct 13, 2026
@juaristi22 juaristi22 changed the title UK local release: nation-grain doctrine at K=15 and 2,000 epochs, a 100,000-household release path, and line-scoped gate exclusions UK local release: nation-grain doctrine at K=15 and 2,000 epochs, a 100,000-household release path, line-scoped gate exclusions, and the QRF tail surface Oct 14, 2026
@vahid-ahmadi

Copy link
Copy Markdown
Contributor

@juaristi22, thanks for the two replies and for pushing the evidence branch. This is my round-2 pass on head 4f824b062.

Needs changes before merge: per-seat support figures in the PR body, local_doctrine.py and two commit messages must come out. Apart from that and item 2 below, the code is close.

Round-1 points

  1. Traceability: partly fixed. e7ad75e97 is now on GitHub, and the candidate manifest records that SHA with git_dirty: false. target_weights.py, dataset_size.py, graph_calibration.py and size_checkpoint.py are identical between that commit and this head, so the rule and size-stage code that ran is the code here. But the build still ran the old doctrine block with flags (n_clones, epochs, target_weight_rule_override, the size settings passed explicitly, skip_holdout, inspection_export). It can't back the constants this PR sets, and with K back at 15 it isn't meant to. The 60k baseline now has a run tree. This closes when the no-override K=15 build's run id and H5 SHA are posted here.
  2. K: fixed. K stays 15 in the doctrine, the dense posture and the pre-flight's ruled tuple.
  3. Source-collapsed support: not fixed, deferred. My "before K moves up" condition no longer applies. See new item 4, though: this PR now accepts that row counting misleads on the QRF gate, and the same applies to the support gate on a selection drawn from a clone pool.
  4. Convergence and holdout: partly fixed. The per-stage loss comparison is now in the epochs note (local_doctrine.py:104 to :110). There's still no holdout under the new rule (the evidence build skipped it). The pre-flight requires one for a release candidate, so that's covered for the release run.
  5. Nation-grain double budget: not fixed, held on purpose so that K is the only change in the comparison. That's reasonable. My view: a sentence in the rule's docstring saying the double budget is known and kept for now costs nothing and makes the current behaviour declared, not accidental.
  6. Pre-flight K pin and the "at the ruled K=15" rationales: fixed, since K is 15 again. The per-clone engine acceptance that replaced the single-block rule is new item 2.

New findings

  1. must: seat-level support figures are committed in public. These are effective sample sizes per constituency and counts of seats under a support floor, which the disclosure rules in UK local: rerun the 2025 Autumn Budget constituency analysis on the local candidate to find data gaps and per-seat support before Budget day #1241 keep out of public threads:

    • local_doctrine.py:122 to :128 (new): minimum constituency ESS by K, and the number of seats under the floor for the 60k draw. The note just above (:118 to :120) already had the same kind of figures on main. Since this block is being edited anyway, I'd clear both and cite the uk-dataset-evaluation report names instead.
    • The PR body: the "Why K stays 15" table (minimum and median constituency ESS) and the count of seats under the floor in the paragraph after it.
    • Commit messages of de426731 and 4beafb3f carry the same figures. They need rewriting before merge, by an interactive rebase and force-push or by squashing with a clean message. Otherwise the merge brings them into main's history.
    • qrf_tail_reviewed_exclusions.json:6: the charitable_investment_gifts reason gives an exact carrier count for a small cell. This predates the PR, but the entry is edited here.
    • Your 14 October comment gives collapsed record counts for the two deferred SPI columns. Your 13 October comment gives per-K minimum constituency ESS. Could you edit those out of the thread too?
  2. must, unless I've missed where this is already handled: release-blocking fit gates and the shipped file may disagree. The pre-flight now accepts one engine block per clone when the build records the pool representation as exact (tools/preflight_uk_local_release_candidate.py:220 to :228). Your own comparison shows "exact" covers the weight scaling only. Per-clone and single-block resolution differ on some person records, which moves some national rows and local-authority metrics. If uk_target_fit and uk_local_target_fit are measured on the per-clone pool values, the signed gate report attests numbers that a consumer running one engine over the 100,000-household file won't reproduce. For the sized release the file is small enough for one engine, so I'd re-run the release-blocking fit gates on the released rows under a single engine, or keep the pre-flight at one block until the cause is found. If the incumbent-surface evaluation already gates local target fit on the shipped file, please say so in the runbook and I'd drop this to a nit. Either way, the representation record should say what "exact" covers.

  3. should: a selection L2 penalty slips past the size doctrine. size_doctrine_drift (rowwise_cli.py:512) checks households, π_hi, floor, refit L2 and seed, but not selection_l2 or --resume-size-checkpoint. I parsed --dataset-households 100000 with --selection-l2-lambda 0.5 --selection-l2-anchor uniform --selection-l2-basis chi_square and the drift is empty. The same is true with --resume-size-checkpoint. So a sized release candidate with a selection penalty passes the CLI, and graph_terminal.py:1800 writes releasable: true. Only the pre-flight catches it, after a multi-hour build. Adding "selection L2 off" to uk_local_size_doctrine would make the CLI, the manifest and the pre-flight agree. changelog.d/1124-uk-size-stage-l2-wiring.added.md still says release candidates refuse every size flag, which is no longer true.

  4. should: the two SPI deferrals' exit condition won't clear them. Both entries say "until the gate counts independent draws". But your own collapsed figures show both columns still above the carrier floor, and still over the share threshold, when counted by independent record. So re-arming on independent draws would fail them again. They look like genuinely concentrated instruments, the same as charitable_investment_gifts. I'd word the reason that way, and add a tracking issue for the re-arm so the 2027-01-14 expiry has an owner. On the same theme, "fewer than 100 independent draws" in both reasons (qrf_tail_reviewed_exclusions.json:16, :26), the changelog and the commit message is a bound on a small cell. "Rare on the SPI tape" already says it. The slice size in the per-clone caveat in the PR body is also a microdata count, and "a slice" would do.

  5. should: the refit's closing iterate now ships. Under the 100,000-household path, the refit's weights are the released weights. The size receipt records refit_iterate_selection: closing_state, and your epochs note puts the closing state well above the refit's own best. I'd switch the refit to keep its best iterate, as the dense solve does, before the release build rather than as a follow-up.

  6. nit: changelog.d/1124-uk-target-weight-rules.added.md ends "the doctrine default stays grain_equal", which contradicts 1124-uk-local-doctrine.changed.md.

  7. nit: UKReviewedExclusion.applies_on (weighted_integrity.py:371) applies a line-scoped entry wherever the context declares no line. The spine gate contexts declare none. That's defensible for pre-clone evidence. But failing closed would be safer: either a scoped entry stands aside when no line is declared, or any context that loads a register with scoped entries must declare one.

What looks right:

  • The line-scoped exclusions are narrow (named columns, one line each), reasoned and dated: three months for the SPI entries, six for atomic_area_basis.
  • Removing the superseded columns is sound. The coherence stage's rows (spi_synthetic_support_channel) cover the stage-2 forest's target rows, and the restore copies from a one-to-one twin. The surface reads 10 of its 48 declared outputs as superseded (ssmg_reported isn't a QRF output), and a test pins the ten names.
  • The scorer change and the size receipt checks in the pre-flight look correct.

What I checked

  • Fresh worktree at 4f824b062, uv sync --all-packages --locked into a new venv. I read the full diff against main (65 files), the PR body, and every commit message on the branch and on evidence/uk-k25-h100000-2026-10-11.
  • Engine-free UK and shared tests for build, calibrate, data and diagnostics: 8,965 passed, 43 skipped, none failed. tools/ci_test_plan.py verify is ok. ruff check and ruff format --check are clean on the 46 changed Python files.
  • Compared e7ad75e97 with this head module by module. I read the round-1 staged manifest's parameters, solve.dataset_size and git_dirty fields.
  • Probed validate_cli_args and size_doctrine_drift with sized release-candidate arguments (item 3). Checked the coherence stage's declared rows and columns against the stage-2 QRF outputs (item 4 context).
  • Not checked: I couldn't run a build or the per-clone vs single-block comparison without the licensed data. I also didn't re-measure anything on the staged H5 this round.

🤖 Generated with Claude Code

@vahid-ahmadi

Copy link
Copy Markdown
Contributor

Thanks María. The port of #1163's rules and the size-stage L2 is clean, and the engine-free suites pass. Verdict: I'd hold the doctrine constants (K=25, 2,000 epochs, the nation-grain rule) until a build from this branch backs them, because they set what the 28 October constituency results stand on (#1241, #1131). Item 1 is the main one.

  1. The evidence build can't be traced to this branch. The staged candidate's rowwise_candidate_manifest.json records git_commit e7ad75e97a600ef9ed074320d1ce48d5e2b507b2, and GitHub has no commit with that SHA (the API returns 422 and git fetch refuses it). The same manifest's parameters.doctrine block still says clone_count 15, solve_epochs 1500 and target_weight_rule grain_equal. The new values came in as flags (target_weight_rule_override, n_clones 25, epochs 2000), so the build ran the old doctrine code with overrides, not these constants. Also, UKFullProblemKernel.implementation_hash now folds target_weights and local_rowwise into the problem key (graph_targets.py:533), so a rebuild from this branch won't reuse the staged artifacts anyway. The λ at local_doctrine.py:153, taken from Add UK target-weight rules, size-stage L2 and a selection experiment harness (#1124) #1163's loss ratio, has the same gap. The 60k grain_equal baseline from 2026-10-06 has no run id or SHA in the PR either. Could you rebuild from 4beafb3f1 (or the merge commit) and add the staged run id and H5 SHA to the PR?

  2. The ESS gain doesn't come from K. Both evidence columns are at K=25. The improvement between them comes with the new rule plus π_hi 0.5, floor 0.5 and the refit L2, which is a direct Kish-ESS control. The PR's only K=15 vs K=25 comparison is the dense line's weakest-seat support note (local_doctrine.py:115 and :120), and the two values there are almost the same. That's the line a release ships, since a release refuses --dataset-households. So K=25 buys the dense line very little at the weakest seat, for 1.6 million rows and a 24.7 GiB peak. A K=15 run at 100k with the same size defaults would show whether K matters for the sized build.

  3. Per-seat ESS counts clone rows, not survey households. uk_area_support_summary takes Kish ESS over rows (local_rowwise.py:2139 to :2180), so two clones of one household in the same seat count as two records. UK local: rerun the 2025 Autumn Budget constituency analysis on the local candidate to find data gaps and per-seat support before Budget day #1241 asks for support collapsed by source_household_id, and more clones per household means more of them land in the same seat. I measured it on the staged H5 (SHA 77456e20…cfcab, verified) by summing weights by source_household_id within constituency_code_oa:

    I'd like the support gate, or at least area_support.csv, to report source-collapsed ESS before the doctrine moves K up.

  4. No convergence or holdout evidence for 2,000 epochs or the new rule. The candidate's holdout.json is skipped ("Explicit development request; no holdout claim."), the manifest has no per-epoch loss, and the note at local_doctrine.py:104 says the loss "still moves" between 1,500 and 2,000 but gives no figure. Could you add the dense loss at 1,500 and 2,000, and one rotated holdout under nation_grain_family_equal_sqrt_count? Then we'd know the extra epochs fit held-out local rows, not just the training rows.

  5. The nation grain doesn't repeat any row, but it does weight the same information twice. Each row is counted once. But _rule_grains (target_weights.py:335 to :353) splits national rows by geography, and the family budget at target_weights.py:385 is then set per grain. From the candidate's own target_loss_weight_receipt:

    • five families sit in both grains (ons_population, hmrc_cgt, dwp_state_pension, dwp_pension_credit, dwp_universal_credit). Each gets 1/88 + 1/56 of the loss, and the nation rows are sub-aggregates of the UK rows beside them;
    • singleton families get a full 1/56 (1.79 %) on one row. Examples are Scottish Child Payment spending, slc.borrowers.plan_5_above_threshold and the single nation-level UC row. That's about 150 times the largest constituency row weight (1.16e-4), and more than the whole 95-row UK UC family (1.14 %);
    • England-only families (SLC repayments, DfE funded childcare, Attendance Allowance) fall into nation_region only because their geography is England;
    • local grains drop from two thirds of the loss to a half.

    If that's intended, a sentence in the rule's docstring would cover it. If not, assigning each family to one grain would avoid the double budget.

  6. Two smaller things.

    • The preflight now pins K=25 (tools/preflight_uk_local_release_candidate.py:102) and still requires one engine block (:161), which the PR says hasn't been measured at 1.6 million households. Until that's settled, no dense release candidate can pass preflight. That matters if UK: two-page note on how constituency impacts are estimated (draft for the Budget-day analysis) #1131 picks Microcosm for Budget day.
    • Some signed rationales still say "at the ruled K=15": local_area_support_exclusions.json:6 and :13, uk_local_target_census.json:860, and local_target_reference_membership.json:5569. The two exclusions are re-measured on every candidate. The band-H deferrals aren't, so they should be re-checked at K=25.

What I checked

  • Fresh worktree at the PR head 4beafb3f1, uv sync --all-packages --locked into a new venv. I read the full diff (42 files) and the PR body.
  • Engine-free UK and shared tests from the four touched packages (build, calibrate, diagnostics, data): 315 files, 8,424 passed, 38 skipped, none failed. tools/ci_test_plan.py verify is ok.
  • From staged/uk-local-candidate-f100-s42-20261011T200435Z-108c1225, I downloaded the manifest, area_support.csv, holdout.json, targets.csv, sha256sums.txt and the H5. The H5 SHA matches sha256sums.txt and UK local: rerun the 2025 Autumn Budget constituency analysis on the local candidate to find data gaps and per-seat support before Budget day #1241.
  • Traced the rule through prepare_uk_full_solve, the rotated holdout (training folds recompute the rule, and held rows are weighted over local grains only) and the size-stage seam. I also checked that the uniform-anchor chi-square penalty equals n/ESS − 1, which matches the candidate's recorded penalty.

…release path, line-scoped exclusions and the QRF tail surface

Part of microcosm#1124; rulings of 2026-10-13 and 2026-10-14.

Doctrine (uk_runtime/local_doctrine.py):
- K stays 15; 2,000 epochs and nation_grain_family_equal_sqrt_count. The
  notes cite the uk-dataset-evaluation reports and receipts, not seat-level
  figures.
- Sized builds default to an exact-count draw at pi_hi 0.5, a refit
  baseline floor of 0.5 and a chi-square refit L2 toward uniform weights at
  lambda 0.0216.
- Ports #1163's row-metadata target-weight rules and the size-stage L2
  penalties, without its experiment harness. The nation-grain rule's
  double family budget is declared in its docstring.

100,000-household release path:
- A release candidate may be sized at the size doctrine
  (uk_local_size_doctrine): 100,000 households, those settings, no
  selection L2, the solve seed and a fresh selection.
  rowwise_cli.size_doctrine_drift names any departure.
- Such a build is releasable on the dense verdict, with no matched-size
  scorecard.
- Each holdout fold's budget search starts from the main search's initial
  lambda.
- The pre-flight checks the size receipt, the size doctrine, no selection
  penalty and no resumed selection.
- The refit keeps its best epoch on the objective it minimizes
  (calibrate/refit_l0_selection retain_best_objective, off by default).

Gates:
- Reviewed exclusions may name release_lines. The full build declares
  local; the national gate kernel and the release-cut certifier declare
  national. A scoped entry stands aside off its lines and where no line is
  declared.
- Local-line exclusion for household.atomic_area_basis.
- National-only entries for charitable_investment_gifts,
  hmrc_spi_other_income and hmrc_spi_taxable_termination_pay. The last
  two are concentrated instruments, time-boxed to 2027-01-14; their re-arm
  is microcosm#1277.
- The QRF tail surface drops the report columns spi_benefit_coherence
  replaces on every SPI-channel row, read from the stage's declaration.
- The pre-flight accepts one engine block per clone when recorded exact.
  The record's exact_scope says it covers the weight-share scaling, not
  position-dependent engine draws. The runbook says the incumbent-surface
  evaluation re-gates the shipped file's fit on one engine.

Also:
- The release scorer reads a graph build's target selection and pins the
  #1123 compiled local reference count.
- The dwp.uc.households_housing_element measure exclusion is renewed to
  2026-11-14.
- The telemetry startup ceiling is 15 seconds.
- A candidate-only --inspection-export.
- The synthetic graph test assembles a sized release candidate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22
juaristi22 force-pushed the uk-local-doctrine-k25-ngfes branch from 4f824b0 to 5fbf114 Compare October 14, 2026 14:36
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Re-posted on 2026-10-14 without the seat-level support figures and the slice size the first version carried (#1241); otherwise unchanged from 13 October.

Thanks Vahid, this was a careful review and it changed the PR. In short: K stays 15, the evidence build will come from this branch with no overrides, and the PR now also carries a release path for the 100,000-household file and three gate-policy fixes. The description is rewritten to match. Point by point:

1. Traceability. Agreed. The 2026-10-11 candidate ran from a local branch (e7ad75e97: #1163's port plus the build fixes now in this PR), with K, epochs and the rule passed as flags over the old doctrine constants. The next evidence build runs from this PR's pushed head, or its merge commit, with no solve overrides. Its manifest's parameters.doctrine block will then carry the values, including the λ. We'll post its staged run id and H5 SHA here. The 60k grain_equal baseline of 2026-10-06 is run tree uk-local-k25-8edd82066 (graph attempt 8d18d3f1…, code 8edd82066 + c1d4f8cf4 on main 45d1ae39). The problem-key change in graph_targets.py means nothing will be reused from the staged artifacts anyway.

2. K. You're right, and K stays 15. On the dense pools in the uk-dataset-evaluation reports, more clones did not lift the weakest seat. The sized file's gain came from the size settings and the size. A K=15 / 100,000-household build will show whether that holds without the larger pool.

3. Source-collapsed ESS. Agreed that row ESS overstates support when clones share a seat. The K=15 build's report will give ESS collapsed by source household per seat beside the row figure, and the distinct-source count. Putting it in area_support.csv, and deciding whether the support gate should read it, is a follow-up; it isn't in this PR.

4. Convergence and holdout. The 2026-10-11 build stores every stage's loss trajectory, and the epochs note now cites them. The losses swing about ±10 % from epoch to epoch.

  • Dense solve (keeps its best iterate): its best loss by epoch 1,500 is 3.5 % above its best by 2,000.
  • Size search: still falling at 2,000. It is 7.5 % above at 1,500.
  • Refit: within 0.2 % of its best by epoch 500, but it returns its closing iterate, about 11 % above its own best. That's a candidate follow-up.

So 2,000 stands for now. The K=15 build reports the same per-stage figures, and the release-candidate run measures the rule with the 5-fold rotated holdout.

5. Nation grain. Thanks for spelling out the double budget. The rule stays unchanged for the K=15 build so that K is the only change against the K=25 run. Whether to document the double budget as intended or assign each family to one grain is still open, and will follow that build.

6. Pre-flight and signed rationales.

  • The pre-flight now accepts an engine resolved per clone when the build recorded the pool representation exact; the incumbent-surface evaluation still runs one engine over the released file. One caveat, measured with the real engine on a slice of the K=25 pool: per-clone and single-block resolution agree on every household column and constituency metric. About 0.1–0.3 % of person records differ (state pension type, pension credit, SLC repayment), which moves 78 of 1,196 national rows on that slice. So the "exact" record covers the weight scaling, not the engine's draws. Finding the cause is a follow-up.
  • With K back at 15, the "at the ruled K=15" rationales stand as written.

New in the PR since your review:

  • A release path for the 100,000-household file (our ruling of 2026-10-13: the local release ships that file under the dense line's requirements). A release candidate may request --dataset-households 100000 only with the size doctrine; any other size stays candidate-only. Such a build is releasable on the same verdict as the dense line, with no matched-size scorecard. Each holdout fold's size search starts from the main search's λ, and the pre-flight checks the size receipt.
  • Line-scoped exclusions. A reviewed exclusion can name the release lines whose evidence earned it. The charitable_investment_gifts QRF-tail entry is now national-only: on the clone pool it read as stale, because the gate counts clone rows. household.atomic_area_basis, which is constant by construction, gets a local-line degenerate exclusion.
  • The dense runbook passes the national line's licensed input-mass reference.

Plan:

  1. An f1 smoke of the release path on licensed data is running now: K=15, 1,000 households, per-clone engine, holdout on, then the evaluation, pre-flight and an assembly attempt. A synthetic sized release candidate will also be built and assembled in the test suite.
  2. Merge, then the K=15 / 100,000-household release candidate from the merge commit, with the holdout, the input-mass reference and the measure-exclusion renewals. dwp.uc.households_housing_element lapses on 2026-10-15 and others follow in October and November.
  3. A comparison report against the K=25 candidate and the incumbent, covering items 2–5, posted here with the run id and SHA.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Re-posted on 2026-10-14 without the record counts and the small-count bound the first version carried (#1241); otherwise unchanged.

@vahid-ahmadi, a follow-up on point 1 and on what has landed in the PR since my last reply.

The K=25 / 100,000-household candidate is now traceable. e7ad75e97 is pushed to evidence/uk-k25-h100000-2026-10-11. The branch sits on main as of 2026-10-10 and carries six commits: #1163's rules and size-stage L2, the telemetry startup window, two evidence-bound fixes since superseded by #1188, and the inspection export. As you found, the doctrine values came in as flags over the old constants.

  • Staged run: staged/uk-local-candidate-f100-s42-20261011T200435Z-108c1225 in policyengine/populace-uk-private
  • H5: microcosm_uk_2024_25_local.h5, sha256 77456e20bc70d8d9116e47559a44de1226e942b68a3a8ad84dc79f5ddfacfcab

The next evidence build comes from this PR's merge commit with no solve overrides, and we'll post its run id and SHA here.

Since my last reply the PR has main merged in, including #1257. We also ran an f1 smoke of the release path on licensed data, with a K=15 dense build, the 5-fold holdout, the incumbent evaluation, the pre-flight and an assembly attempt. It found two problems on main that would have blocked any release:

  1. uk_ledger_compile_parity_production_2023 failed at main's Chronicle pin. Carry the compile's unresolved references to the UK compile-parity gates, and retire the 2023 gate #1257 fixes it.
  2. The release scorer could not score a graph-built candidate. It read only a registry artifact, while a graph build records its registry inside target_selection.json. Its frozen count, 20,885, also predated the UK local build (K=25, 2026-10-06): data-quality defects on the target surface and the spine #1123 surface. It now scores the target selection's local rows and pins 21,804, UK local build (K=25, 2026-10-06): data-quality defects on the target surface and the spine #1123's compiled local reference count. Assembly needs the score receipt.

Everything else in the release path behaved as intended on licensed data. A 1 % sample can't be a release candidate, and its diagnostics drop unreachable cells, so the first complete assembly on licensed data will be the release run. The synthetic graph test now assembles both a whole-pool and a 100,000-household release candidate end to end, and the data contract validates both release directories.

Two more changes:

  • The QRF tail surface, after a national certification failure. spi_benefit_coherence (UK: port the unblocked uk-data fixes tracked in #1095 #1100) zeroes the SPI rows' income-tested legacy reports and restores IIDB, AFCS and bereavement support from the FRS twin, so those ten columns hold no forest draw and the Build M live default: non_sch_d_capital_gains lands at $74.6B vs its $10.2B SOI target (+635%); Schedule-D CGD route is empty #462 broadcast cannot occur in them. The surface now drops them, reading them from the stage's declared operations.
    • hmrc_spi_other_income and hmrc_spi_taxable_termination_pay stay on the surface as real stage-1 draws.
    • Both fail as concentrated instruments: their top 100 records carry nearly their whole mass whether counted by row or by independent record, and the CGT clones and SPI copies also take them past the carrier floor (the same row-counting arithmetic as your point 3).
    • They are deferred behind national-line exclusions to 2027-01-14, until the gate counts independent draws.
  • A renewal: dwp.uc.households_housing_element is renewed one month, to 2026-11-14. Its structural bias is unchanged, since the model has no other/unknown tenure.

The PR description is updated with all of this. After the merge: a K=15 / 100,000-household candidate for the comparison against K=25 (source-collapsed ESS and per-stage convergence included), then the release candidate with the holdout.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

@vahid-ahmadi, thanks for round 2. Everything is addressed on 5fbf1146f. The branch is now one commit on current main.

New findings

  1. Disclosure.

  2. Fit gates and the shipped file. The incumbent-surface evaluation already gates the shipped file. evaluate_uk_incumbent_surface.py re-measures the released H5 on one engine; the data contract refuses any other block count. Its assessment fails release on:

    • any national or local row more than 25 % from its target;
    • any family of five or more rows with fewer than half within 25 %.

    Signed deferrals and measure exclusions don't exempt a row. The runbook now says this beside the evaluation step. The measures record also carries engine_population_representation.exact_scope: the weight-share scaling, and not engine draws that depend on a record's position.

  3. The size doctrine.

    • uk_local_size_doctrine now includes "selection L2 off".
    • size_doctrine_drift names a selection penalty and a resumed size checkpoint, so the CLI refuses both for a release candidate and the manifest can't write releasable: true past them.
    • The pre-flight also refuses a resumed selection.
    • 1124-uk-size-stage-l2-wiring.added.md now says what a release candidate may carry.
  4. The SPI deferrals.

    • Both reasons now describe concentrated instruments, rare on the tape, whose top 100 records carry nearly their whole mass whether counted by row or by independent record.
    • "Fewer than 100 independent draws" is gone from the reasons, the changelog and the commit message.
    • The re-arm on independent draws, and these entries' renewal or retirement before 2027-01-14, is Re-arm the UK QRF tail-concentration gate on independent draws #1277, assigned to me.
  5. The refit's closing iterate. The refit now keeps its best epoch on the objective it minimizes: target loss plus the weighted L2 penalty. calibrate and refit_l0_selection gain retain_best_objective, off by default so other solves are unchanged. The UK refit turns it on, and its options record iterate_selection: best_feasible_objective. Tests cover:

    • the opt-in;
    • a never-worse selected objective on the same trajectory;
    • no change without an L2 penalty.
  6. Changelog. 1124-uk-target-weight-rules.added.md now names the doctrine's rule, and so does 1124-uk-problem-kernel-identity.fixed.md, which had the same stale sentence.

  7. applies_on. Fixed: it now fails closed. A line-scoped entry applies only where the context declares one of its lines, so it stands aside wherever none is declared. Unscoped entries are unchanged.

Round-1 points

  • 1 (traceability): closes when the no-override K=15 build's run id and H5 SHA are posted here.
  • 3 (source-collapsed support): agreed that the same row-counting problem reaches the support gate on a selection from a clone pool. The K=15 comparison will report support collapsed by source household, with seat-level figures shared privately. Changing what the gate counts stays with UK local: rerun the 2025 Autumn Budget constituency analysis on the local candidate to find data gaps and per-seat support before Budget day #1241.
  • 4 (holdout): covered by the release candidate's 5-fold holdout, which the pre-flight requires.
  • 5 (nation-grain double budget): declared in the rule's docstring, as you suggested: known, kept for now so that K is the only change in the comparison, with one grain per family named as the alternative.

Tests: targeted tests over the touched modules pass on the rebased commit, covering the solver, the size stages, the CLI, the pre-flight, the measures, the gates, the registers, the scorer, the assembler and the contract pins. ruff and tools/ci_test_plan.py verify are clean.

The US spec engine's seed protocol attests microcosm.calibrate.solve's
source bytes, so adding the opt-in retain_best_objective moves the protocol
digest and the compiled seed map that embeds it. No US solve uses the
option, so no RNG rule, solve or QRF target changes. The committed
coverage report (docs/evidence/spec-engine/us-f0-coverage.json) is
regenerated by tools/spec_engine_coverage.py.

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

Copy link
Copy Markdown
Contributor

@juaristi22, thanks for the reply and the squash. This is my round-3 pass on head 5f85ebdcb: 5fbf1146f (the squashed PR) plus 5f85ebdcb, which re-pins the US seed protocol and seed map digests in inventory_coverage.py and regenerates us-f0-coverage.json, because the protocol hashes calibrate/solve.py and that file gained retain_best_objective. That second commit is the expected consequence and changes nothing else.

Fine once the spec-engine golden hash is re-pinned (new item 1), CI is green and the description's older revisions are deleted. The code side of every round-2 point is done.

Round-2 points

  1. Disclosure: fixed in the PR's content; two leftovers outside it.
    • local_doctrine.py (both the First calibrated rowwise UK local candidate (#495 increment 6) #762 and UK sparse selection: keep household mass, composition, nation shares and per-area ESS at a tractable size — test the target-weight rule and the L2 penalty first, dataset size only if they fall short #1124 clone-count notes), the PR description, both exclusion reasons, the changelog and both commit messages on the branch are clean. I searched the full diff, the description and every comment in this thread for effective sample sizes, seat or area counts, source record counts, minima and medians, and place names or codes. The only constituency codes are synthetic fixtures in test_uk_target_weight_wiring.py.
    • Your 13 and 14 October comments are gone (the API returns 404 for both), and the re-posts are clean.
    • Left: the description's edit history. GitHub still shows the earlier revisions in the edit history (from 12 October to 13:28 UTC on 14 October), and none is deleted. They carry the figures this round removed: the K table, the seat count under the floor, named local authorities and the slice size. You said you'd delete them. That's the one thing I'd still want before merge.
    • Left, and not fixable from the PR: the replaced commits (de426731, 4beafb3f, 4f824b062) are no longer on any branch, but GitHub still serves them by SHA, and the force-push entries in the timeline link to them. They can't reach main now. Purging them needs a GitHub support request. Whether that's worth doing is your call.
  2. Fit gates vs the shipped file: addressed, now a nit. evaluate_uk_incumbent_surface.py only accepts --engine-blocks 1. uk_incumbent_surface_assessment (contract.py:6763) fails release on any row more than 25 % out, or a family of five or more rows with under half within 25 %, with no exemption for deferrals. The runbook says so beside the evaluation step, and exact_scope (full_measure.py:285 to :297) says what "exact" covers. As promised, I'm dropping this to a nit. What's left: that backstop runs on the incumbent's surface at 25 %, so the signed uk_target_fit and uk_local_target_fit values are still per-clone pool figures. A line in those gates' details naming the engine block count would tell a reader which numbers they are.
  3. Size doctrine: fixed. size_doctrine_drift (rowwise_cli.py:512 to :542) now names a selection penalty and a resumed checkpoint. I re-ran my round-2 probe: both now drift, and validate_cli_args refuses both for a sized release candidate. graph_terminal.py:1792 gates releasable on no drift. The pre-flight refuses a recorded resume_size_checkpoint (:155), and the manifest records that field (rowwise_cli.py:282). The changelog is corrected.
  4. SPI deferrals: fixed. Both reasons now describe concentrated instruments, the small-count bound is gone everywhere, and Re-arm the UK QRF tail-concentration gate on independent draws #1277 is open and assigned to you. The last sentence of each reason still says "until the gate is re-armed on independent draws", which reads as if re-arming would clear them. Since Re-arm the UK QRF tail-concentration gate on independent draws #1277 covers renewal or retirement, that's fine as it is.
  5. Refit iterate: fixed. _optimize (solve.py:1137 to :1185) tracks target loss plus the weighted L2 term when retain_best_objective is on. The closing-state comparison (:1249) uses the same objective. The L0 term is zero on this path, because retention needs no gates. The UK refit turns it on (dataset_size.py:756), and the options and receipt record best_feasible_objective. Default solves are unchanged.
  6. Stale grain_equal changelog lines: fixed in both files.
  7. applies_on: fixed. It now fails closed (weighted_integrity.py:373 to :375). I checked that every context that runs the terminal QRF-tail gate declares a line: the full build declares local (full_gates.py:296), and the national kernel (graph_national.py:946) and the certifier (release_certification.py:355) declare national. So the national-scoped entries still apply where they were earned. The spine contexts declare none, but the gate runs only in the terminal phase.

Round-1 points

New findings

  1. must: one engine-free test fails at this head. test_spec_engine_loader.py::test_semantic_hash_has_golden_vector_and_surface_separation (packages/microcosm-build/tests/engine_free/shared/test_spec_engine_loader.py:14) pins the bundle's spec_sha256, and the hash no longer matches. It passes at the merge base 65d7d153. I swapped files in one at a time on the base, and the calibrate/solve.py change alone is enough to break it. So it's the same cause as your second commit: the semantic hash covers solve.py's bytes, and this golden vector was missed in the re-pin. Re-pinning it with a comment like the one in inventory_coverage.py should be all it needs. I'd expect the engine-free CI job to catch it too.
  2. nit (pre-existing, not in this diff): the uk_qrf_tail_concentration entry's notes in uk/gates.json still carries small-cell carrier counts per column and a source person-row count. Two of the columns with counts are ones this PR scrubbed from the exclusion register (charitable_investment_gifts, hmrc_spi_taxable_termination_pay). It's on main already, so a follow-up is fine. But it undoes the register clean-up for anyone who reads the gate spec.

Nothing else new in the code. The squash brings in no change beyond the round-2 fixes and the digest re-pins those fixes force (the battery policy and manifest pins in contract.py move with the registers).

What I checked

  • Fresh worktree at 5f85ebdcb and uv sync --all-packages --locked into a new venv. I read the diff from 4f824b062 to this head on the PR's files, both commit messages, the PR description and its revision history, and every comment in the thread.
  • Engine-free tests for build (UK and shared), calibrate, data (UK and shared) and diagnostics: 8,979 passed, 43 skipped, 1 failed (new item 1, checked against the merge base). ruff check and ruff format --check are clean on the 50 changed Python files, and tools/ci_test_plan.py verify is ok.
  • Re-ran the round-2 size_doctrine_drift and validate_cli_args probes. Traced which gate contexts declare release_line, and read the incumbent-surface assessment.
  • Not checked: no build, and no per-clone vs single-block comparison, because both need the licensed data. CI was still running when I wrote this.

🤖 Generated with Claude Code

…the solver option

Two more pins hash microcosm.calibrate.solve's source, so the opt-in
retain_best_objective moves them as it moved the US seed digests:

- the spec engine loader's golden spec_sha256, whose seed protocol attests
  solve.py's bytes;
- the calibrate H1 parity case's implementation hash and node key,
  re-recorded on arm64/darwin/py3.14 (the case's one pinned platform) with
  tools/graph_parity_repin.py under the locked dependency versions.

The calibrate kernel's outputs are unchanged; only the hashes move.

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

Copy link
Copy Markdown
Collaborator Author

@vahid-ahmadi, thanks for round 3.

New item 1 (the golden hash): fixed in b586a5447. The engine-free CI job had stopped on a second pin with the same cause first. That was test_graph_parity_pins.py[calibrate]: the calibrate H1 parity case's implementation hash and node key hash solve.py too. Both are re-pinned in that commit:

  • the loader's golden spec_sha256, with a comment like the one in inventory_coverage.py;
  • the calibrate parity case, re-recorded with tools/graph_parity_repin.py on arm64/darwin/py3.14, the case's one pinned platform, under the locked dependency versions. The kernel's outputs are unchanged.

No other file in the repo carries the old calibrate implementation hash, the old spec_sha256 or the old seed digests. The parity and loader tests pass on Python 3.13 and 3.14.

Disclosure leftovers.

  • The description's edit history: I'll delete the earlier revisions before merge.
  • The replaced commits: they're unreachable from every branch. Whether to ask GitHub Support to purge them is a separate decision.

Nits. Both will be follow-ups rather than part of this PR:

  • Fit-gate details: uk_target_fit and uk_local_target_fit should record the engine block count their values were resolved under, so a reader knows they are per-clone pool figures.
  • uk/gates.json: the uk_qrf_tail_concentration notes still carry small-cell carrier counts. That's on main already, and changing them moves the gate-manifest pins.

Round-1 point 1 still closes with the no-override K=15 build's run id and H5 SHA, posted here after merge.

The US bundle's semantic hash covers microcosm.calibrate.solve's bytes
through its seed protocol, so the opt-in retain_best_objective moves the
spec_sha256 the constants adapter binds. The new value is the live
load_bundle("us") hash, which the regenerated us-f0-coverage.json already
records. No other file carries a hash the change moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22
juaristi22 merged commit 1cfbf7d into main Oct 14, 2026
10 checks passed
juaristi22 added a commit that referenced this pull request Oct 14, 2026
María's ruling of 2026-10-14 on Vahid's review of microcosm#1271
(item 4). Once dependants in approved training qualify as young people
(the TRAINEE fix, uk-data#561), 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 of five and of six or more.
The row passed the fit gate before only through the dependants the
missing flag miscounted. The calibration cannot meet both counts, and
the two-child-limit rows carry six times the loss weight, so
dwp.uc.households_children_5_or_more fails uk_target_fit and a blocked
calibration writes no release.

The row is held out with a reason, the tracking issue and the
sparse-band expiry (2026-11-26), and the two-child-limit rows stay
bound. The packaged-exclusions test pins the entry and its ruling, and
tells it apart from main's housing-element renewal of the same date
(#1242) by tracking issue; the R2 changelog fragment says what the fix
leaves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22 added a commit that referenced this pull request Oct 14, 2026
María's ruling of 2026-10-14 on Vahid's review of microcosm#1271
(item 4). Once dependants in approved training qualify as young people
(the TRAINEE fix, uk-data#561), 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 of five and of six or more.
The row passed the fit gate before only through the dependants the
missing flag miscounted. The calibration cannot meet both counts, and
the two-child-limit rows carry six times the loss weight, so
dwp.uc.households_children_5_or_more fails uk_target_fit and a blocked
calibration writes no release.

The row is held out with a reason, the tracking issue and the
sparse-band expiry (2026-11-26), and the two-child-limit rows stay
bound. The packaged-exclusions test pins the entry and its ruling, and
tells it apart from main's housing-element renewal of the same date
(#1242) by tracking issue; the R2 changelog fragment says what the fix
leaves.

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

Copy link
Copy Markdown
Collaborator Author

@vahid-ahmadi, the no-override K=15 build from main is done. It answers round-1 points 1 to 3, including your question on whether K is the lever. The internal report is k15-95k-2026-10-15 on uk-dataset-evaluation; seat-level figures stay there.

The build (point 1)

  • Run uk-local-candidate-f100-s42-20261015T082730Z-68448178, from main 121fd4dd6.
  • H5 SHA-256 b075d3ac455b45270ae1b1ace0b07250f72bbaf56dce433cd92b3a86a9616a84, staged at policyengine/populace-uk-private under staged/uk-local-candidate-f100-s42-20261015T082730Z-68448178.
  • K, epochs, the rule, π_hi, the floor and the refit L2 all came from the doctrine, with no solve overrides. The engine ran one block per clone, recorded exact.
  • Departures: 95,000 households instead of 100,000 (below), a warm start, --inspection-export and --skip-holdout.

Why 95,000. The size search's lowest penalty is 1e-7, the floor of the L0 search range in microcosm.calibrate. At K=15 that penalty leaves the open mass about 5 % short of 100,000. With no lower penalty to try, the bracket closes on the floor and the search re-runs the same probe until its budget runs out. So the 100,000-household release build needs a lower search floor, or a ruling on the K=15 size. 95,000 is feasible at 1e-7, and the search stopped on its first probe.

Gates. Two structural failures:

The two local gates fail as they did on the K=25 build. uk_degenerate_release_surface and uk_input_mass_parity now pass.

Is K the lever? (point 2)

Not for the sized line. Against the K=25 100,000-household build, at the same size defaults:

  • Median constituency ESS is about 12 % lower by rows and about 8 % lower collapsed by source household, with 5 % fewer households (3 % fewer per seat).
  • Most of the row-counted gap is weight evenness within seats (ESS per household about 8 % lower), not missing records.
  • The K=15 file draws on about 3 % more distinct survey households, and its whole-file ESS collapsed by source household is about 8 % higher. More clones put more copies of one survey household into a seat; row-counted ESS rewards that, and collapsed ESS doesn't.

K does move the dense pool: its median constituency ESS is about 40 % lower at K=15 (about 30 % collapsed). The sized file doesn't inherit that. The refit's floor and uniform L2 even the weights out, so the K=15 file has more per-seat support than its own dense pool.

The earlier sized builds agree. At matching refit settings, the K=15 55k and K=25 60k files are within a few percent of each other on median constituency ESS, and at the 0.001 floor the K=20 55k file sat below the K=15 one. The large steps came with the household count and the refit settings.

So per-seat support in a sized file follows k and the refit settings. Between 15 and 25 clones, K buys the dense line support and the sized line none.

Spine and targets

These show up in fit, not support:

  • National rows past 25 %: 21 of 1,167 at K=25, 12 of 1,214 here.
  • Constituency rows within 10 %: 98.5 % to 99.7 %.
  • Local-authority rows within 10 %: 99.3 % to 98.9 %.

This build also moved the spine (#1256's SPI draw), the Chronicle feed (825406f to 942a1fa, a larger target surface) and the code (#1242's refit iterate), so I can't attribute the gain to any one of them. Rebuilding K=25 at 95,000 households on this main would isolate K. It costs one dense solve and a search, if we want it.

Source-collapsed support (point 3)

I measured it on both files and both pools, summing weights by source household within each area. Collapsing costs the K=25 file about a fifth of its median constituency ESS, as you found, and the K=15 file about a seventh. The internal report now shows collapsed ESS beside the row-counted figures. Making the gate count source households stays with #1241.

Also: the Orrery graph export failed on this build, because the execution evidence exceeds the graph metadata bound (#1290).

@juaristi22

Copy link
Copy Markdown
Collaborator Author

On item 4: the release-candidate run will no longer include the 5-fold rotated holdout. #1301 makes it an opt-in diagnostic (#1124 ruling of 2026-10-16). Its value is never compared with a threshold or with the incumbent, and on the sized path it adds about 28–42 h to a release build. The release manifest will say no_out_of_sample_claim. If you want an out-of-sample read on the 150k candidate, we can run it as a diagnostic after publication, or run two folds.

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