Skip to content

Publish v2 catalog regions from checked-in reference files and the certified data release - #4020

Draft
anth-volk wants to merge 25 commits into
v2plan/1f-122-rename-user-policiesfrom
v2plan/1f-123-region-reference-files
Draft

anth-volk wants to merge 25 commits into
v2plan/1f-122-rename-user-policiesfrom
v2plan/1f-123-region-reference-files

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #4019

Summary

Catalog publication now reads each country's regions from region reference files checked into the Public API, instead of copying PolicyEngine.py's region registry. This branch is rebased onto #4010's final commits, which include master, so it targets the PolicyEngine.py release the Public API pins: 6.10.0.

The review of the retarget to 6.10.0 and of Ards and North Down found no bugs. Two small additions followed:

  • unit tests now cover a UK local authority's routed dataset, column and value, that the UK records no fallback, and a release that routes no dataset to local authorities;

  • the Stage 9 runbook paragraphs are rewrapped.

  • Reference files. policyengine_api/data/v2/catalog/region_reference_files/us.csv and uk.csv list every region's code, label, type, parent, US state, the value that selects its households, and the v1 identifier it replaces:

    • country/ni becomes country/northern_ireland;
    • constituency/Aldershot becomes constituency/E14001063;
    • local_authority/Maidstone becomes local_authority/E07000110;
    • local_authority/Ards and North Down becomes local_authority/N09000011.

    US codes are unchanged. The national labels are "United States" and "United Kingdom". A loader checks that codes and v1 identifiers are unique, that each code starts with its type, and that every parent leads to the national region.

  • Scoping from the certified data release. One rule per country and region type takes the dataset from the release manifest's region datasets and sets a row filter:

    Regions Dataset Filter
    US national populace_us_2024 none
    US states and congressional districts the release's state and district dataset (populace_us_2024_acs_local) row_filter on state_fips or congressional_district_geoid
    UK national microcosm_uk_2024_25 none
    UK nations microcosm_uk_2024_25 row_filter on region
    UK constituencies and local authorities the local-area dataset the release routes them to (microcosm_uk_2024_25_dense) row_filter on constituency_code_oa or la_code_oa, value the ONS code

    The UK national file has no area codes. A UK constituency or local authority therefore never falls back to it: a release that routes no dataset to their type stops publication, naming the region.

  • Verification before publishing. Before publishing a catalog version that is not stored yet, the publication command reads each dataset the filters use from Hugging Face, at its pinned revision, using ranged requests. Only the 1 MB blocks holding HDF5 metadata and column names are fetched. Publication stops if a filter's column is not a household column of its dataset. Errors name only artifacts, tables, columns, codes and counts.

  • Omitted regions. Region types without a rule are left out and counted in a new omitted_region_summaries evidence field. Today that is only US places.

  • Stored versions keep their regions. See "Stored catalog versions" below.

  • Direct dependencies: h5py>=3.16,<4 and huggingface-hub>=1.12,<2. The locked versions are unchanged; uv.lock changes only in the project's own metadata.

No route, table or migration changes. /v2/regions serves whatever the catalog stores.

Retargeted to PolicyEngine.py 6.10.0

Earlier versions of this PR scoped UK constituencies and local authorities with a weight_replacement filter on the release's weight matrices. That path, its weight-matrix checks and its matrix reading are removed, because no release the Public API can publish uses them:

  • the pinned 6.10.0 release certifies no weight matrices;
  • stored 6.7.5 versions keep their regions and are never scoped again;
  • the Public API's local-authority list now has 361 rows while 6.7.5's matrix had 360, so the matrix check would be wrong for 6.7.5 anyway.

Ards and North Down (N09000011)

Included by the user's decision. The Public API's own local-authority list on master already has it, and the v1 metadata now serves local_authority/Ards and North Down. Under 6.10.0 it is row-filtered on la_code_oa in the local-area dataset, so it publishes.

Whether that column holds rows for N09000011 is not checked. That would need reading the column's values from the private UK dataset, so it stays an open item.

Counts

  • UK: 1,016 regions (1 national, 4 nations, 650 constituencies, 361 local authorities), all published.
  • US: the file lists 821 regions (1 national, 51 states, 436 districts, 333 places). 488 are published; the places are left out.
  • Total: the installed 6.10.0 catalog extracts 1,504 regions and 4 datasets.

