Conversation
calculate_household's default outputs include SPM poverty and the bundle's default SPM selection is county measurement, so every household that gave a state but no county_fips raised SPM_GEOGRAPHY_REQUIRED, even for tax-only questions. With no geography chosen, a household that names no county is now measured nationally (no geographic adjustment), recorded as provenance["spm_geography_source"] == "national_fallback". Households with county_fips keep their county's SPM estimation area; a geography chosen with spm= is used as given; a county named only as county or county_str keeps county measurement and its error; population simulations are unchanged. Fixes #543 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A county input that is None, empty, NaN, pd.NA, or "UNKNOWN" (the county enum default, for county and county_str) now counts as no county, so a state-only payload carrying placeholders gets the national fallback instead of the county error. Real calculations pin None, "", NaN and UNKNOWN against the explicit national result. - SPMSelection's docstring no longer says national geography is always explicit. - Unit tests cover selections without a geography (an SPMSelection instance, hash-only, county_vintage-only) and a different artifact hash under the fallback; the Hypothesis test is derandomized. - Docs say the fallback also covers the capped SPM housing subsidy and reword the geography_kind row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round-2 review: say that "UNKNOWN" counts as no county only for county and county_str, that pd.NA and bytes are absent for the SPM selection but still rejected as inputs by the country model, and treat the text "nan" (a DataFrame converted to strings) as absent, as the country model's county check does. Reflow the SPMSelection docstring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #543
Why
Since 6.0.0,
pe.us.calculate_householdraisesSPMInputError: County selection has no county FIPS input ('')for every household that gives a state but nocounty_fips. Two defaults combine to cause it:spm_unit_is_in_spm_poverty(us/model.py);data/bundle/manifest.json:measurements.spm.geography_kind = "county").So a tax-only question about a state-only household fails. The PolicyEngine skills' own examples do this, and
skill-examplesin PolicyEngine/policyengine-skills fails on 8 of them (run 36653351748).What changes
calculate_householdnow resolves its SPM selection withresolve_household_spm_selection:spmargumentprovenance["spm_geography_source"]county_fipsgivengeography_kind"default"None,"", NaN or"nan", or acounty/county_strof"UNKNOWN"geography_kind"national_fallback"countyorcounty_strSPM_GEOGRAPHY_REQUIREDasking forcounty_fipsgeography_kindchosencounty_fipsstill raises"selection"Households with
county_fipsare unchanged. For a household withoutcounty_fips, existing behavior is kept only where ageography_kindis chosen: a selection without one, such asspm={"scenario": "zero_real"}, used to raise for a state-only household and now falls back too, keeping the caller's other settings. National measurement covers everything that uses the SPM measurement: thresholds and poverty, and, for a unit allocated housing assistance, the capped SPM housing subsidy and the SPM resources built on it. Population simulations (Simulation, decile impacts, dataset runs) still callresolve_spm_selectionand are unchanged; their data must supply observed counties.Docstrings,
docs/households.md,docs/countries.mdandexamples/household_impact_example.pydescribe the new default.provenancegains one key,spm_geography_source.Design choices
National, not a state average. A state alone does not identify a Census SPM estimation area. The calculator's geography kinds are county, metro area and national (
spm_calculator/policyengine_adapter.pyPolicyEngineSPMProvider), so a state-average factor would be a new, synthetic geography. National measurement is already supported and needs no area. It also matches what state-only households got before 6.0, as observed on the production API:api.policyengine.org(policyengine-us 1.764.6) returns the same 2026spm_unit_spm_threshold, 29,418.32, for 2-person households in CA, MS and NY with no county, and in Los Angeles County, so its household thresholds carried no geographic adjustment. (That model'scountyvariable does default to the state's first county, but its SPM threshold did not depend on it.)Here, not in policyengine-us. This package owns the household calculator's defaults, including the default columns that pull SPM poverty into every call. The country
Simulationkeeps its strict contract, which also guards population data. The bundle's US data release is certified for policyengine-us 2.2.1 exactly (certified_for_model_version), so a country-package change would reachcalculate_householdonly after a re-certified repin. This change needs no repin: pins, the bundle manifest and the certified data release are untouched.Reported, never silent. spm-calculator's PolicyEngine adapter has no location fallback ("there is no consumer extrapolation or location fallback policy",
policyengine_adapter.py), and this PR keeps it that way: the adapter and the country model still fail closed, and the fallback is a household-calculator default. The receipt style follows the calculator's release API, whose opt-ingeography_factor(..., missing="national")marks its resultexplicit_national_fallback: here the result'sspm_configsaysnationalandspm_geography_sourcesays why.County names without FIPS raise. County measurement reads only
county_fips. A household that names its county ascountyorcounty_strasked for a county, so it keeps county measurement and gets the error asking forcounty_fipsrather than a national result. Mapping county names to FIPS codes in the wrapper would need policyengine-us's county table, a country-package internal; that can be a follow-up.Invariants (tests/test_spm_household_geography.py)
For every household that names no county, with no geography chosen:
SPMInputError. All 59 state codes are checked: 58 compute. Puerto Rico fails before any SPM formula runs (ValueErrorfromsnap_region, which has no Puerto Rico value in policyengine-us 2.2.1 or main); the test asserts it fails the same way with an explicit national selection and not with an SPM error.spm={"geography_kind": "national"}result output for output, apart fromspm_geography_source. Checked in CA, MS, NY, with a chosen scenario, withcounty_fipsof"",Noneand NaN, withcountyorcounty_strof"UNKNOWN", and by the property test.spm_unit_is_in_spm_poverty == (spm_unit_net_income < spm_unit_spm_threshold). This is the country formula, rechecked on fallback results for consistency.A household with
county_fipsequals the explicit county result (06037, 28001, 36061), and Los Angeles County's adjustment is above 1 with the same unadjusted threshold as national.The property test (Hypothesis, 20 derandomized examples) draws state code, earnings 0–250,000, 0–3 children and tenure, and checks 1–4 on each. Unit tests cover every branch of the selection logic against a stub bundle: a bundle whose default is not county; selections without a geography (an
SPMSelectioninstance, hash-only,county_vintage-only); a different artifact hash, which is still rejected under the fallback; and every absent and present county-input form. (pd.NAand bytes also count as no county for the selection, but the country model rejects them as inputs whatever the geography, so they are unit-tested only.)Test changes
tests/test_spm_household.pypinned the old raise for state-only households under default settings. Those cases now passspm={"geography_kind": "county"}, so they still test strict county mode, and two cases add a county named withoutcounty_fips.Verification
Local, Python 3.13, the pinned bundle (policyengine-us 2.2.1, core 3.32.5, spm-calculator 1.0.0):
tests/test_spm_household_geography.py: 108 passed.tests/test_spm_household.py,test_spm_selection.py,test_spm_selection_schema.py,test_household_calculator_snapshot.py: 92 passed.SPMInputError.spm_geography_source = "national_fallback"; withcounty_fips="06037"it returns threshold 37,779 (adjustment 1.2325).ruff format --check .andruff check .pass.Review
An independent Opus review (subfleet, read-only) found no path that measures nationally when a county was asked for, and found no leakage into population, replay or bundle paths. Round 1 requested changes, all made in the second commit: a stale
SPMSelectiondocstring; missing values and"UNKNOWN"placeholders counted as naming a county; untested selection shapes; derandomized Hypothesis; one docs sentence; and more precise wording in this description. Round 2 approved and asked for wording fixes (made in the third commit): scope"UNKNOWN"tocounty/county_str, say thatpd.NAand bytes fail as inputs, treat the text"nan"as no county, and reflow a docstring.Downstream
Simulationdirectly, so the pending API bundle bump needs the same default for requests that send nospmand no county.county_fipsto every skill example and describes the error as intended. Once this ships, state-only examples work, and Update documentation and syntax #93's framing needs to change.axiom: n/a: wrapper default for SPM geography selection; no policy rule changes.
🤖 Generated with Claude Code