Require state-level SNAP benefit hard targets#256
Conversation
The Ledger's usda-snap-fy69-to-current package ships per-state total_benefits facts, and the direct-reference path already compiles state-geography usda_snap rows into state_fips-scoped hard targets. Nothing required them, though: a build consuming a facts artifact that predates the state record sets silently ships national-only SNAP calibration. Add a snap_state_benefits coverage requirement (50 states + DC) so the target-profile gate fails loudly when the compiled registry lacks the state SNAP surface, and extend TargetCoverageRequirement with required_metadata_keys so a requirement can demand state-dimensioned rows by metadata-key presence instead of enumerating FIPS values. Territory rows (GU, VI) resolve no PolicyEngine state FIPS and are skipped at reference compilation, so they never count toward the gate. Closes #255 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
42d651f to
19f77c6
Compare
|
@PavelMakarchuk one open item for merge timing (not code review): this gate makes the 51 state SNAP rows mandatory, so it should land only once the production grep -c 'state_benefits' consumer_facts.jsonl # expect >= 51If the artifact predates the state record sets, merging this will (correctly) fail the next build until the artifact is refreshed — that's the gate doing its job, but better to sequence it deliberately. |
|
Independent review verdict: the mechanism is sound (presence-of-key matching validated non-empty; state SNAP already maps to an active hard target on main, so this enforces an existing surface; orthogonal to #245's CD posture) and local gate/target tests pass. Two things block merge, matching the PR's own unchecked boxes: (1) run one fiscal-refresh build on a current artifact to confirm the deployed consumer_facts carries the state_benefits record sets, and (2) record any zero-support states as reviewed exclusions (TN-style). With per-state diagnostics from that run attached, this is merge-ready. |
Summary
Closes #255.
SNAP currently calibrates to a single national hard target. This PR makes the 51 state-level SNAP benefit totals (50 states + DC) a required part of the US target profile, so a build can no longer silently ship national-only SNAP calibration.
Why the change is this small
Investigation for the 2026-07-01 SNAP research readout found the pipeline is already almost fully wired:
usda-snap-fy69-to-currentalready ships per-statetotal_benefitsfacts (usda_snap.fy2024.state_benefits.<region>record sets,geography_level: state, all 53 state agencies)._direct_reference_from_factalready accepts state-geography facts for mapped families and stampsstate_fipsmetadata — the same path per-state TANF targets use today.state_fips-scoped targets by masking households to the state.So with a current
consumer_facts.jsonl, state SNAP hard targets activate with zero mapping changes. The only gap was enforcement.Changes
gates.py: addrequired_metadata_keystoTargetCoverageRequirement— presence-of-key matching, so a requirement can demand state-dimensioned rows (state_fipspresent) without enumerating 50 FIPS values. Validated non-empty in__post_init__.fiscal_targets.py: add thesnap_state_benefitscoverage requirement (familyusda_snap, rolesnap_total,state_fipskey required,min_matches=51).required_metadata_keys(present/absent/empty-key rejection); compile test proving a state-levelusda_snapfact becomes astate_fips-scoped spec while a Guam row is skipped; profile tests proving 50 state rows or a national-only surface fail the gate; state SNAP rows added to the complete-profile fixture.Draft status / rollout note
This gate makes builds fail if the consumer-facts artifact lacks the state SNAP record sets. Before marking ready:
consumer_facts.jsonlincludes thestate_benefitsrecord sets (the source package parses them from the FNS FY24 workbook, but the deployed artifact vintage needs checking).Follow-up (not in this PR)
State participation counts (
average_monthly_households/average_monthly_persons) as indicator targets — needs an explicit average-monthly vs annual-ever bridge before becoming a hard constraint.Test plan
uv run pytest packages/populace-build/tests— full suite passes (269 tests).ruff check/ruff format --checkclean on changed files.🤖 Generated with Claude Code