Sources of codes and labels

  • UK constituencies and local authorities come from the Public API's own lists, policyengine_api/data/constituencies_2024.csv and local_authorities_2021.csv. The local-authority list now has 361 rows and matches PolicyEngine.py 6.10.0's 2023 list. A nation is the parent of each, chosen from the first letter of its ONS code (E, S, W, N).
  • US states and districts come from the Public API's congressional_districts module. US FIPS numbers and places come from PolicyEngine.py's public data.
  • Every US region and UK nation matched PolicyEngine.py 6.7.5's registry in labels, parents, state fields and filter values. The US file is unchanged when regenerated with 6.10.0.

Stored catalog versions

  • The rule. A catalog version is identified by its country and PolicyEngine.py version, and never changes once published. For a stored version, publication still compares every other table exactly, but keeps its region rows as they were published.
    • It prints a warning with the country, the version and the counts of differing region rows.
    • Its evidence counts the stored regions, lists the version under kept_regions with stored and staged counts, and leaves out that country's staged fallback and omitted summaries.
    • A difference in any other table still fails, and the error names the table.
  • Why. Published versions must not change; 1.16 caches its responses as immutable. The seed job republishes the installed version on every release.
  • Trade-off. A stored version's region rows are no longer compared when it is republished. If they were damaged, publication would print the difference instead of failing.

When the new regions reach staging and production

Only when a catalog version that is not yet stored is published. Where the deploy of master has already published the PolicyEngine.py 6.10.0 catalog, that version keeps the regions taken from the registry, because a stored version keeps its regions. The reference-file regions then reach staging and production with the next PolicyEngine.py version that is published, by an operator or by the release workflow's seed job. This PR does not publish anything.

Open risks

  • Nothing automated reads the real certified files before the release workflow's seed job publishes the next catalog version. If an artifact's layout differs from what the reader understands, or the read token cannot read the UK data, that seed job stops before publishing.
  • Earlier, the US check passed locally against the real populace_us_2024_acs_local file (9.8 GB) in 55.5 seconds.
  • The UK datasets could not be read locally: the local token has no access. The reader handles the HDF5 layouts policyengine-uk writes, tested with local fixture files.
  • Whether la_code_oa holds rows for every local authority, N09000011 included, is not checked.

Follow-up

US places stay left out until a place filter's value format is checked and compute accepts places. The certified ACS-local file has a place_fips household column.

Environment variables

Variable Read by Provider How it reaches the job
HUGGING_FACE_TOKEN The catalog publication command, to read certified datasets The GitHub organization secret PE_UK_PRIVATE_HF_READ_TOKEN, listed as available to this repository, with no environment-level secret of the same name env: of the catalog publication step only ("Seed and validate the v2 metadata catalog") in seed-v2-database.yml. It arrives through secrets: inherit from the release workflow's staging and production seed jobs, and directly on manual dispatch. A test checks that the workflow names it only once.
V2_DATA_WRITE_DATABASE_URL Catalog publication Unchanged Unchanged

Not verified: that PE_UK_PRIVATE_HF_READ_TOKEN can read the UK data release. There has been no live run. If a UK version needs checking and the token is unset, the command stops before reading anything, with a configuration error naming the variable.

Suppressions

None added.

What this waits for before merging

Testing

GitHub Actions runs on this branch are cancelled at the user's request. The evidence is these local runs on the tip:

  • Full make test set: 3476 passed.
  • v2 integration tests on a disposable Postgres:
    • catalog publication and metadata routes: passed;
    • installed catalog against PolicyEngine.py 6.10.0 with RUN_V2_CATALOG_COMPATIBILITY=1: passed;
    • production-scale publication qualification: passed, publishing 1,504 regions.
  • ruff format --check, ruff check, mypy on the configured set, python scripts/run_quality_guards.py, python -m scripts.rules validate and guard, alembic -c alembic-v2.ini check: clean.
  • Changelog fragment: changelog.d/v2plan-1f-123-region-reference-files.changed.md.

🤖 Generated with Claude Code

@anth-volk
anth-volk added this pull request to stack #4011 October 22, 2026 18:14
@anth-volk
anth-volk force-pushed the v2plan/1f-123-region-reference-files branch from 3050f02 to 00657f1 Compare October 26, 2026 13:18
anth-volk and others added 23 commits October 26, 2026 17:40
Each file lists every region of one country with its code, label, type,
parent, US state, the value that selects its households, and the v1
identifier it replaces. The UK file lists 1,016 regions: the United
Kingdom, its four nations (Northern Ireland as country/northern_ireland,
replacing country/ni), 650 constituencies and 361 local authorities by ONS
code (constituency/E14001063 replaces constituency/Aldershot), each under
its nation. The 361 include Ards and North Down (N09000011), which the
Public API's own list now carries. The US file lists the United States,
51 states, 436 congressional districts with unchanged codes, and 333
places.

