Skip to content

Give each US geography one label across target sources - #1336

Draft
anth-volk wants to merge 5 commits into
mainfrom
us-geography-one-label-1335
Draft

anth-volk wants to merge 5 commits into
mainfrom
us-geography-one-label-1335

Conversation

@anth-volk

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

Copy link
Copy Markdown
Contributor

Fixes #1335

Cause

The US release populace-us-2024-buildo-acs-local-766b7c7-20261017T030317Z (public policyengine/populace-us, calibration_diagnostics.json, schema 8) gives each of its 51 state geographies two labels. 0400000US23 is "Maine" on 84 IRS SOI, SNAP and Medicaid targets and "ME" on pop_state_23@2024.

  • population_target_specs in tools/build_us_acs_local_release.py labelled the Census population targets with US_STATE_FIPS_TO_POSTAL (postal codes), and districts as "ME congressional district 1".
  • Ledger targets take a state's label from the source facts' geography name, the Census name ("Maine").
  • geography_constants had no FIPS-to-name table, and nothing checked that one geography id has one label in a registry.

Change 1: one label per geography id

  • US_STATE_FIPS_TO_NAME in microcosm.calibrate.geography_constants: the 51 Census names, District of Columbia included. No display-name table existed. The block ladder's US_STATES held the names only as P.L. 94-171 directory names ("District_of_Columbia"); it now derives them from the new table, and the derived tuple equals the old literal. Checked against two sources:

    • the release file's ledger labels: all 51 equal;
    • every Chronicle source package at the pinned US feed commit (51aa40f, 228 package YAMLs): each state and the nation have exactly one declared name, equal to these.
  • State population targets use that name.

  • Congressional districts. No other source in the 766b7c7 release uses 5001900US ids; only the 436 population targets do. Two other producers in the repository do:

    • the IRS SOI district rows translated to the 119th Congress plan (--soi-mode full and state_cd), labelled by _congressional_district_label. They disagreed with the population targets on at-large districts: "AK at-large congressional district" against "AK congressional at-large";
    • Chronicle's ACS S0101 and S2201 district packages, which name the same ids with the Census Bureau's names. They are not in the pinned feed's scope today.

    So I chose the Census Bureau's name for both Microcosm producers: "Congressional District 1 (119th Congress), Alabama", "Congressional District (at Large) (119th Congress), Alaska", and "Delegate District (at Large) (119th Congress), District of Columbia". It comes from one public function, congressional_district_label in microcosm.build.us_runtime.congressional_district_vintage. It spells the state the way the state geography does, and a feed that adds the Census district facts will agree with it.

  • Docs. docs/shared-constants.md documents the table and the district function. docs/calibration-target-hierarchy.md gets a short "One label per geography" section.

  • Not changed. _geography_fallback_label in ledger_targets.py still reads only the UK catalog. _validate_chronicle_hierarchy_labels requires labels owned by Chronicle rather than built from identifiers, and the US facts all carry names. Medicaid's Rhode Island substitution keeps its reviewed state_name, which equals the table.

Change 2: a check that fails early

  • Where. TargetRegistry.__init__ (microcosm.calibrate.registry). Every assembly site therefore runs it: US, UK, NZ, AM, BE and test registries, plus select() and from_json. It runs where targets are assembled into a registry, before calibration. geography_label_conflicts(specs) returns {id: {label: first target}}.

  • Scope. A us registry raises GeographyLabelConflictError (a ValueError). The message names the id, every label and the first target carrying each, for the first five ids plus a count of the rest. Every other country's registry emits a GeographyLabelConflictWarning. The list of refusing countries is GEOGRAPHY_LABEL_REFUSING_COUNTRIES.

  • Why UK only reports. UK publishers name one code differently, and those names reach the ledger labels. In Chronicle's UK packages:

    • nations and regions: "Yorkshire and The Humber" and "Yorkshire and the Humber" (E12000003); "East" and "East of England" (E12000006); "Scotland" and "Scotland (inc unallocated)" (S92000003, DESNZ); likewise K03000001 and K04000001;
    • local authorities: 63 E06 unitary authorities carry two or three names, such as "Hartlepool" (DESNZ, DWP) and "Hartlepool UA" (HMRC SPI, VOA), or "Bristol", "Bristol UA" and "Bristol, City of"; also "St Helens" and "St. Helens", and "Isle of Anglesey" and "Isle of Anglesey / Ynys Môn".

    The UK releases are private, so I could not check them. Refusing would stop UK builds on names nobody has reconciled yet. Choosing one label per UK code (for example from UK_GEOGRAPHY_ID_TO_LABEL and local_authority_names.json) is follow-up work; the warning names every case.

  • Earlier refusal in the ACS local build. Its admin targets and population targets first share a registry in write_lean_checkpoint, after the engine pass over every staged household. do_materialize now reads the ladder and assembles both into one registry (require_one_label_per_geography) right after the admin compile, before it reads the staging frame. population_target_name in tools/us_acs_local_cd_surface.py gives both paths the same target names.

  • Old checkpoints. A lean checkpoint written before this change, with two labels for one US id, is refused when --stage calibrate reloads it. The message tells the operator what to change; re-run --stage materialize.

