Require state-level SNAP benefit hard targets#256
Conversation
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. |
|
Gate-review evidence (independent review pass, 2026-07-06): Code review: clean. The Your first unresolved checklist item is now resolved with evidence: the current curated feed (the v6 surface used in tonight's Build F staging run) carries 53 state-level Second checklist item lands tomorrow: tonight's Build F staging run produces exactly the per-state SNAP calibration diagnostics you flagged as needed. Merge sequencing: once those diagnostics confirm no zero-support state (the TN-style case), this merges. One design note for a follow-up (non-blocking): the failure message reports only the count ("has 50 match(es), needs 51") without naming the missing state, and the only exemption mechanism is requirement-wide — a per-state diagnostic in the failure line would make future breakage self-explaining. |
e5ec306 to
9ce73ed
Compare
MaxGhenis
left a comment
There was a problem hiding this comment.
Review — still valuable; do not close. Needs a rebase and its two rollout items resolved before it's merge-ready.
I checked whether the merged SNAP train (#350/#352/#353) or the state suites (#345/#319) already cover this. They don't:
- SNAP train (#350/#352/#353) is entirely about SNAP input columns — carrying eligibility/exemption inputs through outputs, seeding pregnancy, seeding ABAWD exemptions. Nothing about calibration-target coverage.
- State suites (#345 7 state-program benchmarks + EITC-by-state cross-check; #319 legislative-reform validation) are backtest/validation suites, not target-profile coverage requirements.
- Current
mainhas neither piece of this PR:gates.pyhas norequired_metadata_keys(only key-valuerequired_metadata, line ~357), andfiscal_targets.pyhas nosnap_state_benefitsrequirement — SNAP is still enforced only at the nationalsnap_total. A build can still silently ship national-only SNAP calibration, which is exactly the gap this PR closes (#255).
The mechanism is genuinely needed, not redundant with the existing state coverage. The existing state_income_tax requirement gets away with bare min_matches=44 because that family has no national row. SNAP is different: the usda_snap/target_role=snap_total family carries both a national row and the per-state rows, so a plain count would let the national row (or a partial state surface) pass. required_metadata_keys=("state_fips",) is the right primitive — it demands state-dimensioned rows (state_fips present) without enumerating 50 FIPS values, and the matching logic is correct (AND of value-matches then key-presence; __post_init__ rejects empty keys). The two profile tests pin exactly the failure modes: a national row and a 50-of-51 surface both fail. min_matches=51 is right (50 states + DC; GU FIPS 66 / VI resolve no PE state FIPS and are skipped — the compile test proves CA→06 maps and Guam is dropped).
What remains before this can go ready (the PR's own checkboxes, still open, and correct):
- Rebase — CONFLICTING/DIRTY.
fiscal_targets.pynow carries ~30TargetCoverageRequirementblocks and has moved a lot since this branch's base; the changes here are additive (one dataclass field, one match clause, one requirement), so the rebase should be mechanical, but it's required. - Confirm the deployed
consumer_facts.jsonlcarries theusda_snap.fy2024.state_benefits.*record sets. This gate makes the build fail if they're absent, so this is a hard data dependency (same class as #334's Ledger-fact dependency) — merging it against an artifact that lacks the state rows would red the release build. - Run a fiscal-refresh build and review per-state SNAP diagnostics — any zero-support state needs a reviewed exclusion (the TN pattern) rather than a gate failure.
The deferred follow-up (state participation counts as indicator targets, pending an average-monthly vs annual-ever bridge) is correctly out of scope here.
Recommendation: keep open and pursue — rebase, tick the two data/rollout boxes, then it's a clean, self-contained hardening. Not closing (and not merging) — review-only per request.
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>
Main's profile-coverage tests (added since this branch's base) assert a complete target surface passes the requirements; the new snap_state_benefits requirement makes 51 state-dimensioned SNAP rows part of completeness, so the fixtures gain complete_snap_state_rows(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9ce73ed to
e1e0911
Compare
|
Addressing the three items: 1. Rebase — done (e1e0911, CI running). The rebase surfaced one real interaction beyond the mechanical conflict: the complete-profile coverage tests that landed with the Build G work ( 2. Deployed-artifact confirmation — verified at the Ledger declaration level; the artifact-pin check remains. The 3. Per-state diagnostics / zero-support exclusions — open, needs a build run. Noting that the Build G per-run So: item 1 closed, item 2 four-fifths closed (declaration + ETL verified; pin grep outstanding), item 3 gated on the next fiscal-refresh run. |
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