The constituency and local-authority rows come from the Public API's own
lookup files; the US states, districts and places, and every filter value,
match the region registry of PolicyEngine.py 6.7.5. A loader validates
each file: unique codes and v1 identifiers, codes that start with their
type, and parents that lead to the national region. Nothing reads the
files yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ta release

Catalog extraction no longer reads PolicyEngine.py's region registry. It
takes every region from the checked-in reference file of its country and
gives it a dataset and filter from a scoping rule per country and region
type:

- US national, and UK national: the release's national dataset, no filter.
- US states and congressional districts: the release's state and district
  dataset, a row filter on state_fips or congressional_district_geoid.
- UK nations: the national dataset, a row filter on the stored region.
- UK constituencies and local authorities: the national dataset, a
  weight_replacement filter whose field names the release's weight matrix
  (parliamentary_constituency_weights, local_authority_weights) and whose
  value is the region's ONS code. The matrix must come from the same
  release as the dataset, since weights attach to households by position.

Extraction fails if the national region would not use the reviewed
dataset, a dataset role names no certified dataset or a per-region
template, a filtered region has no filter value or a malformed list, or a
runnable region sits under one that is left out. Region types without a
rule (US places) are left out and counted in the publication evidence.
The installed 6.7.5 catalog now publishes 1,503 regions: 488 US and 1,015
UK.

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

Before it publishes a catalog version that is not stored yet, the
publication command checks each region against the certified artifact its
filter reads, and stops with a clear error if:

- a row filter's column (state_fips, congressional_district_geoid, region)
  is not a household column of the region's dataset; or
- a weight matrix's row count differs from its row lookup, or the lookup
  has no row for a constituency's or local authority's ONS code. Compute
  finds a region's matrix row in the same lookup files
  (constituencies_2024.csv, local_authorities_2021.csv).

The artifacts are read from Hugging Face at their pinned revisions with
ranged requests, so only HDF5 metadata and the column-name arrays are
downloaded: about a minute for the 9.8 GB US file. Pandas fixed and table
formats, one frame per year, and policyengine-core files are understood. A
read failure names only the artifact and the error type. Versions already
stored are not read again, since publication only compares them.

