Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/fixed/snap-work-requirement-issues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix SNAP work-requirement treatment for per-person disqualifications and OBBBA ABAWD Indian exemptions.
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,31 @@
output:
meets_snap_abawd_work_requirements: true

# --- 2015(o)(3)(F)-(G): Indian, Urban Indian, or California Indian ---

- name: Case 24, post-HR1 Indian, Urban Indian, or California Indian exempt.
period: 2027
input:
age: 30
weekly_hours_worked_before_lsr: 1
is_disabled: false
is_snap_abawd_indian_exempt: true
output:
meets_snap_abawd_work_requirements: true

- name: Case 25, pre-HR1 Indian exemption input does not change ABAWD rules.
period: 2024
input:
age: 30
weekly_hours_worked_before_lsr: 1
is_disabled: false
is_snap_abawd_indian_exempt: true
output:
meets_snap_abawd_work_requirements: false

# --- HR1 removed exemptions: Homeless and veteran ---

- name: Case 24, post-HR1 homeless NOT exempt.
- name: Case 26, post-HR1 homeless NOT exempt.
period: 2027
absolute_error_margin: 0.1
input:
Expand All @@ -358,7 +380,7 @@
output:
meets_snap_abawd_work_requirements: false

- name: Case 25, post-HR1 veteran NOT exempt.
- name: Case 27, post-HR1 veteran NOT exempt.
period: 2027
absolute_error_margin: 0.1
input:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@

- name: Case 5, CA pre-HR1 age 55 exempt flows through SPM.
period: 2026-01
absolute_error_margin: 0.9
input:
people:
person1:
Expand All @@ -63,7 +62,6 @@

- name: Case 6, non-CA post-HR1 age 55 not exempt.
period: 2026-01
absolute_error_margin: 0.9
input:
people:
person1:
Expand All @@ -83,3 +81,63 @@
state_code: TX
output:
meets_snap_work_requirements: false

- name: Case 7, two adults remain eligible when one adult passes.
period: 2024-01
input:
people:
person1:
monthly_age: 35
is_tax_unit_dependent: false
meets_snap_general_work_requirements: true
meets_snap_abawd_work_requirements: true
person2:
monthly_age: 30
is_tax_unit_dependent: false
meets_snap_general_work_requirements: false
meets_snap_abawd_work_requirements: false
spm_units:
spm_unit:
members: [person1, person2]
output:
meets_snap_work_requirements: true

- name: Case 8, two adults are ineligible when both adults fail.
period: 2024-01
input:
people:
person1:
monthly_age: 35
is_tax_unit_dependent: false
meets_snap_general_work_requirements: false
meets_snap_abawd_work_requirements: false
person2:
monthly_age: 30
is_tax_unit_dependent: false
meets_snap_general_work_requirements: false
meets_snap_abawd_work_requirements: false
spm_units:
spm_unit:
members: [person1, person2]
output:
meets_snap_work_requirements: false

- name: Case 9, two adults remain eligible when both adults pass.
period: 2024-01
input:
people:
person1:
monthly_age: 35
is_tax_unit_dependent: false
meets_snap_general_work_requirements: true
meets_snap_abawd_work_requirements: true
person2:
monthly_age: 30
is_tax_unit_dependent: false
meets_snap_general_work_requirements: true
meets_snap_abawd_work_requirements: true
spm_units:
spm_unit:
members: [person1, person2]
output:
meets_snap_work_requirements: true
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,27 @@
is_federal_work_study_participant: true
output:
meets_snap_work_exception: true

- name: Case 4, work-requirement-ineligible adult earned income is prorated.
period: 2024-01
absolute_error_margin: 0.01
input:
people:
person1:
monthly_age: 35
is_tax_unit_dependent: false
meets_snap_general_work_requirements: true
meets_snap_abawd_work_requirements: true
person2:
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
spm_units:
spm_unit:
members: [person1, person2]
output:
snap_work_requirement_income_proration_fraction: [1, 0.5]
snap_earned_income: 500
74 changes: 74 additions & 0 deletions policyengine_us/tests/policy/baseline/gov/usda/snap/snap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,77 @@
# And then again subtracted as a deduction from gross income.
snap_net_income: 0
snap: 3_522

- name: Case 5, work-requirement-ineligible adult is excluded from SNAP benefit size.
period: 2024-01
absolute_error_margin: 0.01
input:
people:
person1:
age:
2024: 35
monthly_age: 35
is_tax_unit_dependent: false
meets_snap_general_work_requirements: true
meets_snap_abawd_work_requirements: true
person2:
age:
2024: 30
monthly_age: 30
is_tax_unit_dependent: false
meets_snap_general_work_requirements: false
meets_snap_abawd_work_requirements: false
spm_units:
spm_unit:
members: [person1, person2]
tax_units:
tax_unit:
members: [person1, person2]
households:
household:
members: [person1, person2]
state_code: TX
output:
meets_snap_work_requirements_person: [true, false]
meets_snap_work_requirements: true
snap_unit_size: 1
snap_max_allotment: 291
snap: 291

- name: Case 6, work-requirement-ineligible adult income is prorated for SNAP.
period: 2024-01
absolute_error_margin: 0.01
input:
people:
person1:
age:
2024: 35
monthly_age: 35
is_tax_unit_dependent: false
meets_snap_general_work_requirements: true
meets_snap_abawd_work_requirements: true
person2:
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
spm_units:
spm_unit:
members: [person1, person2]
tax_units:
tax_unit:
members: [person1, person2]
households:
household:
members: [person1, person2]
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
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,24 @@
spm_unit_size: 2
output:
snap_unit_size: 1