Sweep before refusing

  • JSON fixtures. All 529 tracked .json/.jsonl/.ndjson files were scanned. None stores a calibration hierarchy. Two carry source-fact geography names (5,432 records), with no id named two ways.
  • Every place a registry is assembled. A temporary report-only pytest plugin (outside the repository) recorded each TargetRegistry construction in the 74 engine-free test modules that assemble registries with hierarchies. That is about 10% of the test files, not the full suite.
    • Before the fix: 7,088 constructions (us 3,643; uk 1,006; nz 323; am 8; be 5; test country 2,103), 6,142 of them with labelled specs, in 38 modules. Conflicts appeared only in US fixtures: 131 constructions in 24 tests in two modules.

      • test_us_fiscal_targets.py: the fixture facts name the nation "Test geography 0100000US" in some builders and "United States" in others.
      • test_us_acs_local_target_loss_weights.py: admin targets labelled with their ids.

      Both fixtures now name ids the way production facts do, through the shared table and function.

    • After the fix, with refusal off in the plugin: 7,113 constructions and no conflicts other than the three my new tests create on purpose.

    • No engine test (engine_contract, engine_scenario, engine_workflow, engine/uk) compiles a labelled registry.

  • Real US releases, with the fix applied.
    • 766b7c7: the published 4,765 targets are refused for exactly the 51 state ids. With the fixed population targets they assemble into one registry: 487 ids with one label each, 51 of them shared between the population targets and the ledger targets.
    • National release populace-us-2024-0fb05b6-4b57d15a287c-20260930T150401Z: all 5,694 targets are accepted as published (52 ids).
  • Full compiled US registry. It also holds rows that neither release shows. In it, nation and state names come from the Chronicle packages (one name each, above). District rows come only from the IRS SOI translation, because the pinned feed's only district record set is irs_soi.ty2023.congressional_district_2022.all_returns at 5001700US.
  • A caveat for the next US feed update. Chronicle's US county packages name 50 county ids two ways: irs_soi/county_2022 truncates names to 20 characters ("Aleutians East Borou"), while census/pep_county_2024 has "Aleutians East Borough". Neither package is in the pinned feed's scope, and no US registry compiles county rows today. A feed update that adds both would not be refused, because no US compile path accepts county facts (see Review fixes); a change that starts compiling county targets from both would need one name per county first.

Red and green by commit

  1. bf12d2d8 adds the tests. On main, the four test files give 8 failed, 80 passed, each failure for its stated reason:
    • assert 'ME' == 'Maine': state population label;
    • assert {'ME', 'Maine'} == {'Maine'}: population and ledger labels for one state;
    • population and SOI district labels differ at the at-large ids;
    • AssertionError: the staging frame was read before the geography labels were checked;
    • DID NOT RAISE <class 'ValueError'>: US registry with two labels;
    • DID NOT WARN: UK registry with two labels;
    • AttributeError: ... no attribute 'US_STATE_FIPS_TO_NAME';
    • assert 'CA congressional district 1' == 'Congressional District 1 (119th Congress), California'.
  2. afc64901 adds the fix and leaves those tests unchanged: 88 passed.
  3. After the fix, only the added or modified test files ran: 96 passed. That is the four above plus test_us_acs_local_target_loss_weights.py (8). The engine-free modules that import the changed fixture helper also ran: test_us_fiscal_targets.py, test_us_eitc_decomposition_uprating.py and test_us_soi_congressional_district_vintage.py, all passing apart from 5 skips that need the private pinned feed. ruff check and ruff format --check pass on the 13 changed Python files, and tools/ci_test_plan.py verify is ok. CI runs the rest.

