Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

UK local-area release: publish a K=15, 150,000-household dense dataset in at most two builds

Abierto
#1,301 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@juaristi22 ya está trabajando en esto.

Desde el 16/10/2026.

  • #1307 de @juaristi22 — abierto

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
12/100
Tipo de issue
Nueva funcionalidad
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
python

Línea de trabajo

This is release-planning work across several subsystems, so it is not a first task. Start by reading the doctrine constants in uk_runtime/local_doctrine.py and the L0 search in packages/microcosm-calibrate/src/microcosm/calibrate/solve.py (_search_l0_lambda_for_budget). Done means the pinned 100,000-household tests are updated and the open design decisions are settled by maintainers.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Summary

Goal: publish a UK local-area (dense line) dataset before the Budget on 28 October, with at most two builds:

  • Build 1 measures the candidate and produces the rows to sign.
  • Build 2 publishes.
  • If build 1 passes every gate, it is the release.

This issue lists the remaining work, apart from spine fixes. It covers the doctrine change, the two requirements we drop, the code the two-build flow needs, and every certification step with its status.

Code references are at main d61bce076. Paths under uk_runtime/, uk/ and staging_v2.py are relative to packages/microcosm-build/src/microcosm/build/. Figures are aggregates only: no per-area support figures, no single-record values, and no counts under 10.

What blocks a release today:

  • Four gates failed on the last K=15 build. That build had 95,000 households on 121fd4dd6 and was a development run, not a release candidate.
    • uk_target_fit: 12 national rows past 25%.
    • uk_local_target_fit: 14 local cells.
    • uk_local_area_support: the two island authorities, Orkney and Shetland.
    • uk_qrf_tail_concentration: since removed by #1281.
  • No dense candidate has ever passed the incumbent-surface assessment. The 95k one had 5,087 failures. 2,961 of them are rows the comparison cannot measure (not_ported: 43 national, 2,918 local). The evaluation peaked at 32.8 GB.
  • A release candidate must run the 5-fold rotated holdout. On a sized build each fold re-runs the dense solve, the size search and the refit, which adds about 28–42 h. No sized build has ever run it.
  • The size doctrine asks for 100,000 households, which K=15 cannot reach. At the L0 search floor (λ = 1e-7) the open mass is about 94,500. The 100k attempt then spent its third probe re-running λ = 1e-7.
  • Signing off after build 1 forces a rebuild today.
    • Local cells have no register.
    • An area-support exclusion defers the area's own cells, which changes the solve.
    • Any register edit changes the country-spec fingerprint. That re-keys target compilation, the preflight gate and the holdout, so everything from target compilation down is rebuilt. In a build that runs the spine in-graph, the spine gates are re-keyed too.
  • The shipped gate report needs disclosure control. The signed uk_local_gates.json that a release ships carries per-area support figures for every reviewed area exclusion. It is HMAC-signed, so it can't be redacted after the build.
  • Build 1 will be the first K=15 measurement of the current surface. Since 121fd4dd6, main has added #1271, #1270 (high-value homes, 2,673 local cells), #1283 (the #1278 surface) and #1286 (DESNZ energy, 1,040 cells). None of these has been measured at K=15.

1. Doctrine: K=15, 150,000 households, a lower L0 floor

The 13 October ruling on #1124 changes as follows:

  • K stays at 15 clones.
  • The request becomes 150,000 households.
  • The refit settings stay: π_hi 0.5, Horvitz–Thompson floor 0.5, refit L2 λ 0.0216 (uniform anchor, chi-square). With a uniform anchor the penalty equals n/ESS − 1, so its λ means the same at any size.
  • 2,000 epochs and the nation-grain family-equal √count rule stay.

Where λ should land. The 100k attempt measured an open mass of 77,115 at λ = 1.9e-7 and 94,526 at 1e-7. Extrapolating gives about 1.3e-8 (log-linear) to 2.3e-8 (power law). The larger target surface and the warm-start aim push it up, so the plausible range is 1.3e-8 to 5e-8. A warm start at 2.5e-8 with ±0.5-decade edges covers that range. Expect 3–4 probes at 1.7–2.8 h each; the 100k probes took about 1.7 h.

