Add pre-OBBBA SNAP ABAWD work requirements counterfactual reform#8944
Add pre-OBBBA SNAP ABAWD work requirements counterfactual reform#8944daphnehanse11 wants to merge 12 commits into
Conversation
Models 2026/2027 SNAP rules as if the P.L. 119-21 (OBBBA) Section 10102 ABAWD work requirement changes had not passed, by neutralizing is_snap_abawd_hr1_in_effect so the baseline formulas take their pre-HR1 snapshot path in every state. Fixes PolicyEngine#8943 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Replace neutralize_variable with a per-period formula override so a future-dated toggle no longer applies pre-OBBBA rules retroactively (verified: 2028-dated toggle leaves 2026 at current law). - Document scope and limitations in the reform module: waiver geography and data-assigned discretionary exemptions are not reverted, the FRA 2030 sunset is not modeled, and the Medicaid community engagement pass-through intentionally follows the counterfactual SNAP rules. - Narrow the parameter description and changelog to the Section 10102 exemption and age provisions. - Add tests: period gating (toggle off => current law) and the Medicaid pass-through interaction (57-year-old work-program participant). - Note on the baseline is_snap_abawd_hr1_in_effect that it must stay the sole access point for the underlying parameters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8944 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 3 1 -2
Lines 55 14 -41
=========================================
- Hits 55 14 -41
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Covers the bypass flag, the default-inactive return-None path, and the 5-year lookahead window (activation at the simulation year, at the window edge, and non-activation just beyond it) using a lightweight parameter stub, addressing the codecov/project gap on the factory's non-bypass path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two current-law cases (no reforms key) contrast post-OBBBA behavior against the counterfactual and exercise the baseline is_snap_abawd_hr1_in_effect formula, restoring codecov project coverage for the file the selective runner scopes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ian exemption behavior Verified via a 672-cell monotonicity sweep (ages x hours x states x exemption flags): the reform only loosens the requirement except for the OBBBA-created Indian exemption, which correctly does not exist in the counterfactual — now documented in the module docstring. A month-by-month sweep over 2025-2028 confirms pre-toggle months match baseline and each state flips at its true adoption date. End-to-end, an age-60 non-worker with no income goes from $0 to $298/month SNAP under the reform; codified as is_snap_eligible contrast tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The case fails on Linux CI only (computed [0,1], expected [1,1]) while passing on macOS with identical package versions; asserting is_snap_abawd_hr1_in_effect isolates whether the reform override or the dependent-child linkage diverges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The case failed only on Linux CI with explicit spm_units/tax_units blocks while the equivalent baseline cases (people + households only) pass on all platforms; instrumented CI runs showed the hr1 switch was correct and the parent/dependent-child linkage was what diverged. Mirror the proven baseline structure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lure Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both parent cases fail on Linux CI regardless of dependent threshold, so the parent/qualifying-child linkage is what diverges; asserting is_parent, is_tax_unit_dependent, and monthly_age per person will name the component that drops. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Input assertions passed on Linux, so the remaining suspect is the SPM unit aggregation: assert spm_unit_size in the diagnostic case and add an explicit spm_units block (without tax_units) to the 15-year-old case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Upstream PolicyEngine#8942 (merged after this branch was cut) removed the exempt_parent path from meets_snap_abawd_work_requirements and made meets_snap_work_requirements_person the single source of truth for the 7 CFR 273.24(c)(4) household-child exception. CI tests the merge with main, which is why the parent cases failed there while passing locally. Replace the person-level parent cases with a routing test at meets_snap_work_requirements_person: an adult who complies with general work requirements via work program participation but fails the 20-hour ABAWD test is routed around the ABAWD test by a 15-year-old household member under the pre-OBBBA threshold (18) and not under the post-OBBBA threshold (14), plus a baseline-contrast case. Drop the diagnostic scaffolding and update household-child wording. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This can be a pure parametric reform
So setting those four booleans to Empirical proof of equivalenceI ran the PR's structural reform (variable override + contrib toggle ON) against a pure parametric reform (the four booleans →
Outputs compared: The parametric form: Reform.from_dict({
"gov.usda.snap.work_requirements.abawd.in_effect": {"2026-01-01.2100-12-31": False},
"gov.states.ca.cdss.snap.work_requirements.abawd.hr1_in_effect": {"2026-01-01.2100-12-31": False},
"gov.states.hi.dhs.snap.work_requirements.abawd.hr1_in_effect": {"2026-01-01.2100-12-31": False},
"gov.states.ak.dpa.snap.work_requirements.abawd.hr1_in_effect": {"2026-01-01.2100-12-31": False},
}, "policyengine_us")Why reconsider the structural approachThe structural version produces bit-for-bit identical results while adding ~115 lines of reform code, a duplicated The one thing the structural approach buys is the single Recommendation: convert to the pure parametric reform above, or the ~2-line contrib-gated variant if the single toggle is a hard requirement. |
Fixes #8943
Summary
Adds a contributed counterfactual-baseline reform modeling 2026/2027 SNAP rules as if the OBBBA (P.L. 119-21, §10102) ABAWD work requirement exemption and age provisions had not passed.
Since the OBBBA provisions are now encoded in the current-law baseline, this provides the inverse lever of the pre-passage
gov/contrib/reconciliation/snap_abawd_work_requirementreform: togglinggov.contrib.snap.pre_obbba_abawd_work_requirements.in_effectrestores prior law against the post-OBBBA baseline.Implementation
The baseline ABAWD formulas (
meets_snap_abawd_work_requirements,meets_snap_work_requirements) already branch on the person-levelis_snap_abawd_hr1_in_effectvariable, using aparameters("2025-06-01")pre-HR1 snapshot for the prior-law path. The reform overridesis_snap_abawd_hr1_in_effectwith a formula that returns false for periods where the contrib toggle is true and otherwise mirrors the baseline logic, restoring in every state (including CA/HI/AK delayed adoption):The per-period override (rather than
neutralize_variable) ensures a future-dated toggle does not apply retroactively to earlier simulated years. Follows the standard contrib factory pattern (in_effecttoggle, 5-year lookahead, module-levelbypass=Trueinstance, registration inreforms.py).Review round (multi-angle, findings addressed here)
An 8-angle adversarial review surfaced and this PR addresses:
neutralize_variableapproach ignored the toggle's start date — a toggle dated 2028 reverted 2026 results. Now period-gated and covered by a test.medicaid_community_engagement_pass_through_eligiblereadsis_snap_abawd_hr1_in_effect, so the pass-through follows the counterfactual SNAP rules. This is the intended behavior (in a no-OBBBA-SNAP-WR world, the Medicaid pass-through references actual SNAP rules); documented in the module docstring and covered by a test (57-year-old work-program participant flips eligible).waived_county_fips) is not reverted. Parameter description, changelog, and docstring now claim only the exemption and age provisions.is_snap_abawd_hr1_in_effectnow documents that it must remain the sole access point for the underlying federal/state parameters.Verification
tests/policy/contrib/snap/pre_obbba_abawd_work_requirements.yaml): age exemption in 2026/2027, non-exempt control, parent of a 15-year-old, homeless/veteran/former-foster-youth exemptions, CA after delayed adoption, period gating (reform applied, toggle off ⇒ current law), baseline-contrast cases (no reform ⇒ post-OBBBA rules), and the Medicaid pass-through interaction. 5 pytest unit tests cover the autoloader (bypass, default-inactive, and the 5-year lookahead window edges). Federal cases pinstate_code: TXsince the default state (CA) is pre-HR1 through 2026-05 in baseline.Microsimulation note
On the default CPS-based dataset, this reform (and the baseline OBBBA work requirement itself) produces no population-level SNAP delta, because
weekly_hours_worked_before_lsrdefaults to 40 for every person when the dataset lacks hours data — everyone trivially satisfies the 20-hour ABAWD test. Population-level analysis of SNAP work requirements requires microdata with real hours and exemption assignment (tracked under the #8820 data work / PolicyEngine/populace#24).🤖 Generated with Claude Code