Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1376-us-county-fips-2023.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
US household ladders write `county_fips_2023`, each household's 2023 county-equivalent, beside the engine's legacy `county_fips`: Connecticut households take the planning region of their assigned 2020 tract, or, without a tract rung, a tract drawn after the ladder's original draws conditional on their PUMA and 2020 county; every other state's code is unchanged. The new draw module is attested by the seed protocol, so the seed-protocol and seed-map digests, the configuration field counts and pointer inventory, the loader golden vector, the US pool-tool `spec_sha256` and `docs/evidence/spec-engine/us-f0-coverage.json` are re-pinned at their observed values (microcosm#1376, for the LWI County Files).
5 changes: 5 additions & 0 deletions changelog.d/us-county-2023-geography.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
US block and PUMA household ladders now carry `county_fips_2023` for county
calibration on the 3,144-area 2023 geography, including Connecticut's nine
planning regions. The existing `county_fips` remains on the locked engine's
2020 geography. Packaged Census crosswalks carry source digests and population
conservation evidence, and geography gates validate the additional column.
6 changes: 6 additions & 0 deletions docs/agent-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,12 @@ and the ACS local-area `stored_inputs` gate, each naming the built-with engine
on which an SPM unit the built-with engine measures has no classified adult,
and binds the `spm_measurement_composition` gate the publisher requires
(microcosm#1263; `microcosm.build.us_runtime.spm_measurement_gate`).
US household ladders carry both `county_fips` (2020 counties for the locked
policyengine-us rules) and `county_fips_2023` (3,144 county-equivalents for
calibration, including Connecticut's nine planning regions). The latter is
registered non-engine metadata. Geography gates require both columns; older
artifacts need the new mapping before they pass. County target joins must
choose the 2023 column explicitly. See [the vintage investigation](us-county-2023-geography.md).
A source-enrichment publisher claim widens the range
consumers accept, not what was checked: the bundle's
`compatibility.claim_measurement` names the one version the probe measured.
Expand Down
32 changes: 16 additions & 16 deletions docs/evidence/spec-engine/us-f0-coverage.json
Original file line number Diff line number Diff line change
Expand Up @@ -736,8 +736,8 @@
"legacy_sinks": [],
"mode": "compiler_semantic",
"pointer_class": "all",
"pointer_count": 826,
"pointer_sha256": "7ff2d5d1c2fd8026d17a57244f969dc0e9625a9e47304b693ad15df329041282",
"pointer_count": 827,
"pointer_sha256": "525ed8621baa2c6e0c0970a6cec85b3b250f8aadfe93750a08deb0d9f240f15e",
"rationale": null,
"relative_sink_prefix": null,
"source_prefix": "/resolved/seed_protocol",
Expand Down Expand Up @@ -772,21 +772,21 @@
"verifier": "vintages"
}
],
"configuration_field_count": 42217,
"consumed_field_count": 42217,
"configuration_field_count": 42218,
"consumed_field_count": 42218,
"generation0_effect_counts": {
"legacy_behavior": 38531,
"no_generation0_effect": 3686
"no_generation0_effect": 3687
},
"mode_counts": {
"compiler_semantic": 27717,
"compiler_semantic": 27718,
"front_end_validation": 354,
"identity_only": 103,
"legacy_behavior": 14043
},
"multiple_primary_use_field_count": 0,
"pointer_inventory_sha256": "c1e2a118a0d53db0bb628b14357a2c86db2956332f5b359c9092764f9c451a95",
"resolved_binding_field_count": 9780,
"pointer_inventory_sha256": "a0da3562ad5559cbbcdf5ee14ebd21db2f067c90e7e470e24d4e3762d7b90848",
"resolved_binding_field_count": 9781,
"unused_field_count": 0
},
"inventory_coverage": {
Expand Down Expand Up @@ -1656,13 +1656,13 @@
"compiler_ir.node_slices"
],
"expected": {
"map_sha256": "3cf71bdd3e6df567a4f9eb8d182bea160108cc9941e6ff08b8a0eee4e9fc8ba2",
"protocol_sha256": "15e9b110999c37a0a78ecd001d6b79bbd860b408cf08a1e0ea435690f0a5f041"
"map_sha256": "58741b7c6c8ca8f0ead476ec9893355419c9550d9bdb9287bec57928ce345afb",
"protocol_sha256": "b4c450a618c5ca3116f806d1ae270ae6fb410517404c204e505d10418bc1181f"
},
"failures": [],
"observed": {
"map_sha256": "3cf71bdd3e6df567a4f9eb8d182bea160108cc9941e6ff08b8a0eee4e9fc8ba2",
"protocol_sha256": "15e9b110999c37a0a78ecd001d6b79bbd860b408cf08a1e0ea435690f0a5f041"
"map_sha256": "58741b7c6c8ca8f0ead476ec9893355419c9550d9bdb9287bec57928ce345afb",
"protocol_sha256": "b4c450a618c5ca3116f806d1ae270ae6fb410517404c204e505d10418bc1181f"
},
"status": "covered"
},
Expand All @@ -1677,7 +1677,7 @@
"compiler_ir.seed_stream_map"
],
"expected": {
"implementation_sha256": "15e9b110999c37a0a78ecd001d6b79bbd860b408cf08a1e0ea435690f0a5f041",
"implementation_sha256": "b4c450a618c5ca3116f806d1ae270ae6fb410517404c204e505d10418bc1181f",
"protocol": "legacy-v1",
"streams": [
"build_model",
Expand All @@ -1698,7 +1698,7 @@
},
"failures": [],
"observed": {
"implementation_sha256": "15e9b110999c37a0a78ecd001d6b79bbd860b408cf08a1e0ea435690f0a5f041",
"implementation_sha256": "b4c450a618c5ca3116f806d1ae270ae6fb410517404c204e505d10418bc1181f",
"protocol": "legacy-v1",
"streams": [
"build_model",
Expand Down Expand Up @@ -2599,7 +2599,7 @@
"country": "us",
"schema_id": "country_spec",
"schema_version": 1,
"spec_sha256": "97f41a9fedba0ed1f26dd5f457997c10ffefe852c75f8fc18c793acdf21ebfd3"
"spec_sha256": "77df608c9b343219cd2717aa08af5198c0ba69b70c61691fb2459f7a886528ba"
}
},
"report_schema_version": 3,
Expand All @@ -2609,7 +2609,7 @@
"country": "us",
"schema_id": "country_spec",
"schema_version": 1,
"spec_sha256": "97f41a9fedba0ed1f26dd5f457997c10ffefe852c75f8fc18c793acdf21ebfd3"
"spec_sha256": "77df608c9b343219cd2717aa08af5198c0ba69b70c61691fb2459f7a886528ba"
},
"status": "pass"
}
124 changes: 124 additions & 0 deletions docs/us-county-2023-geography.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# US household county geography, 2023