Doctrine and pins:

  • uk_runtime/local_doctrine.py:
    • Set UK_LOCAL_DATASET_HOUSEHOLDS = 150_000.
    • Rewrite the ruling text above it: drop "rotated holdout … incumbent-surface evaluation on one engine" from the publication requirements, and cite the new ruling.
    • Fix the "K=15 / 100,000-household" sentence on UK_LOCAL_CLONE_COUNT.
  • tools/preflight_uk_local_release_candidate.py: set SIZE_DOCTRINE["dataset_households"] to 150,000 and update the docstring. Add a test that SIZE_DOCTRINE equals uk_local_size_doctrine().
  • uk_runtime/rowwise_cli.py: size_doctrine_drift and its release-candidate refusal read the doctrine, so only the ruling references change. --n-clones off the doctrine is already refused.
  • Decide whether a dense --release-candidate must be sized. Today it may also be unsized (the whole pool), which the doctrine itself says is too large for consumers.
  • Update the tests that pin 100,000:
    • test_uk_rowwise_candidate.py
    • test_uk_local_release_preflight.py (add a case showing 100,000 is now refused)
    • test_uk_dense_release_assembler.py
    • test_uk_full_build_cli.py
    • add a pin in test_uk_local_doctrine.py
  • Docs: docs/uk-dense-release-assembly-runbook-762.md, docs/uk-dataset-size-plan-355.md and docs/uk-full-build-graph.md, plus a changelog fragment.

L0 search, in packages/microcosm-calibrate/src/microcosm/calibrate/solve.py (_search_l0_lambda_for_budget):

  • Lower floor. Make the search bracket a parameter of calibrate, and keep the default _L0_SEARCH_LO = 1e-7.
    • Thread the parameter through uk_runtime/dataset_size.select_uk_dataset_size and graph_calibration.
    • Set it from a doctrine constant, e.g. UK_LOCAL_L0_SEARCH_FLOOR = 1e-10. If select_uk_dataset_size applies the default itself, the holdout's fold searches (local_rowwise.py) inherit it.
    • Keeping the global default leaves alone the US contract, which records the bracket (tools/us_bundle_generation/contracts.py, budget_search), and the cold-start pins in test_informed_gates.py.
    • Record the bracket in the size receipt, and check it in the preflight.
  • Refuse an out-of-bracket warm start instead of clamping it, both in the solver and in dataset_size._check_initial_lambda. Today --selection-initial-lambda 2.5e-8 silently becomes 1e-7, while the receipt records 2.5e-8.
  • Stop wasting probes at the floor.
    • Never probe the same λ twice.
    • When the warm-start or edge probe sits on a bound and steers outward, stop with a named reason (search_floor_reached / bracket_exhausted) instead of bisecting a collapsed bracket.
    • Derive steered_larger from steer() instead of lo_u == first_u.
    • Report the stop reason in both receipts.
  • Optional: when the edge probe steers the same way as the warm-start probe, extrapolate through the two measured points instead of resetting to the global bound. This saves about two probes.
  • Tests with the stub optimizer (test_informed_gates.py):
    • A budget that needs λ near 10^-8.5 settles below 1e-7.
    • When even the floor gives too few households, the search stops in at most 3 probes and never repeats a λ. Same at the ceiling.
    • A replay of the 100k sequence has no duplicate probe.
    • An out-of-bracket warm start is refused.
    • A real-optimizer smoke test: open mass does not fall as λ goes from 1e-7 to 1e-10.
  • Measure the solver's eps in build 1.
    • Adam uses torch's default eps of 1e-8. Below λ = 1e-7 the gate penalty gradient (at most λ/4) gets close to it.
    • The search's secant step assumes the response is log-linear in λ, and that may bend.
    • Record every probe. A per-group gate_adam_eps is a follow-up only if the probes flatten.

These stay as they are at 150k: the exact-k draw, BUDGET_ITERS = 10, and the protected-carrier check.

Optional, decide before build 1: a new weighting rule. The proposed rule, nation_grain_family_equal_local_sqrt_count, is family-equal at national and region grains and √count only at local grains.

  • Why: part of the 12 national misses comes from the √count rule starving small national and region count rows.
  • Cost:
    • It changes the dense solve and has never been measured.
    • The refit λ is defined from the rule's loss ratio, so it has to be re-derived or re-ruled.
    • It must still pass the #1207 family loss-share floors (domestic energy 2.5%, bus journeys 1.25%), or the solve is refused.
  • Where it touches:
    • the cell loop in uk_runtime/target_weights.py;
    • the rule registry and default in local_doctrine.py;
    • the rule vocabulary in packages/microcosm-diagnostics/src/microcosm/diagnostics/schema.py;
    • the preflight (both the --env ruled tuple and check_candidate_dir);
    • about 10 rule-name test pins.
  • If we don't take it, the national rows are signed after build 1 instead.

2. Requirements removed or made opt-in

Rotated holdout: no longer required; an opt-in diagnostic

