diff --git a/changelog.d/snap-ineligible-member-income.added.md b/changelog.d/snap-ineligible-member-income.added.md new file mode 100644 index 00000000000..52d3020ba8f --- /dev/null +++ b/changelog.d/snap-ineligible-member-income.added.md @@ -0,0 +1 @@ +Prorate the income and deductible expenses of SNAP-ineligible household members per 7 CFR 273.11(c) and (d), including full income counting for members sanctioned under the general work requirements, state income counting options for ineligible immigrants, and full exclusion of ineligible students' income. Excluded members no longer confer household-wide elderly or disabled status for the gross income test, shelter deduction cap, and BBCE screens; standard utility allowances are no longer prorated; and non-farm self-employment losses no longer offset other members' income per 7 CFR 273.11(a)(2)(ii). snap_earned_income_person is now a person-level employment income variable and snap_work_requirement_income_proration_fraction is removed. diff --git a/policyengine_us/parameters/gov/usda/snap/income/ineligible_members/count_all_income_states.yaml b/policyengine_us/parameters/gov/usda/snap/income/ineligible_members/count_all_income_states.yaml new file mode 100644 index 00000000000..17bc1af1d54 --- /dev/null +++ b/policyengine_us/parameters/gov/usda/snap/income/ineligible_members/count_all_income_states.yaml @@ -0,0 +1,23 @@ +description: These states count all rather than all but a prorated share of the income of household members who are ineligible for the Supplemental Nutrition Assistance Program based on certain immigration statuses. +values: + # Elections as documented in the State Options Report, 16th Edition + # (current as of October 1, 2023); earlier editions not verified. + 2018-01-01: + - AZ + - GU + - ID + - IN + - KS + - ME + - MA + - TN + - UT +metadata: + label: SNAP ineligible member full income counting states + period: year + unit: list + reference: + - title: 7 CFR 273.11(c)(3)(i) + href: https://www.law.cornell.edu/cfr/text/7/273.11#c_3_i + - title: SNAP State Options Report, 16th Edition + href: https://fns-prod.azureedge.us/sites/default/files/resource-files/snap-16th-state-options-report-june24.pdf#page=15 diff --git a/policyengine_us/parameters/gov/usda/snap/income/ineligible_members/gross_test_full_count_states.yaml b/policyengine_us/parameters/gov/usda/snap/income/ineligible_members/gross_test_full_count_states.yaml new file mode 100644 index 00000000000..51fde74aab7 --- /dev/null +++ b/policyengine_us/parameters/gov/usda/snap/income/ineligible_members/gross_test_full_count_states.yaml @@ -0,0 +1,18 @@ +description: These states count all of the income of household members who are ineligible for the Supplemental Nutrition Assistance Program based on certain immigration statuses under the gross income test, while counting all but a prorated share under the net income test. +values: + # Elections as documented in the State Options Report, 16th Edition + # (current as of October 1, 2023); earlier editions not verified. + 2018-01-01: + - AR + - HI + - KY + - NC +metadata: + label: SNAP ineligible member gross income test full counting states + period: year + unit: list + reference: + - title: 7 CFR 273.11(c)(3)(i) + href: https://www.law.cornell.edu/cfr/text/7/273.11#c_3_i + - title: SNAP State Options Report, 16th Edition + href: https://fns-prod.azureedge.us/sites/default/files/resource-files/snap-16th-state-options-report-june24.pdf#page=15 diff --git a/policyengine_us/parameters/gov/usda/snap/income/ineligible_members/pre_prwora_statuses.yaml b/policyengine_us/parameters/gov/usda/snap/income/ineligible_members/pre_prwora_statuses.yaml new file mode 100644 index 00000000000..5bff538aff0 --- /dev/null +++ b/policyengine_us/parameters/gov/usda/snap/income/ineligible_members/pre_prwora_statuses.yaml @@ -0,0 +1,23 @@ +description: The Department of Agriculture grants states discretion over counting the income of household members who are ineligible for the Supplemental Nutrition Assistance Program due to these immigration statuses. +values: + 2018-01-01: + # Statuses outside the excepted categories of 7 CFR 273.11(c)(3)(i)(A)-(G), + # for which states elect full counting, proration, or full counting under + # the gross income test only. + - UNDOCUMENTED + - DACA + - TPS + # Conditional entrants (INA 203(a)(7)) are not listed in the excepted + # categories of 273.11(c)(3)(i)(A)-(G). + - CONDITIONAL_ENTRANT +metadata: + label: SNAP ineligible member state income counting discretion immigration statuses + period: year + unit: list + reference: + - title: 7 CFR 273.11(c)(3)(i) + href: https://www.law.cornell.edu/cfr/text/7/273.11#c_3_i + - title: 7 USC 2015(f) - Aliens + href: https://www.law.cornell.edu/uscode/text/7/2015#f + - title: SNAP State Options Report, 16th Edition + href: https://fns-prod.azureedge.us/sites/default/files/resource-files/snap-16th-state-options-report-june24.pdf#page=15 diff --git a/policyengine_us/parameters/gov/usda/snap/income/sources/earned.yaml b/policyengine_us/parameters/gov/usda/snap/income/sources/earned.yaml index d4d7e25e6ee..5a69cf5554f 100644 --- a/policyengine_us/parameters/gov/usda/snap/income/sources/earned.yaml +++ b/policyengine_us/parameters/gov/usda/snap/income/sources/earned.yaml @@ -1,10 +1,12 @@ -description: Income sources that count as earned income for SNAP +description: Person-level income sources that count as earned income for SNAP values: 2009-01-01: - employment_income - - snap_self_employment_income_after_expense_deduction + # Self-employment income after the expense deduction is computed at the + # SPM unit level and attributed to members in snap_earned_income and + # snap_gross_test_income. metadata: reference: - - title: United States Code, Title 7, Subsection 273.9(b)(1) + - title: 7 CFR 273.9(b)(1) href: https://www.law.cornell.edu/cfr/text/7/273.9#b_1 diff --git a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml index 3d6a1e2dc6a..b382e18a9b0 100644 --- a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml @@ -1,10 +1,9 @@ -description: Income sources that count as unearned income for SNAP +description: Person-level income sources that count as unearned income for SNAP values: 2009-01-01: # (i) - ssi - - tanf - general_assistance # (ii) - pension_income # Includes annuities. @@ -13,6 +12,7 @@ values: - disability_benefits - workers_compensation - social_security + - survivor_benefits - retirement_distributions # Assume all rental income meets this criteria (rest is earned): # "gross income minus the cost of doing business derived from rental @@ -22,16 +22,15 @@ values: # (iii) - child_support_received - alimony_income - - financial_assistance - - survivor_benefits # (iv) educational assistance is countable only after educational # exclusions. Keep it out until the excluded share is modeled. # (v) - dividend_income - interest_income + - financial_assistance - miscellaneous_income metadata: reference: - - title: United States Code, Title 7, Subsection 273.9(b)(2) + - title: 7 CFR 273.9(b)(2) href: https://www.law.cornell.edu/cfr/text/7/273.9#b_2 diff --git a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned_spm_unit.yaml b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned_spm_unit.yaml new file mode 100644 index 00000000000..bba0008edf8 --- /dev/null +++ b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned_spm_unit.yaml @@ -0,0 +1,12 @@ +description: SPM unit-level income sources that count as unearned income for SNAP + +values: + 2009-01-01: + # (i) Unit-level assistance payments are counted in full since they + # cannot be attributed to an individual ineligible member. + - tanf + +metadata: + reference: + - title: 7 CFR 273.9(b)(2) + href: https://www.law.cornell.edu/cfr/text/7/273.9#b_2 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/is_tanf_non_cash_hheod.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/is_tanf_non_cash_hheod.yaml index eb8ddd40250..2b01a20fe27 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/is_tanf_non_cash_hheod.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/is_tanf_non_cash_hheod.yaml @@ -6,6 +6,6 @@ - name: California doesn't use HHEOD, but by default having any satisfies the requirement. period: 2022 input: - has_usda_elderly_disabled: true + has_snap_elderly_disabled_member: true output: is_tanf_non_cash_hheod: true diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_gross_income_test.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_gross_income_test.yaml index 7b1843ef26a..066d114d042 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_gross_income_test.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_gross_income_test.yaml @@ -6,7 +6,7 @@ - name: "California gross income limit is 200% of FPL" period: 2022 input: - snap_gross_income_fpg_ratio: 2.05 + snap_gross_test_income_fpg_ratio: 2.05 output: meets_tanf_non_cash_gross_income_test: false @@ -18,14 +18,14 @@ state_code: NC output: snap_gross_income: 2_550 - snap_gross_income_fpg_ratio: 1.95 + snap_gross_test_income_fpg_ratio: 1.95 # Gross income limit is 200% of FPL, so this should be true. meets_tanf_non_cash_gross_income_test: true - name: CO has BBCE at 200% FPL (bug fix verification) period: 2024-01 input: - snap_gross_income_fpg_ratio: 1.95 + snap_gross_test_income_fpg_ratio: 1.95 state_code: CO output: meets_tanf_non_cash_gross_income_test: true @@ -33,7 +33,7 @@ - name: CO rejects income above 200% FPL period: 2024-01 input: - snap_gross_income_fpg_ratio: 2.05 + snap_gross_test_income_fpg_ratio: 2.05 state_code: CO output: meets_tanf_non_cash_gross_income_test: false @@ -41,7 +41,7 @@ - name: MS has no BBCE (non-BBCE state, bug fix verification) period: 2024-01 input: - snap_gross_income_fpg_ratio: 0.5 + snap_gross_test_income_fpg_ratio: 0.5 state_code: MS output: meets_tanf_non_cash_gross_income_test: false @@ -49,7 +49,7 @@ - name: IN has BBCE at 130% FPL period: 2024-01 input: - snap_gross_income_fpg_ratio: 1.25 + snap_gross_test_income_fpg_ratio: 1.25 state_code: IN output: meets_tanf_non_cash_gross_income_test: true @@ -57,10 +57,10 @@ - name: NY without earned income, dependent care, or elderly/disabled status is capped at 130% FPL period: 2026-01 input: - snap_gross_income_fpg_ratio: 1.45 + snap_gross_test_income_fpg_ratio: 1.45 snap_earned_income: 0 snap_dependent_care_deduction: 0 - has_usda_elderly_disabled: false + has_snap_elderly_disabled_member: false state_code: NY output: meets_tanf_non_cash_gross_income_test: false @@ -68,10 +68,10 @@ - name: NY with earned income is eligible through 150% FPL period: 2026-01 input: - snap_gross_income_fpg_ratio: 1.45 + snap_gross_test_income_fpg_ratio: 1.45 snap_earned_income: 1 snap_dependent_care_deduction: 0 - has_usda_elderly_disabled: false + has_snap_elderly_disabled_member: false state_code: NY output: meets_tanf_non_cash_gross_income_test: true @@ -79,10 +79,10 @@ - name: NY with earned income is ineligible above 150% FPL period: 2026-01 input: - snap_gross_income_fpg_ratio: 1.55 + snap_gross_test_income_fpg_ratio: 1.55 snap_earned_income: 1 snap_dependent_care_deduction: 0 - has_usda_elderly_disabled: false + has_snap_elderly_disabled_member: false state_code: NY output: meets_tanf_non_cash_gross_income_test: false @@ -90,10 +90,10 @@ - name: NY with dependent care expenses is eligible through 200% FPL period: 2026-01 input: - snap_gross_income_fpg_ratio: 1.9 + snap_gross_test_income_fpg_ratio: 1.9 snap_earned_income: 0 snap_dependent_care_deduction: 1 - has_usda_elderly_disabled: false + has_snap_elderly_disabled_member: false state_code: NY output: meets_tanf_non_cash_gross_income_test: true @@ -111,9 +111,9 @@ spm_units: spm_unit: members: [person1, person2] - snap_gross_income_fpg_ratio: 1.9 + snap_gross_test_income_fpg_ratio: 1.9 snap_earned_income: 0 - has_usda_elderly_disabled: false + has_snap_elderly_disabled_member: false households: household: members: [person1, person2] @@ -133,9 +133,9 @@ spm_units: spm_unit: members: [person1, person2] - snap_gross_income_fpg_ratio: 1.9 + snap_gross_test_income_fpg_ratio: 1.9 snap_earned_income: 0 - has_usda_elderly_disabled: false + has_snap_elderly_disabled_member: false households: household: members: [person1, person2] @@ -146,10 +146,10 @@ - name: NY elderly or disabled household is eligible through 200% FPL period: 2026-01 input: - snap_gross_income_fpg_ratio: 1.9 + snap_gross_test_income_fpg_ratio: 1.9 snap_earned_income: 0 snap_dependent_care_deduction: 0 - has_usda_elderly_disabled: true + has_snap_elderly_disabled_member: true state_code: NY output: meets_tanf_non_cash_gross_income_test: true @@ -157,8 +157,8 @@ - name: AL all elderly or disabled household is eligible through 200% FPL period: 2026-01 input: - snap_gross_income_fpg_ratio: 1.9 - has_usda_elderly_disabled: true + snap_gross_test_income_fpg_ratio: 1.9 + has_snap_elderly_disabled_member: true has_all_usda_elderly_disabled: true state_code: AL output: @@ -167,8 +167,8 @@ - name: AK elderly or disabled household had no BBCE through ERS coverage period: 2020 input: - snap_gross_income_fpg_ratio: 1 - has_usda_elderly_disabled: true + snap_gross_test_income_fpg_ratio: 1 + has_snap_elderly_disabled_member: true state_code: AK output: meets_tanf_non_cash_gross_income_test: false @@ -176,8 +176,8 @@ - name: CO elderly or disabled household had 200% FPL BBCE before the general limit changed period: 2016-01 input: - snap_gross_income_fpg_ratio: 1.9 - has_usda_elderly_disabled: true + snap_gross_test_income_fpg_ratio: 1.9 + has_snap_elderly_disabled_member: true state_code: CO output: meets_tanf_non_cash_gross_income_test: true @@ -185,8 +185,8 @@ - name: ID elderly or disabled household has no BBCE gross income test period: 2026-01 input: - snap_gross_income_fpg_ratio: 2.5 - has_usda_elderly_disabled: true + snap_gross_test_income_fpg_ratio: 2.5 + has_snap_elderly_disabled_member: true state_code: ID output: meets_tanf_non_cash_gross_income_test: true @@ -194,8 +194,8 @@ - name: MS elderly or disabled household had no BBCE gross income test before BBCE ended period: 2016-01 input: - snap_gross_income_fpg_ratio: 2.5 - has_usda_elderly_disabled: true + snap_gross_test_income_fpg_ratio: 2.5 + has_snap_elderly_disabled_member: true state_code: MS output: meets_tanf_non_cash_gross_income_test: true @@ -203,9 +203,39 @@ - name: WV all elderly or disabled household had 200% FPL BBCE before the general limit changed period: 2016-01 input: - snap_gross_income_fpg_ratio: 1.9 - has_usda_elderly_disabled: true + snap_gross_test_income_fpg_ratio: 1.9 + has_snap_elderly_disabled_member: true has_all_usda_elderly_disabled: true state_code: WV output: meets_tanf_non_cash_gross_income_test: true + +- name: NC full-count alien income flips the BBCE gross screen where the prorated income would pass + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 48_000 + employment_income_before_lsr: 48_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NC + output: + # North Carolina fully counts the alien's income under gross screens + # (7 CFR 273.11(c)(3)(i)) while prorating it on the net side. The + # full $4,000 monthly income exceeds NC's 200% FPL BBCE limit for the + # 2-person unit, though the prorated $2,666.67 would have passed. + snap_gross_income: 2_666.67 + snap_gross_test_income: 4_000 + meets_tanf_non_cash_gross_income_test: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_asset_test.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_asset_test.yaml index bd4a28d9b22..461d967429d 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_asset_test.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_asset_test.yaml @@ -16,7 +16,7 @@ period: 2022 input: snap_assets: 2_600 - has_usda_elderly_disabled: true + has_snap_elderly_disabled_member: true output: meets_snap_asset_test: true @@ -24,6 +24,51 @@ period: 2022 input: snap_assets: 3_800 - has_usda_elderly_disabled: true + has_snap_elderly_disabled_member: true output: meets_snap_asset_test: false + +- name: Excluded elderly member does not confer the elderly/disabled resource limit. + period: 2026 + input: + people: + person1: + age: 35 + person2: + age: 70 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + snap_assets: 4_000 + households: + household: + members: [person1, person2] + state_code: TX + output: + # $4,000 exceeds the $3,000 standard limit, and the excluded elderly + # member does not confer the $4,500 elderly/disabled limit + # (7 CFR 273.11(c)(2)(iv)(D)). + meets_snap_asset_test: false + +- name: Eligible elderly member confers the elderly/disabled resource limit. + period: 2026 + input: + people: + person1: + age: 35 + person2: + age: 70 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2] + snap_assets: 4_000 + households: + household: + members: [person1, person2] + state_code: TX + output: + # $4,000 is between the $3,000 standard and $4,500 elderly/disabled + # limits, and the eligible elderly member confers the higher limit. + meets_snap_asset_test: true diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_gross_income_test.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_gross_income_test.yaml index 979eb8f8500..c47c0034592 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_gross_income_test.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_gross_income_test.yaml @@ -6,21 +6,26 @@ - name: Income up to 130% meets test period: 2022 input: - snap_gross_income_fpg_ratio: 1.3 + # $1,300 per month against a $1,000 monthly poverty guideline. + snap_gross_test_income: 15_600 + snap_fpg: 12_000 output: meets_snap_gross_income_test: true - name: Income above 130% fails test period: 2022 input: - snap_gross_income_fpg_ratio: 1.31 + # $1,310 per month against a $1,000 monthly poverty guideline. + snap_gross_test_income: 15_720 + snap_fpg: 12_000 output: meets_snap_gross_income_test: false -- name: Households with elderly or disabled members are exempt. +- name: Households with elderly or disabled unit members are exempt. period: 2022 input: - snap_gross_income_fpg_ratio: 1.31 - has_usda_elderly_disabled: true + snap_gross_test_income: 15_720 + snap_fpg: 12_000 + has_snap_elderly_disabled_member: true output: meets_snap_gross_income_test: true diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/has_snap_elderly_disabled_member.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/has_snap_elderly_disabled_member.yaml new file mode 100644 index 00000000000..d27b3b904ed --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/has_snap_elderly_disabled_member.yaml @@ -0,0 +1,93 @@ +- name: Case 1, disabled eligible member confers elderly or disabled status. + period: 2026-01 + input: + people: + person1: + age: 30 + is_usda_disabled: true + person2: + age: 33 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + has_snap_elderly_disabled_member: true + +- name: Case 2, disabled excluded member does not confer status. + period: 2026-01 + input: + people: + person1: + age: 30 + is_usda_disabled: true + immigration_status: UNDOCUMENTED + person2: + age: 33 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + has_snap_elderly_disabled_member: false + +- name: Case 3, elderly eligible member confers status. + period: 2026-01 + input: + people: + person1: + age: 65 + person2: + age: 33 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + has_snap_elderly_disabled_member: true + +- name: Case 4, elderly excluded member does not confer status. + period: 2026-01 + input: + people: + person1: + age: 70 + immigration_status: UNDOCUMENTED + person2: + age: 33 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + has_snap_elderly_disabled_member: false + +- name: Case 5, household with no elderly or disabled members. + period: 2026-01 + input: + people: + person1: + age: 35 + person2: + age: 33 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + has_snap_elderly_disabled_member: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_countable_child_support_expense.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_countable_child_support_expense.yaml new file mode 100644 index 00000000000..b71e0e62933 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_countable_child_support_expense.yaml @@ -0,0 +1,77 @@ +- name: Case 1, prorated payer's child support counts at the income fraction and an eligible payer's in full. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + child_support_expense: 3_600 + person2: + age: 33 + child_support_expense: 1_200 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + # The prorated member's $300 monthly payment counts at 2 unit members + # / 3 household members = 2/3, and the eligible member's $100 counts + # in full: $200 + $100 = $300. + snap_countable_child_support_expense: 300 + +- name: Case 2, ineligible student's child support payment is not the household's expense. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 40 + child_support_expense: 3_600 + person2: + age: 20 + is_snap_ineligible_student: true + child_support_expense: 1_200 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # Only the eligible parent's $300 monthly payment counts; the + # nonhousehold member's payment is excluded (7 CFR 273.11(d)(1)). + snap_countable_child_support_expense: 300 + +- name: Case 3, sanctioned member's child support expense continues to count in its entirety. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + meets_snap_general_work_requirements: false + meets_snap_work_requirements_person: false + child_support_expense: 3_600 + person2: + age: 33 + meets_snap_general_work_requirements: true + meets_snap_work_requirements_person: true + child_support_expense: 1_200 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # 7 CFR 273.11(c)(1): the sanctioned member's $300 monthly payment + # counts in full alongside the eligible member's $100. + snap_countable_child_support_expense: 400 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml index 8ee9e83b005..b1f8b36b652 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml @@ -98,3 +98,50 @@ state_code_str: CA output: snap_excess_medical_expense_deduction: 0 + +- name: Prorated ineligible elderly member's medical expenses are excluded. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: UNDOCUMENTED + other_medical_expenses: 6_000 + person2: + age: 33 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # The prorated member is not a household member (7 CFR 273.9(d)(3), + # 271.2), so their $500 monthly expenses do not count, and with zero + # excess the standard medical deduction is not claimable either. + snap_excess_medical_expense_deduction: 0 + +- name: Eligible elderly member's medical expenses remain deductible. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + other_medical_expenses: 6_000 + person2: + age: 33 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # $500 monthly expenses less the $35 disregard, above Texas's $135 + # standard medical deduction. + snap_excess_medical_expense_deduction: 465 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_shelter_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_shelter_expense_deduction.yaml index 133c6f8eedc..8c657a81f5f 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_shelter_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_shelter_expense_deduction.yaml @@ -36,7 +36,7 @@ input: state_code: TX housing_cost: 600 * 12 - has_usda_elderly_disabled: true + has_snap_elderly_disabled_member: true output: snap_excess_shelter_expense_deduction: 600 * 12 @@ -80,7 +80,7 @@ snap_region_str: GU housing_cost: 4_800 # 400 monthly snap_utility_allowance: 300 - has_usda_elderly_disabled: false + has_snap_elderly_disabled_member: false is_homeless: false output: # cap at 701, 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..f448c21df57 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,7 +23,7 @@ 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 is prorated. period: 2024-01 absolute_error_margin: 0.01 input: @@ -38,11 +38,12 @@ is_tax_unit_dependent: false employment_income: 2024: 12_000 - 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: - snap_work_requirement_income_proration_fraction: [1, 0.5] + snap_prorated_income_fraction: 0.5 + snap_income_counted_share: [1, 0.5] snap_earned_income: 500 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/gross/snap_gross_test_income.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/gross/snap_gross_test_income.yaml new file mode 100644 index 00000000000..11ef908c940 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/gross/snap_gross_test_income.yaml @@ -0,0 +1,147 @@ +- name: Case 1, hybrid state counts the undocumented member's full income under the gross test only. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: AR + output: + # Net side counts 2/3 of the undocumented member's $2,000: $1,333.33. + snap_gross_income: 1_333.33 + # Gross test counts the full $2,000. + snap_gross_test_income: 2_000 + +- name: Case 2, prorating state uses the prorated income for both tests. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + snap_gross_income: 1_333.33 + snap_gross_test_income: 1_333.33 + +- name: Case 3, hybrid state counts the full-count alien's child support deduction in full under the gross test. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + child_support_expense: 3_600 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: AR + output: + # Arkansas deducts child support from gross income. Net side counts + # 2/3 of the $2,000 earnings and 2/3 of the $300 child support: + # $1,333.33 - $200 = $1,133.33. + snap_gross_income: 1_133.33 + # Gross test counts the full earnings and the full child support + # payment: $2,000 - $300 = $1,700. + snap_gross_test_income: 1_700 + +- name: Case 4, hybrid state counts the full-count alien's self-employment income in full under the gross test only. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + self_employment_income: 24_000 + self_employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + # Provide the unit-level net directly so the test does not depend + # on the state's self-employment expense deduction election. + snap_self_employment_income_after_expense_deduction: 24_000 + households: + household: + members: [person1, person2, person3] + state_code: AR + output: + # The unit's $2,000 monthly net self-employment income is attributed + # to the alien, prorated at 2/3 on the net side and counted in full + # under the gross test. + snap_earned_income: 1_333.33 + snap_gross_income: 1_333.33 + snap_gross_test_income: 2_000 + +- name: Case 5, another member's self-employment loss neither offsets nor inflates the full-count alien's income. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + self_employment_income: 24_000 + self_employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + self_employment_income: -12_000 + self_employment_income_before_lsr: -12_000 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + snap_self_employment_income_after_expense_deduction: 24_000 + households: + household: + members: [person1, person2, person3] + state_code: AR + output: + # The eligible member's non-farm loss is floored at zero + # (7 CFR 273.11(a)(2)(ii) offsets only farm losses), so the whole + # $2,000 monthly net amount is attributed to the alien: the + # attribution ratio stays bounded with mixed-sign gross amounts. + snap_earned_income: 1_333.33 + snap_gross_income: 1_333.33 + snap_gross_test_income: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/gross/snap_gross_test_income_fpg_ratio.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/gross/snap_gross_test_income_fpg_ratio.yaml new file mode 100644 index 00000000000..53519afbf91 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/gross/snap_gross_test_income_fpg_ratio.yaml @@ -0,0 +1,9 @@ +- name: Gross test income FPG ratio + period: 2020 + input: + snap_gross_test_income: 15_000 + snap_fpg: + 2020-01: 10_000 / 12 + output: + snap_gross_test_income_fpg_ratio: + 2020-01: 1.5 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/integration.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/integration.yaml new file mode 100644 index 00000000000..935c2fb577e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/integration.yaml @@ -0,0 +1,629 @@ +# Integration tests for 7 CFR 273.11(c)/(d): treatment of income and +# deductible expenses of ineligible household members. +# Family of 3: working parent earns $24,000/year ($2,000/month), +# non-working spouse, child age 8. + +- name: Case 1, all-citizen baseline household. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: CITIZEN + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + snap_unit_size: 3 + snap_earned_income: 2_000 + snap: 367.7 + +- name: Case 2, refugee earner is prorated after OBBBA. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: REFUGEE + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + snap_unit_size: 2 + # Counted share: 2 eligible / 3 household members. + # $2,000 x 2/3 = $1,333.33 + snap_earned_income: 1_333.33 + snap: 288.9 + +- name: Case 3, undocumented earner in a prorating state. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + snap_unit_size: 2 + snap_earned_income: 1_333.33 + snap: 288.9 + +- name: Case 4, undocumented earner in a count-all state. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MA + output: + snap_unit_size: 2 + # Massachusetts counts all of the undocumented member's income. + snap_earned_income: 2_000 + snap: 194.4 + +- name: Case 5, undocumented earner in a hybrid gross-test state. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: AR + output: + snap_unit_size: 2 + # Net side prorated, gross test counts the full income. + snap_earned_income: 1_333.33 + snap_gross_test_income: 2_000 + snap: 288.9 + +- name: Case 6, prorated member's shares of shelter and dependent care expenses are not deductible. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 8 + spm_units: + spm_unit: + members: [person1, person2] + housing_cost: 12_000 + childcare_expenses: 2_400 + households: + household: + members: [person1, person2] + state_code: TX + output: + # With 2 household members and 1 prorated member paying per capita: + # the eligible member's half counts in full and the prorated member's + # half is divided evenly, with the eligible member's share counted: + # 1/2 + 1/2 x 1/2 = 0.75. + snap_expense_counted_share: 0.75 + # $200/month childcare x 0.75 = $150 + snap_dependent_care_deduction: 150 + # ($1,000/month housing x 0.75) - 0.5 x net income pre-shelter = $529.50 + snap_excess_shelter_expense_deduction: 529.5 + snap_earned_income: 1_000 + # Net income is fully offset by deductions, so the unit receives the + # one-person maximum allotment. + snap: 298 + +- name: Case 7, ineligible student's income is not counted. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 40 + employment_income: 12_000 + employment_income_before_lsr: 12_000 + person2: + age: 20 + dividend_income: 2_400 + is_full_time_college_student: true + weekly_hours_worked_before_lsr: 0 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + snap_unit_size: 1 + snap_earned_income: 1_000 + # The ineligible student's dividend income is excluded entirely. + snap_unearned_income: 0 + snap: 120.7 + +- name: Case 8, California delays OBBBA so refugees remain eligible in January 2026. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: REFUGEE + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: CA + output: + snap_unit_size: 3 + snap_income_counted_share: [1, 1, 1] + snap_earned_income: 2_000 + snap: 225.2 + +- name: Case 9, household with no eligible members receives nothing. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + is_snap_eligible: false + snap: 0 + +- name: Case 10, excluded K-12 child earner's self-employment income is not counted. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + self_employment_income: 24_000 + self_employment_income_before_lsr: 24_000 + person2: + age: 16 + is_in_k12_school: true + self_employment_income: 18_000 + self_employment_income_before_lsr: 18_000 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # Texas takes no flat percentage deduction, so the parent's $2,000 + # monthly self-employment income is counted and the child's $1,500 is + # excluded under 7 CFR 273.9(c)(7). + snap_earned_income: 2_000 + # Net income: $2,000 - $400 earned income deduction - $209 standard + # deduction = $1,391; benefit: $546 - 0.3 x $1,391 = $128.70. + snap: 128.7 + +- name: Case 11, prorated member's self-employment income is prorated like employment income. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + self_employment_income: 24_000 + self_employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + snap_unit_size: 2 + # $2,000 monthly self-employment income x 2/3, mirroring Case 3. + snap_earned_income: 1_333.33 + snap: 288.9 + +- name: Case 12, prorated member's self-employment loss does not offset other members' income. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + self_employment_income: 24_000 + self_employment_income_before_lsr: 24_000 + person2: + age: 33 + self_employment_income: -12_000 + self_employment_income_before_lsr: -12_000 + immigration_status: UNDOCUMENTED + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + snap_unit_size: 2 + # 7 CFR 273.11(a)(2)(ii) offsets self-employment losses against other + # household income only for farming operations, so the prorated + # member's $1,000 monthly non-farm loss is dropped and the eligible + # member's $2,000 monthly income counts in full. + snap_earned_income: 2_000 + # Net income: $2,000 - $400 earned income deduction - $209 standard + # deduction = $1,391; benefit: $546 - 0.3 x $1,391 = $128.70. + snap: 128.7 + +- name: Case 13, excluded elderly member does not confer the gross income test exemption. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 48_000 + employment_income_before_lsr: 48_000 + person2: + age: 33 + person3: + age: 70 + immigration_status: UNDOCUMENTED + person4: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: TX + output: + snap_unit_size: 3 + # The elderly grandparent is an excluded member (7 CFR 273.11(c)) and + # does not confer household-wide elderly status, so the household's + # $4,000 monthly income faces (and fails) the 130% gross income test. + has_snap_elderly_disabled_member: false + meets_snap_gross_income_test: false + snap: 0 + +- name: Case 14, eligible elderly member confers the gross income test exemption. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 48_000 + employment_income_before_lsr: 48_000 + person2: + age: 33 + person3: + age: 70 + immigration_status: CITIZEN + person4: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: TX + output: + snap_unit_size: 4 + has_snap_elderly_disabled_member: true + meets_snap_gross_income_test: true + +- name: Case 15, the standard utility allowance is not prorated. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 8 + spm_units: + spm_unit: + members: [person1, person2] + housing_cost: 6_000 + snap_utility_allowance: 300 + households: + household: + members: [person1, person2] + state_code: TX + output: + snap_expense_counted_share: 0.75 + # Only the $500 monthly housing cost is prorated; the $300 standard + # utility allowance is not an expense paid by or billed to the + # prorated member, so it counts in full (7 CFR 273.11(c)(2)(iii)): + # (0.75 x $500 + $300) - 0.5 x $591 net income pre-shelter = $379.50. + # Prorating the allowance as well would yield $304.50. + snap_excess_shelter_expense_deduction: 379.5 + +- name: Case 16, prorated member's unearned income is prorated like earned income. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + social_security_retirement: 12_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + snap_unit_size: 2 + # $1,000 monthly Social Security x 2/3. + snap_unearned_income: 666.67 + +- name: Case 17, unit-level assistance counts in full despite a prorated member. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 2_400 + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + snap_unit_size: 2 + # The $200 monthly TANF grant is paid to the unit as a whole and + # cannot be attributed to the prorated member, so it counts in full. + snap_unearned_income: 200 + +- name: Case 18, excluded elderly member does not waive the shelter deduction cap. + period: 2026-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + person2: + age: 70 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + housing_cost: 24_000 + households: + household: + members: [person1, person2] + state_code: TX + output: + has_snap_elderly_disabled_member: false + # Counted shelter: 0.75 x $2,000 = $1,500; uncapped deduction + # $1,500 - 0.5 x $1,391 = $804.50 exceeds the $744 cap, which applies + # because the excluded elderly member does not waive it. + snap_excess_shelter_expense_deduction: 744 + +- name: Case 19, California refugee is prorated once the OBBBA delay lapses. + period: 2027-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: REFUGEE + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: CA + output: + # California's ACL 25-92 delay ends 2026-04-01, so by 2027 the + # refugee is an excluded member with prorated income, matching the + # treatment in non-delaying states. + snap_unit_size: 2 + snap_income_counted_share: [0.6667, 1, 1] + snap_earned_income: 1_333.33 + +- name: Case 20, student, prorated alien, and sanctioned member in one household. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 20 + is_snap_ineligible_student: true + person2: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person3: + age: 30 + weekly_hours_worked_before_lsr: 0 + person4: + age: 33 + person5: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5] + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: TX + output: + # Household members exclude the student: 4; unit members exclude the + # student, the prorated alien, and the sanctioned member: 2. The + # proration fraction is 2/4 and the sanctioned member's share is 1. + snap_unit_size: 2 + snap_prorated_income_fraction: 0.5 + snap_income_counted_share: [0, 0.5, 1, 1, 1] + # Expense share: 1 - (1 + 0.5) / 5 = 0.7. + snap_expense_counted_share: 0.7 + snap_earned_income: 1_000 + +- name: Case 21, all-ineligible multi-member household receives nothing. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: UNDOCUMENTED + person2: + age: 33 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + snap_unit_size: 0 + snap_prorated_income_fraction: 0 + is_snap_eligible: false + snap: 0 + +- name: Case 22, refugee earner counts fully before OBBBA. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + employment_income: 24_000 + employment_income_before_lsr: 24_000 + immigration_status: REFUGEE + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + # Refugees remain SNAP-eligible until OBBBA takes effect in November + # 2025, so the earner is a unit member with fully counted income. + snap_unit_size: 3 + snap_income_counted_share: [1, 1, 1] + snap_earned_income: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/is_snap_gross_test_full_income_count_alien.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/is_snap_gross_test_full_income_count_alien.yaml new file mode 100644 index 00000000000..46b65846854 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/is_snap_gross_test_full_income_count_alien.yaml @@ -0,0 +1,31 @@ +- name: Case 1, undocumented member in a state counting all income under the gross test. + period: 2026-01 + input: + immigration_status: UNDOCUMENTED + state_code: AR + output: + is_snap_gross_test_full_income_count_alien: true + +- name: Case 2, undocumented member in a prorating state. + period: 2026-01 + input: + immigration_status: UNDOCUMENTED + state_code: TX + output: + is_snap_gross_test_full_income_count_alien: false + +- name: Case 3, refugee is prorated under both tests. + period: 2026-01 + input: + immigration_status: REFUGEE + state_code: AR + output: + is_snap_gross_test_full_income_count_alien: false + +- name: Case 4, citizen is not an ineligible alien. + period: 2026-01 + input: + immigration_status: CITIZEN + state_code: AR + output: + is_snap_gross_test_full_income_count_alien: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/is_snap_prorated_income_member.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/is_snap_prorated_income_member.yaml new file mode 100644 index 00000000000..2e40b7497b8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/is_snap_prorated_income_member.yaml @@ -0,0 +1,75 @@ +- name: Case 1, undocumented member in a prorating state. + period: 2026-01 + input: + immigration_status: UNDOCUMENTED + meets_snap_work_requirements_person: true + state_code: TX + output: + is_snap_prorated_income_member: true + +- name: Case 2, undocumented member in a state that counts all income. + period: 2026-01 + input: + immigration_status: UNDOCUMENTED + meets_snap_work_requirements_person: true + state_code: MA + output: + is_snap_prorated_income_member: false + +- name: Case 3, refugee is prorated in every state after OBBBA. + period: 2026-01 + input: + immigration_status: REFUGEE + meets_snap_work_requirements_person: true + state_code: MA + output: + is_snap_prorated_income_member: true + +- name: Case 4, citizen meeting work requirements is not prorated. + period: 2026-01 + input: + immigration_status: CITIZEN + meets_snap_work_requirements_person: true + state_code: TX + output: + is_snap_prorated_income_member: false + +- name: Case 5, citizen failing the ABAWD time limit is prorated. + period: 2026-01 + input: + immigration_status: CITIZEN + meets_snap_general_work_requirements: true + meets_snap_work_requirements_person: false + state_code: TX + output: + is_snap_prorated_income_member: true + +- name: Case 6, ineligible student is a nonhousehold member, not prorated. + period: 2026-01 + input: + immigration_status: UNDOCUMENTED + meets_snap_work_requirements_person: true + is_snap_ineligible_student: true + state_code: TX + output: + is_snap_prorated_income_member: false + +- name: Case 7, citizen sanctioned under the general work requirements is counted in full, not prorated. + period: 2026-01 + input: + immigration_status: CITIZEN + meets_snap_general_work_requirements: false + meets_snap_work_requirements_person: false + state_code: TX + output: + is_snap_prorated_income_member: false + +- name: Case 8, count-all state election governs an undocumented member who also fails the ABAWD time limit. + period: 2026-01 + input: + immigration_status: UNDOCUMENTED + meets_snap_general_work_requirements: true + meets_snap_work_requirements_person: false + state_code: MA + output: + is_snap_prorated_income_member: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/is_snap_state_discretion_ineligible_alien.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/is_snap_state_discretion_ineligible_alien.yaml new file mode 100644 index 00000000000..bb34839c44e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/is_snap_state_discretion_ineligible_alien.yaml @@ -0,0 +1,98 @@ +- name: Case 1, undocumented member is a state-discretion ineligible alien. + period: 2026-01 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + households: + household: + members: [person1] + state_code: TX + output: + is_snap_state_discretion_ineligible_alien: true + +- name: Case 2, DACA member is a state-discretion ineligible alien. + period: 2026-01 + input: + people: + person1: + age: 35 + immigration_status: DACA + households: + household: + members: [person1] + state_code: TX + output: + is_snap_state_discretion_ineligible_alien: true + +- name: Case 3, TPS member is a state-discretion ineligible alien. + period: 2026-01 + input: + people: + person1: + age: 35 + immigration_status: TPS + households: + household: + members: [person1] + state_code: TX + output: + is_snap_state_discretion_ineligible_alien: true + +- name: Case 4, conditional entrant is a state-discretion ineligible alien. + period: 2026-01 + input: + people: + person1: + age: 35 + immigration_status: CONDITIONAL_ENTRANT + households: + household: + members: [person1] + state_code: TX + output: + is_snap_state_discretion_ineligible_alien: true + +- name: Case 5, refugee is ineligible after OBBBA but falls in the excepted categories, which always prorate. + period: 2026-01 + input: + people: + person1: + age: 35 + immigration_status: REFUGEE + households: + household: + members: [person1] + state_code: TX + output: + is_snap_state_discretion_ineligible_alien: false + +- name: Case 6, citizen is not an ineligible alien. + period: 2026-01 + input: + people: + person1: + age: 35 + immigration_status: CITIZEN + households: + household: + members: [person1] + state_code: TX + output: + is_snap_state_discretion_ineligible_alien: false + +- name: Case 7, ineligible students are treated as nonhousehold members rather than under the ineligible-alien rules. + period: 2026-01 + input: + people: + person1: + age: 20 + immigration_status: UNDOCUMENTED + is_snap_ineligible_student: true + households: + household: + members: [person1] + state_code: TX + output: + is_snap_state_discretion_ineligible_alien: false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/snap_expense_counted_share.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/snap_expense_counted_share.yaml new file mode 100644 index 00000000000..8cbbdfd84db --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/snap_expense_counted_share.yaml @@ -0,0 +1,93 @@ +- name: Case 1, prorated member's per capita expense share is not deductible. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + person2: + age: 8 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # The eligible member's per capita half counts in full; the prorated + # member's half is divided evenly between the two household members, + # and only the eligible member's share counts: + # 1/2 + 1/2 x 1/2 = 0.75. + snap_expense_counted_share: 0.75 + +- name: Case 2, count-all states keep full deductible expenses. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + person2: + age: 8 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MA + output: + snap_expense_counted_share: 1 + +- name: Case 3, no ineligible members. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + person2: + age: 8 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + snap_expense_counted_share: 1 + +- name: Case 4, sanctioned member's expenses count in full but their slice of the prorated member's payment does not. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + person2: + age: 30 + weekly_hours_worked_before_lsr: 0 + person3: + age: 35 + person4: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: TX + output: + snap_unit_size: 2 + # The prorated member's payment counts at the income fraction (2 unit + # members / 4 household members = 1/2), which excludes the sanctioned + # member's slice; the sanctioned member's own payments count in full + # (7 CFR 273.11(c)(1)): (1/2 + 1 + 1 + 1) / 4 = 0.875. + snap_income_counted_share: [0.5, 1, 1, 1] + snap_expense_counted_share: 0.875 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/snap_income_counted_share.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/snap_income_counted_share.yaml new file mode 100644 index 00000000000..e3145bebdef --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/snap_income_counted_share.yaml @@ -0,0 +1,110 @@ +- name: Case 1, undocumented member's income share is prorated in a prorating state. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + snap_income_counted_share: [0.6667, 1, 1] + +- name: Case 2, ineligible student's income is not counted at all. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 40 + person2: + age: 20 + is_snap_ineligible_student: true + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + snap_income_counted_share: [1, 0] + +- name: Case 3, undocumented member's income is fully counted in a count-all state. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + person2: + age: 8 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MA + output: + snap_income_counted_share: [1, 1] + +- name: Case 4, ABAWD time-limit-ineligible member's income share is prorated. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + meets_snap_general_work_requirements: true + meets_snap_work_requirements_person: false + person2: + age: 33 + # Explicit inputs: providing these variables for person1 makes + # them input arrays, so unspecified members would get the false + # default instead of their computed values. + meets_snap_general_work_requirements: true + meets_snap_work_requirements_person: true + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + snap_income_counted_share: [0.5, 1] + +- name: Case 5, income of a member sanctioned under the general work requirements is fully counted. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + meets_snap_general_work_requirements: false + meets_snap_work_requirements_person: false + person2: + age: 33 + meets_snap_general_work_requirements: true + meets_snap_work_requirements_person: true + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + snap_income_counted_share: [1, 1] diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/snap_prorated_income_fraction.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/snap_prorated_income_fraction.yaml new file mode 100644 index 00000000000..bfef3580d5c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/ineligible_members/snap_prorated_income_fraction.yaml @@ -0,0 +1,80 @@ +- name: Case 1, one ineligible member of three household members. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + person2: + age: 33 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + # 2 eligible members / 3 household members + snap_prorated_income_fraction: 0.6667 + +- name: Case 2, ineligible students are excluded from the division. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 40 + person2: + age: 20 + is_snap_ineligible_student: true + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # 1 eligible member / 1 household member (student is a nonhousehold member) + snap_prorated_income_fraction: 1 + +- name: Case 3, no ineligible members. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 35 + person2: + age: 8 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + snap_prorated_income_fraction: 1 + +- name: Case 4, student-only household has no members among whom to divide income. + period: 2026-01 + absolute_error_margin: 0.001 + input: + people: + person1: + age: 20 + is_snap_ineligible_student: true + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + snap_prorated_income_fraction: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/snap_gross_self_employment_income_person.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/snap_gross_self_employment_income_person.yaml new file mode 100644 index 00000000000..83f0b7fcc2f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/snap_gross_self_employment_income_person.yaml @@ -0,0 +1,49 @@ +- name: Case 1, losses are floored per enterprise, not netted within the person. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + self_employment_income_before_lsr: 1_200 + sstb_self_employment_income_before_lsr: -600 + households: + household: + members: [person1] + state_code: TX + output: + # $100 monthly regular self-employment income counts in full; the $50 + # monthly SSTB loss is floored at zero (7 CFR 273.11(a)(2)(ii) offsets + # losses only for farm operations), so the result is $100, not $50. + snap_gross_self_employment_income_person: 100 + +- name: Case 2, a sole loss-making enterprise yields zero gross income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + self_employment_income_before_lsr: -12_000 + households: + household: + members: [person1] + state_code: TX + output: + snap_gross_self_employment_income_person: 0 + +- name: Case 3, positive enterprises sum. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + self_employment_income_before_lsr: 12_000 + sstb_self_employment_income_before_lsr: 6_000 + households: + household: + members: [person1] + state_code: TX + output: + snap_gross_self_employment_income_person: 1_500 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/is_snap_excluded_member.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/is_snap_excluded_member.yaml new file mode 100644 index 00000000000..389e359458d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/is_snap_excluded_member.yaml @@ -0,0 +1,55 @@ +- name: Case 1, eligible working-age citizen is not excluded. + period: 2026-01 + input: + people: + person1: + age: 35 + immigration_status: CITIZEN + households: + household: + members: [person1] + state_code: TX + output: + is_snap_excluded_member: false + +- name: Case 2, ineligible student is excluded as a nonhousehold member. + period: 2026-01 + input: + people: + person1: + age: 20 + is_snap_ineligible_student: true + households: + household: + members: [person1] + state_code: TX + output: + is_snap_excluded_member: true + +- name: Case 3, immigration-status-ineligible member is excluded. + period: 2026-01 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + households: + household: + members: [person1] + state_code: TX + output: + is_snap_excluded_member: true + +- name: Case 4, member failing the work requirements is excluded. + period: 2026-01 + input: + people: + person1: + age: 35 + meets_snap_work_requirements_person: false + households: + household: + members: [person1] + state_code: TX + output: + is_snap_excluded_member: true 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..90c9bfe7d17 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap.yaml @@ -153,7 +153,7 @@ 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 is prorated for SNAP. period: 2024-01 absolute_error_margin: 0.01 input: @@ -172,7 +172,7 @@ is_tax_unit_dependent: false employment_income: 2024: 12_000 - meets_snap_general_work_requirements: false + meets_snap_general_work_requirements: true meets_snap_abawd_work_requirements: false spm_units: spm_unit: @@ -186,7 +186,50 @@ state_code: TX output: snap_unit_size: 1 - snap_work_requirement_income_proration_fraction: [1, 0.5] + snap_prorated_income_fraction: 0.5 + snap_income_counted_share: [1, 0.5] snap_earned_income: 500 snap_gross_income: 500 snap: 230.40 + +- name: Case 7, income of an adult sanctioned under the general work requirements is counted in full. + 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 + # 7 CFR 273.11(c)(1): income of members sanctioned under 273.7 counts + # in full, while the member is still excluded from the unit size. + snap_income_counted_share: [1, 1] + snap_earned_income: 1_000 + snap_gross_income: 1_000 + # Net income: $1,000 - $200 earned income deduction - $198 standard + # deduction = $602; benefit: $291 - 0.3 x $602 = $110.40. + snap: 110.40 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_expected_contribution.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_expected_contribution.yaml index 7c1d89d7215..458208461d5 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_expected_contribution.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_expected_contribution.yaml @@ -19,7 +19,7 @@ snap_net_income_pre_shelter: (2000 - (2000 * 0.2) - 181) * 12 snap_excess_shelter_expense_deduction: 586 * 12 housing_cost: 1700 * 12 - has_usda_elderly_disabled: false + has_snap_elderly_disabled_member: false members: [person_1, person_2, person_3, person_4] snap_net_income: 833 * 12 households: @@ -50,7 +50,7 @@ snap_net_income_pre_shelter: (2000 - (2000 * 0.2) - 181) * 12 snap_excess_shelter_expense_deduction: (1700 - (1419 * 0.5)) * 12 housing_cost: 1700 * 12 - has_usda_elderly_disabled: true + has_snap_elderly_disabled_member: true members: [person_1, person_2, person_3, person_4] snap_net_income: 428.5 * 12 households: diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_net_income.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_net_income.yaml index 2686209123c..6ef1596499d 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_net_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_net_income.yaml @@ -18,7 +18,7 @@ snap_net_income_pre_shelter: (2000 - (2000 * 0.2) - 181) * 12 snap_excess_shelter_expense_deduction: 586 * 12 housing_cost: 1700 * 12 - has_usda_elderly_disabled: false + has_snap_elderly_disabled_member: false members: [person_1, person_2, person_3, person_4] households: household: @@ -45,7 +45,7 @@ snap_earned_income_deduction: (2000 * 0.2) * 12 snap_standard_deduction: 181 * 12 housing_cost: 1700 * 12 - has_usda_elderly_disabled: true + has_snap_elderly_disabled_member: true snap_net_income_pre_shelter: (2000 - (2000 * 0.2) - 181) * 12 snap_excess_shelter_expense_deduction: (1700 - (1419 * 0.5)) * 12 members: [person_1, person_2, person_3, person_4] diff --git a/policyengine_us/tests/policy/baseline/partners/amplifi/2025.yaml b/policyengine_us/tests/policy/baseline/partners/amplifi/2025.yaml index 39752d71782..b2f0b861f06 100644 --- a/policyengine_us/tests/policy/baseline/partners/amplifi/2025.yaml +++ b/policyengine_us/tests/policy/baseline/partners/amplifi/2025.yaml @@ -110,8 +110,8 @@ spm_unit: ca_tanf: 0.0 ca_tanf_eligible: false - snap: 0.0 - is_snap_eligible: false + snap: 2114.10 + is_snap_eligible: true lifeline: 228.0 is_lifeline_eligible: true la_general_relief: 0.0 diff --git a/policyengine_us/tests/policy/baseline/partners/amplifi/2026.yaml b/policyengine_us/tests/policy/baseline/partners/amplifi/2026.yaml index ea7545bfd23..04b5b298e12 100644 --- a/policyengine_us/tests/policy/baseline/partners/amplifi/2026.yaml +++ b/policyengine_us/tests/policy/baseline/partners/amplifi/2026.yaml @@ -110,8 +110,8 @@ spm_unit: ca_tanf: 0.0 ca_tanf_eligible: false - snap: 0.0 - is_snap_eligible: false + snap: 2360.52 + is_snap_eligible: true lifeline: 228.0 is_lifeline_eligible: true la_general_relief: 0.0 diff --git a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/ca.yaml b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/ca.yaml index 79cf34e4c04..ae4d8111604 100644 --- a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/ca.yaml +++ b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/ca.yaml @@ -275,14 +275,12 @@ spm_units: spm_unit: # CA delays the OBBB refugee exclusion to 2026-04-01 (ACL 25-92), and - # CalWORKs ($7,700/year here) counts as unearned income ($641.67/mo): - # Jan-Mar (refugee still in unit, size 3): net = 1,333.33 (earned after - # 20% deduction) + 641.67 - 209 = 1,766.00 -> EC = floor(1,766) x 0.3 = - # 529.50 -> 785 - 529.50 = 255.50/month. Apr-Sep (head excluded, size 2): 546 - 529.50 - # = 16.50 < minimum allotment 23.84 -> 23.84/month. Oct-Dec: 558.24 - - # 528.30 = 29.94/month. Annual = 3 x 255.50 + 6 x 23.84 + 3 x 29.94 = - # 999.36. - snap: 999.36 + # CalWORKs counts as unearned income at the unit level (in full). + # Jan-Mar the refugee is still in the unit (size 3) with earnings + # fully counted; from April the head is excluded (size 2) and their + # earnings count at the 2/3 prorated share under 7 CFR + # 273.11(c)(2)-(3), raising the benefit relative to full counting. + snap: 2_162.22 is_snap_eligible: true - name: snap_ca_head_immigration_undocumented_calworks_interaction (ca) period: 2026 @@ -345,10 +343,8 @@ spm_units: spm_unit: # Undocumented head is excluded all year (not a CFAP status): unit size - # 2, and the CalWORKs grant is smaller ($5,133.33/year -> $427.78/mo - # unearned) because the head is also excluded from the CalWORKs AU: - # net = 1,333.33 + 427.78 - 209 = 1,552.11 -> EC 465.60 -> 546 - 465.60 - # = 80.40/month Jan-Sep; Oct-Dec: 558.24 - 464.10 = 94.14. - # Annual = 9 x 80.40 + 3 x 94.14 = 1,006.02. - snap: 1_006.02 + # 2, the CalWORKs grant excludes the head from the AU but counts in + # full as unit-level unearned income, and the head's earnings count + # at the 2/3 prorated share under 7 CFR 273.11(c)(2)-(3). + snap: 3_002.22 is_snap_eligible: true diff --git a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/federal.yaml b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/federal.yaml index ba3456dc010..04ab43fe95b 100644 --- a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/federal.yaml +++ b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/federal.yaml @@ -804,10 +804,12 @@ spm_unit: # OBBB (PL 119-21, eff. 2025-07-04) removed refugees from SNAP-eligible # immigration statuses: the refugee head is excluded (unit size 2, max - # allotment 546) but their earnings still count in full (they meet work - # requirements, so no income proration). Same math as the childless- - # couple 20k case: annual = 9 x 208.80 + 3 x 222.54 = 2,546.82. - snap: 2_546.82 + # allotment 546) and their earnings count at the 2/3 prorated share + # under 7 CFR 273.11(c)(2)-(3) (TX prorates): counted earnings + # 1,111.11/mo, net income 679.89 -> EC = floor(679.89) x 0.3 = 203.70 + # -> 546 - 203.70 = 342.30/month Jan-Sep; Oct-Dec (FY2027): 558.24 - + # 202.50 = 355.74. Annual = 9 x 342.30 + 3 x 355.74 = 4,147.92. + snap: 4_147.92 is_snap_eligible: true - name: snap_head_immigration_undocumented_excluded (federal) period: 2026 @@ -870,9 +872,9 @@ spm_units: spm_unit: # Undocumented head is excluded from the unit (size 2) with earnings - # fully counted; identical arithmetic to the refugee case: - # annual = 9 x 208.80 + 3 x 222.54 = 2,546.82. - snap: 2_546.82 + # counted at the 2/3 prorated share; identical arithmetic to the + # refugee case: annual = 9 x 342.30 + 3 x 355.74 = 4,147.92. + snap: 4_147.92 is_snap_eligible: true - name: snap_abawd_zero_hours_fails_work_requirements (federal) period: 2026 @@ -1425,3 +1427,62 @@ # 802.60 - 201.00 = 601.60. Annual = 9 x 579.20 + 3 x 601.60 = 7,017.60. snap: 7_017.59 is_snap_eligible: true + +- name: snap_ineligible_student_income_excluded_mixed_household (federal) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + head: + age: 40 + is_tax_unit_head: true + is_tax_unit_spouse: false + is_tax_unit_dependent: false + is_disabled: false + immigration_status_str: CITIZEN + employment_income_before_lsr: 20_000 + student: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: false + is_tax_unit_dependent: false + is_disabled: false + immigration_status_str: CITIZEN + is_full_time_college_student: true + weekly_hours_worked_before_lsr: 0 + employment_income_before_lsr: 10_000 + households: + household: + members: &id903 + - head + - student + state_code: TX + zip_code: '75201' + county_str: DALLAS_COUNTY_TX + spm_units: + spm_unit: + members: *id903 + snap_assets: 500 + tax_units: + tax_unit: + members: *id903 + tax_unit_is_joint: false + families: + family: + members: *id903 + marital_units: + mu_head: + members: + - head + mu_student: + members: + - student + output: + spm_units: + spm_unit: + # The ineligible student is a nonhousehold member (7 CFR 273.11(d)): + # their $10,000 earnings are excluded entirely and the unit size is 1; + # the 130%-FPG gross test passes only because of the exclusion, and + # the unit receives the 1-2 person minimum allotment. + snap: 287.68 + is_snap_eligible: true diff --git a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/ma.yaml b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/ma.yaml index 9a4f18af776..942dfad08dc 100644 --- a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/ma.yaml +++ b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/ma.yaml @@ -141,3 +141,69 @@ # ineligible all year. snap: 0.0 is_snap_eligible: false + +# MA is one of the nine 7 CFR 273.11(c)(3)(i) count-all states: the +# undocumented head is excluded from the unit size but their income counts +# in full rather than at the prorated share used in the TX baseline. +- name: snap_ma_head_immigration_undocumented_count_all_state (ma) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + head: + age: 35 + is_tax_unit_head: true + is_tax_unit_spouse: false + is_tax_unit_dependent: false + is_disabled: false + immigration_status_str: UNDOCUMENTED + employment_income_before_lsr: 20_000 + spouse: + age: 33 + is_tax_unit_head: false + is_tax_unit_spouse: true + is_tax_unit_dependent: false + is_disabled: false + immigration_status_str: CITIZEN + employment_income_before_lsr: 0 + child1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + is_tax_unit_dependent: true + is_disabled: false + immigration_status_str: CITIZEN + employment_income_before_lsr: 0 + households: + household: + members: &id801 + - head + - spouse + - child1 + state_code: MA + zip_code: '02118' + county_str: SUFFOLK_COUNTY_MA + spm_units: + spm_unit: + members: *id801 + snap_assets: 500 + tax_units: + tax_unit: + members: *id801 + tax_unit_is_joint: true + families: + family: + members: *id801 + marital_units: + mu_couple: + members: + - head + - spouse + mu_child1: + members: + - child1 + output: + spm_units: + spm_unit: + snap: 3_815.82 + is_snap_eligible: true diff --git a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/nc.yaml b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/nc.yaml index 631dc7c7570..0156d3b238a 100644 --- a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/nc.yaml +++ b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/federal/nutrition/snap/nc.yaml @@ -142,3 +142,138 @@ # ineligible all year. snap: 0.0 is_snap_eligible: false + +# NC is one of the four 7 CFR 273.11(c)(3)(i) hybrid states: an undocumented +# member's income counts in full under the gross income test (and the BBCE +# gross screen) while counting at the prorated share on the net side. +- name: snap_nc_head_immigration_undocumented_gross_test_full_count_denies (nc) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + head: + age: 35 + is_tax_unit_head: true + is_tax_unit_spouse: false + is_tax_unit_dependent: false + is_disabled: false + immigration_status_str: UNDOCUMENTED + employment_income_before_lsr: 48_000 + spouse: + age: 33 + is_tax_unit_head: false + is_tax_unit_spouse: true + is_tax_unit_dependent: false + is_disabled: false + immigration_status_str: CITIZEN + employment_income_before_lsr: 0 + child1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + is_tax_unit_dependent: true + is_disabled: false + immigration_status_str: CITIZEN + employment_income_before_lsr: 0 + households: + household: + members: &id901 + - head + - spouse + - child1 + state_code: NC + zip_code: '27601' + county_str: WAKE_COUNTY_NC + spm_units: + spm_unit: + members: *id901 + snap_assets: 500 + tax_units: + tax_unit: + members: *id901 + tax_unit_is_joint: true + families: + family: + members: *id901 + marital_units: + mu_couple: + members: + - head + - spouse + mu_child1: + members: + - child1 + output: + spm_units: + spm_unit: + # The full $4,000/month exceeds both the 130% gross limit and NC's + # 200% BBCE screen for the 2-person unit, though the prorated + # $2,666.67 would have passed the BBCE screen. + snap: 0 + is_snap_eligible: false + +- name: snap_nc_head_immigration_undocumented_prorated_net_benefit (nc) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + head: + age: 35 + is_tax_unit_head: true + is_tax_unit_spouse: false + is_tax_unit_dependent: false + is_disabled: false + immigration_status_str: UNDOCUMENTED + employment_income_before_lsr: 20_000 + spouse: + age: 33 + is_tax_unit_head: false + is_tax_unit_spouse: true + is_tax_unit_dependent: false + is_disabled: false + immigration_status_str: CITIZEN + employment_income_before_lsr: 0 + child1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + is_tax_unit_dependent: true + is_disabled: false + immigration_status_str: CITIZEN + employment_income_before_lsr: 0 + households: + household: + members: &id902 + - head + - spouse + - child1 + state_code: NC + zip_code: '27601' + county_str: WAKE_COUNTY_NC + spm_units: + spm_unit: + members: *id902 + snap_assets: 500 + tax_units: + tax_unit: + members: *id902 + tax_unit_is_joint: true + families: + family: + members: *id902 + marital_units: + mu_couple: + members: + - head + - spouse + mu_child1: + members: + - child1 + output: + spm_units: + spm_unit: + # The full $1,666.67/month passes the gross screens and the benefit + # is computed from the 2/3-prorated income, matching the TX + # prorate-state baseline. + snap: 4_147.92 + is_snap_eligible: true diff --git a/policyengine_us/variables/gov/hhs/tanf/non_cash/is_tanf_non_cash_hheod.py b/policyengine_us/variables/gov/hhs/tanf/non_cash/is_tanf_non_cash_hheod.py index 37dbacac301..734d85c6d8a 100644 --- a/policyengine_us/variables/gov/hhs/tanf/non_cash/is_tanf_non_cash_hheod.py +++ b/policyengine_us/variables/gov/hhs/tanf/non_cash/is_tanf_non_cash_hheod.py @@ -12,8 +12,10 @@ def formula(spm_unit, period, parameters): state = spm_unit.household("state_code_str", period) bbce = parameters(period).gov.hhs.tanf.non_cash requires_all = bbce.requires_all_for_hheod[state] + # Excluded members are not household members under the 7 CFR 271.2 + # definition, so they do not confer elderly or disabled status. return where( requires_all, spm_unit("has_all_usda_elderly_disabled", period), - spm_unit("has_usda_elderly_disabled", period), + spm_unit("has_snap_elderly_disabled_member", period), ) diff --git a/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_gross_income_test.py b/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_gross_income_test.py index 34c35c0353b..d9f59960d9b 100644 --- a/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_gross_income_test.py +++ b/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_gross_income_test.py @@ -30,5 +30,9 @@ def formula(spm_unit, period, parameters): ) gross_limit = where(ny, ny_gross_limit, gross_limit) - gross_income = spm_unit("snap_gross_income_fpg_ratio", period) + # Use the gross-test income concept so states electing full + # counting of certain ineligible aliens' income under the gross + # income test (7 CFR 273.11(c)(3)(i)) apply it to this categorical + # eligibility screen as well. + gross_income = spm_unit("snap_gross_test_income_fpg_ratio", period) return gross_income <= gross_limit diff --git a/policyengine_us/variables/gov/usda/snap/eligibility/meets_snap_asset_test.py b/policyengine_us/variables/gov/usda/snap/eligibility/meets_snap_asset_test.py index 8911b6f0b60..5aec563cf15 100644 --- a/policyengine_us/variables/gov/usda/snap/eligibility/meets_snap_asset_test.py +++ b/policyengine_us/variables/gov/usda/snap/eligibility/meets_snap_asset_test.py @@ -9,9 +9,24 @@ class meets_snap_asset_test(Variable): "Whether the SPM unit's financial resources are within SNAP's allowable limit" ) definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/7/2014#g", + "https://www.law.cornell.edu/cfr/text/7/273.11#c", + ) def formula(spm_unit, period, parameters): - has_elderly_or_disabled = spm_unit("has_usda_elderly_disabled", period) + # Ineligible members' resources continue to count in their + # entirety (7 CFR 273.11(c)(1)(i), (c)(2)(i), (c)(3)), but the + # members themselves are excluded when comparing resources with + # the resource limits (273.11(c)(1)(ii)(D), (c)(2)(iv)(D)), so an + # excluded elderly or disabled member does not confer the higher + # limit. Nonhousehold members' (ineligible students') resources + # are not considered available to the household (273.11(d)), but + # assets are modeled at the unit level, so we do not carve out + # their share at the moment. + has_elderly_or_disabled = spm_unit( + "has_snap_elderly_disabled_member", period.first_month + ) asset_test = parameters(period).gov.usda.snap.asset_test assets = spm_unit("snap_assets", period) asset_limit = where( diff --git a/policyengine_us/variables/gov/usda/snap/eligibility/meets_snap_gross_income_test.py b/policyengine_us/variables/gov/usda/snap/eligibility/meets_snap_gross_income_test.py index 3aab36e77a7..c42cbafd470 100644 --- a/policyengine_us/variables/gov/usda/snap/eligibility/meets_snap_gross_income_test.py +++ b/policyengine_us/variables/gov/usda/snap/eligibility/meets_snap_gross_income_test.py @@ -13,9 +13,13 @@ class meets_snap_gross_income_test(Variable): ) def formula(spm_unit, period, parameters): - limit = parameters(period).gov.usda.snap.income.limit.gross - income = spm_unit("snap_gross_income_fpg_ratio", period) - # Households with elderly and disabled people are exempt from the - # gross income test. - has_elderly_disabled = spm_unit("has_usda_elderly_disabled", period) - return has_elderly_disabled | (income <= limit) + p = parameters(period).gov.usda.snap.income.limit + # The gross test uses state-specific income counting for certain + # ineligible aliens (7 CFR 273.11(c)(3)(i)). + ratio = spm_unit("snap_gross_test_income_fpg_ratio", period) + # Households with an elderly or disabled unit member are exempt + # from the gross income test; excluded members are not household + # members under the 7 CFR 271.2 definition, so they do not confer + # this status. + has_elderly_disabled = spm_unit("has_snap_elderly_disabled_member", period) + return has_elderly_disabled | (ratio <= p.gross) diff --git a/policyengine_us/variables/gov/usda/snap/has_snap_elderly_disabled_member.py b/policyengine_us/variables/gov/usda/snap/has_snap_elderly_disabled_member.py new file mode 100644 index 00000000000..756fade4882 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/has_snap_elderly_disabled_member.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class has_snap_elderly_disabled_member(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + label = "Has an elderly or disabled SNAP unit member" + documentation = ( + "Whether the SNAP unit includes an elderly or disabled member. " + "7 CFR 271.2 defines an elderly or disabled member as a member of " + "a household; ineligible and nonhousehold members are excluded " + "from the household, so they do not confer household-wide elderly " + "or disabled status." + ) + reference = ( + "https://www.law.cornell.edu/cfr/text/7/271.2", + "https://www.law.cornell.edu/cfr/text/7/273.1", + ) + + def formula(spm_unit, period, parameters): + person = spm_unit.members + elderly = person("is_usda_elderly", period.this_year) + disabled = person("is_usda_disabled", period.this_year) + excluded = person("is_snap_excluded_member", period) + return spm_unit.any((elderly | disabled) & ~excluded) diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/self_employment/snap_self_employment_expense_deduction.py b/policyengine_us/variables/gov/usda/snap/income/deductions/self_employment/snap_self_employment_expense_deduction.py index f78dab19001..07e8c244346 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/self_employment/snap_self_employment_expense_deduction.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/self_employment/snap_self_employment_expense_deduction.py @@ -10,16 +10,14 @@ class snap_self_employment_expense_deduction(Variable): ) label = "SNAP self-employment expense deduction" unit = USD - reference = "https://www.snapscreener.com/blog/self-employment" + reference = ( + "https://www.law.cornell.edu/cfr/text/7/273.9#c_9", + "https://www.law.cornell.edu/cfr/text/7/273.11#b", + ) def formula(spm_unit, period, parameters): self_employment_income = add( - spm_unit, - period, - [ - "self_employment_income_before_lsr", - "sstb_self_employment_income_before_lsr", - ], + spm_unit, period, ["snap_gross_self_employment_income_person"] ) expenses = spm_unit("snap_self_employment_income_expense", period) p = parameters(period).gov.usda.snap.income.deductions.self_employment diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/self_employment/snap_self_employment_income_after_expense_deduction.py b/policyengine_us/variables/gov/usda/snap/income/deductions/self_employment/snap_self_employment_income_after_expense_deduction.py index eee469b1c9b..1c5028336fd 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/self_employment/snap_self_employment_income_after_expense_deduction.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/self_employment/snap_self_employment_income_after_expense_deduction.py @@ -10,12 +10,7 @@ class snap_self_employment_income_after_expense_deduction(Variable): def formula(spm_unit, period, parameters): self_employment_income = add( - spm_unit, - period, - [ - "self_employment_income_before_lsr", - "sstb_self_employment_income_before_lsr", - ], + spm_unit, period, ["snap_gross_self_employment_income_person"] ) expense_deduction = spm_unit("snap_self_employment_expense_deduction", period) return max_(self_employment_income - expense_deduction, 0) diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_excess_shelter_expense_deduction.py b/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_excess_shelter_expense_deduction.py index b20f22ab2ee..a11484b3c44 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_excess_shelter_expense_deduction.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_excess_shelter_expense_deduction.py @@ -18,13 +18,20 @@ def formula(spm_unit, period, parameters): # income threshold. net_income_pre_shelter = spm_unit("snap_net_income_pre_shelter", period) subtracted_income = p.income_share_disregard * net_income_pre_shelter - housing_cost = add(spm_unit, period, ["snap_utility_allowance", "housing_cost"]) + expense_share = spm_unit("snap_expense_counted_share", period) + # Standard utility allowances are not expenses paid by or billed + # to an ineligible member, so only actual housing costs are + # prorated (7 CFR 273.11(c)(2)(iii)). + utility_allowance = spm_unit("snap_utility_allowance", period) + housing_cost = ( + expense_share * add(spm_unit, period, ["housing_cost"]) + utility_allowance + ) uncapped_ded = max_(housing_cost - subtracted_income, 0) # Calculate capped deduction based on state group parameter. state_group = spm_unit.household("snap_region_str", period) ded_cap = p.cap[state_group] capped_ded = min_(uncapped_ded, ded_cap) - has_elderly_disabled = spm_unit("has_usda_elderly_disabled", period) + has_elderly_disabled = spm_unit("has_snap_elderly_disabled_member", period) # Cap for all but elderly/disabled people and add utility allowance. non_homeless_shelter_deduction = where( has_elderly_disabled, uncapped_ded, capped_ded diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_child_support_deduction.py b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_child_support_deduction.py index 3e66ccbe87a..e8a79f725f2 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_child_support_deduction.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_child_support_deduction.py @@ -13,7 +13,7 @@ class snap_child_support_deduction(Variable): # Excluding deduction for child support, which is applies to the gross income # calculation def formula(spm_unit, period, parameters): - child_support = add(spm_unit, period, ["child_support_expense"]) + child_support = spm_unit("snap_countable_child_support_expense", period) gross_income_deduction = spm_unit( "snap_child_support_gross_income_deduction", period ) diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_countable_child_support_expense.py b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_countable_child_support_expense.py new file mode 100644 index 00000000000..8c365b837b9 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_countable_child_support_expense.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class snap_countable_child_support_expense(Variable): + value_type = float + entity = SPMUnit + definition_period = MONTH + label = "SNAP countable child support expense" + reference = ( + "https://www.law.cornell.edu/uscode/text/7/2014#e_4", + "https://www.law.cornell.edu/cfr/text/7/273.11#c_2_iii", + ) + unit = USD + + def formula(spm_unit, period, parameters): + person = spm_unit.members + expense = person("child_support_expense", period) + share = person("snap_income_counted_share", period) + return spm_unit.sum(expense * share) diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_dependent_care_deduction.py b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_dependent_care_deduction.py index 8e3c655db8e..1a89410b2d1 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_dependent_care_deduction.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_dependent_care_deduction.py @@ -20,8 +20,12 @@ class snap_dependent_care_deduction(Variable): reference = ( "https://www.law.cornell.edu/uscode/text/7/2014#e_3", "https://www.law.cornell.edu/cfr/text/7/273.9#d_4", + "https://www.law.cornell.edu/cfr/text/7/273.11#c_2_iii", ) - # Per 7 CFR 273.9(d)(4), deductible costs cover care of a child under - # age 18 or an incapacitated person of any age. - adds = ["childcare_expenses", "care_expenses"] + def formula(spm_unit, period, parameters): + # Per 7 CFR 273.9(d)(4), deductible costs cover care of a child + # under age 18 or an incapacitated person of any age. + expenses = add(spm_unit, period, ["childcare_expenses", "care_expenses"]) + share = spm_unit("snap_expense_counted_share", period) + return expenses * share diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.py b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.py index 9a5860c921f..ecad0f4e226 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.py @@ -14,12 +14,16 @@ class snap_excess_medical_expense_deduction(Variable): ] def formula(spm_unit, period, parameters): - # Deduction applies to medical expenses incurred by elderly or disabled - # members only. + # 7 CFR 273.9(d)(3) allows the deduction only for elderly or + # disabled household members (as defined in 271.2); nonhousehold + # members and prorated ineligible members are not household + # members, so their expenses do not count. person = spm_unit.members - elderly = person("is_usda_elderly", period) - disabled = person("is_usda_disabled", period) - moop = person("snap_allowable_medical_expenses", period) + elderly = person("is_usda_elderly", period.this_year) + disabled = person("is_usda_disabled", period.this_year) + student = person("is_snap_ineligible_student", period.this_year) + prorated = person("is_snap_prorated_income_member", period) + moop = person("snap_allowable_medical_expenses", period) * ~(student | prorated) elderly_disabled_moop = spm_unit.sum(moop * (elderly | disabled)) p = parameters(period).gov.usda.snap.income.deductions.excess_medical_expense excess = max_(elderly_disabled_moop - p.disregard, 0) diff --git a/policyengine_us/variables/gov/usda/snap/income/gross/snap_child_support_gross_income_deduction.py b/policyengine_us/variables/gov/usda/snap/income/gross/snap_child_support_gross_income_deduction.py index ed2ca911305..b34930a2494 100644 --- a/policyengine_us/variables/gov/usda/snap/income/gross/snap_child_support_gross_income_deduction.py +++ b/policyengine_us/variables/gov/usda/snap/income/gross/snap_child_support_gross_income_deduction.py @@ -13,7 +13,7 @@ class snap_child_support_gross_income_deduction(Variable): reference = "https://www.law.cornell.edu/uscode/text/7/2014#e_4" def formula(spm_unit, period, parameters): - child_support = add(spm_unit, period, ["child_support_expense"]) + child_support = spm_unit("snap_countable_child_support_expense", period) state = spm_unit.household("state_code_str", period) is_deductible = parameters( period diff --git a/policyengine_us/variables/gov/usda/snap/income/gross/snap_gross_test_income.py b/policyengine_us/variables/gov/usda/snap/income/gross/snap_gross_test_income.py new file mode 100644 index 00000000000..baddd29d662 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/gross/snap_gross_test_income.py @@ -0,0 +1,64 @@ +from policyengine_us.model_api import * + + +class snap_gross_test_income(Variable): + value_type = float + entity = SPMUnit + definition_period = MONTH + label = "SNAP gross income for the gross income test" + reference = ( + "https://www.law.cornell.edu/uscode/text/7/2014#c", + "https://www.law.cornell.edu/cfr/text/7/273.11#c_3_i", + ) + unit = USD + + def formula(spm_unit, period, parameters): + # Certain states count the full income of certain ineligible aliens + # under the gross income test while prorating it under the net + # income test. This mirrors the composition of snap_gross_income + # (snap_earned_income + snap_unearned_income + # - snap_child_support_gross_income_deduction) with the full-count + # share substituted for these aliens' income counted share. + gross_income = spm_unit("snap_gross_income", period) + person = spm_unit.members + full_count = person("is_snap_gross_test_full_income_count_alien", period) + share = person("snap_income_counted_share", period) + gross_test_share = where(full_count, 1, share) + employment = person("snap_earned_income_person", period) + # Attribute unit-level net self-employment income by gross + # self-employment income, as in snap_earned_income. + countable = person("snap_countable_earner", period) + gross_self_employment = person( + "snap_gross_self_employment_income_person", period + ) + unit_gross = spm_unit.sum(gross_self_employment) + unit_net = spm_unit( + "snap_self_employment_income_after_expense_deduction", period + ) + counted_weight = spm_unit.sum( + gross_self_employment * countable * gross_test_share + ) + counted_self_employment = ( + unit_net * counted_weight / where(unit_gross > 0, unit_gross, 1) + ) + earned = max_( + spm_unit.sum(employment * gross_test_share) + counted_self_employment, + 0, + ) + unearned = person("snap_unearned_income_person", period) + p = parameters(period).gov.usda.snap.income + unit_level_unearned = add(spm_unit, period, p.sources.unearned_spm_unit) + # Count these aliens' child support payments in full as well, in + # states that deduct child support when computing gross income. + child_support = person("child_support_expense", period) + state = spm_unit.household("state_code_str", period) + cs_deductible = p.deductions.child_support[state] + full_count_income = ( + earned + + spm_unit.sum(unearned * gross_test_share) + + unit_level_unearned + - cs_deductible * spm_unit.sum(child_support * gross_test_share) + ) + # Households without full-count aliens use snap_gross_income itself + # so both tests read an identical income concept. + return where(spm_unit.any(full_count), full_count_income, gross_income) diff --git a/policyengine_us/variables/gov/usda/snap/income/gross/snap_gross_test_income_fpg_ratio.py b/policyengine_us/variables/gov/usda/snap/income/gross/snap_gross_test_income_fpg_ratio.py new file mode 100644 index 00000000000..deb4dd237ec --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/gross/snap_gross_test_income_fpg_ratio.py @@ -0,0 +1,18 @@ +from policyengine_us.model_api import * + + +class snap_gross_test_income_fpg_ratio(Variable): + value_type = float + entity = SPMUnit + definition_period = MONTH + label = "SNAP gross test income to FPL ratio" + documentation = ( + "SNAP gross income for the gross income test as a percentage of " + "the federal poverty line" + ) + unit = "/1" + + def formula(spm_unit, period): + income = spm_unit("snap_gross_test_income", period) + fpg = spm_unit("snap_fpg", period) + return income / fpg diff --git a/policyengine_us/variables/gov/usda/snap/income/ineligible_members/is_snap_gross_test_full_income_count_alien.py b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/is_snap_gross_test_full_income_count_alien.py new file mode 100644 index 00000000000..5fb7b971df3 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/is_snap_gross_test_full_income_count_alien.py @@ -0,0 +1,18 @@ +from policyengine_us.model_api import * + + +class is_snap_gross_test_full_income_count_alien(Variable): + value_type = bool + entity = Person + label = ( + "SNAP ineligible alien with fully counted income under the gross income test" + ) + definition_period = MONTH + reference = "https://www.law.cornell.edu/cfr/text/7/273.11#c_3_i" + + def formula(person, period, parameters): + p = parameters(period).gov.usda.snap.income.ineligible_members + state = person.household("state_code_str", period) + discretion_alien = person("is_snap_state_discretion_ineligible_alien", period) + hybrid_state = np.isin(state, p.gross_test_full_count_states) + return discretion_alien & hybrid_state diff --git a/policyengine_us/variables/gov/usda/snap/income/ineligible_members/is_snap_prorated_income_member.py b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/is_snap_prorated_income_member.py new file mode 100644 index 00000000000..600d890a8b8 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/is_snap_prorated_income_member.py @@ -0,0 +1,41 @@ +from policyengine_us.model_api import * + + +class is_snap_prorated_income_member(Variable): + value_type = bool + entity = Person + label = "SNAP ineligible member with prorated income" + definition_period = MONTH + documentation = ( + "Whether this person's income is prorated under 7 CFR " + "273.11(c)(2): ineligible aliens without a state full-count " + "election and ABAWD time-limit ineligible members (273.24). " + "Members failing the general work requirements fall under " + "273.11(c)(1), which counts their income in full. The model " + "treats modeled noncompliance as a state-imposed disqualification, " + "consistent with these members' exclusion from the SNAP unit size." + ) + reference = ( + "https://www.law.cornell.edu/cfr/text/7/273.11#c_2_ii", + "https://www.law.cornell.edu/cfr/text/7/273.11#c_3", + "https://www.law.cornell.edu/uscode/text/7/2015#f", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.usda.snap.income.ineligible_members + immigration_ineligible = ~person("is_snap_immigration_status_eligible", period) + state = person.household("state_code_str", period) + discretion_alien = person("is_snap_state_discretion_ineligible_alien", period) + counts_all = np.isin(state, p.count_all_income_states) + full_count_alien = discretion_alien & counts_all + student = person("is_snap_ineligible_student", period.this_year) + prorated_alien = immigration_ineligible & ~student & ~full_count_alien + # ABAWD time-limit ineligible members are prorated under + # 273.11(c)(2); members sanctioned for noncompliance with the + # general work requirements fall under 273.11(c)(1), which counts + # their income and expenses in full. In count-all states the + # state's full-count election for the alien's income governs. + fails_abawd_time_limit = person( + "meets_snap_general_work_requirements", period + ) & ~person("meets_snap_work_requirements_person", period) + return prorated_alien | (fails_abawd_time_limit & ~student & ~full_count_alien) diff --git a/policyengine_us/variables/gov/usda/snap/income/ineligible_members/is_snap_state_discretion_ineligible_alien.py b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/is_snap_state_discretion_ineligible_alien.py new file mode 100644 index 00000000000..771c95e949b --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/is_snap_state_discretion_ineligible_alien.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class is_snap_state_discretion_ineligible_alien(Variable): + value_type = bool + entity = Person + label = "SNAP ineligible alien whose income treatment is at state discretion" + definition_period = MONTH + reference = "https://www.law.cornell.edu/cfr/text/7/273.11#c_3" + + def formula(person, period, parameters): + p = parameters(period).gov.usda.snap.income.ineligible_members + immigration_ineligible = ~person("is_snap_immigration_status_eligible", period) + status = person("immigration_status", period.this_year).decode_to_str() + state_discretion_status = np.isin(status, p.pre_prwora_statuses) + # Ineligible students are treated as nonhousehold members under + # 273.11(d) rather than under the ineligible-alien rules. + student = person("is_snap_ineligible_student", period.this_year) + return ~student & immigration_ineligible & state_discretion_status diff --git a/policyengine_us/variables/gov/usda/snap/income/ineligible_members/snap_expense_counted_share.py b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/snap_expense_counted_share.py new file mode 100644 index 00000000000..fb56c57fe61 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/snap_expense_counted_share.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class snap_expense_counted_share(Variable): + value_type = float + entity = SPMUnit + label = "SNAP share of expenses counted toward deductions" + definition_period = MONTH + unit = "/1" + reference = ( + "https://www.law.cornell.edu/cfr/text/7/273.11#c_2_iii", + "https://www.law.cornell.edu/cfr/text/7/273.11#d_1", + ) + + def formula(spm_unit, period, parameters): + # Shared expenses are assumed to be paid evenly per capita, and + # each payer's per capita portion counts at that member's income + # counted share: ineligible students are nonhousehold members + # whose payments are not the household's expense (273.11(d)(1)); + # the portion paid by prorated members is divided evenly among + # household members with all but the ineligible members' shares + # counted (273.11(c)(2)(iii)); and expenses of members sanctioned + # under 273.11(c)(1) continue to count in their entirety. + person = spm_unit.members + share = person("snap_income_counted_share", period) + size = spm_unit("spm_unit_size", period.this_year) + # Subtract the uncounted portion of each member's per capita + # payment rather than summing counted shares directly, so a + # spm_unit_size exceeding the modeled member count leaves the + # share at one. + return max_(1 - spm_unit.sum(1 - share) / max_(size, 1), 0) diff --git a/policyengine_us/variables/gov/usda/snap/income/ineligible_members/snap_income_counted_share.py b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/snap_income_counted_share.py new file mode 100644 index 00000000000..458002c5cf0 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/snap_income_counted_share.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class snap_income_counted_share(Variable): + value_type = float + entity = Person + label = "SNAP share of income counted toward the household" + definition_period = MONTH + unit = "/1" + reference = ( + "https://www.law.cornell.edu/cfr/text/7/273.11#c_2_ii", + "https://www.law.cornell.edu/cfr/text/7/273.11#d", + ) + + def formula(person, period, parameters): + student = person("is_snap_ineligible_student", period.this_year) + prorated = person("is_snap_prorated_income_member", period) + fraction = person.spm_unit("snap_prorated_income_fraction", period) + return select([student, prorated], [0, fraction], default=1) diff --git a/policyengine_us/variables/gov/usda/snap/income/ineligible_members/snap_prorated_income_fraction.py b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/snap_prorated_income_fraction.py new file mode 100644 index 00000000000..857d3d864b7 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/ineligible_members/snap_prorated_income_fraction.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class snap_prorated_income_fraction(Variable): + value_type = float + entity = SPMUnit + label = "SNAP prorated income fraction" + definition_period = MONTH + unit = "/1" + reference = "https://www.law.cornell.edu/cfr/text/7/273.11#c_2_ii" + + def formula(spm_unit, period, parameters): + # The ineligible member's income is divided evenly among household + # members (excluding nonhousehold members such as ineligible + # students); all but the ineligible members' shares are counted. + # Per the plural "ineligible members' share" in 273.11(c)(2)(ii), + # the shares of all ineligible members — including full-count + # aliens and members sanctioned under 273.11(c)(1), whose own + # income counts in full — are excluded from the counted fraction. + size = spm_unit("spm_unit_size", period.this_year) + students = add(spm_unit, period.this_year, ["is_snap_ineligible_student"]) + household_members = size - students + eligible_members = spm_unit("snap_unit_size", period) + return where( + household_members > 0, + eligible_members / max_(household_members, 1), + 0, + ) 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..18038f0b637 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 @@ -7,19 +7,34 @@ class snap_earned_income(Variable): definition_period = MONTH label = "SNAP earned income" documentation = "Earned income for calculating the SNAP earned income deduction" - reference = "https://www.law.cornell.edu/cfr/text/7/273.9#b_1" + reference = ( + "https://www.law.cornell.edu/cfr/text/7/273.9#b_1", + "https://www.law.cornell.edu/cfr/text/7/273.11#c", + ) unit = USD - def formula(spm_unit, period): + def formula(spm_unit, period, parameters): 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) + employment = person("snap_earned_income_person", period) + share = person("snap_income_counted_share", period) + # Self-employment income net of the expense deduction is computed + # at the SPM unit level; attribute it to members in proportion to + # their gross self-employment income. Gross amounts are floored at + # zero per enterprise, so the attribution ratio is bounded to + # [0, 1]. Non-countable earners remain in the attribution base, so + # their shares are dropped rather than reattributed to countable + # earners. This mirrors the attribution in snap_gross_test_income, + # which substitutes the full-count share. + countable = person("snap_countable_earner", period) + gross_self_employment = person( + "snap_gross_self_employment_income_person", period ) - fraction = person("snap_work_requirement_income_proration_fraction", period) - prorated_employment_income = spm_unit.sum( - where(countable_earner, employment_income * fraction, 0) + unit_gross = spm_unit.sum(gross_self_employment) + unit_net = spm_unit( + "snap_self_employment_income_after_expense_deduction", period ) - return income - full_employment_income + prorated_employment_income + counted_weight = spm_unit.sum(gross_self_employment * countable * share) + counted_self_employment = ( + unit_net * counted_weight / where(unit_gross > 0, unit_gross, 1) + ) + return max_(spm_unit.sum(employment * share) + counted_self_employment, 0) diff --git a/policyengine_us/variables/gov/usda/snap/income/snap_earned_income_person.py b/policyengine_us/variables/gov/usda/snap/income/snap_earned_income_person.py index 0cc7cc23296..cd1e19067e0 100644 --- a/policyengine_us/variables/gov/usda/snap/income/snap_earned_income_person.py +++ b/policyengine_us/variables/gov/usda/snap/income/snap_earned_income_person.py @@ -3,12 +3,9 @@ class snap_earned_income_person(Variable): value_type = float - entity = SPMUnit + entity = Person definition_period = MONTH - label = "SNAP earned income person" - documentation = ( - "Earned income per person for calculating the SNAP earned income deduction" - ) + label = "SNAP earned income per person" reference = "https://www.law.cornell.edu/cfr/text/7/273.9#b_1" unit = USD defined_for = "snap_countable_earner" diff --git a/policyengine_us/variables/gov/usda/snap/income/snap_gross_self_employment_income_person.py b/policyengine_us/variables/gov/usda/snap/income/snap_gross_self_employment_income_person.py new file mode 100644 index 00000000000..51c7a477112 --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/snap_gross_self_employment_income_person.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class snap_gross_self_employment_income_person(Variable): + value_type = float + entity = Person + definition_period = MONTH + label = "SNAP gross self-employment income per person" + reference = ( + "https://www.law.cornell.edu/cfr/text/7/273.9#b_1", + "https://www.law.cornell.edu/cfr/text/7/273.11#a_2_ii", + ) + unit = USD + + def formula(person, period, parameters): + # 7 CFR 273.11(a)(2)(ii) offsets losses against other household + # income only for farming operations. The self-employment income + # modeled here is non-farm, so each enterprise's loss is floored + # at zero rather than netted against other members' income. + regular = person("self_employment_income_before_lsr", period) + sstb = person("sstb_self_employment_income_before_lsr", period) + return max_(regular, 0) + max_(sstb, 0) diff --git a/policyengine_us/variables/gov/usda/snap/income/snap_unearned_income.py b/policyengine_us/variables/gov/usda/snap/income/snap_unearned_income.py index 0d6b5368aa2..3c078583396 100644 --- a/policyengine_us/variables/gov/usda/snap/income/snap_unearned_income.py +++ b/policyengine_us/variables/gov/usda/snap/income/snap_unearned_income.py @@ -7,7 +7,16 @@ class snap_unearned_income(Variable): definition_period = MONTH label = "SNAP unearned income" documentation = "Unearned income for calculating the SNAP benefit" - reference = "https://www.law.cornell.edu/cfr/text/7/273.9#b_2" + reference = ( + "https://www.law.cornell.edu/cfr/text/7/273.9#b_2", + "https://www.law.cornell.edu/cfr/text/7/273.11#c", + ) unit = USD - adds = "gov.usda.snap.income.sources.unearned" + def formula(spm_unit, period, parameters): + person = spm_unit.members + unearned = person("snap_unearned_income_person", period) + share = person("snap_income_counted_share", period) + p = parameters(period).gov.usda.snap.income.sources + unit_level = add(spm_unit, period, p.unearned_spm_unit) + return spm_unit.sum(unearned * share) + unit_level diff --git a/policyengine_us/variables/gov/usda/snap/income/snap_unearned_income_person.py b/policyengine_us/variables/gov/usda/snap/income/snap_unearned_income_person.py new file mode 100644 index 00000000000..1c462df6a5e --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/snap_unearned_income_person.py @@ -0,0 +1,12 @@ +from policyengine_us.model_api import * + + +class snap_unearned_income_person(Variable): + value_type = float + entity = Person + definition_period = MONTH + label = "SNAP unearned income per person" + reference = "https://www.law.cornell.edu/cfr/text/7/273.9#b_2" + unit = USD + + adds = "gov.usda.snap.income.sources.unearned" 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) diff --git a/policyengine_us/variables/gov/usda/snap/is_snap_excluded_member.py b/policyengine_us/variables/gov/usda/snap/is_snap_excluded_member.py new file mode 100644 index 00000000000..e0d4726078f --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/is_snap_excluded_member.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class is_snap_excluded_member(Variable): + value_type = bool + entity = Person + definition_period = MONTH + label = "SNAP excluded member" + documentation = ( + "Whether this person is a nonhousehold member or an ineligible " + "household member excluded from the SNAP unit" + ) + reference = ( + "https://www.law.cornell.edu/cfr/text/7/273.11#c", + "https://www.law.cornell.edu/cfr/text/7/273.11#d", + "https://www.law.cornell.edu/uscode/text/7/2015#f", + ) + + def formula(person, period, parameters): + return ( + person("is_snap_ineligible_student", period.this_year) + | ~person("is_snap_immigration_status_eligible", period) + | ~person("meets_snap_work_requirements_person", period) + ) diff --git a/policyengine_us/variables/gov/usda/snap/snap_unit_size.py b/policyengine_us/variables/gov/usda/snap/snap_unit_size.py index dbb0b2f4d0f..1b7478fb39a 100644 --- a/policyengine_us/variables/gov/usda/snap/snap_unit_size.py +++ b/policyengine_us/variables/gov/usda/snap/snap_unit_size.py @@ -12,12 +12,6 @@ class snap_unit_size(Variable): ) 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) - | ~person("meets_snap_work_requirements_person", period) - ) - ineligible_count = spm_unit.sum(ineligible) - return max_(unit_size - ineligible_count, 0) + unit_size = spm_unit("spm_unit_size", period.this_year) + excluded_count = add(spm_unit, period, ["is_snap_excluded_member"]) + return max_(unit_size - excluded_count, 0)