diff --git a/changelog.d/hr1-partial-waiver-counties.added.md b/changelog.d/hr1-partial-waiver-counties.added.md new file mode 100644 index 00000000000..ab7cd6749b9 --- /dev/null +++ b/changelog.d/hr1-partial-waiver-counties.added.md @@ -0,0 +1,8 @@ +- Pre-HR1 partial-state ABAWD waiver map: 436 county FIPS across 16 states + (KY 117, MI 82, WA 38, PA 60, OR 30, NJ 20, CO 18, MN 15, AZ 14, VA 12, + WI 8, SD 7, MA 5, MD 5, ND 3, HI 2) compiled at full-county grain from + the FY2025-cycle FNS waiver approvals, joining the existing 29 Alaska + areas. Sub-county waivers (ME/CT/RI/NH towns, MT reservations, cities) + are omitted and documented as an understatement. Non-delayed states' + waivers end at P.L. 119-21 Sec. 10102 enactment; Alaska's persist per + its delayed adoption. diff --git a/changelog.d/hr1-statewide-waiver-map.added.md b/changelog.d/hr1-statewide-waiver-map.added.md new file mode 100644 index 00000000000..51a026861f8 --- /dev/null +++ b/changelog.d/hr1-statewide-waiver-map.added.md @@ -0,0 +1,9 @@ +- Statewide SNAP ABAWD time-limit waivers: new `waived_states` parameter + carrying the FNS FY2025 Q1 status-report map (nine states/territories + waived statewide as of October 1, 2024, ending at P.L. 119-21 Sec. 10102 + enactment), resolved by state postal code alongside county waivers. + Previously the waiver surface was 29 Alaska county FIPS, so pre-HR1 law + offered almost no waiver protection in-model and HR1 waiver terminations + scored near zero. Also records Sec. 10102's discretionary-exemption cut + (8% -> 1%) on the rate parameter, with runtime consumption noted as a + populace-side follow-up. diff --git a/changelog.d/medicaid-ce-snap-status-test.fixed.md b/changelog.d/medicaid-ce-snap-status-test.fixed.md new file mode 100644 index 00000000000..a9261e2cfdc --- /dev/null +++ b/changelog.d/medicaid-ce-snap-status-test.fixed.md @@ -0,0 +1 @@ +Encode the Medicaid community engagement SNAP exclusion as the status test in 42 CFR 435.554(c)(7) - SNAP receipt plus being subject to a SNAP work requirement - removing the actual-work-compliance condition, per interim final rule CMS-2454-IFC. diff --git a/changelog.d/snap-work-req-full-income-counting.fixed.md b/changelog.d/snap-work-req-full-income-counting.fixed.md new file mode 100644 index 00000000000..a06de3e6693 --- /dev/null +++ b/changelog.d/snap-work-req-full-income-counting.fixed.md @@ -0,0 +1,8 @@ +- SNAP work-requirement-ineligible members' earned income now counts in full + (7 CFR 273.11(c)(1) treatment) instead of being prorated under 273.11(c)(2). + Proration let households over the income limits at full composition qualify + at the reduced unit size on the disqualified earner's halved income, so + failing the ABAWD time limit could create eligibility and raise benefits — + inverting the sign of ABAWD reforms at population scale. With full counting + a disqualification can only reduce eligibility and benefits; the invariant + is pinned by snap_abawd_disqualification_cannot_expand_eligibility.yaml. diff --git a/changelog.d/snap-work-req-person-level.fixed.md b/changelog.d/snap-work-req-person-level.fixed.md new file mode 100644 index 00000000000..6dd61a8bb79 --- /dev/null +++ b/changelog.d/snap-work-req-person-level.fixed.md @@ -0,0 +1 @@ +Assume baseline compliance with SNAP work registration for non-exempt registrants per 7 CFR 273.7, adding an `is_snap_work_registration_noncompliant` input as a sanction-modeling hook, so that work-requirement disqualification applies to individuals (ABAWD time limits, explicit sanctions) rather than zeroing entire households. diff --git a/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/discretionary_exemption_rate.yaml b/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/discretionary_exemption_rate.yaml index f002a494554..d52532ee969 100644 --- a/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/discretionary_exemption_rate.yaml +++ b/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/discretionary_exemption_rate.yaml @@ -11,6 +11,13 @@ values: # Fiscal Responsibility Act of 2023 (P.L. 118-5, Section 312). # OBBBA (P.L. 119-21, Section 10102) did not amend 2015(o)(6). 2023-10-01: 0.08 + # 1% from enactment — 7 U.S.C. 2015(o)(6), amended by P.L. 119-21 + # (One Big Beautiful Bill Act) Section 10102, enacted 2025-07-04. + # NOTE: not yet consumed at runtime — exemptions enter via the + # is_snap_abawd_discretionary_exempt data input seeded at the FY2024 8% + # cap; period-aware thinning requires a persisted seed quantile on the + # dataset (populace-side follow-up). + 2025-07-04: 0.01 metadata: unit: /1 diff --git a/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/waived_county_fips.yaml b/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/waived_county_fips.yaml index e02b5e223a7..6e4dc005bdb 100644 --- a/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/waived_county_fips.yaml +++ b/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/waived_county_fips.yaml @@ -1,69 +1,562 @@ -# County FIPS codes of areas with an approved USDA FNS waiver of the SNAP -# ABAWD time limit under 7 U.S.C. 2015(o)(4) and 7 CFR 273.24(f). -# -# From 2024-11-01 through 2025-10-31, FNS approved Alaska's request to waive -# the ABAWD time limit in 29 boroughs and census areas — every Alaska -# county-equivalent except the Municipality of Anchorage (FIPS 02020) — plus -# the Eklutna Alaska Native Village Statistical Area, which lies within the -# Anchorage municipality and is below county-level geography, so it is not -# represented here. -# From 2025-11-01 through 2026-10-31, FNS approved Alaska's good faith -# exemption waiver under P.L. 119-21 (OBBBA) section 10102, covering the same -# 29 boroughs and census areas (all except the Municipality of Anchorage). -# Waivers in effect before 2024-11-01 (in Alaska and elsewhere), and after -# the documented 2026-10-31 Alaska approval end date, are not modeled. -description: The Department of Agriculture waives the Able-Bodied Adult Without Dependents (ABAWD) time limit under the Supplemental Nutrition Assistance Program for residents of areas with these county FIPS codes. - +description: "The Department of Agriculture waives the Able-Bodied Adult Without Dependents (ABAWD) time\ + \ limit under the Supplemental Nutrition Assistance Program for residents of areas with these county\ + \ FIPS codes. The 2024-11-01 vintage carries the FY2025-cycle FNS partial-waiver approvals at FULL-COUNTY\ + \ grain for 16 states plus Alaska (AZ 14, CO 18, HI 2, KY 117, MA 5, MD 5, MI 82, MN 15, ND 3, NJ 20,\ + \ OR 30, PA 60 including two balance-of-county areas, SD 7, VA 12 county-equivalents, WA 38, WI 8, AK\ + \ 29). Sub-county waivers (Maine's 213 towns, Connecticut's 68 towns, Rhode Island and New Hampshire\ + \ towns, Montana's reservation-only waiver, and city/reservation areas elsewhere) are NOT represented\ + \ \u2014 county-grain inclusion would overstate them, so their omission UNDERSTATES pre-HR1 waiver coverage.\ + \ Statewide waivers live in the companion waived_states parameter. Waivers for non-delayed states end\ + \ at P.L. 119-21 Sec. 10102 enactment (2025-07-04, modeled; some ran weeks to their expiration dates);\ + \ Alaska's delayed HR1 adoption keeps its counties until 2026-11-01." values: - 2018-01-01: - [] + 2018-01-01: [] 2024-11-01: - - "02013" # Aleutians East Borough, AK - - "02016" # Aleutians West Census Area, AK - - "02050" # Bethel Census Area, AK - - "02060" # Bristol Bay Borough, AK - - "02063" # Chugach Census Area, AK - - "02066" # Copper River Census Area, AK - - "02068" # Denali Borough, AK - - "02070" # Dillingham Census Area, AK - - "02090" # Fairbanks North Star Borough, AK - - "02100" # Haines Borough, AK - - "02105" # Hoonah-Angoon Census Area, AK - - "02110" # Juneau City and Borough, AK - - "02122" # Kenai Peninsula Borough, AK - - "02130" # Ketchikan Gateway Borough, AK - - "02150" # Kodiak Island Borough, AK - - "02158" # Kusilvak Census Area, AK - - "02164" # Lake and Peninsula Borough, AK - - "02170" # Matanuska-Susitna Borough, AK - - "02180" # Nome Census Area, AK - - "02185" # North Slope Borough, AK - - "02188" # Northwest Arctic Borough, AK - - "02195" # Petersburg Borough, AK - - "02198" # Prince of Wales-Hyder Census Area, AK - - "02220" # Sitka City and Borough, AK - - "02230" # Skagway Municipality, AK - - "02240" # Southeast Fairbanks Census Area, AK - - "02275" # Wrangell City and Borough, AK - - "02282" # Yakutat City and Borough, AK - - "02290" # Yukon-Koyukuk Census Area, AK - 2026-11-01: - [] - + - '02013' + - '02016' + - '02050' + - '02060' + - '02063' + - '02066' + - 02068 + - '02070' + - 02090 + - '02100' + - '02105' + - '02110' + - '02122' + - '02130' + - '02150' + - 02158 + - '02164' + - '02170' + - 02180 + - 02185 + - 02188 + - 02195 + - 02198 + - '02220' + - '02230' + - '02240' + - '02275' + - 02282 + - 02290 + - '04001' + - '04003' + - '04005' + - '04007' + - 04009 + - '04011' + - '04012' + - '04015' + - '04017' + - 04019 + - '04021' + - '04023' + - '04025' + - '04027' + - 08003 + - 08009 + - 08011 + - 08021 + - 08023 + - 08025 + - 08027 + - 08043 + - 08053 + - 08055 + - 08061 + - 08071 + - 08073 + - 08079 + - 08089 + - 08101 + - 08105 + - 08109 + - '15007' + - '15009' + - '21001' + - '21003' + - '21005' + - '21007' + - '21009' + - '21011' + - '21013' + - '21015' + - '21017' + - '21019' + - '21021' + - '21023' + - '21025' + - '21027' + - '21029' + - '21031' + - '21033' + - '21035' + - '21037' + - '21039' + - '21041' + - '21043' + - '21045' + - '21047' + - '21049' + - '21051' + - '21053' + - '21055' + - '21057' + - '21059' + - '21061' + - '21063' + - '21065' + - '21069' + - '21071' + - '21073' + - '21075' + - '21077' + - '21079' + - '21081' + - '21083' + - '21085' + - '21087' + - '21089' + - '21091' + - '21093' + - '21095' + - '21097' + - '21099' + - '21101' + - '21103' + - '21105' + - '21107' + - '21109' + - '21111' + - '21113' + - '21115' + - '21117' + - '21119' + - '21121' + - '21123' + - '21125' + - '21127' + - '21129' + - '21131' + - '21133' + - '21135' + - '21137' + - '21139' + - '21141' + - '21143' + - '21145' + - '21147' + - '21149' + - '21151' + - '21153' + - '21155' + - '21157' + - '21159' + - '21161' + - '21163' + - '21165' + - '21167' + - '21169' + - '21171' + - '21173' + - '21175' + - '21177' + - '21179' + - '21181' + - '21183' + - '21187' + - '21189' + - '21191' + - '21193' + - '21195' + - '21197' + - '21199' + - '21201' + - '21203' + - '21205' + - '21207' + - '21211' + - '21213' + - '21215' + - '21217' + - '21219' + - '21221' + - '21223' + - '21225' + - '21227' + - '21229' + - '21231' + - '21233' + - '21235' + - '21237' + - '21239' + - '24019' + - '24029' + - '24039' + - '24047' + - '24510' + - '25003' + - '25007' + - '25013' + - '25015' + - '25019' + - '26001' + - '26003' + - '26005' + - '26007' + - '26009' + - '26011' + - '26013' + - '26015' + - '26017' + - '26019' + - '26021' + - '26023' + - '26025' + - '26027' + - '26029' + - '26031' + - '26033' + - '26035' + - '26037' + - '26039' + - '26041' + - '26043' + - '26045' + - '26047' + - '26049' + - '26051' + - '26053' + - '26055' + - '26057' + - '26059' + - '26061' + - '26063' + - '26065' + - '26067' + - '26069' + - '26071' + - '26073' + - '26075' + - '26077' + - '26079' + - '26081' + - '26083' + - '26085' + - '26087' + - '26089' + - '26091' + - '26093' + - '26095' + - '26097' + - '26099' + - '26101' + - '26103' + - '26105' + - '26107' + - '26109' + - '26111' + - '26113' + - '26115' + - '26117' + - '26119' + - '26121' + - '26123' + - '26127' + - '26129' + - '26131' + - '26133' + - '26135' + - '26137' + - '26139' + - '26141' + - '26143' + - '26145' + - '26147' + - '26149' + - '26151' + - '26153' + - '26155' + - '26157' + - '26159' + - '26161' + - '26163' + - '26165' + - '27001' + - '27017' + - '27021' + - '27029' + - '27057' + - '27059' + - '27061' + - '27065' + - '27071' + - '27075' + - '27087' + - '27095' + - '27097' + - '27115' + - '27159' + - '34001' + - '34003' + - '34005' + - '34007' + - '34009' + - '34011' + - '34013' + - '34015' + - '34017' + - '34019' + - '34021' + - '34023' + - '34025' + - '34029' + - '34031' + - '34033' + - '34035' + - '34037' + - '34039' + - '34041' + - '38009' + - '38069' + - '38079' + - '41001' + - '41007' + - '41009' + - '41011' + - '41013' + - '41015' + - '41017' + - '41019' + - '41021' + - '41023' + - '41025' + - '41029' + - '41031' + - '41033' + - '41035' + - '41037' + - '41039' + - '41041' + - '41043' + - '41045' + - '41047' + - '41049' + - '41053' + - '41055' + - '41057' + - '41059' + - '41061' + - '41063' + - '41065' + - '41069' + - '42003' + - '42005' + - '42007' + - '42009' + - '42011' + - '42013' + - '42015' + - '42017' + - '42019' + - '42021' + - '42023' + - '42025' + - '42031' + - '42033' + - '42035' + - '42037' + - '42039' + - '42041' + - '42043' + - '42045' + - '42047' + - '42049' + - '42051' + - '42053' + - '42055' + - '42057' + - '42059' + - '42061' + - '42063' + - '42065' + - '42067' + - '42069' + - '42073' + - '42077' + - '42079' + - '42081' + - '42083' + - '42085' + - '42087' + - '42089' + - '42093' + - '42095' + - '42097' + - '42099' + - '42101' + - '42103' + - '42105' + - '42107' + - '42109' + - '42111' + - '42113' + - '42115' + - '42117' + - '42119' + - '42121' + - '42123' + - '42125' + - '42127' + - '42129' + - '42131' + - '46007' + - '46017' + - '46031' + - '46041' + - '46102' + - '46121' + - '46137' + - '51025' + - '51027' + - '51051' + - '51053' + - '51081' + - '51181' + - '51183' + - '51590' + - '51595' + - '51670' + - '51690' + - '51730' + - '53001' + - '53003' + - '53005' + - '53007' + - '53009' + - '53011' + - '53013' + - '53015' + - '53017' + - '53019' + - '53021' + - '53023' + - '53025' + - '53027' + - '53029' + - '53031' + - '53035' + - '53037' + - '53039' + - '53041' + - '53043' + - '53045' + - '53047' + - '53049' + - '53051' + - '53053' + - '53055' + - '53057' + - '53059' + - '53061' + - '53063' + - '53065' + - '53067' + - '53069' + - '53071' + - '53073' + - '53075' + - '53077' + - '55001' + - '55007' + - '55031' + - '55037' + - '55041' + - '55051' + - '55077' + - '55078' + 2026-11-01: [] + 2025-07-04: + - '02013' + - '02016' + - '02050' + - '02060' + - '02063' + - '02066' + - 02068 + - '02070' + - 02090 + - '02100' + - '02105' + - '02110' + - '02122' + - '02130' + - '02150' + - 02158 + - '02164' + - '02170' + - 02180 + - 02185 + - 02188 + - 02195 + - 02198 + - '02220' + - '02230' + - '02240' + - '02275' + - 02282 + - 02290 metadata: label: SNAP ABAWD time limit waived county FIPS codes period: month unit: list reference: - - title: 7 U.S.C. 2015(o)(4) - ABAWD Time Limit Waivers - href: https://www.law.cornell.edu/uscode/text/7/2015#o_4 - - title: 7 CFR 273.24(f) - Waivers - href: https://www.law.cornell.edu/cfr/text/7/273.24#f - - title: USDA FNS Alaska FY 2025 ABAWD Waiver Response (September 20, 2024) - href: https://www.fna.usda.gov/sites/default/files/resource-files/ak-abawd-response-fy2025.pdf - - title: Public Law 119-21, Section 10102 - Modifications to SNAP ABAWD Requirements - href: https://www.congress.gov/119/plaws/publ21/PLAW-119publ21.pdf#page=81 - - title: USDA FNS ABAWD Waivers Implementation Memorandum (October 3, 2025) - href: https://www.fns.usda.gov/snap/obbb-ABAWD-Waivers-Implementation-Memo - - title: State of Alaska Department of Health, H.R. 1 - AK Impacts (good faith exemption waiver, November 1, 2025 - October 31, 2026) - href: https://health.alaska.gov/en/education/hr-1-ak-impacts/ + - title: 7 U.S.C. 2015(o)(4) - ABAWD Time Limit Waivers + href: https://www.law.cornell.edu/uscode/text/7/2015#o_4 + - title: 7 CFR 273.24(f) - Waivers + href: https://www.law.cornell.edu/cfr/text/7/273.24#f + - title: USDA FNS Alaska FY 2025 ABAWD Waiver Response (September 20, 2024) + href: https://www.fna.usda.gov/sites/default/files/resource-files/ak-abawd-response-fy2025.pdf + - title: Public Law 119-21, Section 10102 - Modifications to SNAP ABAWD Requirements + href: https://www.congress.gov/119/plaws/publ21/PLAW-119publ21.pdf#page=81 + - title: USDA FNS ABAWD Waivers Implementation Memorandum (October 3, 2025) + href: https://www.fns.usda.gov/snap/obbb-ABAWD-Waivers-Implementation-Memo + - title: State of Alaska Department of Health, H.R. 1 - AK Impacts (good faith exemption waiver, November + 1, 2025 - October 31, 2026) + href: https://health.alaska.gov/en/education/hr-1-ak-impacts/ + - title: FNS FY2025 Q1 ABAWD Waiver Status (partial-waiver state list) + href: https://fns-prod.azureedge.us/sites/default/files/resource-files/FY25-Quarter-1-ABAWD-Waiver-Status.pdf + - title: AZ approval 2024-09-12 + href: https://www.fna.usda.gov/sites/default/files/resource-files/az-abawd-response-fy2025.pdf + - title: CO approval 2024-08-15 + href: https://www.fna.usda.gov/sites/default/files/resource-files/co-abawd-response-fy2025.pdf + - title: HI approval 2024-05-31 (term spans 2024-10-01) + href: https://www.fns.usda.gov/sites/default/files/resource-files/hi-abawd-response-fy2024.pdf + - title: KY approval 2024-11-27 (effective 2024-12-01) + href: https://www.fna.usda.gov/sites/default/files/resource-files/ky-abawd-response-fy2025.pdf + - title: MA approval 2024-06-27 (term spans 2024-10-01) + href: https://www.fna.usda.gov/sites/default/files/resource-files/ma-abawd-response-fy2024.pdf + - title: MD approval 2024-04-12 (term spans 2024-10-01) + href: https://www.fna.usda.gov/sites/default/files/resource-files/md-abawd-response-fy2024.pdf + - title: MI approval 2023-10-19, 82 counties (in effect 2024-10-01) + href: https://www.fna.usda.gov/sites/default/files/resource-files/mi-abawd-response-fy2024.pdf + - title: MN approval (term spans 2024-10-01) + href: https://fns-prod.azureedge.us/sites/default/files/resource-files/mn-abawd-response-fy2024.pdf + - title: ND approval (term spans 2024-10-01) + href: https://fns-prod.azureedge.us/sites/default/files/resource-files/nd-abawd-response-fy2024.pdf + - title: NJ approval 2024-12-16 (effective 2025-02-01) + href: https://www.fna.usda.gov/sites/default/files/resource-files/nj-abawd-response-fy2025.pdf + - title: OR approval 2024-10-22 (effective 2025-01-01) + href: https://www.fna.usda.gov/sites/default/files/resource-files/or-abawd-response-fy2025.pdf + - title: PA approval 2024-08-20 (in effect 2024-10-01) + href: https://fns-prod.azureedge.us/sites/default/files/resource-files/pa-abawd-response-fy2024.pdf + - title: SD approval 2024-08-12 + href: https://www.fna.usda.gov/sites/default/files/resource-files/sd-abawd-response-fy2025.pdf + - title: VA approval 2024-06-21 (term spans 2024-10-01) + href: https://www.fns.usda.gov/sites/default/files/resource-files/va-abawd-response-fy2024.pdf + - title: WA approval 2024-12-16 (effective 2025-02-01) + href: https://www.fna.usda.gov/sites/default/files/resource-files/wa-abawd-response-fy2025.pdf + - title: WI partial approval 2024-09-13 (Milwaukee ZCTAs denied) + href: https://www.fna.usda.gov/sites/default/files/resource-files/wi-abawd-response-fy2025.pdf diff --git a/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/waived_states.yaml b/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/waived_states.yaml new file mode 100644 index 00000000000..f29a653e0c9 --- /dev/null +++ b/policyengine_us/parameters/gov/usda/snap/work_requirements/abawd/waived_states.yaml @@ -0,0 +1,24 @@ +description: The USDA Food and Nutrition Service waives the SNAP ABAWD time limit statewide for these states, identified by state postal code. +values: + 2018-01-01: [] + # FY2025 Q1 statewide waivers in effect as of October 1, 2024: California, + # Delaware, District of Columbia, Guam, Illinois, Nevada, New Mexico, + # New York, U.S. Virgin Islands. (Guam and USVI included for + # completeness; national datasets carry the 50 states + DC.) + 2024-10-01: [CA, DE, DC, GU, IL, NV, NM, NY, VI] + # P.L. 119-21 (One Big Beautiful Bill Act) Section 10102, enacted + # 2025-07-04, restricts waivers to areas with unemployment over 10 + # percent; the statewide waivers above did not qualify and were + # terminated or allowed to lapse. Simplification: modeled as ending at + # enactment; some ran weeks longer to their expiration dates. + 2025-07-04: [] +metadata: + unit: list + label: SNAP ABAWD statewide time limit waivers (state postal codes) + reference: + - title: FNS, Status of State ABAWD Time Limit Waivers, FY2025 Q1 (waivers in effect October 1, 2024) + href: https://fns-prod.azureedge.us/sites/default/files/resource-files/FY25-Quarter-1-ABAWD-Waiver-Status.pdf + - title: 7 U.S.C. 2015(o)(4) - Waivers + href: https://www.law.cornell.edu/uscode/text/7/2015#o_4 + - title: Public Law 119-21, Section 10102 - waiver criteria restriction + href: https://www.congress.gov/119/plaws/publ21/PLAW-119publ21.pdf#page=81 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.yaml index 50d53c99241..767c90d2e5e 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.yaml @@ -120,7 +120,7 @@ output: medicaid_community_engagement_pass_through_eligible: false -- name: Case 7, age 16 is at the non-exempt age boundary and passes through when work compliant. +- name: Case 7, age 16 is at the non-exempt age boundary and passes through. period: 2026-01 input: people: @@ -138,7 +138,7 @@ output: medicaid_community_engagement_pass_through_eligible: true -- name: Case 8, age 59 is just below the upper exempt age threshold and passes through when work compliant. +- name: Case 8, age 59 is just below the upper exempt age threshold and passes through. period: 2026-01 input: people: @@ -174,15 +174,12 @@ output: medicaid_community_engagement_pass_through_eligible: false -# Issue #8863 mirror: 7 CFR 273.24(c)(4) keys the ABAWD child exception on any -# household member under the age threshold, regardless of tax-unit dependency. -# The adult participates in a SNAP work program (general-compliant per 7 CFR -# 273.7(a)(1)) but logs zero qualifying hours, so she fails the ABAWD 20-hour -# test. A 13-year-old household member who is NOT her tax dependent still -# routes her around the ABAWD test post-HR1 (threshold 14), so she passes -# through on general compliance alone. The 13-year-old is under the general -# work-rule age floor and does not pass through. -- name: Case 10, non-dependent under-14 household member triggers the household-wide ABAWD exception. +# Under the 42 CFR 435.554(c)(7) status test, the adult passes through on +# SNAP receipt plus subject-to status alone; her work program hours and the +# ABAWD test are not consulted. The 13-year-old household member is under +# the general work-rule age floor (exempt) and does not pass through, +# regardless of tax-unit dependency. +- name: Case 10, adult passes through on status while an under-14 member is age-exempt. period: 2026-01 input: people: @@ -210,9 +207,10 @@ output: medicaid_community_engagement_pass_through_eligible: [true, false] -# Same adult without an under-threshold household member: the ABAWD hours -# test binds, and zero qualifying hours fail it. -- name: Case 11, with no under-threshold household member the ABAWD hours test binds. +# Same adult without an under-threshold household member: under the status +# test the ABAWD hours test still does not bind — both non-exempt adults in +# the SNAP household pass through regardless of hours worked. +- name: Case 11, with no under-threshold household member both non-exempt adults still pass through. period: 2026-01 input: people: @@ -238,4 +236,27 @@ members: [adult, housemate] state_code: TX output: - medicaid_community_engagement_pass_through_eligible: [false, false] + medicaid_community_engagement_pass_through_eligible: [true, true] + +- name: Case 12, non-working SNAP member subject to work rules passes through without a compliance check. + period: 2026-01 + input: + people: + person1: + # Subject to (not exempt from) the SNAP general work requirement. Under + # 42 CFR 435.554(c)(7) the SNAP exclusion is a status test - unlike the + # TANF prong, actual work-requirement compliance is not confirmed - so + # not working and failing the SNAP ABAWD time limit are irrelevant. + age: 30 + is_disabled: false + weekly_hours_worked_before_lsr: 0 + spm_units: + spm_unit: + members: [person1] + snap: 100 + households: + household: + members: [person1] + state_code: TX + output: + medicaid_community_engagement_pass_through_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/is_in_snap_abawd_waived_area.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/is_in_snap_abawd_waived_area.yaml index 24f8e49f63d..b7acf635457 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/is_in_snap_abawd_waived_area.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/is_in_snap_abawd_waived_area.yaml @@ -1,81 +1,61 @@ -# SNAP ABAWD area waivers — 7 U.S.C. 2015(o)(4), 7 CFR 273.24(f). -# From 2024-11-01, FNS waived the ABAWD time limit in every Alaska -# borough and census area except the Municipality of Anchorage -# (FIPS 02020). +# Statewide ABAWD time-limit waivers (FNS FY2025 Q1 status report: nine +# states/territories waived statewide as of October 1, 2024). P.L. 119-21 +# Sec. 10102 restricted waiver criteria at enactment (2025-07-04); the +# statewide waivers are modeled as ending then. -- name: Case 1, post-HR1 AK resident of waived borough (Fairbanks North Star) meets ABAWD requirements via area waiver. - period: 2026-01 - absolute_error_margin: 0.1 +- name: California is statewide-waived before HR1 enactment. + period: 2025-01 input: - age: 30 - weekly_hours_worked_before_lsr: 1 - is_disabled: false - state_code: AK - county_fips: "02090" + people: + person: + age: + 2025: 30 + households: + household: + members: [person] + state_code: CA output: is_in_snap_abawd_waived_area: true - meets_snap_abawd_work_requirements: true - -- name: Case 2, post-HR1 AK resident of non-waived Municipality of Anchorage is subject to the time limit. - period: 2026-01 - absolute_error_margin: 0.1 - input: - age: 30 - weekly_hours_worked_before_lsr: 1 - is_disabled: false - state_code: AK - county_fips: "02020" - output: - is_in_snap_abawd_waived_area: false - meets_snap_abawd_work_requirements: false -- name: Case 3, post-HR1 lower-48 resident (Harris County, TX) is unaffected by the Alaska waiver. - period: 2026-01 - absolute_error_margin: 0.1 +- name: Texas has no waiver before HR1 enactment. + period: 2025-01 input: - age: 30 - weekly_hours_worked_before_lsr: 1 - is_disabled: false - state_code: TX - county_fips: "48201" + people: + person: + age: + 2025: 30 + households: + household: + members: [person] + state_code: TX output: is_in_snap_abawd_waived_area: false - meets_snap_abawd_work_requirements: false -- name: Case 4, AK household without county geography falls back to no area waiver. +- name: California statewide waiver ends with HR1 waiver restriction. period: 2026-01 - absolute_error_margin: 0.1 input: - age: 30 - weekly_hours_worked_before_lsr: 1 - is_disabled: false - state_code: AK + people: + person: + age: + 2026: 30 + households: + household: + members: [person] + state_code: CA output: is_in_snap_abawd_waived_area: false - meets_snap_abawd_work_requirements: false -- name: Case 5, pre-HR1 AK resident of waived census area (Bethel) meets ABAWD requirements via area waiver. +- name: County waivers still resolve alongside statewide waivers. period: 2025-01 - absolute_error_margin: 0.1 input: - age: 30 - weekly_hours_worked_before_lsr: 1 - is_disabled: false - state_code: AK - county_fips: "02050" + people: + person: + age: + 2025: 30 + households: + household: + members: [person] + state_code: AK + county_fips: "02050" # Bethel Census Area, AK (FY25 county waiver) output: is_in_snap_abawd_waived_area: true - meets_snap_abawd_work_requirements: true - -- name: Case 6, after the Alaska waiver approval end date the area waiver no longer applies. - period: 2027-01 - absolute_error_margin: 0.1 - input: - age: 30 - weekly_hours_worked_before_lsr: 1 - is_disabled: false - state_code: AK - county_fips: "02090" - output: - is_in_snap_abawd_waived_area: false - meets_snap_abawd_work_requirements: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_abawd_work_requirements.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_abawd_work_requirements.yaml index c34433402b3..4b855816c67 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_abawd_work_requirements.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_abawd_work_requirements.yaml @@ -40,6 +40,9 @@ age: 54 weekly_hours_worked_before_lsr: 1 is_disabled: false + # TX: never waived — the default state (CA) is statewide-waived from + # October 2024, which would exempt via the waiver rather than age. + state_code: TX output: meets_snap_abawd_work_requirements: false @@ -373,6 +376,9 @@ weekly_hours_worked_before_lsr: 1 is_disabled: false is_snap_abawd_indian_exempt: true + # TX: never waived — the default state (CA) is statewide-waived from + # October 2024, which would exempt via the waiver rather than the input. + state_code: TX output: meets_snap_abawd_work_requirements: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_general_work_requirements.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_general_work_requirements.yaml index fdd04eb3c8f..f69f6c53d0f 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_general_work_requirements.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_general_work_requirements.yaml @@ -1,3 +1,11 @@ +# Under 7 CFR 273.7(b)(1), working 30+ hours/week (or the equivalent earnings), +# being under 16 or 60+, being disabled, or caring for a dependent child under 6 +# or an incapacitated person are EXEMPTIONS from the general work requirements. +# The affirmative requirement (7 CFR 273.7(a)(1)) is only to register and not +# without-good-cause refuse/quit. Simply not working is NOT a violation, so the +# baseline assumes registration compliance and every person meets the general +# requirement unless is_snap_work_registration_noncompliant is set true. + - name: Case 1, age 15, exempted from the general work requirements. period: 2022 input: @@ -18,7 +26,7 @@ output: meets_snap_general_work_requirements: true -- name: Case 3, age 16, not exempted from the general work requirements. +- name: Case 3, age 16, non-exempt and not working, but assumed compliant with work registration in baseline. period: 2022 input: age: 16 @@ -26,7 +34,8 @@ is_disabled: false is_incapable_of_self_care: false output: - meets_snap_general_work_requirements: false + # Not exempt, but not affirmatively noncompliant, so still meets the requirement. + meets_snap_general_work_requirements: true - name: Case 4, age 30, but disabled. period: 2022 @@ -48,7 +57,7 @@ output: meets_snap_general_work_requirements: true -- name: Case 6, age 30, working 29 hours per week. +- name: Case 6, age 30, working 29 hours per week, but assumed compliant with work registration in baseline. period: 2022 input: age: 30 @@ -56,9 +65,12 @@ is_disabled: false is_incapable_of_self_care: false output: - meets_snap_general_work_requirements: false + # Working fewer than 30 hours removes the 273.7(b)(1)(vii) exemption, but a + # non-exempt registrant who is simply not working 30+ hours is not violating + # the requirement, so the baseline still meets it. + meets_snap_general_work_requirements: true -- name: Case 7, integration test. +- name: Case 7, integration test, non-working 30-year-old caring for a 5-year-old dependent. period: 2022 input: people: @@ -80,7 +92,91 @@ output: meets_snap_general_work_requirements: [true, true] -- name: Case 8, age 30, not working but a SNAP work program participant. +# Sanction-hook cases: is_snap_work_registration_noncompliant set true models a +# State-agency "without good cause" work-registration sanction under +# 7 CFR 273.7(f)(1)-(2). A noncompliant person only meets the general requirement +# if they are otherwise exempt under 7 CFR 273.7(b)(1). + +- name: Case 8, noncompliant non-exempt 30-year-old not working is disqualified. + period: 2022 + input: + age: 30 + weekly_hours_worked_before_lsr: 1 + is_disabled: false + is_incapable_of_self_care: false + is_snap_work_registration_noncompliant: true + output: + # Non-exempt and affirmatively noncompliant, so fails the general requirement. + meets_snap_general_work_requirements: false + +- name: Case 9, noncompliant 30-year-old working 30 hours is exempt. + period: 2022 + input: + age: 30 + weekly_hours_worked_before_lsr: 30 + is_disabled: false + is_incapable_of_self_care: false + is_snap_work_registration_noncompliant: true + output: + # Exempt under 7 CFR 273.7(b)(1)(vii) (30+ hours), so the sanction does not apply. + meets_snap_general_work_requirements: true + +- name: Case 10, noncompliant age 60 is age-exempt. + period: 2022 + input: + age: 60 + weekly_hours_worked_before_lsr: 1 + is_disabled: false + is_incapable_of_self_care: false + is_snap_work_registration_noncompliant: true + output: + # Exempt under 7 CFR 273.7(b)(1)(i) (60 or older), so the sanction does not apply. + meets_snap_general_work_requirements: true + +- name: Case 11, noncompliant 30-year-old but disabled is exempt. + period: 2022 + input: + age: 30 + weekly_hours_worked_before_lsr: 1 + is_disabled: true + is_incapable_of_self_care: false + is_snap_work_registration_noncompliant: true + output: + # Exempt under 7 CFR 273.7(b)(1)(ii) (physically or mentally unfit), so exempt. + meets_snap_general_work_requirements: true + +- name: Case 12, noncompliant 30-year-old caring for a 5-year-old dependent is exempt. + period: 2022 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 1 + is_disabled: false + is_incapable_of_self_care: false + is_snap_work_registration_noncompliant: true + person2: + age: 5 + is_tax_unit_dependent: true + spm_units: + spm_unit: + members: [person1, person2] + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + output: + # person1 exempt under 7 CFR 273.7(b)(1)(iv) (caring for a dependent child under 6); + # person2 is exempt by age (under 16). + meets_snap_general_work_requirements: [true, true] + +# Work program participation under 7 CFR 273.7(a)(1)(ii) is affirmative +# compliance, so it satisfies the requirement even alongside a noncompliance +# flag. + +- name: Case 13, age 30, not working but a SNAP work program participant. period: 2022 input: age: 30 @@ -89,11 +185,15 @@ output: meets_snap_general_work_requirements: true -- name: Case 9, age 30, not working and not a SNAP work program participant. +- name: Case 14, noncompliant 30-year-old who participates in a work program still meets the requirement. period: 2022 input: age: 30 - weekly_hours_worked_before_lsr: 0 - is_snap_work_program_participant: false + weekly_hours_worked_before_lsr: 1 + is_disabled: false + is_incapable_of_self_care: false + is_snap_work_program_participant: true + is_snap_work_registration_noncompliant: true output: - meets_snap_general_work_requirements: false + # Participation is affirmative compliance under 7 CFR 273.7(a)(1)(ii). + meets_snap_general_work_requirements: true diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_work_requirements.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_work_requirements.yaml index c250012177e..0af2f35fbe3 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_work_requirements.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/meets_snap_work_requirements.yaml @@ -1,31 +1,49 @@ +# meets_snap_work_requirements is an SPMUnit any() over person-level results. +# Under the corrected reading (issue #8862), a person only fails +# meets_snap_work_requirements_person via ABAWD facts (no dependent child in the +# unit, in the ABAWD age band, under the work-hours threshold, no ABAWD exemption) +# or via an explicit work-registration noncompliance sanction. The general +# requirement is met in the baseline for everyone. These cases override the +# person-level general/ABAWD flags to exercise the combination logic and the +# household any() semantics; the flag values represent those two failure paths. + - name: Case 1, has dependent child, meets both work requirements. period: 2022 input: meets_snap_general_work_requirements: true meets_snap_abawd_work_requirements: true - monthly_age: 17 + # age (YEAR) rather than monthly_age: setting monthly_age at a year + # period divides the value across months, which would make the person + # read as a household child under the 273.24(c)(4) any-member exception. + age: 17 is_tax_unit_dependent: true - output: + output: meets_snap_work_requirements: true -- name: Case 2, has dependent child, meets the general work requirements. +- name: Case 2, has dependent child so ABAWD is skipped and only the general requirement applies. period: 2022 input: meets_snap_general_work_requirements: true meets_snap_abawd_work_requirements: false - monthly_age: 17 + # age (YEAR) rather than monthly_age: setting monthly_age at a year + # period divides the value across months, which would make the person + # read as a household child under the 273.24(c)(4) any-member exception. + age: 17 is_tax_unit_dependent: true - output: + output: + # With a dependent child in the unit, ABAWD is not tested; the general + # requirement (met in baseline) governs, so the person passes. meets_snap_work_requirements: true -- name: Case 3, no dependent child, meets only one work requirements. +- name: Case 3, no dependent child, fails ABAWD time limit despite meeting the general requirement. period: 2022 input: - meets_snap_general_work_requirements: false - meets_snap_abawd_work_requirements: true - monthly_age: 18 + # No dependent child: ABAWD applies. General met (baseline) but ABAWD failed. + meets_snap_general_work_requirements: true + meets_snap_abawd_work_requirements: false + age: 18 is_tax_unit_dependent: false - output: + output: meets_snap_work_requirements: false - name: Case 4, no dependent child, meets both work requirements. @@ -33,12 +51,12 @@ input: meets_snap_general_work_requirements: true meets_snap_abawd_work_requirements: true - monthly_age: 18 + age: 18 is_tax_unit_dependent: false output: meets_snap_work_requirements: true -- name: Case 5, CA pre-HR1 age 55 exempt flows through SPM. +- name: Case 5, CA pre-HR1 age 55 ABAWD-exempt flows through SPM. period: 2026-01 input: people: @@ -46,7 +64,6 @@ age: 55 weekly_hours_worked_before_lsr: 1 is_disabled: false - meets_snap_general_work_requirements: true spm_units: spm_unit: members: [person1] @@ -58,9 +75,11 @@ members: [person1] state_code: CA output: + # CA delays HR1 to 2026-06, so in 2026-01 the pre-HR1 ABAWD band (18-54) + # applies and age 55 is exempt. General requirement met in baseline. meets_snap_work_requirements: true -- name: Case 6, non-CA post-HR1 age 55 not exempt. +- name: Case 6, non-CA post-HR1 age 55 in ABAWD band and not working fails. period: 2026-01 input: people: @@ -68,7 +87,8 @@ age: 55 weekly_hours_worked_before_lsr: 1 is_disabled: false - meets_snap_general_work_requirements: true + is_pregnant: false + is_veteran: false spm_units: spm_unit: members: [person1] @@ -80,6 +100,8 @@ members: [person1] state_code: TX output: + # Post-HR1 the ABAWD band extends to 64 (exempt at 65+), so age 55 is in-band. + # No dependent child, under the hours threshold, no exemption, so ABAWD fails. meets_snap_work_requirements: false - name: Case 7, two adults remain eligible when one adult passes. @@ -94,7 +116,8 @@ person2: monthly_age: 30 is_tax_unit_dependent: false - meets_snap_general_work_requirements: false + # ABAWD time-limit failure (no dependent child, in-band, not exempt). + meets_snap_general_work_requirements: true meets_snap_abawd_work_requirements: false spm_units: spm_unit: @@ -102,24 +125,25 @@ output: meets_snap_work_requirements: true -- name: Case 8, two adults are ineligible when both adults fail. +- name: Case 8, two adults are ineligible when both fail the ABAWD time limit. period: 2024-01 input: people: person1: monthly_age: 35 is_tax_unit_dependent: false - meets_snap_general_work_requirements: false + meets_snap_general_work_requirements: true meets_snap_abawd_work_requirements: false person2: monthly_age: 30 is_tax_unit_dependent: false - meets_snap_general_work_requirements: false + meets_snap_general_work_requirements: true meets_snap_abawd_work_requirements: false spm_units: spm_unit: members: [person1, person2] output: + # Both fail ABAWD with no dependent child in the unit, so the any() is false. meets_snap_work_requirements: false - name: Case 9, two adults remain eligible when both adults pass. @@ -159,7 +183,7 @@ meets_snap_abawd_work_requirements: false meets_snap_work_requirements: false -- name: Case 11, non-working parent of a teenager fails without the work program flag. +- name: Case 11, sanctioned parent of a sanctioned teenager fails without the work program flag. period: 2024-01 input: people: @@ -167,18 +191,20 @@ monthly_age: 30 is_tax_unit_dependent: false is_snap_work_program_participant: false + is_snap_work_registration_noncompliant: true weekly_hours_worked_before_lsr: 0 person2: monthly_age: 17 is_tax_unit_dependent: true + is_snap_work_registration_noncompliant: true weekly_hours_worked_before_lsr: 0 spm_units: spm_unit: members: [person1, person2] output: - # Neither member passes the general check: the parent is non-exempt - # and not working, and the 17-year-old is past the under-16 age - # exemption. + # Neither member passes the general check: both are sanctioned via the + # noncompliance hook, the parent is non-exempt and not working, and the + # 17-year-old is past the under-16 age exemption. meets_snap_general_work_requirements: [false, false] meets_snap_work_requirements: false @@ -190,17 +216,20 @@ monthly_age: 30 is_tax_unit_dependent: false is_snap_work_program_participant: true + is_snap_work_registration_noncompliant: true weekly_hours_worked_before_lsr: 0 person2: monthly_age: 17 is_tax_unit_dependent: true + is_snap_work_registration_noncompliant: true weekly_hours_worked_before_lsr: 0 spm_units: spm_unit: members: [person1, person2] output: - # The dependent child under 18 waives the ABAWD check (pre-HR1), so - # the parent's work program participation alone flips the unit from + # Work program participation is affirmative compliance, overriding the + # parent's sanction. The dependent child under 18 waives the ABAWD check + # (pre-HR1), so the parent's participation alone flips the unit from # failing to passing. meets_snap_general_work_requirements: [true, false] meets_snap_work_requirements: true @@ -320,3 +349,31 @@ # No household member under 14, so person1 must satisfy the ABAWD test, # which fails. meets_snap_work_requirements: false + +- name: Case 17, noncompliant non-exempt adult fails the general requirement organically. + period: 2024-01 + input: + people: + person1: + age: + 2024: 30 + is_tax_unit_dependent: false + weekly_hours_worked_before_lsr: 0 + is_disabled: false + is_incapable_of_self_care: false + is_snap_work_registration_noncompliant: true + spm_units: + spm_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + # Noncompliant and non-exempt, so the general requirement fails; ABAWD also + # fails (in-band, no dependent child, not working), so the person fails and + # the single-person SPM unit fails. + meets_snap_work_requirements: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/snap_abawd_disqualification_cannot_expand_eligibility.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/snap_abawd_disqualification_cannot_expand_eligibility.yaml new file mode 100644 index 00000000000..91fbb2dc982 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/work_requirements/snap_abawd_disqualification_cannot_expand_eligibility.yaml @@ -0,0 +1,97 @@ +# ABAWD disqualification is an eligibility LIMITATION (7 U.S.C. 2015(o)); it +# must never make an otherwise-ineligible household eligible or raise its +# benefit. The model excludes a work-requirement-ineligible member's needs +# (snap_unit_size) while counting the member's income in full (the +# 7 CFR 273.11(c)(1) treatment), so disqualification can only reduce +# eligibility and benefits. A prior 273.11(c)(2) proration of the member's +# income let households over the limits at full composition qualify at the +# reduced size on the disqualified earner's halved income — failing a work +# requirement CREATED a ~$288/yr benefit in Case 2 and inverted the sign of +# ABAWD reforms at population scale (switching the time limit ON raised +# SNAP spending +$0.12B on the first release-scale run). +# +# Case 1 pins the loss direction for a zero-income noncompliant member; +# Cases 2-3 jointly pin the no-gain invariant for an earning noncompliant +# member (2 = failing, 3 = the compliant counterfactual). + +- name: Case 1, noncompliant zero-income ABAWD shrinks the unit and lowers the benefit. + period: 2026 + absolute_error_margin: 1 + input: + people: + worker: + # age (YEAR) rather than monthly_age: setting monthly_age at a year + # period divides the value across months (273.24(c)(4) gotcha). + age: 30 + employment_income: 12_000 + weekly_hours_worked_before_lsr: 25 + abawd: + age: 30 + employment_income: 0 + weekly_hours_worked_before_lsr: 0 + spm_units: + unit: + members: [worker, abawd] + households: + household: + members: [worker, abawd] + state_code: TX + output: + snap_unit_size: 1 + is_snap_eligible: true + # 1-person allotment on the compliant worker's full income; strictly + # below the ~4,466 the unit receives when both members comply. + snap: 1_473 + +- name: Case 2, household over-income at full size stays ineligible when the earner fails the time limit. + period: 2026 + absolute_error_margin: 0 + input: + people: + elder: + age: 66 + social_security_retirement: 9_600 + abawd: + age: 30 + employment_income: 30_000 + weekly_hours_worked_before_lsr: 0 + spm_units: + unit: + members: [elder, abawd] + households: + household: + members: [elder, abawd] + state_code: TX + output: + # The same household with the earner COMPLYING (25h/week) is ineligible: + # $30,000 wages + $9,600 Social Security exceed the 2-person limits. + # His failing the ABAWD time limit cannot qualify it. Current + # implementation: is_snap_eligible flips true and snap = ~288. + is_snap_eligible: false + snap: 0 + +- name: Case 3, benefit with a noncompliant earning member never exceeds the compliant counterfactual. + period: 2026 + absolute_error_margin: 0 + input: + people: + elder: + age: 66 + social_security_retirement: 9_600 + abawd: + age: 30 + employment_income: 30_000 + weekly_hours_worked_before_lsr: 25 + spm_units: + unit: + members: [elder, abawd] + households: + household: + members: [elder, abawd] + state_code: TX + output: + # Compliant counterfactual for Case 2: same unit, earner works 25h/week. + # Pinned so Cases 2 and 3 together assert the no-gain invariant + # (snap[noncompliant] <= snap[compliant]). + is_snap_eligible: false + snap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/federal_work_study_income_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/federal_work_study_income_exclusion.yaml index b0214a4a966..6d4fdd1f421 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/federal_work_study_income_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/federal_work_study_income_exclusion.yaml @@ -23,26 +23,39 @@ output: meets_snap_work_exception: true -- name: Case 4, work-requirement-ineligible adult earned income is prorated. +- name: Case 4, ABAWD-time-limit-ineligible adult earned income counts in full. period: 2024-01 absolute_error_margin: 0.01 input: people: person1: + # Works 40 hours: exempt from ABAWD via hours; general met in baseline. monthly_age: 35 + age: + 2024: 35 is_tax_unit_dependent: false - meets_snap_general_work_requirements: true - meets_snap_abawd_work_requirements: true + weekly_hours_worked_before_lsr: 40 person2: + # In-band, no dependent child, not working, not exempt: fails ABAWD. monthly_age: 30 + age: + 2024: 30 is_tax_unit_dependent: false employment_income: 2024: 12_000 - meets_snap_general_work_requirements: false - meets_snap_abawd_work_requirements: false + weekly_hours_worked_before_lsr: 0 + is_disabled: false + is_pregnant: false + is_veteran: false spm_units: spm_unit: members: [person1, person2] + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX output: - snap_work_requirement_income_proration_fraction: [1, 0.5] - snap_earned_income: 500 + snap_earned_income: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap.yaml index 049e0d79fed..8eca662202d 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap.yaml @@ -117,25 +117,28 @@ snap_net_income: 0 snap: 3_522 -- name: Case 5, work-requirement-ineligible adult is excluded from SNAP benefit size. +- name: Case 5, ABAWD-time-limit-ineligible adult is excluded from SNAP benefit size. period: 2024-01 absolute_error_margin: 0.01 input: people: person1: + # Working 40 hours: meets ABAWD via hours; general met in baseline. age: 2024: 35 monthly_age: 35 is_tax_unit_dependent: false - meets_snap_general_work_requirements: true - meets_snap_abawd_work_requirements: true + weekly_hours_worked_before_lsr: 40 person2: + # In-band, no dependent child, not working, not exempt: fails ABAWD. age: 2024: 30 monthly_age: 30 is_tax_unit_dependent: false - meets_snap_general_work_requirements: false - meets_snap_abawd_work_requirements: false + weekly_hours_worked_before_lsr: 0 + is_disabled: false + is_pregnant: false + is_veteran: false spm_units: spm_unit: members: [person1, person2] @@ -153,27 +156,32 @@ snap_max_allotment: 291 snap: 291 -- name: Case 6, work-requirement-ineligible adult income is prorated for SNAP. +- name: Case 6, ABAWD-time-limit-ineligible adult income counts in full for SNAP. period: 2024-01 absolute_error_margin: 0.01 input: people: person1: + # Working 40 hours: meets ABAWD via hours; general met in baseline. age: 2024: 35 monthly_age: 35 is_tax_unit_dependent: false - meets_snap_general_work_requirements: true - meets_snap_abawd_work_requirements: true + weekly_hours_worked_before_lsr: 40 person2: + # Earns $12,000/yr but works 0 hours (e.g., lump-sum), no dependent child, + # in-band, not exempt: fails ABAWD. Needs leave the unit; income counts + # in full (273.11(c)(1) treatment) so disqualification cannot raise the benefit. age: 2024: 30 monthly_age: 30 is_tax_unit_dependent: false employment_income: 2024: 12_000 - meets_snap_general_work_requirements: false - meets_snap_abawd_work_requirements: false + weekly_hours_worked_before_lsr: 0 + is_disabled: false + is_pregnant: false + is_veteran: false spm_units: spm_unit: members: [person1, person2] @@ -186,7 +194,126 @@ state_code: TX output: snap_unit_size: 1 - snap_work_requirement_income_proration_fraction: [1, 0.5] - snap_earned_income: 500 - snap_gross_income: 500 - snap: 230.40 + snap_earned_income: 1_000 + snap_gross_income: 1_000 + snap: 110.40 + +# Acceptance-criteria integration tests for issue #8862 (person-level SNAP work +# requirements). Period 2024-01 is pre-HR1 (ABAWD band 18-52, dependent-child +# threshold 18, homeless/veteran exempt); ABAWD exemptions are set explicitly +# false on the ABAWD failers so no unintended exemption triggers. + +- name: Case 7, issue example, unemployed non-disabled adult with a 10-year-old dependent is eligible. + period: 2024-01 + absolute_error_margin: 0.01 + input: + people: + person1: + # Unemployed, non-disabled 30-year-old. A dependent child under 18 is in + # the unit, so ABAWD is skipped; general requirement met in baseline. + age: + 2024: 30 + monthly_age: 30 + is_tax_unit_dependent: false + weekly_hours_worked_before_lsr: 0 + is_disabled: false + person2: + # 10-year-old tax-unit dependent: exempt from the general requirement by + # age (under 16) and triggers the household's dependent-child status. + age: + 2024: 10 + monthly_age: 10 + is_tax_unit_dependent: true + spm_units: + spm_unit: + members: [person1, person2] + # A zero-income TX family of two qualifies for TANF organically, which + # counts as SNAP unearned income; zero it to isolate work requirements. + tanf: 0 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # ABAWD is skipped for both (dependent child present); general met in baseline. + meets_snap_work_requirements_person: [true, true] + meets_snap_work_requirements: true + is_snap_eligible: true + snap_unit_size: 2 + # Zero income, FY2024 CONTIGUOUS_US size-2 max allotment. + snap_max_allotment: 535 + snap: 535 + +- name: Case 8, mixed household with no children, one working adult and one non-working ABAWD. + period: 2024-01 + absolute_error_margin: 0.01 + input: + people: + person1: + # Works 30 hours: exempt from ABAWD via hours; general met in baseline. + age: + 2024: 40 + monthly_age: 40 + is_tax_unit_dependent: false + weekly_hours_worked_before_lsr: 30 + person2: + # In-band, no dependent child, not working, no exemption: fails ABAWD. + age: + 2024: 30 + monthly_age: 30 + is_tax_unit_dependent: false + weekly_hours_worked_before_lsr: 0 + is_disabled: false + is_pregnant: false + is_veteran: false + spm_units: + spm_unit: + members: [person1, person2] + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # person1 passes, person2 fails ABAWD and is removed from the SNAP unit. + meets_snap_work_requirements_person: [true, false] + meets_snap_work_requirements: true + is_snap_eligible: true + snap_unit_size: 1 + +- name: Case 9, single non-working non-disabled adult with no children is work-requirement ineligible. + period: 2024-01 + absolute_error_margin: 0.01 + input: + people: + person1: + # In-band, no dependent child, not working, no exemption: fails ABAWD, and + # with no dependent child the person-level test requires both ABAWD and + # general, so the person fails. + age: + 2024: 30 + monthly_age: 30 + is_tax_unit_dependent: false + weekly_hours_worked_before_lsr: 0 + is_disabled: false + is_pregnant: false + is_veteran: false + spm_units: + spm_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + meets_snap_work_requirements_person: [false] + meets_snap_work_requirements: false + snap_unit_size: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_unit_size.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_unit_size.yaml index 76ffa386d72..861aa10cbe5 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_unit_size.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_unit_size.yaml @@ -161,23 +161,38 @@ output: snap_unit_size: 1 -- name: Case 10, work-requirement-ineligible adult reduces unit size. +- name: Case 10, ABAWD-time-limit-ineligible adult reduces unit size. period: 2024-01 input: people: person1: + # Working adult, meets ABAWD via hours; general met in baseline. monthly_age: 35 + age: + 2024: 35 is_tax_unit_dependent: false - meets_snap_general_work_requirements: true - meets_snap_abawd_work_requirements: true + weekly_hours_worked_before_lsr: 40 person2: + # In-band, no dependent child, not working, not exempt: fails ABAWD. monthly_age: 30 + age: + 2024: 30 is_tax_unit_dependent: false - meets_snap_general_work_requirements: false - meets_snap_abawd_work_requirements: false + weekly_hours_worked_before_lsr: 0 + is_disabled: false + is_pregnant: false + is_veteran: false spm_units: spm_unit: members: [person1, person2] spm_unit_size: 2 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX output: + # person2 fails the ABAWD time limit and is removed from the SNAP unit. snap_unit_size: 1 diff --git a/policyengine_us/variables/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.py b/policyengine_us/variables/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.py index df5768470a9..cb56a9d3229 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.py @@ -6,9 +6,11 @@ class medicaid_community_engagement_pass_through_eligible(Variable): entity = Person label = "Medicaid community engagement pass-through eligibility" definition_period = MONTH + documentation = "Under 42 CFR 435.554(c)(7) (CMS-2454-IFC), the SNAP exclusion from the Medicaid community engagement requirement is a status test: the individual is a member of a household receiving SNAP and is not exempt from (i.e., is subject to) a SNAP work requirement. Unlike the TANF exclusion, states do not confirm actual compliance with SNAP work requirements. The model tests subject-to status with the general work requirement age brackets and non-age registration exemptions; the 7 CFR 273.7(b)(1)(vii) exemption for people working 30 or more hours weekly is not netted out, and post-HR1 adults aged 60-64 who are subject only to the ABAWD requirement are not separately captured." reference = ( "https://www.congress.gov/bill/119th-congress/house-bill/1/text", "https://www.medicaid.gov/federal-policy-guidance/downloads/cib12082025.pdf#page=6", + "https://www.federalregister.gov/documents/2026/06/03/2026-11094/medicaid-program-community-engagement-requirement-for-certain-individuals", ) def formula(person, period, parameters): @@ -23,29 +25,7 @@ def formula(person, period, parameters): bool ) snap_non_age_exempt = person("is_snap_work_registration_exempt_non_age", period) - general_work_compliant = person("meets_snap_general_work_requirements", period) - abawd_work_compliant = person("meets_snap_abawd_work_requirements", period) - hr1_in_effect = person("is_snap_abawd_hr1_in_effect", period) - pre_hr1_abawd = parameters("2025-06-01").gov.usda.snap.work_requirements.abawd - dependent_age_threshold = where( - hr1_in_effect, - snap_work.abawd.age_threshold.dependent, - pre_hr1_abawd.age_threshold.dependent, - ) - # Mirror the SNAP ABAWD child exception exactly: 7 CFR 273.24(c)(4) - # keys on any household member under the age threshold, regardless of - # tax-unit dependency (see meets_snap_work_requirements_person). The - # pass-through deems a person compliant because they satisfy SNAP's - # requirements, so it must apply SNAP's own exception semantics. - has_household_child = person.spm_unit.any(age < dependent_age_threshold) - snap_work_compliant = where( - has_household_child, - general_work_compliant, - general_work_compliant & abawd_work_compliant, - ) - snap_pass_through = ( - snap & ~snap_age_exempt & ~snap_non_age_exempt & snap_work_compliant - ) + snap_pass_through = snap & ~snap_age_exempt & ~snap_non_age_exempt tanf_pass_through = tanf & person.spm_unit( "meets_tanf_work_requirements", period diff --git a/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/is_in_snap_abawd_waived_area.py b/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/is_in_snap_abawd_waived_area.py index 548ad83eb4f..235035b30f7 100644 --- a/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/is_in_snap_abawd_waived_area.py +++ b/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/is_in_snap_abawd_waived_area.py @@ -23,4 +23,11 @@ class is_in_snap_abawd_waived_area(Variable): def formula(person, period, parameters): p = parameters(period).gov.usda.snap.work_requirements.abawd county_fips = person.household("county_fips", period.this_year) - return np.isin(county_fips, p.waived_county_fips) + in_waived_county = np.isin(county_fips, p.waived_county_fips) + # Statewide waivers (nine states/territories as of October 1, 2024) + # are keyed by state postal code so both datasets and household + # situations resolve them (state_fips is an input defaulting to 6, + # so it cannot key policy). + state = person.household("state_code_str", period.this_year) + in_waived_state = np.isin(state, p.waived_states) + return in_waived_county | in_waived_state diff --git a/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/is_snap_work_registration_noncompliant.py b/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/is_snap_work_registration_noncompliant.py new file mode 100644 index 00000000000..081299308e3 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/is_snap_work_registration_noncompliant.py @@ -0,0 +1,14 @@ +from policyengine_us.model_api import * + + +class is_snap_work_registration_noncompliant(Variable): + value_type = bool + entity = Person + label = "SNAP work registration noncompliant" + definition_period = MONTH + default_value = False + documentation = "Whether a non-exempt SNAP work registrant has affirmatively failed to comply with SNAP work requirements without good cause (refusing suitable employment, employment and training program noncompliance, or voluntarily quitting a job or reducing work effort under 7 CFR 273.7(j)). These State-agency 'without good cause' determinations are not observable in survey data, so the baseline assumes compliance (false). Set true to model work-registration sanctions under 7 CFR 273.7(f)(1)-(2)." + reference = ( + "https://www.ecfr.gov/current/title-7/subtitle-B/chapter-II/subchapter-C/part-273/subpart-E/section-273.7#p-273.7(f)", + "https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title7-section2015&num=0&edition=prelim", + ) diff --git a/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/meets_snap_general_work_requirements.py b/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/meets_snap_general_work_requirements.py index 26fe0420835..d82e3e36620 100644 --- a/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/meets_snap_general_work_requirements.py +++ b/policyengine_us/variables/gov/usda/snap/eligibility/work_requirements/meets_snap_general_work_requirements.py @@ -6,13 +6,7 @@ class meets_snap_general_work_requirements(Variable): entity = Person label = "Person is eligible for SNAP benefits via general work requirements" definition_period = MONTH - documentation = ( - "Whether the person meets the SNAP general work requirements or is " - "exempt from them. Non-modeled provision: voluntary quit and reduced " - "work effort disqualifications (7 CFR 273.7(j)) are not modeled, " - "because job-separation events are unobservable in cross-sectional " - "survey data." - ) + documentation = "Working 30 or more hours weekly is an exemption from SNAP work registration under 7 CFR 273.7(b)(1)(vii), not an affirmative requirement. Non-exempt registrants remain eligible unless they affirmatively fail to comply without good cause (refuse suitable employment, employment and training noncompliance, or voluntary quit/reduction under 7 CFR 273.7(j)). Because those 'without good cause' noncompliance events are not observable in survey data, the baseline assumes registration compliance; the is_snap_work_registration_noncompliant hook can be set true to model sanctions under 7 CFR 273.7(f)(1)-(2). Work program participation under 7 CFR 273.7(a)(1)(ii) is affirmative compliance and overrides the noncompliance hook." reference = "https://www.law.cornell.edu/cfr/text/7/273.7" def formula(person, period, parameters): @@ -44,7 +38,10 @@ def formula(person, period, parameters): | is_working ) # Non-exempt registrants comply by participating in, or otherwise - # complying with, a work program under 7 CFR 273.7(a)(1). - compliant = person("is_snap_work_program_participant", period) - + # complying with, a work program under 7 CFR 273.7(a)(1). Absent + # an affirmative noncompliance determination (unobservable in + # survey data), registrants are assumed compliant. + compliant = person("is_snap_work_program_participant", period) | ~person( + "is_snap_work_registration_noncompliant", period + ) return exempted | compliant diff --git a/policyengine_us/variables/gov/usda/snap/income/snap_earned_income.py b/policyengine_us/variables/gov/usda/snap/income/snap_earned_income.py index eaef5e8148e..af536e0e04a 100644 --- a/policyengine_us/variables/gov/usda/snap/income/snap_earned_income.py +++ b/policyengine_us/variables/gov/usda/snap/income/snap_earned_income.py @@ -6,20 +6,9 @@ class snap_earned_income(Variable): entity = SPMUnit definition_period = MONTH label = "SNAP earned income" - documentation = "Earned income for calculating the SNAP earned income deduction" + documentation = "Earned income for calculating the SNAP earned income deduction. Work-requirement-ineligible members' earned income counts in full (the 7 CFR 273.11(c)(1) treatment): their needs already leave the unit via snap_unit_size, and counting income in full guarantees a work-requirement disqualification can only reduce eligibility and benefits (7 U.S.C. 2015(o) is an eligibility limitation). The prior 273.11(c)(2) proration let households over the income limits at full composition qualify at the reduced size on the disqualified member's halved income, inverting the sign of ABAWD reforms at population scale." reference = "https://www.law.cornell.edu/cfr/text/7/273.9#b_1" unit = USD def formula(spm_unit, period): - person = spm_unit.members - income = spm_unit("snap_earned_income_person", period) - countable_earner = person("snap_countable_earner", period) - employment_income = person("employment_income", period) - full_employment_income = spm_unit.sum( - where(countable_earner, employment_income, 0) - ) - fraction = person("snap_work_requirement_income_proration_fraction", period) - prorated_employment_income = spm_unit.sum( - where(countable_earner, employment_income * fraction, 0) - ) - return income - full_employment_income + prorated_employment_income + return spm_unit("snap_earned_income_person", period) diff --git a/policyengine_us/variables/gov/usda/snap/income/snap_work_requirement_income_proration_fraction.py b/policyengine_us/variables/gov/usda/snap/income/snap_work_requirement_income_proration_fraction.py deleted file mode 100644 index c2c73fdef06..00000000000 --- a/policyengine_us/variables/gov/usda/snap/income/snap_work_requirement_income_proration_fraction.py +++ /dev/null @@ -1,16 +0,0 @@ -from policyengine_us.model_api import * - - -class snap_work_requirement_income_proration_fraction(Variable): - value_type = float - entity = Person - label = "SNAP work requirement income proration fraction" - definition_period = MONTH - reference = "https://www.law.cornell.edu/cfr/text/7/273.11#c_2" - - def formula(person, period, parameters): - meets_work_requirements = person("meets_snap_work_requirements_person", period) - snap_unit_size = person.spm_unit("snap_unit_size", period) - unit_size = person.spm_unit("spm_unit_size", period) - ineligible_fraction = where(unit_size > 0, snap_unit_size / unit_size, 0) - return where(meets_work_requirements, 1, ineligible_fraction)