Nothing depends on the holdout's value:

  • No gate reads it.
  • releasable doesn't depend on it.
  • The scorer itself says it isn't comparable with the fitted loss.

Only the checks that require it to exist depend on it. Plan: keep the uk.full.holdout node, default it to the skip marker (the diagnostics schema already accepts it), and add a --rotated-holdout flag to run it.

  • uk_runtime/full_build_cli.py and rowwise_cli.py:
    • Replace --skip-holdout with --rotated-holdout, off by default for dense.
    • Delete the release-candidate refusal of the skip.
    • The national line's refusal flips to refusing --rotated-holdout.
  • uk_runtime/graph_terminal.py and country_adapter.py:
    • Default to skipped.
    • Skip reason: "Not requested: opt-in diagnostic; no out-of-sample claim."
    • Describe the node as optional.
  • uk_runtime/rowwise_staging.py and uk_runtime/size_evaluation.py: read the new flag and the skip marker.
  • Preflight: drop the parameters.skip_holdout refusal and accept the skip marker. When the holdout was run, still require 5 folds and a finite mean.
  • tools/score_uk_local_candidate.py: _candidate_holdout returns None when the holdout is skipped or absent, and the score records holdout_basis: "none_requested".
  • uk_runtime/full_certification.py: drop the five-fold readiness reason.
  • tools/assemble_uk_dense_release_dir.py:
    • When skipped, write a no_out_of_sample_claim limitation and coverage.holdout = {"run": false, "claim": "none"}.
    • When run, write the measured summary with claim: "report_only".
  • packages/microcosm-data/src/microcosm/data/contract.py:
    • Keep holdout among the coverage keys.
    • Validate it by claim: a finite mean only for report_only.
  • If the opt-in holdout should work at 150k, make sure the fold search in uk_runtime/local_rowwise.py gets the L0 floor, in the same PR. Editing that file re-keys the problem kernel, which is fine before build 1 but not between builds.
  • Tests:
    • preflight: a skipped holdout passes, and a malformed measured holdout fails;
    • certification, scorer, assembler and contract;
    • test_uk_rowwise_candidate.py;
    • test_uk_full_build_cli.py: a release candidate defaults to skipped.
  • Docs: the runbook, the size plan and docs/uk-full-build-graph.md.
  • Record the amendment on #1124. Tell Vahid on #1242: his item 4 was answered with "the release-candidate run measures the rule with the 5-fold rotated holdout".
Incumbent-surface assessment and scorer comparison: opt-in diagnostic

There are three incumbent files: score_vs_incumbent.json, incumbent_surface_evaluation.json and incumbent_manifest.json. A release attaches all three or none.

  • When attached, their identities and summary are still verified.

  • A failing assessment is recorded, not blocking.

  • contract.py: remove the three files from _UK_DENSE_REQUIRED_RELEASE_FILES. If any one is present, require all three, and require sha256sums.txt to cover them.

  • contract.py _check_uk_dense_surface_files: split it.

    • Today it returns early if any of its four files is missing. Making the incumbent files optional would therefore silently skip the candidate checks as well.
    • Checks that must run on every release:
      • dataset, diagnostics and gate-report hashes;
      • git_dirty and the commit;
      • measure-exclusion parity;
      • Ledger pins;
      • shipped-diagnostics equality;
      • the attempt binding.
    • Checks that run only when the incumbent files are attached:
      • the coverage snapshot;
      • the score-receipt hashes;
      • _check_uk_incumbent_surface_evaluation.
  • _check_uk_incumbent_surface_evaluation: add an enforce_limits argument.

    • The schema, identity, summary and pinned row-inventory checks stay blocking. Move the inventory check out of uk_incumbent_surface_assessment's failure list.
    • Limit failures and the single-block check become recorded.
    • full_certification.py calls this function too.
  • The coverage entry becomes incumbent: {"compared": false}, or {"compared": true, "snapshot": …}. _check_uk_dense_source_coverage stops requiring incumbent.snapshot unconditionally.

  • Assembler:

    • --incumbent-manifest becomes optional, and the incumbent joins run only when it is given.
    • Also make optional the presence checks for the score and surface files, the coverage incumbent block that reads the score, the score copy, and the evidence-file list.
    • An identity mismatch still refuses; limit failures are recorded.
    • Add a no_incumbent_comparison limitation when the files are absent.
  • full_certification.py: an absent scorecard is not_requested, never a readiness failure.

  • tools/evaluate_uk_dataset_size.py: make the 30-incumbent-score and 40-incumbent-surface steps opt-in. Step 40 runs whenever --ledger-facts is given, and it is the 14.1 min / 32.8 GB step.

  • Scorer: read the active reference count (25,309 today) from uk/local_target_reference_membership.json (active_reference_count) at run time instead of pinning UK_LOCAL_ACTIVE_REFERENCE_COUNT. The runbook still says 24,269.

  • Tests:

    • Split the contract fixture into a core bundle and a with-incumbent bundle.
    • The missing_file case becomes "valid without incumbent"; tampered attached evidence still refuses.
    • Assembler runs without --incumbent-manifest.
    • A failing attached assessment is recorded, not refused.
    • Certification flips to ready.
  • Update the runbook, the size plan and the incumbent labels in dataset_size.py.