The seed workflow's publication step receives the organization secret
PE_UK_PRIVATE_HF_READ_TOKEN as HUGGING_FACE_TOKEN, because the UK data
repository is private. The release workflow passes it through its
existing `secrets: inherit`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A catalog version is identified by its country and PolicyEngine.py
version and never changes once published, but its regions now come from
the Public API's reference files. Republishing a stored version compared
every table, regions included, so a reference file change under an
unchanged PolicyEngine.py version (6.7.5 today, whose stored version has
the 826 regions of PolicyEngine.py's registry) made the seed job fail.

For a stored version, publication now compares every table except
regions exactly as before, keeps the stored region rows unchanged, and
logs kept_published_catalog_regions with the country, the version and
the counts of staged region rows the stored version lacks and of stored
rows not staged. A version not yet stored still publishes, and is checked
against, the reference-file regions. Region file changes therefore take
effect in the next catalog version that is published.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The artifact test now checks that the UK release fixture's artifacts name
the placeholder repository example-org/example-uk-data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Catalog publication's data reader imports both, which the project had only
through PolicyEngine.py and PolicyEngine Core. They are now direct
dependencies, constrained to keep the locked versions (h5py 3.16.0,
huggingface-hub 1.12.2). uv.lock, refreshed with `uv lock` (uv 0.13.4,
as the release workflow uses), changes only the project's dependency
metadata. A test requires every third-party package the reader imports to
be a direct dependency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The seed command configures no logging handler, so Python's last-resort
handler printed only the warning's bare event name and dropped the
structured attributes. The message now carries the country, the
PolicyEngine.py version and both region-row counts; the attributes stay
for structured handlers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Republishing a stored catalog version with different content failed with
"persisted <country> catalog differs from PolicyEngine.py", even when the
difference came from the Public API's own selections, such as a dataset.
The error now names the country, the version and the first table that
differs (model version, variables, parameter nodes, parameters, parameter
values, datasets or regions), and still no row data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verification reduced every read failure to the artifact's name and the
error type, so a structure error such as "household group holds no data
frame" reached the operator only as "CertifiedDataReadError". That error,
which names only HDF5 entries and counts, now keeps its message; any other
failure is still reduced to the name and type, since its text may hold a
location. The error class moves to the catalog's errors module so
verification can recognize it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The region data check reads stored catalog versions before publication
checks the database revision, so on a database without the v2 schema the
command failed with "catalog initialization failed unexpectedly" instead
of the specific revision error. unpublished_countries now requires the
expected Alembic revision first, as publication does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UK credential document said the catalog publication step reads only
UK artifacts, but the same authenticated file system also reads the public
US dataset. The weight-matrix lookup comment and the catalog document now
say that the certified data release lists no lookup file, so the check uses
the Public API's own copies, and that on 2026-10-22 both copies were
byte-identical to the files compute downloads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a stored catalog version kept its region rows, the publication
evidence still counted the staged regions and reported the staged
fallbacks and omissions, so the record kept before a release described
regions that were never published. The evidence now counts the stored
region rows for such a country, leaves its staged fallback and omitted
summaries out, and lists it under kept_regions with the stored and staged
region counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a region's parent existed but a region further up its chain did not,
the reference file check named the region's direct parent as unknown. It
now names the missing code and the region that names it as parent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without HUGGING_FACE_TOKEN, the UK artifact reads failed with "could not
read the certified artifact enhanced_frs_2024_25: FileNotFoundError",
which gave no sign of the cause. The installed check now stops before any
read when a country whose certified data repository is private (the UK)
needs checking and the token is unset, with a message naming the variable,
never its value. The public US data is still read without it.

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

Adds tests for errors that had none: an invalid region code and a
national region not named after its country; a release manifest without
a dataset map or a region-dataset map, without the reviewed default
dataset, with a region dataset without a path, and with an artifact
without a path, revision or repository; a household entry that is not an
HDF5 group. Also covers a weight matrix with a non-array entry, the
default Hugging Face file system and its token, a verification error the
inspector raises, and the default row lookups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lookup comment and the catalog document now say only that on
2026-10-22 both copies were checked to be identical in content to the
lookups the calculation workers download.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The data reader's docstring and the UK credential document said only HDF5
metadata and column names are downloaded. Reads go through a cache of
1 MB blocks, so they download the blocks holding that metadata, which can
include neighbouring bytes; the household arrays are never read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`make test` runs coverage, which writes .coverage (an SQLite database
with local paths) and coverage.xml at the repository root; neither was
ignored, so a broad `git add` could commit them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The constant holding the environment variable's name is now
HUGGING_FACE_CREDENTIAL_VARIABLE, which the hardcoded-password check does
not flag, so its suppression is removed. The environment variable stays
HUGGING_FACE_TOKEN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y occur

RegionDataVerificationError inherited from CatalogExtractionError, which
is raised before any database access, although verification runs after
the stored catalog versions are read and reads certified data over the
network. It is now its own error, documented as such, and the command
still prints its message. A missing HUGGING_FACE_TOKEN is now reported as
the existing V2ConfigurationError, the error for absent configuration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Region scoping typed the PolicyEngine.py release manifest as object and
read its dataset map, region-dataset map and data package with getattr. A
runtime-checkable Protocol now states those three attributes; a manifest
without them fails with a message naming them, and their values are still
checked where they are read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… release routes them to

The Public API now pins PolicyEngine.py 6.10.0. Its UK release certifies no
constituency or local-authority weight matrix; it routes both region types
to a local-area dataset (microcosm_uk_2024_25_dense) whose households carry
constituency_code_oa and la_code_oa. UK constituencies and local
authorities are now scoped like US states and districts: a row filter on
that column, with the region's ONS code, on the dataset the release routes
their type to. Because the national file has no area codes, they never
fall back to it; a release that routes no dataset to their type stops
publication, naming the region.

The weight_replacement scoping, the weight-matrix artifact checks and the
row-count reading of weight matrices are removed. No release the Public
API can publish uses them: the pinned 6.10.0 release has none, stored
6.7.5 versions keep their regions without being scoped again, and the
Public API's local-authority list now has 361 rows while 6.7.5's matrix
had 360, so that check would be wrong for 6.7.5 anyway.

The installed 6.10.0 catalog now extracts 1,504 regions (488 US, 1,016
UK) and 4 datasets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 2 commits October 26, 2026 17:43
The catalog fixture's UK reference file now also lists Northern Ireland
and Ards and North Down (local_authority/N09000011). The unit tests
assert that the local authority is row-filtered on la_code_oa with its
ONS code in the local-area dataset the release routes it to, that the UK
records no fallback, and that a release routing no dataset to local
authorities stops publication naming the region, as for constituencies.
These replace, at unit level, master's registry-based test of the UK area
regions' dataset.

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

This branch was successfully deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Publish v2 catalog regions from the Public API's own region reference files

1 participant