Skip to content

Compile USDA SNAP state and national caseload targets from FNS average-monthly facts#371

Open
daphnehanse11 wants to merge 3 commits into
mainfrom
snap-caseload-targets
Open

Compile USDA SNAP state and national caseload targets from FNS average-monthly facts#371
daphnehanse11 wants to merge 3 commits into
mainfrom
snap-caseload-targets

Conversation

@daphnehanse11

@daphnehanse11 daphnehanse11 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Implements #370.

What

Maps the FNS household-caseload measure the ledger usda-snap-fy69-to-current package already ships (national + per-state, alongside the total_benefits facts populace compiles today) into calibration targets:

  • ("usda_snap", "average_monthly_households")indicator_sum over SPM units with positive annual snap (target_role snap_households)

It flows through the existing INDICATOR_LEDGER_TARGETS_direct_reference_from_fact machinery, so state facts compile per state with state_fips metadata automatically, exactly like the benefit-dollar targets.

average_monthly_persons is deliberately not mapped (second commit): previewed against the buildi-sparse release, a person indicator over taker-unit members overcounts FNS participants by +48% (61.8M vs 41.7M) because the real SNAP assistance unit is often a subset of the SPM unit (FNS persons per household 1.88 vs 2.82 members per simulated taker unit) and PolicyEngine-US does not model sub-unit participation. A test pins that the persons measure does not compile.

Aggregation contract change

FNS caseload facts carry aggregation: mean (a fiscal-year mean of 12 monthly stock counts), and target_spec_from_ledger_reference rejected every non-sum fact. This PR adds a scoped opt-in: a mapping may declare fact_aggregation: time_mean, asserting the mean is taken over time periods on a stock count — still a linear level target. Per-unit ratios (e.g. average_monthly_benefit_per_person) remain rejected by default, and a new test pins the rejection of mean facts without the contract.

Concept mapping and validation

The compiled counts are of the simulated taker set. The take-up assignment (#294) seeds takers to reproduce the FNS participation rate, so the taker set is the model counterpart of the average monthly caseload rather than an annual-ever count. Previewed on populace-us-2024-buildi-sparse-rmloss100 with shipped weights: 21.89M weighted taker units vs the 22.20M FY2024 FNS national average (−1.4%) with no calibration pressure on counts. Per-state preview in the PR comment below.

Why

The SNAP surface is currently dollars-only. In the default release, 10 states undershoot their benefit targets by 7–43% while a feasibility audit shows all but CA are reachable within the 5× weight cap — the dollar targets lose loss-weight trades (the usda_snap family holds ~0.9% of total loss weight under sqrt-value weighting). Caseload counts land in the count basis, which holds its own half of the 50/50 loss budget, raising SNAP's effective share within the standard scheme; and dollars + caseload jointly pin average benefit per household, anchoring recipient composition for downstream reform work. Full audit numbers in #370.

Dependency

Targets compile only for facts present in the consumer feed. If the current consumer_facts_buildh_v8.jsonl filters out the caseload measures, the consumer bundle needs regeneration before these targets appear in a build.

Tests

  • test_snap_household_caseload_fact_maps_to_snap_indicator (national, household grain)
  • test_state_snap_household_caseload_fact_compiles_with_state_fips (state grain, state_fips)
  • test_snap_person_caseload_fact_is_not_compiled (persons stays unmapped)
  • test__given_mean_fact_without_time_mean_contract__then_compilation_fails (doctrine guard)
  • Full test_us_fiscal_targets.py and test_ledger_targets.py pass.

🤖 Generated with Claude Code

daphnehanse11 and others added 2 commits July 9, 2026 13:55
…e-monthly facts

The ledger usda-snap-fy69-to-current package already ships
average_monthly_households and average_monthly_persons record sets at
national and per-state grain alongside the total_benefits facts populace
compiles today, but only the dollar measures were mapped. That leaves the
SNAP surface dollars-only: a build can hit a state's benefit total with an
unrepresentative recipient set, and nothing pins caseload or average
benefit per household.

Map both caseload measures as indicator_sum targets over the simulated
taker set: SPM units with positive annual snap (households) and their
members via indicator_map_to person (persons). The take-up assignment
(#294) seeds takers to reproduce the FNS participation rate, so the taker
set is the model counterpart of the average monthly caseload.

FNS caseload facts carry aggregation 'mean' (a fiscal-year mean of monthly
stock counts), which the ledger compile guard rejected wholesale. Add an
explicit per-mapping fact_aggregation=time_mean contract: a time-mean of a
stock count is still a linear level target, while per-unit ratios (e.g.
average_monthly_benefit_per_person) remain rejected by default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cipants ~50%

Previewing both indicator targets against the buildi-sparse release showed
the household mapping lands at -1.4% of the FY2024 FNS national average
(21.89M vs 22.20M) with no calibration pressure, while the person mapping
overcounts by +48% (61.8M vs 41.7M): the real SNAP assistance unit is often
a subset of the SPM unit (FNS persons per household 1.88 vs 2.82 members
per simulated taker unit) and PolicyEngine-US does not model sub-unit
participation. Calibrating to the person counts would fight the household
target, so the persons measure stays unmapped, with a test pinning that it
does not compile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@daphnehanse11

Copy link
Copy Markdown
Collaborator Author

Preview of the compiled household-caseload targets evaluated against populace-us-2024-buildi-sparse-rmloss100-6e8e929-20260709T034135Z with its shipped weights (no calibration pressure on counts), FNS FY24 values parsed from the ledger workbook using the source package's cell coordinates:

National: 21.89M weighted taker units vs 22.20M FNS (−1.4%). Feasibility ceiling under the 5× weight cap: 3.75× the target.

States (largest household-count errors, current weights):

state FNS hh (k) model hh (k) error ceiling/target
ID 65 278 +325% 15.1
MT 42 82 +97% 7.7
AK 31 51 +63% 7.2
NH 42 67 +57% 7.9
KS 92 129 +39% 7.7
DC 84 53 −38% 1.6
IL 1,061 732 −31% 3.2
CA 3,129 2,162 −31% 1.05
OK 334 234 −30% 2.7
TX 1,466 1,897 +29% 2.6

Two observations:

  1. The overshoots motivate the target directly: Idaho carries 4× too many taker households while its benefit-dollar target sits at +0.0% — dollars-only calibration leaves average benefit per household off by a factor of ~4 in the worst states. These rows are exactly what this target pins.
  2. Every state's household ceiling clears its target except the territories (GU/VI, which have no support and are already skipped by _state_fips), with CA marginal at 1.05× — consistent with the dollar-side feasibility audit in SNAP target surface is dollars-only: add FNS state/national caseload targets #370.

The persons measure was dropped from the mapping after the same preview showed a structural +48% overcount (see second commit); the per-state persons errors ranged +11% to +398% with the bias always positive, confirming it is a unit-definition mismatch rather than noise.

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.

1 participant