Not in scope: dimension value labels

In populace-us-2024-0fb05b6-4b57d15a287c-20260930T150401Z, dimension us:statutes/26/62#adjusted_gross_income value under_1 is "No adjusted gross income" on 106 targets (Historic Table 2 state AGI rows). It is "No adjusted gross income and deficit" on 2 targets (irs_soi.ty2024.filing_season_week47.eitc_by_agi.under_1.*).

  • Source. The label comes from Chronicle's irs_soi/filing_season_week47_2024 package. Its row label for under_1 is the publication's row header, "No adjusted gross income and deficit", although that package's own value map says "No adjusted gross income", as Historic Table 2, Table 1.1 and Table 2.5 do.
  • Path into Microcosm. _inherited_dimensions in ledger_targets.py takes the value label from the fact's layout.groupby_value_label. _validate_chronicle_hierarchy_labels compares labels only among one target's member facts, never across a registry.
  • Other variants in the same file. adjusted_gross_income / all is "All returns" (47) and "All returns, total" (53). age / 85_plus is "Age 85 plus" (51, Census PEP state) and "Age 85 and over" (1, Census PEP national).
  • Recommendation. The same registry check should cover dimension labels and dimension value labels later, keyed by (dimension id) and (dimension id, value id). It should start as report-only, because it would refuse that national registry today. The labels should be reconciled in Chronicle, which owns them, before it refuses.

Overlap with open PRs

packages/microcosm-build/tests/engine_free/us/test_us_acs_local_target_loss_weights.py is also changed by #1298 (stack #1296 → #1297). #1298 adds an import and a test; this PR changes the fixture's _hierarchy label and adds two imports. A three-way merge of the file against #1298's head has no conflicts. No other file overlaps #1296, #1297 or #1298, and this PR does not touch docs/agent-guide.md, which all three change. uv.lock is unchanged. No workflow, Docker, Terraform or environment-variable change.

Review fixes

A first review checked the state names against the release file (all 51 equal), the district names against the Census Bureau's own ACS 2024 names stored in Chronicle (all 436 districts equal; DC compared with the Census row 5001900US1198), the red and green counts above, and the overlap with #1296, #1297 and #1298. It found one defect and corrected three statements.

  1. d1ef7474 adds test_calibrate_refuses_an_earlier_checkpoint_and_names_the_remedy to test_us_acs_local_geography_labels.py. A checkpoint written by the earlier materialize stage ("ME" beside "Maine") made --stage calibrate stop with the registry's GeographyLabelConflictError, which names the labels but not the remedy. Red: 1 failed, 4 passed in the module: GeographyLabelConflictError was raised instead of SystemExit.
  2. fe235980: load_checkpoint_surface turns that error into the tool's usual SystemExit for an unusable checkpoint. The message names the registry file, says that checkpoints written before Give each US geography one label across target sources #1335 give a geography two labels, says to re-run --stage materialize with the current tool, and keeps the ids and labels. The changelog fragment says so. The test is unchanged. Green: 5 passed.
  3. bb3f7c88 (comment only): the note on ACS_LOCAL_PER_DISTRICT_METADATA gave the Census district name as the example for both ledger_fact_label and ledger_layout_groupby_value_label. The vintage translation sets only the group value label and the geography name; it copies the source fact's label unchanged.

Corrections to this description:

  • County names. The caveat above is narrowed: a feed update that adds both county packages would not be refused. No US compile path accepts county facts: _soi_reference_from_fact and _population_age_reference_from_fact take only the country, state and congressional_district levels, and _direct_reference_from_fact only state and country. The ACS local admin surface uses the same compiler. The two county names would conflict only in a change that starts compiling county targets from both packages, and that change should choose one name per county first.
  • DC's district id. Chronicle's ACS district facts and the Census Bureau number DC's delegate district 98 (5001900US1198). Microcosm's crosswalk and PUMA population file use 00 (5001900US1100). So those facts agree with the labels here for the 435 seats, but for DC they would add a second id with the same label. The registry check does not object, because it checks one label per id. This predates the PR.
  • Old checkpoints. The statement that the message tells the operator to re-run --stage materialize is true from fe235980.

