UK local-area release: publish a K=15, 150,000-household dense dataset in at most two builds
Los mantenedores suelen responder en 1 día
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
- Área
- build-system, data, release, tooling
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
121fd4dd6and 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.jsonthat 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.
- Set
-
tools/preflight_uk_local_release_candidate.py: setSIZE_DOCTRINE["dataset_households"]to 150,000 and update the docstring. Add a test thatSIZE_DOCTRINEequalsuk_local_size_doctrine(). -
uk_runtime/rowwise_cli.py:size_doctrine_driftand its release-candidate refusal read the doctrine, so only the ruling references change.--n-clonesoff the doctrine is already refused. - Decide whether a dense
--release-candidatemust 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.pytest_uk_local_release_preflight.py(add a case showing 100,000 is now refused)test_uk_dense_release_assembler.pytest_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.mdanddocs/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_sizeandgraph_calibration. - Set it from a doctrine constant, e.g.
UK_LOCAL_L0_SEARCH_FLOOR = 1e-10. Ifselect_uk_dataset_sizeapplies 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 intest_informed_gates.py. - Record the bracket in the size receipt, and check it in the preflight.
- Thread the parameter through
- 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-8silently 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_largerfromsteer()instead oflo_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_epsis 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
--envruled tuple andcheck_candidate_dir); - about 10 rule-name test pins.
- the cell loop in
- 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.
releasabledoesn'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.pyandrowwise_cli.py:- Replace
--skip-holdoutwith--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.
- Replace
-
uk_runtime/graph_terminal.pyandcountry_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.pyanduk_runtime/size_evaluation.py: read the new flag and the skip marker. - Preflight: drop the
parameters.skip_holdoutrefusal 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_holdoutreturns None when the holdout is skipped or absent, and the score recordsholdout_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_claimlimitation andcoverage.holdout = {"run": false, "claim": "none"}. - When run, write the measured summary with
claim: "report_only".
- When skipped, write a
-
packages/microcosm-data/src/microcosm/data/contract.py:- Keep
holdoutamong the coverage keys. - Validate it by
claim: a finite mean only forreport_only.
- Keep
- If the opt-in holdout should work at 150k, make sure the fold search in
uk_runtime/local_rowwise.pygets 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 requiresha256sums.txtto 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_dirtyand 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 anenforce_limitsargument.- 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.pycalls this function too.
- The schema, identity, summary and pinned row-inventory checks stay blocking. Move the inventory check out of
-
The coverage entry becomes
incumbent: {"compared": false}, or{"compared": true, "snapshot": …}._check_uk_dense_source_coveragestops requiringincumbent.snapshotunconditionally. -
Assembler:
--incumbent-manifestbecomes 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
incumbentblock 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_comparisonlimitation when the files are absent.
-
full_certification.py: an absent scorecard isnot_requested, never a readiness failure. -
tools/evaluate_uk_dataset_size.py: make the30-incumbent-scoreand40-incumbent-surfacesteps opt-in. Step 40 runs whenever--ledger-factsis 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 pinningUK_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_filecase 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_limitationslists 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_measurementlimitation. - (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.
- (a) Accept per-clone measurement and record the
-
Disclosure: no per-area support figures in shipped files.
- The signed
uk_local_gates.jsoncarries per-arearows,essandsourcesfor each reviewed area-support exclusion (thearea_support_gatedetails). - 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.
- The signed
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-referencefile in both builds; - a persistent
--graph-store; - a clean checkout of the merged commit;
- either a pinned
--input-h5spine 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-exportalongside--release-candidate(withreleasable: 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. Todayrowwise_clirefuses 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.jsonanduk/local_target_reference_membership.json(signed deferrals), anduk/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_fitpasses no exclusions. - Add
uk/local_target_fit_reviewed_exclusions.json: schema-2UKReviewedExclusion, keyedfamily/area_code/metric. - Set
reviewed_exclusions_resourceonuk_local_target_fitingates.json. - Pass the register to
uk_target_fit_gatewithnow=_exclusion_clock(context). That gives the stale, expired and premature checks for free. - Add
exclusions_evaluated_onto_target_fit_required_artifacts. - Register the file in
country_package.json, and add it to the resource-list pins intest_country_spec.py. - Re-pin
_UK_DENSE_GATE_DIGESTSand the national_UK_GATE_BATTERY_POLICY_SHA256/_GATES_MANIFEST_SHA256/_SPEC_FINGERPRINT. These are taken over the wholegates.json, so re-pin their lockstep tests (test_gate_battery_contract_pins.py) too. - Note:
uk_local_per_family_fithas no register and keeps none, so a family-level failure still needs a solve change (§5).
- Today the local branch of
- Area-support exclusions that don't change the solve.
- Today
_support_floor_register_scopeintools/generate_uk_local_target_references.pydefers 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
exclusionsblock then becomes gate-only and honoursrelease_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.
- Today
- 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 afterfrs_brma. - Add a solve fingerprint that leaves out a declared
UK_GATE_ONLY_RESOURCESset: 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_sha256parameter, both ingraph_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-h5checkpoint. - 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.
- Today every register feeds
-
--require-solve-reuse.--resume requirealready exists, but it covers the whole numerical phase throughuk.full.gates.calibrated, which build 2 must recompute.- The new flag requires cache hits only on the solve sub-graph (through
size_refitanduk.full.holdout) and runs the gates withauto. 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.
- Add
- 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_fitand 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'sassert 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 latestapproved_onin the sign-off PR.- Without the clock split, run build 1 with
--review-dateset to the planned sign-off date. Nothing refuses a future date.
- Without the clock split, run build 1 with
- 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_thresholdandslc.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_fitnor 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.
- Sign a small-cell rule on
-
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_dirpasses without the incumbent files and without a holdout. The manifest recordsno_out_of_sample_claim, the incumbent status,engine_measurementand the signed rows. - No shipped release file carries a per-area count under 10.
- The release is published tag-only (
--tag-only --no-latest) topolicyengine/populace-uk-private. - policyengine.py certifies it as the
local_arearegional 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
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de PolicyEngine/microcosm
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
PolicyEngine/microcosm#1220 ·
Los mantenedores suelen responder en 1 día
-
Update two stale comments about the export-mass reviewed-exclusion registerPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
PolicyEngine/microcosm#1108 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
PolicyEngine/microcosm#475 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 45/100
PolicyEngine/microcosm#1303 ·
Los mantenedores suelen responder en 1 día
-
UK: re-pin the Chronicle feed to chronicle#359 (paid-claims UC area cells, OBR table 4.9 geography, DESNZ gas years and Northern Ireland energy)Posiblemente ocupada @juaristi22 la tomó hoy. Abierto
Dificultad 5/5 Más de una semana Aptitud para principiantes 12/100
PolicyEngine/microcosm#1300 ·
Los mantenedores suelen responder en 1 día
Todos los issues de PolicyEngine/microcosm
Issues similares
-
bot-triaged module: LrScheduler module: optimizer triaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
Los mantenedores suelen responder en 1 día
-
documentation glm5 llm-model
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
vllm-project/vllm-ascend#19062 ·
Los mantenedores suelen responder en 1 día
-
Author Page: shuai-zhao-llmAbiertoauthor-page correction
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
acl-org/acl-anthology#10496 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Issue Type: Bug Report
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
celery/celery#10875 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
enhancement good first issue
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
SasanLabs/VulnerableApp#836 ·
Los mantenedores suelen responder en 1 día