These two changes move none of these pins: _UK_DENSE_GATE_DIGESTS (neither change is a gate), _UK_DENSE_SURFACE_LIMITS, _UK_DENSE_SURFACE_INVENTORIES, and the country-spec fingerprint. The local register in §3 does move the gate pins.

What the release manifest says instead

reviewed_limitations and uk_source_coverage.json record:

  • no_out_of_sample_claim: fit figures are in-sample on the calibrated surface, and the rotated holdout was not run.

  • no_incumbent_comparison. When the incumbent files are attached instead: incumbent_surface_diagnostic, with the pass/fail result and the failure count.

  • engine_measurement: fit is measured per clone on the pool and recorded as exact (exact_scope), not by one engine over the released file.

  • New: the reviewed exclusions the gates relied on. Today _reviewed_limitations lists area-support entries but not the signed target-fit rows or local cells. Names and reasons only, no errors. The national rows come from the full gate report in the candidate directory, which the release does not ship.

  • The size doctrine: K=15, 150,000 households, the selected λ and the search bracket.

  • Decide how the released file is checked on one engine. The only check of the released file on one engine today sits inside the incumbent check. Options:

    • (a) Accept per-clone measurement and record the engine_measurement limitation.
    • (b) Add a candidate-only, single-engine readback of the released 150k file against its own selected targets, reusing the candidate half of tools/evaluate_uk_incumbent_surface.py. This is new code, and memory at 150k is the risk.
  • Disclosure: no per-area support figures in shipped files.

    • The signed uk_local_gates.json carries per-area rows, ess and sources for each reviewed area-support exclusion (the area_support_gate details).
    • The report is HMAC-signed, so it cannot be redacted after the build.
    • Withhold the per-area figures, or bucket them below 10, in the gate details before build 1. This changes gate code.
    • Add a test that no shipped release file carries a per-area count under 10.

3. The two-build flow

Build 1 (measure) uses:

  • --release-candidate, --engine-blocks 15, --dataset-households 150000;
  • --selection-initial-lambda 2.5e-8;
  • an explicit --review-date (the default is today);
  • the same --input-mass-reference file in both builds;
  • a persistent --graph-store;
  • a clean checkout of the merged commit;
  • either a pinned --input-h5 spine checkpoint, or in-graph spine stages with the spine gate kernel scoped (see below).

A blocked release candidate still writes everything we need to sign before it refuses export: the full gate report, the signed *.local_gates.json, *.targets.csv, *.area_support.csv and the diagnostics.

  • If a structural gate fails (uk_target_fit, or any non-local release-blocking gate), it writes no H5.
  • If only the five local fit, support and weight gates fail, it still exports an unreleasable H5 and exits 1.
  • Optionally, allow --inspection-export alongside --release-candidate (with releasable: false). That gives an H5 to inspect, and it exercises the export, readback and package path at 150k before build 2, without moving any solve key. Today rowwise_cli refuses the combination.

Build 1 has to be a release candidate. release_candidate is a parameter of the preflight gate node, and that node's report feeds the dense solve. In an in-graph spine build it is also a parameter of the spine gates and of the spine stage after frs_brma. So a development build 1 followed by a release-candidate build 2 re-solves. The 95k launcher's note that a later release-candidate run reuses its solves is wrong.

Between builds: one PR with gate-only sign-offs. It contains register data plus the test pins that move with it. It has no runtime code, no solve-changing register and no gates.json edit. The degenerate register is excluded, because the contract pins its digest.

Build 2 (publish): same code and arguments as build 1, including the same --selection-initial-lambda 2.5e-8. Build 1's selected λ must not become the new warm start: initial_lambda keys the search, draw, refit and holdout nodes, and a different warm start can select different rows. Then preflight, assemble, validate_release_dir, and publish.