Locally, only the changed test file ran (5 passed), plus ruff check ., ruff format --check on the changed Python files, and tools/ci_test_plan.py verify (ok). uv.lock and the workflows are unchanged.

🤖 Generated with Claude Code

anth-volk and others added 5 commits October 21, 2026 03:33
These tests fail on main (8 failed, 80 passed across the four files):

- the ACS local release's population targets label a state with its
  postal code ("ME") where the ledger targets use the Census name
  ("Maine"), and label at-large districts differently from the IRS SOI
  district rows for the same id;
- the IRS SOI district rows translated to the 119th Congress plan are
  not labelled with the Census Bureau's district names, which
  Chronicle's ACS district facts carry for the same ids;
- microcosm.calibrate.geography_constants has no US state FIPS-to-name
  table;
- a US target registry accepts a geography id with two labels, and a
  registry for another country does not report one;
- the ACS local materialize stage reads the staging frame before it
  checks the admin and population target labels together.

Refs #1335

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ACS local release labelled each state's Census population target
with its postal code ("ME") where the ledger targets use the state's
Census name ("Maine"), so its calibration diagnostics file gave all 51
states two labels.

Labels:
- Add US_STATE_FIPS_TO_NAME to microcosm.calibrate.geography_constants:
  the 51 names the Chronicle state facts declare and the release's ledger
  targets carry. The block ladder's US_STATES table derives its P.L.
  94-171 directory names from it (the derived table equals the old
  literal).
- population_target_specs labels a state with that name and a district
  with congressional_district_label, the Census Bureau's district name
  ("Congressional District 1 (119th Congress), Alabama"). The IRS SOI
  district rows translated to the 119th Congress plan use the same
  function, which replaces the postal-code labels that disagreed with the
  population targets on at-large districts. Chronicle's ACS district
  facts carry these names for the same ids.

Check:
- TargetRegistry refuses a US registry in which one geography id has two
  labels (GeographyLabelConflictError names the id, the labels and the
  first target carrying each) and reports it for other countries
  (GeographyLabelConflictWarning), because UK publishers name some
  regions, nations and local authorities differently.
- The ACS local materialize stage assembles its admin targets and the
  ladder's population targets into one registry before reading the
  staging frame, so a conflict stops the build before the engine pass.
- Two test fixtures gave one id two labels and now name ids the way
  production facts do: the US fiscal fixtures ("Test geography 0100000US"
  and "United States") and the ACS local loss-weights fixture (ids as
  labels).

The tests from the previous commit pass unchanged: 88 passed across the
four files.

Fixes #1335

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lean checkpoint written by the materialize stage before this branch
labels a state's population target with its postal code ("ME") next to
ledger targets labelled "Maine". The registry now refuses that
target_registry.json, but --stage calibrate stops with the registry's
GeographyLabelConflictError, which names the labels and not what the
operator should do, although the pull request says the stage tells the
operator to re-run --stage materialize.

The test writes such a checkpoint and expects load_checkpoint_surface to
exit with that remedy. It fails on this branch (1 failed, 4 passed in the
module): GeographyLabelConflictError is raised instead of SystemExit.

Refs #1335

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
load_checkpoint_surface (the calibrate stage's checkpoint reader) now
turns the registry's GeographyLabelConflictError into the tool's usual
SystemExit for an unusable checkpoint: it names the registry file, says
that checkpoints written before microcosm#1335 give a geography two
labels, tells the operator to re-run --stage materialize with the current
tool, and keeps the registry's message with the ids and labels. The
changelog fragment says the refusal carries that message.

The test from the previous commit passes unchanged: 5 passed in
test_us_acs_local_geography_labels.py.

Refs #1335

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The note on ACS_LOCAL_PER_DISTRICT_METADATA gave the Census district name
as the example for both ledger_fact_label and
ledger_layout_groupby_value_label. The vintage translation sets only the
group value label (and the geography name) to the current district's
Census name; it copies the source-plan fact's label unchanged into the
translated fact, so ledger_fact_label keeps it. Comment only.

Refs #1335

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

This branch has not been 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.

Give each US geography one label across target sources

1 participant