Investigation for microcosm#922 item 3, against base `dec45a439`.

## Engine and downstream contracts

`uv.lock:1692–1707` pins **policyengine-us 2.2.1**, with wheel SHA-256
`0993a6c73fcdfbe171a796aca9d302741bd8e00ab83388ad81c15a08740318c6`.
The [locked wheel](https://files.pythonhosted.org/packages/5e/09/86ac3968d2a18e28fce3ba1186fc21728e9f299b96a806d6f204f1e0aaed/policyengine_us-2.2.1-py3-none-any.whl)
was independently downloaded and hash-verified. Paths below refer to files in
that wheel; there is no matching GitHub version tag. Inspection without
importing the engine finds:

- `policyengine_us/variables/household/demographic/geographic/county/county_enum.py:315–322`
names Connecticut's eight legacy counties, rather than planning regions.
- `policyengine_us/variables/household/demographic/geographic/county/county.py:36–69`
joins `county_fips` to the engine county dataset, converts the result to
that enum, and falls back to the first county in the state on an unrecognized
code. Replacing Connecticut's legacy codes would therefore change rules
evaluation rather than merely relabel geography.
- `policyengine_us/tools/geography/county_helpers.py:12–23`
explicitly loads `county_fips_2020.csv.gz`. Its Connecticut rows are
`09001,09003,09005,09007,09009,09011,09013,09015`.
- `policyengine_us/variables/input/geography.py:130–135`
declares `county_fips` as a string household input. The engine's
`policyengine_us/variables/gov/states/ct/oec/c4k/ct_c4k_region.py:22–30`
uses county enum names to approximate Care 4 Kids regions. The unrecognized
planning-region codes would fall back to Fairfield, changing the program's
regional computation.
- `packages/microcosm-frame/src/microcosm/frame/adapters/policyengine_us.py:670–682`
documents the engine's default county-based SPM measurement and its use of
stored `county_fips`.
- `packages/microcosm-build/src/microcosm/build/us_runtime/geography_ladder.py:443–447,500–525`
uses `county_fips` for NYC shares and requires the legacy county to match the
block prefix. `puma_ladder.py:464–468,517–536` also checks county/state and NYC
geography. These contracts must keep their current interpretation.
- `packages/microcosm-build/src/microcosm/build/us_runtime/base_pool.py:529–547`
checks donor `county_fips` against the legacy tract prefix during pooling.

**Decision:** add the five-digit text household column `county_fips_2023`.
Preserve `county_fips` for the locked engine, and preserve 2020 block/tract IDs.
Register the new column as deliberately stored geography metadata, since it
is not a PE-US input (`packages/microcosm-data/src/microcosm/data/stored_inputs.py:320`).

## Reusing the existing mapping

PR #1072 already supplies the correct route:

- `block_ladder_sources.py:225–275` parses the 2020 BAF MCD block-to-town layer.
- `block_ladder_sources.py:393–515` parses the official town-to-planning-region
crosswalk and joins blocks through their towns, refusing missing towns.
- `tools/build_us_block_ladder_artifact.py:67–82,285–310` identifies the Census
source URLs and applies that join for CBSA assignment.

These paths are under `packages/microcosm-build/src/microcosm/build/us_runtime/`
unless prefixed with `tools/`. Read-only inspection of the existing cached
sources finds 169 defined towns, nine planning regions, and 49,845 defined-town
blocks. All 880 corresponding 2020 tracts resolve to exactly one planning
region. Undefined coastal-water geography is outside that support. A compact
tract table can therefore reuse the existing block/town mapping without
deriving boundaries or guessing from legacy counties.

The PUMA ladder's default county-only path needs a population-weighted tract
draw conditional on the already drawn PUMA and legacy county. This draw runs
after the existing draws and supplies only the new county column. With
`assign_tract=True`, the new county comes directly from the assigned tract.

## Implementation

The compact crosswalk, original Census town crosswalk, and provenance JSON
are packaged under `us_runtime/data/`. The loader checks SHA-256 digests.
`tools/build_us_county_vintage_crosswalk.py` reproduces them from the existing
cached CT BAF, town crosswalk, and P.L. 94-171 archives using the maintained
parsers. Its normal CLI was run successfully without building a ladder.

Both household ladder assignments now write `county_fips_2023`. PUMA's
conditional draw is isolated in `puma_county_vintage.py`. The new stored-input
register entry is bound to its producer by tests. Both geography gates require
the additional column and check completeness, text format, valid CT regions,
non-CT identity, and agreement with assigned block/tract geography when present.
Consequently, older artifacts without the new column fail these updated gates
and the default L0 export's required-geography check.

## Validation

Engine-free checks cover national support of 3,144 county-equivalents in the
50 states plus DC, one region per CT town, non-CT identity, one non-null value
per household, and CT population conservation across county vintages. Hypothesis
checks generated mapping invariants and both PUMA tract modes are exercised.
The source-backed crosswalk conserves Connecticut's 3,605,944 people across
all 42,008 populated blocks, eight legacy counties and nine planning regions.

The primary engine-free run passed **275 tests**, with **one skip**, serially
on Python 3.14.7. It covered the four new mapping/assignment/gate modules and
the existing block/PUMA ladder, stored-input register, L0 export, base-pool,
and block/PUMA source suites. `uv run ruff check .`,
`python3 tools/ci_test_plan.py verify`, and `git diff --check` passed.
Two additional seeded regression tests passed against outputs captured from
the original `dec45a439` PUMA implementation: PUMA, CD, legacy county and optional
tract assignments remain unchanged. Across both invocations: **277 passed,
one skipped**.

No full ladder NPZ is present in this workspace or the existing geography
cache. Use source-derived fixtures for CI and provide an optional read-only
check against an existing national ladder.

The skipped test is
`test_us_county_vintage.py::test_existing_full_ladder_has_3144_counties_without_rebuilding`.
To run it, set `MICROCOSM_US_BLOCK_LADDER_ARTIFACT` to an existing national
block-ladder NPZ and invoke that pytest node. The passing fixture check covers
the complete county support universe using 4,015 actual Census tracts; it does
not certify a full NPZ artifact. No ladder or release was built or published.

## Release-tool follow-up

The task excludes edits to `tools/build_us_acs_local_release.py` and
`tools/us_acs_local_cd_surface.py`. In particular,
`tools/build_us_acs_local_release.py:1203–1211` has a lean household checkpoint
allowlist containing only `county_fips`; that checkpoint will need to retain
`county_fips_2023` before the local county-target lane can use the new geography.
County calibration/target joins must explicitly select the 2023 column while
engine computation continues to use the legacy column.

axiom: n/a: data geography infrastructure
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
from .schemas import load_schema_registry

EXPECTED_AUTHORED_FIELD_COUNT = 32_437
EXPECTED_RESOLVED_BINDING_FIELD_COUNT = 9_780
EXPECTED_CONFIGURATION_FIELD_COUNT = 42_217
EXPECTED_RESOLVED_BINDING_FIELD_COUNT = 9_781
EXPECTED_CONFIGURATION_FIELD_COUNT = 42_218


class FieldUsageError(AssertionError):
Expand Down Expand Up @@ -453,8 +453,8 @@ def _path_inventory(rows: Sequence[tuple[str, object]]) -> tuple[int, str]:
"6a781915fd491d2c4b16d2b7d482f69cf362c904130093c59f9629f7a319269b",
),
"resolved_seed_protocol": (
826,
"7ff2d5d1c2fd8026d17a57244f969dc0e9625a9e47304b693ad15df329041282",
827,
"525ed8621baa2c6e0c0970a6cec85b3b250f8aadfe93750a08deb0d9f240f15e",
),
"resolved_seed_site_bindings": (
277,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,10 @@
# keeps the US bracket, and the budget search, which the US solve does
# not run, changes its probes, so again no US solve changes. #454: the
# meps_esi_premiums stage joins the attested sources and the source
# manifest.
"seed_map": "3cf71bdd3e6df567a4f9eb8d182bea160108cc9941e6ff08b8a0eee4e9fc8ba2",
"seed_protocol": "15e9b110999c37a0a78ecd001d6b79bbd860b408cf08a1e0ea435690f0a5f041",
# manifest. #1376: the puma_county_vintage draw module joins the seed
# protocol's attested kernel modules.
"seed_map": "58741b7c6c8ca8f0ead476ec9893355419c9550d9bdb9287bec57928ce345afb",
"seed_protocol": "b4c450a618c5ca3116f806d1ae270ae6fb410517404c204e505d10418bc1181f",
"source_manifest": "629a0712659a26b2d6aa6c0e5672421f415d625e0599d6baa0a834d118953c6a",
"take_up": "9522ce40f7dea569312dd7a7beb474e5a5afadd2f1e10cb5876534c3ef623d35",
"tail": "ac92829c88a1a4fb6460d61190918d5d99c6c377fc8dd8f62f02b332d09bf59c",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ def _site(
"microcosm.build.us_runtime.puf_qrf_chain",
"microcosm.build.us_runtime.puf_source_agi",
"microcosm.build.us_runtime.puf_support",
"microcosm.build.us_runtime.puma_county_vintage",
"microcosm.build.us_runtime.puma_ladder",
"microcosm.build.us_runtime.retirement_contributions",
"microcosm.build.us_runtime.retirement_distributions",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import pandas as pd

from microcosm.build.us_runtime.acs_pums import ACS_2024_1YR_SPINE
from microcosm.build.us_runtime.county_vintage import (
COUNTY_FIPS_2023_COLUMN,
county_fips_2023_from_tracts,
)
from microcosm.build.us_runtime.puf_support import (
support_channel_column,
support_clone_index_column,
Expand Down Expand Up @@ -684,6 +688,16 @@ def _assign_pooled_puma_ladder(
assert preserved_cd is not None and preserved_county is not None
assigned.loc[donor_rows, "congressional_district_geoid"] = preserved_cd
assigned.loc[donor_rows, "county_fips"] = preserved_county
# The sampled county is overwritten above; the retained tract also
# determines the donor's calibration county, including CT regions.
# _derived_donor_puma validated the tracts as integral, but pooling
# beside tractless ACS rows can leave them as floats.
tracts = pd.to_numeric(assigned.loc[donor_rows, "tract_geoid"])
assigned.loc[donor_rows, COUNTY_FIPS_2023_COLUMN] = (
county_fips_2023_from_tracts(
[f"{tract:011d}" for tract in tracts.astype(np.int64).tolist()]
)
)
assigned.drop(columns=[_PUMA_LADDER_ANCHOR_COLUMN], inplace=True)
tables["household"] = assigned

Expand Down
Loading
Loading