Decide before build 1 (these change the solve)
  • uk/calibration_measure_exclusions.json, including renewals.
  • uk/local_target_references.json and uk/local_target_reference_membership.json (signed deferrals), and uk/local_small_cell_rule.json.
  • uk/local_area_support_exclusions.json, for as long as the generator turns its entries into deferrals.
  • uk/local_binding_adjudications.json.
  • uk/gates.json: the whole manifest is a parameter of both full gate nodes and, in its spine scope, of the spine gates.
  • Every other country-spec resource outside the declared gate-only set, for example uk/uprating_holds.json, the cross-grain declarations, and the high-value-home and DESNZ facts.
  • The doctrine, solve.py, the size, seed, epoch and warm-start arguments, --review-date, --release-candidate, and the choice between a spine checkpoint and an in-graph spine.
  • Open PRs that would change the solve, each ruled in or out: #1299, #1273, #1092 and #1163. #1299 moves policyengine-uk to 2.132.6, which breaks the consumer pin (§4).
What is missing
  • A register for local target-fit cells.
    • Today the local branch of uk_runtime/battery_bindings._evaluate_target_fit passes no exclusions.
    • Add uk/local_target_fit_reviewed_exclusions.json: schema-2 UKReviewedExclusion, keyed family/area_code/metric.
    • Set reviewed_exclusions_resource on uk_local_target_fit in gates.json.
    • Pass the register to uk_target_fit_gate with now=_exclusion_clock(context). That gives the stale, expired and premature checks for free.
    • Add exclusions_evaluated_on to _target_fit_required_artifacts.
    • Register the file in country_package.json, and add it to the resource-list pins in test_country_spec.py.
    • Re-pin _UK_DENSE_GATE_DIGESTS and the national _UK_GATE_BATTERY_POLICY_SHA256 / _GATES_MANIFEST_SHA256 / _SPEC_FINGERPRINT. These are taken over the whole gates.json, so re-pin their lockstep tests (test_gate_battery_contract_pins.py) too.
    • Note: uk_local_per_family_fit has no register and keeps none, so a family-level failure still needs a solve change (§5).
  • Area-support exclusions that don't change the solve.
    • Today _support_floor_register_scope in tools/generate_uk_local_target_references.py defers the cells of every listed area (currently Scilly and the City of London).
    • Move that list into a separate solve-scoped list, keeping the membership file byte-identical.
    • The exclusions block then becomes gate-only and honours release_lines. Constituency keys are optional: the island failures are local-authority keys.
    • The islands can then be signed after build 1.
    • Fallback: sign Orkney and Shetland before build 1 with the existing deferral. If either reaches ESS 50 at 150k, the entry is stale, the gate fails, and a third build is needed.
  • Node-key scoping, so build 2 reuses the solve.
    • Today every register feeds load_country_spec("uk").fingerprint. A sign-off therefore re-keys everything from target compilation down, about 10–14 h at 150k. In an in-graph spine build it also re-keys the spine from the stage after frs_brma.
    • Add a solve fingerprint that leaves out a declared UK_GATE_ONLY_RESOURCES set: the target-fit, local target-fit and input-mass registers, and the gate-only support block. The degenerate register stays out, because the contract pins its digest.
    • Use the solve fingerprint in the target kernel hash and in the target-compilation node's country_contract_sha256 parameter, both in graph_targets.py.
    • Use it in the holdout kernel.
    • Use it in the spine gate kernel (graph_evidence.py, UKSpineGateKernel), unless both builds run from a pinned --input-h5 checkpoint.
    • Give the preflight gate node its own kernel, carrying only the preflight-phase manifest entries.
    • Build 2 then recomputes only the calibrated gates, the export, the package and certification, about 1–2 h.
    • Tests: no target-compile, calibration or spine module reads a gate-only file, and perturbing each gate-only file leaves every solve node key unchanged.
  • --require-solve-reuse.
    • --resume require already exists, but it covers the whole numerical phase through uk.full.gates.calibrated, which build 2 must recompute.
    • The new flag requires cache hits only on the solve sub-graph (through size_refit and uk.full.holdout) and runs the gates with auto. Build 2 then fails within minutes instead of silently re-solving.
  • A separate gate clock.
    • Add --gate-review-date, defaulting to --review-date. Refuse a date earlier than the solve clock or later than today.
    • The calibrated gates use the gate clock. The preflight gate keeps the solve clock, because it feeds the solve.
    • Record both clocks in the export bindings.
  • A sign-off drafting tool, tools/draft_uk_dense_signoff.py.
    • It reads build 1's full gate report, local gates, targets and area support.
    • It drafts entries for uk_target_fit (name@2025, release_lines: ["local"]), uk_local_target_fit and area support, plus a list of stale entries to remove.
    • It flags rows within a point of the 25% bound as stale-risk if build 2 does not reproduce build 1 exactly.
    • Reasons are templated by class and cite the build-1 attempt id and the gate-report sha256. No per-area figures or cell estimates.
    • It outputs a reviewable patch, including the edit to test_uk_terminal_gates.py's assert register == {}. It never commits.
    • --verify <build-2 dir> asserts that build 2's gates actually used every entry, that none is stale, premature or expired, and that no gate fails.