- name: Case 10, work-requirement-ineligible adult reduces unit size.
period: 2024-01
input:
people:
person1:
monthly_age: 35
is_tax_unit_dependent: false
meets_snap_general_work_requirements: true
meets_snap_abawd_work_requirements: true
person2:
monthly_age: 30
is_tax_unit_dependent: false
meets_snap_general_work_requirements: false
meets_snap_abawd_work_requirements: false
spm_units:
spm_unit:
members: [person1, person2]
spm_unit_size: 2
output:
snap_unit_size: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from policyengine_us.model_api import *


class is_snap_abawd_indian_exempt(Variable):
value_type = bool
entity = Person
label = "Exempt from SNAP ABAWD work requirements due to Indian, Urban Indian, or California Indian status"
definition_period = YEAR
reference = (
"https://www.congress.gov/119/plaws/publ21/PLAW-119publ21.pdf#page=11",
"https://www.congress.gov/119/plaws/publ21/PLAW-119publ21.pdf#page=12",
"https://www.law.cornell.edu/uscode/text/25/1603",
"https://www.law.cornell.edu/uscode/text/25/1679",
)
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ def formula(person, period, parameters):
# (D) Work registration exempt (non-age) — 7 U.S.C. 2015(o)(3)(D)
work_reg_exempt = person("is_snap_work_registration_exempt_non_age", period)
# (E) Pregnant — 7 U.S.C. 2015(o)(3)(E)
# NOTE: (F)-(G) Native American/Indian exemptions per IHCIA
# definitions are not yet implemented (requires tribal
# membership input variable).
is_pregnant = person("is_pregnant", period)
# (F)-(G) Indian, Urban Indian, or California Indian.
is_indian_exempt = person("is_snap_abawd_indian_exempt", period)
# TODO: HI/AK delayed adoption (2025-11-01) to be handled
# in a follow-up PR via state-level hr1_in_effect parameters.
base_conditions = (
Expand All @@ -59,8 +58,10 @@ def formula(person, period, parameters):
# Pre-HR1 exemptions: homeless, veteran
is_homeless = person.household("is_homeless", period)
is_veteran = person("is_veteran", period)
post_hr1_conditions = base_conditions | is_indian_exempt
pre_hr1_conditions = base_conditions | is_homeless | is_veteran
return where(
hr1_in_effect,
base_conditions,
base_conditions | is_homeless | is_veteran,
post_hr1_conditions,
pre_hr1_conditions,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,12 @@ class meets_snap_work_requirements(Variable):
entity = SPMUnit
label = "SPM Unit is eligible for SNAP benefits via work requirements"
definition_period = MONTH
reference = "https://www.fns.usda.gov/snap/work-requirements"
reference = (
"https://www.fns.usda.gov/snap/work-requirements",
"https://www.law.cornell.edu/cfr/text/7/273.7#f_1",
"https://www.law.cornell.edu/cfr/text/7/273.24#b",
)

def formula(spm_unit, period, parameters):
person = spm_unit.members
general_work_requirements = person(
"meets_snap_general_work_requirements", period
)
abawd_work_requirements = person("meets_snap_abawd_work_requirements", period)
# Dependent child threshold differs: pre-HR1 (18) vs post-HR1 (14)
hr1_in_effect = person("is_snap_abawd_hr1_in_effect", period)
p = parameters(period).gov.usda.snap.work_requirements.abawd.age_threshold
# Snapshot pre-HR1 values (last month before 2025-07-04 effective date).
p_pre = parameters(
"2025-06-01"
).gov.usda.snap.work_requirements.abawd.age_threshold
dep_threshold = where(hr1_in_effect, p.dependent, p_pre.dependent)
age = person("monthly_age", period)
is_dependent = person("is_tax_unit_dependent", period)
is_child = age < dep_threshold
no_dependent_child = person.spm_unit.sum(is_dependent & is_child) == 0
meets_work_requirements_person = where(
no_dependent_child,
abawd_work_requirements & general_work_requirements,
general_work_requirements,
)
return spm_unit.sum(~meets_work_requirements_person) == 0
return spm_unit.any(person("meets_snap_work_requirements_person", period))
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from policyengine_us.model_api import *


class meets_snap_work_requirements_person(Variable):
value_type = bool
entity = Person
label = "Person is eligible for SNAP benefits via work requirements"
definition_period = MONTH
reference = (
"https://www.fns.usda.gov/snap/work-requirements",
"https://www.law.cornell.edu/cfr/text/7/273.7#f_1",
"https://www.law.cornell.edu/cfr/text/7/273.24#b",
)

def formula(person, period, parameters):
general_work_requirements = person(
"meets_snap_general_work_requirements", period
)
abawd_work_requirements = person("meets_snap_abawd_work_requirements", period)
# Dependent child threshold differs: pre-HR1 (18) vs post-HR1 (14)
hr1_in_effect = person("is_snap_abawd_hr1_in_effect", period)
p = parameters(period).gov.usda.snap.work_requirements.abawd.age_threshold
# Snapshot pre-HR1 values (last month before 2025-07-04 effective date).
p_pre = parameters(
"2025-06-01"
).gov.usda.snap.work_requirements.abawd.age_threshold
dep_threshold = where(hr1_in_effect, p.dependent, p_pre.dependent)
age = person("monthly_age", period)
is_dependent = person("is_tax_unit_dependent", period)
is_child = age < dep_threshold
no_dependent_child = person.spm_unit.sum(is_dependent & is_child) == 0
return where(
no_dependent_child,
abawd_work_requirements & general_work_requirements,
general_work_requirements,
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,16 @@ class snap_earned_income(Variable):
reference = "https://www.law.cornell.edu/cfr/text/7/273.9#b_1"
unit = USD

adds = ["snap_earned_income_person"]
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
Loading
Loading