Repository navigation
Admit UK datasets without area codes only when local-area runs are routed, and guard the weight-matrix fallback - #727
Conversation
…t-matrix fallback A UK dataset without `la_code_oa` (a national-only file) now has no local-authority metadata instead of failing every run and the UK worker check, and its constituency and local-authority breakdowns are omitted. Stage 12 accepts a baseline/reform pair that both lack the metadata. Constituency and local-authority regions built on the enhanced-FRS weight matrices now stop before the simulations are built when the matrix has no weights for the run year or was built for a different number of households, instead of failing inside the run with an out-of-date-matrix error. Refs #725. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@juaristi22, could you confirm the intended UK Microcosm release sequence? My understanding from PolicyEngine/policyengine.py#553 and PolicyEngine/microcosm#1114 is:
If that is correct, switching the UK default after this PR but before the local-area dataset and routing are available would make constituency and local-authority simulations unavailable and omit those breakdowns from national reports. We do not want any interval in which those existing subnational capabilities are deactivated. Could we revise the release plan so that subnational simulations remain available throughout—for example, by continuing to route those requests to the enhanced FRS until the Microcosm local-area release is ready, or by releasing the national dataset, local-area dataset, and routing together? I do not think silently returning no geographic output is an acceptable interim state. |
|
Thanks @anth-volk. We don't necessarily intend to publish the national dataset separately from the local-area one. This PR is about having the flexibility in case that becomes necessary: with it, the executor no longer fails outright on a UK dataset without area codes. Nothing changes while the enhanced FRS is the default. If that eventuality did arise, constituency and local-authority simulations, and the breakdowns in national reports, would route to the enhanced FRS local-area dataset in the meantime, so those subnational capabilities stay available throughout. We wouldn't switch the UK default to a dataset without area codes before that routing is in place. I've filed #728 to make this explicit, alongside the later move to the Microcosm local-area dataset. |
… routed Review on #727: treating a missing `la_code_oa` as "this dataset is national-only" let a local-area dataset that lost its area columns pass the worker check and every run, with its breakdowns silently dropped. A UK dataset may now lack area codes only for a national or nation-level run whose policyengine.py bundle routes both constituency and local-authority regions to another dataset (`region_datasets` lists them). Constituency and local-authority runs always require the codes, the installed-dataset check keeps main's strict detection unless that routing exists, and the constituency and local-authority breakdowns raise a clear error instead of returning nothing when the codes are missing. With today's bundle (policyengine 6.2.1) nothing is routed, so the executor admits exactly what main admits. Refs #725, #728. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@anth-volk, on your concern that letting a national dataset pass without local-area values would also let a local-area dataset with broken local-area handling through undetected: agreed. The first version inferred "this dataset is national-only" from the missing column, so a file that lost its area codes by mistake looked the same. I've pushed a rework that makes it a declaration instead:
One check is still open, and #728 tracks it: the routed dataset is validated when a run loads it, not yet at worker start-up, because workers only install the default dataset. |
anth-volk
left a comment
There was a problem hiding this comment.
Two inline findings from the code review.
| weights = matrix[year] | ||
| if not isinstance(weights, h5py.Dataset): | ||
| raise TypeError(f"{matrix_name} entry {year} is not a weight matrix") | ||
| matrix_households = weights.shape[-1] |
There was a problem hiding this comment.
Matching the matrix year and household dimension does not establish that this matrix belongs to the selected dataset. WeightReplacementStrategy.apply assigns a matrix row positionally, so a matrix from another dataset revision or household ordering will pass this check whenever it has the same number of households and can attach weights to the wrong records. Please bind the matrix to the certified dataset revision or digest, or validate equivalent provenance that covers household ordering.
There was a problem hiding this comment.
Agreed: the shape check couldn't tell releases apart. uk-data 1.57.4's constituency matrix is 650 × 52,846 like the certified 1.56.16 one, and its enhanced FRS has the same 52,846 households in a different order. Fixed in 1442197:
- No more bucket downloads. The fallback regions no longer download the unversioned bucket copy (
download_missing_assets=False). Every uk-data release overwrites that object, so it was never bound to the certified dataset. - The matrix comes from the certified release.
_require_certified_uk_weight_matrixtakes the matrix the bundle certifies in the same release (repository and revision) as the selected dataset. It materializes that matrix through policyengine.py's digest-checked materializer, where the strategy looks first, and confirms the strategy resolves exactly that file before the run. The year and household checks stay as a last sanity check. - Checked end to end against the real bundle: it fetched the certified 1.56.16 constituency matrix (sha256
6870c360…) and passed for the 52,846-household enhanced FRS.
This surfaced two things, now noted in the PR description:
- Results may move on deploy. If production has been reading the 1.57.4 bucket objects (constituency matrix sha256
73944f69…), its constituency and local-authority results will change when this deploys. Someone with bucket access can confirm by comparing the object's digest with6870c360…. - These runs only work for 2025. The certified matrices only cover 2025, so these runs fail for any other year. Before, that failure was a
KeyErrorinside policyengine.py; now it's a clear message.
| """ | ||
|
|
||
| if country == "uk": | ||
| if baseline is None and reform is None: |
There was a problem hiding this comment.
This accepts every pair of UK artifacts whose boundary metadata is absent, but the coordinator can deserialize retained artifacts without rerunning detect_uk_local_authority_metadata. An invalid local-area artifact pair therefore passes this validation and fails only later during output construction. Please require evidence here that the request is national or nation-level and that both local-area dataset mappings exist, either by passing that context into this validator or encoding it in the artifact contract.
There was a problem hiding this comment.
Done in 1442197.
- The check now sees the report's region.
validate_uk_local_authority_metadatatakesreport.baseline.geography.region, passed in by the coordinator and by the qualification path. - What a UK pair without metadata needs: the region must be national or nation-level, and the bundle must route both constituency and local-authority regions to another dataset. That is the same rule
detect_uk_local_authority_metadataapplies when the simulation runs. - A missing region doesn't count as evidence.
…idence for artifacts without metadata Review on #727: - The weight-matrix guard compared only the year key and the household count, but WeightReplacementStrategy gives households their weights by column position, so a matrix from another release would pass and attach weights to the wrong records (uk-data 1.57.4's matrices have the same 650 x 52,846 shape as the certified 1.56.16 ones, and its households are in a different order). The fallback regions no longer download the unversioned bucket copy. `_require_certified_uk_weight_matrix` materializes, digest-checked, the matrix the policyengine.py bundle certifies in the same release (repository and revision) as the selected dataset, where the strategy looks first, confirms the strategy resolves exactly that file, then checks the run year and household dimension. - `validate_uk_local_authority_metadata` accepted any UK artifact pair without metadata. It now takes the report's region and accepts such a pair only for a national or nation-level request whose bundle routes both constituency and local-authority regions to another dataset: the same evidence per-run detection requires. Refs #725. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #725
Summary
policyengine.py may one day publish a UK default without area codes: a Microcosm national file whose local-area data come from a separate dataset (PolicyEngine/policyengine.py#553). Today every UK run on such a dataset fails, because the executor requires
la_code_oawhen it loads a UK dataset and when a UK worker starts.This PR lets the executor accept such a dataset only when it is declared national-only, meaning the policyengine.py bundle routes both constituency and local-authority regions to another dataset. A missing column on its own is never read as "national-only", so a local-area dataset that lost its area codes still fails. The PR also stops the weight-matrix fallback for constituency and local-authority runs early, with a clear message, when the matrix does not fit the dataset.
With today's bundle (policyengine 6.2.1) nothing is routed, so the executor admits exactly what
mainadmits.Changes
uk_local_authority_metadata.py):uk_area_regions_routed()is true when policyengine.py's UKregion_datasetslists bothconstituencyandlocal_authority. Itsregional_dataset_defaultsproduce those entries (Use ACS-local data for US regional simulations policyengine.py#552).uk_area_codes_required(region_code)is true for every constituency or local-authority run. For any other run it is true unless both area region types are routed.detect_uk_local_authority_metadatatakes the run's region code.la_code_oawhenever codes are required, asmaindoes.Noneonly for a national or nation-level run with routing declared.stage12_worker_validation.py): admits an installed default withoutla_code_oaonly when routing is declared; otherwise it runsmain's strict detection.detect_uk_local_authority_metadata_from_hdfis unchanged frommain, so the smoke apps still read a boundary version off it.simulation_output_geographic.py):build_uk_constituency_impactandbuild_uk_local_authority_impactraise a clear error when the simulated household output lacksconstituency_code_oaorla_code_oa. They never drop the breakdown, and the constituency builder skips its GCS lookup in that case.stage12_runtime/aggregation.py,coordination.py,stage12_qualification.py):validate_uk_local_authority_metadatanow takes the report's region.simulation_runtime.py):download_missing_assets=False). Every uk-data release replaces that object, so it is not tied to the dataset being run._require_certified_uk_weight_matrixruns after the dataset loads, in both runtimes. It takes the matrix the policyengine.py bundle certifies in the same release (repository and revision) as the selected dataset, and materializes it through policyengine.py's digest-checked materializer where the strategy looks first. It then confirms the strategy will read exactly that file.WeightReplacementStrategy.applywith aKeyErroror an out-of-date-matrix error.Behaviour changes to expect
KeyErrorinsideWeightReplacementStrategy.apply.Not in this PR
These move to #728:
Testing
test_uk_local_authority_metadata.py,test_stage12_worker_validation.py,test_simulation_output_geographic.py,test_simulation_output_builder.pyandtest_stage12_runtime.py.test_uk_weight_matrix_guard.py.uv run pytest tests/, Python 3.13): 692 passed, 22 skipped. The 9 failures intest_policyengine_package_update_scripts.pyalso fail onmainlocally, because the systempython3used by those scripts lackstomllib.ruff format --checkpasses on the changedsrcfiles, andruff checkadds no findings overmain.6870c360…) from Hugging Face and passed for the 52,846-household enhanced FRS. A 2026 run stopped with the missing-year message.🤖 Generated with Claude Code