diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..91b689ea2fe 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,6 @@ +- bump: minor + changes: + added: + - Add SNAP immigration status eligibility, reflecting changes from the One Big Beautiful Bill Act of 2025. + - Exclude immigration-ineligible members from SNAP unit size. + - Add California-specific delayed effective date (April 1, 2026) for SNAP immigration eligibility changes per ACL 25-92. diff --git a/policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/eligible_immigration_statuses.yaml b/policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/eligible_immigration_statuses.yaml new file mode 100644 index 00000000000..73e21945e25 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/eligible_immigration_statuses.yaml @@ -0,0 +1,26 @@ +description: California limits Supplemental Nutrition Assistance Program eligibility to these immigration statuses. +values: + 2018-01-01: + - CITIZEN + - LEGAL_PERMANENT_RESIDENT + - REFUGEE + - ASYLEE + - DEPORTATION_WITHHELD + - CUBAN_HAITIAN_ENTRANT + - CONDITIONAL_ENTRANT + - PAROLED_ONE_YEAR + + 2026-04-01: + - CITIZEN + - LEGAL_PERMANENT_RESIDENT + - CUBAN_HAITIAN_ENTRANT + +metadata: + label: California SNAP eligible immigration statuses + period: year + unit: list + reference: + - title: California CDSS All County Letter No. 25-92 - CalFresh Implementation of H.R. 1 Noncitizen Eligibility Changes + href: https://www.cdss.ca.gov/Portals/9/Additional-Resources/Letters-and-Notices/ACLs/2025/25-92.pdf#page=7 + - title: 7 USC 2015(f) - Eligibility disqualifications + href: https://www.law.cornell.edu/uscode/text/7/2015#f diff --git a/policyengine_us/parameters/gov/usda/snap/eligibility/eligible_immigration_statuses.yaml b/policyengine_us/parameters/gov/usda/snap/eligibility/eligible_immigration_statuses.yaml new file mode 100644 index 00000000000..626fee9b30e --- /dev/null +++ b/policyengine_us/parameters/gov/usda/snap/eligibility/eligible_immigration_statuses.yaml @@ -0,0 +1,28 @@ +description: The Department of Agriculture limits Supplemental Nutrition Assistance Program eligibility to these immigration statuses. +values: + 2018-01-01: + - CITIZEN + - LEGAL_PERMANENT_RESIDENT + - REFUGEE + - ASYLEE + - DEPORTATION_WITHHELD + - CUBAN_HAITIAN_ENTRANT + - CONDITIONAL_ENTRANT + - PAROLED_ONE_YEAR + + 2025-07-01: # Real effective date July 4th. + - CITIZEN + - LEGAL_PERMANENT_RESIDENT + - CUBAN_HAITIAN_ENTRANT + # Compacts of Free Association (COFA) citizens +metadata: + label: SNAP eligible immigration statuses + period: year + unit: list + reference: + - title: 7 USC 2015(f) - Eligibility disqualifications + href: https://www.law.cornell.edu/uscode/text/7/2015#f + - title: 8 USC 1612 - Limited eligibility of qualified aliens for certain Federal programs + href: https://www.law.cornell.edu/uscode/text/8/1612 + - title: USDA FNS - SNAP Implementation of OBBB - Alien SNAP Eligibility + href: https://www.fns.usda.gov/snap/obbb-alien-eligibility diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/snap/eligibility/ca_snap_immigration_status_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/snap/eligibility/ca_snap_immigration_status_eligible.yaml new file mode 100644 index 00000000000..a0b7a12ff56 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/snap/eligibility/ca_snap_immigration_status_eligible.yaml @@ -0,0 +1,111 @@ +- name: Case 1, CA citizen always eligible. + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: CITIZEN + households: + household1: + members: [person1] + state_code: CA + output: + ca_snap_immigration_status_eligible: true + +- name: Case 2, CA refugee eligible pre-OBBBA. + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: REFUGEE + households: + household1: + members: [person1] + state_code: CA + output: + ca_snap_immigration_status_eligible: true + +- name: Case 3, CA refugee still eligible in 2025 due to delayed implementation. + period: 2025 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: REFUGEE + households: + household1: + members: [person1] + state_code: CA + output: + ca_snap_immigration_status_eligible: true + +- name: Case 4, CA refugee ineligible after April 2026. + period: 2027 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: REFUGEE + households: + household1: + members: [person1] + state_code: CA + output: + ca_snap_immigration_status_eligible: false + +- name: Case 5, CA asylee still eligible in 2025. + period: 2025 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: ASYLEE + households: + household1: + members: [person1] + state_code: CA + output: + ca_snap_immigration_status_eligible: true + +- name: Case 6, CA asylee ineligible after April 2026. + period: 2027 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: ASYLEE + households: + household1: + members: [person1] + state_code: CA + output: + ca_snap_immigration_status_eligible: false + +- name: Case 7, CA LPR always eligible. + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: LEGAL_PERMANENT_RESIDENT + households: + household1: + members: [person1] + state_code: CA + output: + ca_snap_immigration_status_eligible: true + +- name: Case 8, CA undocumented never eligible. + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: UNDOCUMENTED + households: + household1: + members: [person1] + state_code: CA + output: + ca_snap_immigration_status_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/is_snap_immigration_status_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/is_snap_immigration_status_eligible.yaml new file mode 100644 index 00000000000..43868fb9686 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/is_snap_immigration_status_eligible.yaml @@ -0,0 +1,118 @@ +- name: Case 1, citizen always eligible. + period: 2024 + input: + immigration_status: CITIZEN + output: + is_snap_immigration_status_eligible: true + +- name: Case 2, refugee eligible pre-OBBBA. + period: 2024 + input: + immigration_status: REFUGEE + output: + is_snap_immigration_status_eligible: true + +- name: Case 3, undocumented never eligible. + period: 2024 + input: + immigration_status: UNDOCUMENTED + output: + is_snap_immigration_status_eligible: false + +- name: Case 4, refugee ineligible post-OBBBA federal date. + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: REFUGEE + households: + household1: + members: [person1] + state_code: TX + output: + is_snap_immigration_status_eligible: false + +- name: Case 5, asylee ineligible post-OBBBA federal date. + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: ASYLEE + households: + household1: + members: [person1] + state_code: TX + output: + is_snap_immigration_status_eligible: false + +- name: Case 6, citizen still eligible post-OBBBA. + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: CITIZEN + households: + household1: + members: [person1] + state_code: TX + output: + is_snap_immigration_status_eligible: true + +- name: Case 7, LPR still eligible post-OBBBA. + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: LEGAL_PERMANENT_RESIDENT + households: + household1: + members: [person1] + state_code: TX + output: + is_snap_immigration_status_eligible: true + +- name: Case 8, CA refugee still eligible in 2025 due to delayed implementation. + period: 2025 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: REFUGEE + households: + household1: + members: [person1] + state_code: CA + output: + is_snap_immigration_status_eligible: true + +- name: Case 9, CA refugee still eligible in January 2026 due to delayed implementation. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: REFUGEE + households: + household1: + members: [person1] + state_code: CA + output: + is_snap_immigration_status_eligible: true + +- name: Case 10, CA refugee ineligible in 2027 after delayed implementation ends. + period: 2027 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: REFUGEE + households: + household1: + members: [person1] + state_code: CA + output: + is_snap_immigration_status_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/is_snap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/is_snap_eligible.yaml index 6699ea6fb28..d13d7f7e2ae 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/is_snap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/is_snap_eligible.yaml @@ -99,3 +99,21 @@ meets_snap_categorical_eligibility: false output: is_snap_eligible: true + +- name: Case 8, single undocumented ineligible student. + period: 2024-01 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: UNDOCUMENTED + is_snap_ineligible_student: true + spm_units: + spm_unit1: + members: [person1] + meets_snap_gross_income_test: true + meets_snap_net_income_test: true + meets_snap_asset_test: true + meets_snap_categorical_eligibility: false + output: + is_snap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_unit.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_unit.yaml deleted file mode 100644 index d93b2db0c7c..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_unit.yaml +++ /dev/null @@ -1,50 +0,0 @@ -- name: Household with one ineligible studnet - period: 2022 - input: - people: - person1: - is_snap_ineligible_student: false - person2: - is_snap_ineligible_student: false - person3: - is_snap_ineligible_student: true - spm_units: - spm_unit1: - members: [person1, person2, person3] - spm_unit_size: 3 - output: - snap_unit_size: 2 - -- name: Everybody eligible - period: 2022 - input: - people: - person1: - is_snap_ineligible_student: false - person2: - is_snap_ineligible_student: false - person3: - is_snap_ineligible_student: false - spm_units: - spm_unit1: - members: [person1, person2, person3] - spm_unit_size: 3 - output: - snap_unit_size: 3 - -- name: No eligible people - period: 2022 - input: - people: - person1: - is_snap_ineligible_student: true - person2: - is_snap_ineligible_student: true - person3: - is_snap_ineligible_student: true - spm_units: - spm_unit1: - members: [person1, person2, person3] - spm_unit_size: 3 - output: - 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 new file mode 100644 index 00000000000..8ae05db6bc2 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_unit_size.yaml @@ -0,0 +1,162 @@ +- name: Household with one ineligible student + period: 2022 + input: + people: + person1: + is_snap_ineligible_student: false + person2: + is_snap_ineligible_student: false + person3: + is_snap_ineligible_student: true + spm_units: + spm_unit1: + members: [person1, person2, person3] + spm_unit_size: 3 + output: + snap_unit_size: 2 + +- name: Everybody eligible + period: 2022 + input: + people: + person1: + is_snap_ineligible_student: false + person2: + is_snap_ineligible_student: false + person3: + is_snap_ineligible_student: false + spm_units: + spm_unit1: + members: [person1, person2, person3] + spm_unit_size: 3 + output: + snap_unit_size: 3 + +- name: No eligible people + period: 2022 + input: + people: + person1: + is_snap_ineligible_student: true + person2: + is_snap_ineligible_student: true + person3: + is_snap_ineligible_student: true + spm_units: + spm_unit1: + members: [person1, person2, person3] + spm_unit_size: 3 + output: + snap_unit_size: 0 + +- name: Case 4, mixed-status household with one immigration-ineligible member. + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: CITIZEN + person2: + immigration_status: UNDOCUMENTED + person3: + immigration_status: CITIZEN + spm_units: + spm_unit1: + members: [person1, person2, person3] + spm_unit_size: 3 + output: + snap_unit_size: 2 + +- name: Case 5, all members immigration-ineligible. + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: UNDOCUMENTED + person2: + immigration_status: UNDOCUMENTED + spm_units: + spm_unit1: + members: [person1, person2] + spm_unit_size: 2 + output: + snap_unit_size: 0 + +- name: Case 6, ineligible student and immigration-ineligible member. + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: CITIZEN + person2: + immigration_status: UNDOCUMENTED + person3: + immigration_status: CITIZEN + is_snap_ineligible_student: true + person4: + immigration_status: CITIZEN + spm_units: + spm_unit1: + members: [person1, person2, person3, person4] + spm_unit_size: 4 + output: + snap_unit_size: 2 + +- name: Case 7, post-OBBBA refugee no longer eligible reduces unit size. + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: CITIZEN + person2: + immigration_status: REFUGEE + person3: + immigration_status: CITIZEN + households: + household1: + members: [person1, person2, person3] + state_code: TX + spm_units: + spm_unit1: + members: [person1, person2, person3] + spm_unit_size: 3 + output: + snap_unit_size: 2 + +- name: Case 8, pre-OBBBA refugee is eligible, full unit size. + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: CITIZEN + person2: + immigration_status: REFUGEE + person3: + immigration_status: CITIZEN + spm_units: + spm_unit1: + members: [person1, person2, person3] + spm_unit_size: 3 + output: + snap_unit_size: 3 + +- name: Case 9, undocumented ineligible student not double-counted. + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person1: + immigration_status: CITIZEN + person2: + immigration_status: UNDOCUMENTED + is_snap_ineligible_student: true + spm_units: + spm_unit1: + members: [person1, person2] + spm_unit_size: 2 + output: + snap_unit_size: 1 diff --git a/policyengine_us/variables/gov/states/ca/cdss/snap/eligibility/ca_snap_immigration_status_eligible.py b/policyengine_us/variables/gov/states/ca/cdss/snap/eligibility/ca_snap_immigration_status_eligible.py new file mode 100644 index 00000000000..a09386c0bc5 --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/cdss/snap/eligibility/ca_snap_immigration_status_eligible.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class ca_snap_immigration_status_eligible(Variable): + value_type = bool + entity = Person + label = "Person is eligible for California SNAP due to immigration status" + definition_period = MONTH + defined_for = StateCode.CA + reference = ( + "https://www.cdss.ca.gov/Portals/9/Additional-Resources/Letters-and-Notices/ACLs/2025/25-92.pdf#page=7", + "https://www.law.cornell.edu/uscode/text/7/2015#f", + ) + + def formula(person, period, parameters): + # California delays OBBBA implementation to April 1, 2026 per ACL 25-92. + p = parameters(period).gov.states.ca.cdss.snap.eligibility + immigration_status = person("immigration_status", period) + immigration_status_str = immigration_status.decode_to_str() + return np.isin( + immigration_status_str, + p.eligible_immigration_statuses, + ) diff --git a/policyengine_us/variables/gov/usda/snap/eligibility/is_snap_eligible.py b/policyengine_us/variables/gov/usda/snap/eligibility/is_snap_eligible.py index c8f4efedc34..94c59da1e7e 100644 --- a/policyengine_us/variables/gov/usda/snap/eligibility/is_snap_eligible.py +++ b/policyengine_us/variables/gov/usda/snap/eligibility/is_snap_eligible.py @@ -10,6 +10,7 @@ class is_snap_eligible(Variable): reference = ( "https://www.law.cornell.edu/uscode/text/7/2017#a", "https://www.law.cornell.edu/uscode/text/7/2014#c", + "https://www.law.cornell.edu/uscode/text/7/2015#f", ) def formula(spm_unit, period, parameters): @@ -28,8 +29,12 @@ def formula(spm_unit, period, parameters): work_requirements_eligibility = spm_unit( "meets_snap_work_requirements", period ) + immigration_eligible_member_present = spm_unit.any( + person("is_snap_immigration_status_eligible", period) + ) return ( (normal_eligibility | categorical_eligibility) & eligible_person_present & work_requirements_eligibility + & immigration_eligible_member_present ) diff --git a/policyengine_us/variables/gov/usda/snap/eligibility/is_snap_immigration_status_eligible.py b/policyengine_us/variables/gov/usda/snap/eligibility/is_snap_immigration_status_eligible.py new file mode 100644 index 00000000000..f396a238b5d --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/eligibility/is_snap_immigration_status_eligible.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class is_snap_immigration_status_eligible(Variable): + value_type = bool + entity = Person + label = "Person is eligible for SNAP due to immigration status" + definition_period = MONTH + reference = ( + "https://www.law.cornell.edu/uscode/text/7/2015#f", + "https://www.law.cornell.edu/uscode/text/8/1612", + "https://www.fns.usda.gov/snap/obbb-alien-eligibility", + ) + + def formula(person, period, parameters): + immigration_status = person("immigration_status", period) + immigration_status_str = immigration_status.decode_to_str() + + p = parameters(period).gov.usda.snap.eligibility + federal_eligible = np.isin( + immigration_status_str, + p.eligible_immigration_statuses, + ) + ca_eligible = person("ca_snap_immigration_status_eligible", period) + + return federal_eligible | ca_eligible diff --git a/policyengine_us/variables/gov/usda/snap/income/snap_fpg.py b/policyengine_us/variables/gov/usda/snap/income/snap_fpg.py index ca0144bc512..aa51ccad72d 100644 --- a/policyengine_us/variables/gov/usda/snap/income/snap_fpg.py +++ b/policyengine_us/variables/gov/usda/snap/income/snap_fpg.py @@ -12,7 +12,7 @@ class snap_fpg(Variable): definition_period = MONTH def formula(spm_unit, period, parameters): - n = spm_unit("snap_unit_size", period.this_year) + n = spm_unit("snap_unit_size", period) state_group = spm_unit.household("state_group_str", period.this_year) year = period.start.year month = period.start.month diff --git a/policyengine_us/variables/gov/usda/snap/snap_unit.py b/policyengine_us/variables/gov/usda/snap/snap_unit.py deleted file mode 100644 index 0bb83609ba8..00000000000 --- a/policyengine_us/variables/gov/usda/snap/snap_unit.py +++ /dev/null @@ -1,15 +0,0 @@ -from policyengine_us.model_api import * - - -class snap_unit_size(Variable): - value_type = int - entity = SPMUnit - label = "SNAP unit" - definition_period = YEAR - - def formula(spm_unit, period, parameters): - unit_size = spm_unit("spm_unit_size", period) - person = spm_unit.members - ineligible_student = person("is_snap_ineligible_student", period) - ineligible_students = spm_unit.sum(ineligible_student) - return max_(unit_size - ineligible_students, 0) diff --git a/policyengine_us/variables/gov/usda/snap/snap_unit_size.py b/policyengine_us/variables/gov/usda/snap/snap_unit_size.py new file mode 100644 index 00000000000..d4e9ab9194f --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/snap_unit_size.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class snap_unit_size(Variable): + value_type = int + entity = SPMUnit + label = "SNAP unit size" + definition_period = MONTH + reference = ( + "https://www.law.cornell.edu/uscode/text/7/2014#b", + "https://www.law.cornell.edu/uscode/text/7/2015#f", + ) + + def formula(spm_unit, period, parameters): + unit_size = spm_unit("spm_unit_size", period) + person = spm_unit.members + ineligible = person("is_snap_ineligible_student", period) | ~person( + "is_snap_immigration_status_eligible", period + ) + ineligible_count = spm_unit.sum(ineligible) + return max_(unit_size - ineligible_count, 0)