Fallback without scoping. Build 2 is then a full rebuild, about as long as build 1 (10–16 h), not a third build.

  • The solve kernels are deterministic, with their seeds passed as parameters.
  • The 100k search's two consecutive probes at λ = 1e-7, in one process, logged identical losses.
  • Reproduction across processes is unmeasured. So before assembly, compare build 2's per-row errors with build 1's. A drift that crosses the bound makes a signed entry stale or a new row fail.
Review-date rule
  • Solve clock (--review-date): identical in both builds. Measure exclusions, small-cell deferrals and adjudications must be in force on it.
  • Gate clock: every gate-only entry needs approved_on ≤ clock ≤ expires_on, and build 2's gate clock must be on or after the latest approved_on in the sign-off PR.
    • Without the clock split, run build 1 with --review-date set to the planned sign-off date. Nothing refuses a future date.
  • Re-checked against today: measure exclusions are re-checked at preflight, assembly and upload. Gate-only registers are not re-checked at upload; adding that check is optional.

4. Certification and publication readiness

Step Where Status What's needed
Chronicle feed pin (735820c) uk/chronicle_feed.json READY Digests verified against the artifact.
Release-candidate CLI posture uk_runtime/rowwise_cli.py CHANGE Holdout and size (§1, §2). Optionally allow --inspection-export with a release candidate.
L0 size search solve.py, uk_runtime/dataset_size.py CHANGE Floor, no duplicate probes, refuse an out-of-bracket warm start (§1).
Engine blocks preflight, rowwise_cli.release_verdict READY --engine-blocks 15, recorded as exact.
Input-mass reference uk_runtime/full_build_cli.py READY Pass uk-inputs/input-mass/uk_input_mass_reference_2024_25_v1_56_16.json, the same file in both builds. Without it the release candidate is blocked as evidence-absent.
National gate enforcement uk_runtime/full_gates.py READY Gap: the release doesn't ship the full gate report, and the contract doesn't re-check national outcomes or releasable at publish. Adding that is optional.
Signed local gate report (HMAC) gate_battery.py, contract.py READY, re-pin, fix disclosure The keychain key signed the 95k (dev) report, and its digests match the contract pins. When the local register lands, re-pin the dense and the national gate-battery pins. Withhold per-area support counts in the gate details (§2). Export the key in the assembly and publish shells too. The preflight strips whitespace from it; the build and contract don't.
Reviewed-exclusion registers battery_bindings.py, terminal_gates.py ADD / CHANGE National is ready. Local target fit: ADD. Area support: CHANGE (§3). Per-family fit, aggregate admin, support and the weight gates have none: a failure there means a solve change.
Node-key scoping, gate clock, reuse guard graph_targets.py, graph_terminal.py, graph_evidence.py, country_spec.py, full_build_cli.py ADD §3.
Sign-off drafting tool tools/ ADD §3.
full_certification.py uk_runtime/ CHANGE It is not on the release path: no release step reads certification.json, and the build's exit code ignores it. As written it can never report ready for dense: the holdout and absent-scorecard reasons, and subnational_fit_certified is always false. Either mark it non-gating and fix the reasons, or ship it and have the contract require ready_for_external_review.
Preflight check_candidate_dir tools/preflight_uk_local_release_candidate.py CHANGE Holdout (both the skip_holdout refusal and the 5-fold check), size, search bracket.
Scorer tools/score_uk_local_candidate.py CHANGE (opt-in) Holdout optional; reference count read from the membership file.
Incumbent-surface evaluation tools/evaluate_uk_incumbent_surface.py READY as a diagnostic Make its steps opt-in. It may exceed this machine's 32 GiB at 150k.
Released file checked on one engine — ADD, or accept the limitation Decision in §2.
Assembler tools/assemble_uk_dense_release_dir.py CHANGE Optional incumbent files (args, presence checks, coverage, copies), holdout claim, signed rows in reviewed_limitations.
Release contract contract.py validate_release_dir CHANGE Required files, the split in _check_uk_dense_surface_files, coverage discriminators, new gate digests.
Logbook, git_dirty, commit binding assembler, contract READY Build from a clean checkout; git_dirty must be false.
Measure-exclusion windows uk/calibration_measure_exclusions.json; checked at preflight, assembly and upload CHANGE ons.savings_interest_income expires 22 Oct: renewing it before build 1 is mandatory. The two SLC plan 2 rows expire 3 Nov, dwp.uc.households_housing_element 14 Nov, and 40 entries 26 Nov. Renew those only as insurance against a slip; each renewal changes the solve.
Other register windows support exclusions, adjudications, small-cell rule, uprating holds READY The earliest expiry is 2 Dec.
Staging read-back staging_v2.py, uk_runtime/full_build_cli.py CHANGE A failed upload after a passed read-back sets last_error_code = "UPLOAD_FAILED". delivery_summary rejects that, and the error is raised inside _close_attempt's finally after export. That kills the build before the Logbook row is written, so preflight and assembly refuse. Fix it. Workaround: run both builds without --staging-read-back.
Engine version uv.lock; the assembler writes == specifiers READY on main's lock policyengine-uk 2.130.1 with core 3.33.3; policyengine.py pins policyengine-uk 2.130.1. The 95k run used 2.123.0, so rebuild the venv. Optional: a contract check that the specifier matches the lock.
Publish (inspect lane) publish_cli.py, release.py READY --tag-only --no-latest --tag-name <cut> to policyengine/populace-uk-private. Without --tag-only it also commits to main. Needs an HF write token.
Consumer: policyengine.py#615 PolicyEngine/policyengine.py CHANGE Draft, one commit, no reviews. Its region filters read constituency_code_oa / la_code_oa, which Microcosm exports. It documents and tests the local_area route only with a Microcosm national primary. Review and merge.
Consumer: the certification PR after #615 PolicyEngine/policyengine.py ADD Add and test the route that keeps the enhanced FRS 1.58.1 (main's current UK default) as the primary manifest and routes constituency and local-authority regions to the dense cut as local_area. _route_region_types refuses if the primary already declares area templates. The release must serve policyengine-uk 2.130.1. HEAD-check every artifact; give sim-api and CI read access to populace-uk-private; update the routing tests; coordinate sim-api#728.
UC coverage floor (#491, in #615) scripts/check_uk_uc_floor.py Decide It is attached for national cuts. Decide whether to also run it on the local file as a sanity check.

5. Registers and rulings to sign before build 1 (non-spine)

  • Renewals. Signing these after build 1 re-keys the solve.

    • ons.savings_interest_income (expires 22 Oct): mandatory.
    • If publication could slip: slc.borrowers.plan_2_above_threshold and slc.borrowers.plan_2_liable (3 Nov), dwp.uc.households_housing_element (14 Nov), and the 40 entries that expire on 26 Nov.
  • High-value homes local family (HVCTS; #1240, #1270). Required before build 1.

    • There are 2,673 active cells. 1,321 have targets under 100 homes, and 808 under 25.
    • The gate's error is not floored; only the loss uses the floor of 100. Many of these cells will fail.
    • Neither uk_local_per_family_fit nor any other family-level check has a register. A family-level failure in build 1 therefore needs a third build.
    • Options:
      • Sign a small-cell rule on microcosm.high_value_homes.. A floor of 100 defers 1,321 cells, 250 defers 1,674, and 1,000 defers 2,184. Then regenerate the references and parity, and update the active reference count (25,309 → 23,988 at a floor of 100).
      • Make the gate use the loss floor.
      • Keep the family at region grain only.
  • Islands (Orkney, Shetland). Sign them gate-only after build 1, which needs §3. Otherwise defer them before build 1. Either way, the shipped gate report must not carry their support figures (§2).

  • Council-tax band cells that failed at 95k: Scottish band A (Na h-Eileanan Siar), and band G and band H cells in a few English and Welsh authorities. Check whether #1283's under-50-dwellings rule now covers them. If not, leave them bound and sign them gate-only.

  • Student-age (20–30) cells in a few constituencies, including E14001131 and E14001319. Leave them bound and sign them gate-only. Deferring them instead changes the solve and must land first.

  • Private-rent and tenure cells in a few authorities: same as the student-age cells.

  • obr.ni_employer. It sat at about +19% in the 95k build, and it is off on every line. Options:

    • Unbind it with a reason (Employment Allowance is not modelled; calendar versus fiscal year). This changes the solve.
    • Bind the FY2025-26 value.
    • Leave it bound and sign it gate-only if it crosses 25%.
  • National rows. At 95k these failed:

    • dividend bands £20–50k;
    • private pension bands £12,570–20k;
    • £200k+ taxpayers in some English regions;
    • the single-claimant UC payment band £2,400–3,600;
    • the property-income count band £300–500k;
    • the savings-interest count band at £150k+;
    • the State Pension band £50–70k.

    The private pension and SPI rows belong to the spine track. Whatever still fails in build 1 is signed gate-only with release_lines: ["local"].

  • The weighting rule (optional, §1).

  • Rule each open solve-changing PR in or out: #1299, #1273, #1092, #1163.

6. Out of scope

  • Spine fixes: band-donor seating, private pensions, and the SPI residuals (#1288).
  • #1276 (SPI stage 1 donor retention). The QRF tail gate itself is already gone (#1281).
  • Counting source households in the area-support gate (#1241). Source-collapsed support stays a reported figure.
  • Switching the UK national default to a Microcosm national cut (the national half of policyengine.py#553).

7. Sequencing to the Budget (28 October)

Code lands as one PR with ordered commits, merged before build 1. Each commit has a class:

  • Must: needed for any release.
  • Two-build: keeps it to two builds.
  • Saves time: only makes build 2 faster. It is the last commit, so it can be dropped if review runs short.
# Commit Class
1 Holdout and incumbent comparison made opt-in across the CLI, preflight, scorer, certification, assembler and contract (§2); the manifest limitations Must
2 Gate-report disclosure fix; staging read-back fix (or run both builds without --staging-read-back) Must
3 Doctrine at 150k; L0 floor, duplicate-probe stop, out-of-bracket warm-start refusal (§1) Must
4 Local target-fit register with the dense and national gate pins; gate-only area-support block (§3) Two-build
5 Sign-off drafting tool (§3) Two-build
6 Registers and rulings that change the solve: the savings renewal (and any slip renewals), the high-value-homes local family, any deferrals chosen over gate-only sign-off, obr.ni_employer, the optional weighting rule (§5) Must
7 Node-key scoping (including the target-compilation param and the spine gate kernel, or a pinned spine checkpoint), the gate clock and --require-solve-reuse (§3) Saves time (build 2 drops from 10–16 h to about 1–2 h)

Before the PR merges, amend the ruling on #1124, and rule #1299, #1273, #1092 and #1163 in or out (§5). The only other PR is the sign-off PR (PR-B) between the two builds, because it needs build 1's results.

When What
16–20 Oct The PR reviewed and merged; commit 7 if it is ready.
20–21 Oct Build 1, about 10–16 h: dense solve about 2 h, 3–4 probes at 1.7–2.8 h each, draw and refit about 1 h, then gates and export.
22–23 Oct PR-B (register data and test pins): the drafted sign-offs, reviewed and merged.
23–24 Oct Build 2, with the same --selection-initial-lambda as build 1: about 1–2 h with commit 7, about 10–16 h without. Then preflight, assemble, validate_release_dir, and publish with --tag-only --no-latest.
24–27 Oct Merge policyengine.py#615 and land the certification PR (enhanced FRS primary plus dense local_area); sim-api#728. Post the run id and H5 sha on #1242. Run the incumbent diagnostic if wanted.

If build 1 passes every gate, it is the release and build 2 is skipped.

Done when

  • A K=15, 150,000-household dense release candidate passes every release-blocking gate. Every exclusion it relied on is signed, in force and disclosed in the manifest.
  • At most two builds ran. Build 2 differed from build 1 only in gate-only register data, test pins and the gate clock, used the same warm start, and reused the solve (or reproduced it, checked row by row).
  • validate_release_dir passes without the incumbent files and without a holdout. The manifest records no_out_of_sample_claim, the incumbent status, engine_measurement and the signed rows.
  • No shipped release file carries a per-area count under 10.
  • The release is published tag-only (--tag-only --no-latest) to policyengine/populace-uk-private.
  • policyengine.py certifies it as the local_area regional release beside the enhanced FRS 1.58.1 national default, on policyengine-uk 2.130.1. sim-api routes constituency and local-authority runs to it.
  • The #1124 ruling is amended, and #1242's items 1 and 4 are answered.

Relates to #1124, #762, #1207, #1241, #1242, #1278, #1283, #1286, #1270, #1240, #1288, #1276, #1281, #1299, #1273, #1163, #1092, #355, #578, #147, #830, PolicyEngine/policyengine.py#615, PolicyEngine/policyengine.py#553, sim-api#728.

Lenguaje dominante
Python
Estrellas
0
Forks
5
Merge medio
1 d 6 h
PR fusionados (30 d)
146

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de PolicyEngine/microcosm

Todos los issues de PolicyEngine/microcosm

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.