From fb16c8e1fd7efdfb9c4adbc2f6be5b8372da16cd Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 08:29:40 -0400 Subject: [PATCH 01/10] Add regression coverage for federal SNAP heat-and-eat SUA eligibility --- .../tests/core/test_snap_heat_and_eat.py | 173 +++++++++++++++ .../income/deductions/heat_and_eat/snap.yaml | 210 ++++++++++++++++++ .../snap_utility_allowance_type.yaml | 146 ++++++++++++ 3 files changed, 529 insertions(+) create mode 100644 policyengine_us/tests/core/test_snap_heat_and_eat.py create mode 100644 policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml diff --git a/policyengine_us/tests/core/test_snap_heat_and_eat.py b/policyengine_us/tests/core/test_snap_heat_and_eat.py new file mode 100644 index 00000000000..e651aca0883 --- /dev/null +++ b/policyengine_us/tests/core/test_snap_heat_and_eat.py @@ -0,0 +1,173 @@ +"""Reform compatibility invariants for the federal heat-and-eat restriction. + +YAML covers individual policy examples. This test compares a parameter reform +that restores the former rule against its baseline, across a vectorized grid. +It detects unintended changes to other states and expense-based eligibility, +and checks fallback against matched households with state deeming disabled. +Only one reform simulation and its baseline are constructed for all dates. +""" + +from itertools import product + +import numpy as np +from policyengine_core.periods import instant +from policyengine_core.reforms import Reform + +from policyengine_us import Simulation + + +# All SNAP-covered state codes: 50 states, DC, Guam, and the Virgin Islands. +# Other StateCode enum members do not have SNAP utility allowance parameters. +SNAP_STATES = tuple( + "AL AK AZ AR CA CO CT DE FL GA HI ID IL IN IA KS KY LA ME MD MA MI MN " + "MS MO MT NE NV NH NJ NM NY NC ND OH OK OR PA RI SC SD TN TX UT VT VA " + "WA WV WI WY DC GU VI".split() +) +PERIODS = ("2025-06", "2025-07", "2025-08", "2026-01") + + +class LegacyHeatAndEat(Reform): + def apply(self): + def modify(parameters): + parameters.gov.usda.snap.income.deductions.utility.heat_and_eat_requires_elderly_disabled.update( + start=instant("2025-07-04"), + stop=instant("2100-12-31"), + value=False, + ) + return parameters + + self.modify_parameters(modify) + + +def _household_grid(): + situation = { + entity: {} + for entity in ( + "people", + "marital_units", + "tax_units", + "spm_units", + "families", + "households", + ) + } + facts = [] + fallback_indices = {} + + def add(state, status, heating, bills, reference=False): + index = len(facts) + key = f"case_{index}" + members = [key] + + def annual(value): + return {"2025": value, "2026": value} + + person = { + "age": annual(60 if status == "elderly" else 40), + # The broad disability flag alone must not qualify under USDA rules. + "is_disabled": annual(True), + } + if status == "ssdi": + person["social_security_disability"] = annual(1_200) + situation["people"][key] = person + for entity in ("marital_units", "tax_units", "families"): + situation[entity][key] = {"members": members} + spm_unit = {"members": members} + if heating: + spm_unit["heating_cooling_expense"] = annual(1_200) + if bills == "phone": + spm_unit["phone_expense"] = annual(360) + elif bills == "two": + spm_unit["pre_subsidy_electricity_expense"] = annual(600) + spm_unit["water_expense"] = annual(300) + if reference: + # Matched control: derive expense-based fallback through the + # existing allowance formulas without duplicating those formulas. + spm_unit["snap_state_using_standard_utility_allowance"] = { + month: False for month in PERIODS + } + fallback_indices[(state, bills)] = index + situation["spm_units"][key] = spm_unit + situation["households"][key] = { + "members": members, + "state_code": annual(state), + } + facts.append((state, status, heating, bills, reference)) + + for state, status, heating, bills in product( + SNAP_STATES, + ("ordinary", "elderly", "ssdi"), + (False, True), + ("none", "phone", "two"), + ): + add(state, status, heating, bills) + for state, bills in product(SNAP_STATES, ("none", "phone", "two")): + add(state, "ordinary", False, bills, reference=True) + controls = np.array( + [fallback_indices[(state, bills)] for state, _, _, bills, _ in facts] + ) + return situation, facts, controls + + +def test_heat_and_eat_reform_preserves_other_utility_allowance_paths(): + situation, facts, controls = _household_grid() + legacy = Simulation(situation=situation, reform=LegacyHeatAndEat) + fixed = legacy.baseline + parameters = fixed.tax_benefit_system.parameters + + def path(date): + return parameters(date).gov.usda.snap.income.deductions.utility + + assert not bool(path("2025-07-03").heat_and_eat_requires_elderly_disabled) + assert bool(path("2025-07-04").heat_and_eat_requires_elderly_disabled) + + ordinary = np.array([status == "ordinary" for _, status, _, _, _ in facts]) + heating = np.array([heat for _, _, heat, _, _ in facts]) + bill_style = np.array([bills for _, _, _, bills, _ in facts]) + + for month in PERIODS: + legacy_type = legacy.calculate( + "snap_utility_allowance_type", month + ).decode_to_str() + fixed_type = fixed.calculate( + "snap_utility_allowance_type", month + ).decode_to_str() + legacy_allowance = legacy.calculate("snap_utility_allowance", month) + fixed_allowance = fixed.calculate("snap_utility_allowance", month) + state_deeming = fixed.calculate( + "snap_state_using_standard_utility_allowance", month + ).astype(bool) + + # The former rule grants SUA whenever the state deems a heating cost. + np.testing.assert_array_equal(legacy_type[state_deeming], "SUA") + if month in ("2025-06", "2025-07"): + # July is evaluated at the start of the month; the legal parameter + # changes on July 4, making August the first full affected month. + np.testing.assert_array_equal(fixed_type, legacy_type) + np.testing.assert_allclose(fixed_allowance, legacy_allowance) + continue + + affected = state_deeming & ordinary & ~heating + assert affected.any() + preserved = ~affected + np.testing.assert_array_equal(fixed_type[preserved], legacy_type[preserved]) + np.testing.assert_allclose( + fixed_allowance[preserved], legacy_allowance[preserved] + ) + + # Affected households match expense-only controls, including states + # with and without a two-utility LUA. Removing deeming never raises SUA. + np.testing.assert_array_equal( + fixed_type[affected], fixed_type[controls[affected]] + ) + np.testing.assert_allclose( + fixed_allowance[affected], fixed_allowance[controls[affected]] + ) + np.testing.assert_array_equal( + fixed_type[affected & (bill_style == "none")], "NONE" + ) + np.testing.assert_array_equal( + fixed_type[affected & (bill_style == "phone")], "IUA" + ) + assert set(fixed_type[affected]) == {"NONE", "IUA", "LUA"} + assert np.all(fixed_allowance <= legacy_allowance + 1e-6) diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml new file mode 100644 index 00000000000..12a10ffb125 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml @@ -0,0 +1,210 @@ +# Exact PolicyBench scenario facts and conversion defaults. Expected SNAP values +# are the HUB_BRIEF audited targets, not results verified against current gh/main. +- name: PolicyBench scenario_080 PA household receives SNAP without the heat-and-eat + SUA + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: + '2026': 41 + employment_income: + '2026': 0.0 + bank_account_assets: + '2026': 19827.646484375 + financial_assistance: + '2026': 3600.0 + is_disabled: + '2026': true + is_tax_unit_head: + '2026': true + is_tax_unit_spouse: + '2026': false + long_term_capital_gains: + '2026': 924.0 + other_medical_expenses: + '2026': 12.0 + over_the_counter_health_expenses: + '2026': 100.0 + qualified_dividend_income: + '2026': 16.0 + takes_up_medicaid_if_eligible: + '2026': true + takes_up_ssi_if_eligible: + '2026': true + marital_units: + marital_unit_1: + members: + - head + tax_units: + tax_unit: + members: + - head + first_home_mortgage_balance: + '2026': 93000.0 + takes_up_aca_if_eligible: + '2026': true + takes_up_dc_ptc: + '2026': true + takes_up_eitc: + '2026': true + would_file_if_eligible_for_refundable_credit: + '2026': true + would_file_taxes_voluntarily: + '2026': true + spm_units: + spm_unit: + members: + - head + spm_unit_energy_subsidy: + '2026': 2000.0 + takes_up_snap_if_eligible: + '2026': true + families: + family: + members: + - head + households: + household: + members: + - head + state_code: + '2026': PA + household_vehicles_value: + '2026': 3570.0 + output: + snap: 3270 +- name: PolicyBench scenario_100 MT household receives SNAP without the heat-and-eat + SUA + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: + '2026': 46 + employment_income: + '2026': 5914.720703125 + bank_account_assets: + '2026': 2800.0 + employer_sponsored_insurance_premiums: + '2026': 20643.525390625 + fsla_overtime_premium: + '2026': 709.7664794921875 + has_esi: + '2026': true + health_insurance_premiums_without_medicare_part_b: + '2026': 564.0 + hourly_wage: + '2026': 2.319999933242798 + hours_worked_last_week: + '2026': 55.0 + is_disabled: + '2026': true + is_tax_unit_head: + '2026': true + is_tax_unit_spouse: + '2026': false + other_health_insurance_premiums: + '2026': 564.0 + other_medical_expenses: + '2026': 500.0 + over_the_counter_health_expenses: + '2026': 480.0 + roth_401k_contributions_desired: + '2026': 681.0 + roth_ira_contributions_desired: + '2026': 279.67999267578125 + traditional_401k_contributions_desired: + '2026': 3859.0 + traditional_ira_contributions_desired: + '2026': 180.32000732421875 + weekly_hours_worked_before_lsr: + '2026': 55.0 + takes_up_medicaid_if_eligible: + '2026': true + takes_up_ssi_if_eligible: + '2026': true + child1: + age: + '2026': 6 + employment_income: + '2026': 0.0 + has_esi: + '2026': true + is_tax_unit_head: + '2026': false + is_tax_unit_spouse: + '2026': false + takes_up_medicaid_if_eligible: + '2026': true + takes_up_ssi_if_eligible: + '2026': true + child2: + age: + '2026': 5 + employment_income: + '2026': 0.0 + is_tax_unit_head: + '2026': false + is_tax_unit_spouse: + '2026': false + other_medical_expenses: + '2026': 500.0 + takes_up_medicaid_if_eligible: + '2026': true + takes_up_ssi_if_eligible: + '2026': true + marital_units: + marital_unit_1: + members: + - head + marital_unit_2: + members: + - child1 + marital_unit_3: + members: + - child2 + tax_units: + tax_unit: + members: + - head + - child1 + - child2 + takes_up_aca_if_eligible: + '2026': true + takes_up_dc_ptc: + '2026': true + takes_up_eitc: + '2026': true + would_file_if_eligible_for_refundable_credit: + '2026': true + would_file_taxes_voluntarily: + '2026': true + spm_units: + spm_unit: + members: + - head + - child1 + - child2 + takes_up_snap_if_eligible: + '2026': true + families: + family: + members: + - head + - child1 + - child2 + households: + household: + members: + - head + - child1 + - child2 + state_code: + '2026': MT + household_vehicles_value: + '2026': 1980.88232421875 + output: + snap: 7002 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml new file mode 100644 index 00000000000..69bb3f1921b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml @@ -0,0 +1,146 @@ +# P.L. 119-21 section 10103(a) and the August 29, 2025 FNS memo. +# SNAP evaluates monthly policy at month start: the July 4 change first +# applies to the full benefit month beginning August 1, 2025. +- name: Pennsylvania heat-and-eat eligibility changes after enactment + period: 2025 + input: + age: 40 + state_code: PA + output: + snap_state_using_standard_utility_allowance: true + snap_utility_allowance_type: + 2025-06: SUA + 2025-07: SUA + 2025-08: NONE + +- name: Pennsylvania adult without qualifying utility costs loses deemed SUA + period: 2026 + input: + age: 40 + state_code: PA + output: + snap_utility_allowance_type: NONE + snap_utility_allowance: 0 + +- name: Montana adult without qualifying utility costs loses deemed SUA + period: 2026 + input: + age: 40 + state_code: MT + output: + snap_utility_allowance_type: NONE + snap_utility_allowance: 0 + +- name: California adult without qualifying utility costs loses deemed SUA + period: 2026 + input: + age: 40 + state_code: CA + output: + snap_utility_allowance_type: NONE + snap_utility_allowance: 0 + +- name: The federal restriction continues after 2026 + period: 2027 + input: + age: 40 + state_code: PA + output: + snap_utility_allowance_type: NONE + +- name: Age 59 does not qualify for the elderly exception + period: 2026 + input: + age: 59 + state_code: PA + output: + has_snap_elderly_disabled_member: false + snap_utility_allowance_type: NONE + +- name: Age 60 preserves the heat-and-eat SUA + period: 2026 + input: + age: 60 + state_code: PA + output: + has_snap_elderly_disabled_member: true + snap_utility_allowance_type: SUA + +- name: SSDI receipt preserves the heat-and-eat SUA below age 60 + period: 2026 + input: + age: 40 + social_security_disability: 1_200 + state_code: MT + output: + has_snap_elderly_disabled_member: true + snap_utility_allowance_type: SUA + +- name: General disability alone does not meet the USDA definition + period: 2026 + input: + age: 40 + is_disabled: true + state_code: PA + output: + has_snap_elderly_disabled_member: false + snap_utility_allowance_type: NONE + +- name: Actual heating costs independently preserve SUA eligibility + period: 2026 + input: + age: 40 + state_code: PA + heating_cooling_expense: 120 + output: + has_snap_elderly_disabled_member: false + snap_utility_allowance_type: SUA + +- name: Two non-heating utility bills qualify for Pennsylvania LUA + period: 2026 + input: + age: 40 + state_code: PA + pre_subsidy_electricity_expense: 120 + water_expense: 120 + output: + snap_utility_allowance_type: LUA + +- name: A phone bill qualifies for California IUA + period: 2026 + input: + age: 40 + state_code: CA + phone_expense: 120 + output: + snap_utility_allowance_type: IUA + +- name: Elderly members do not create heat-and-eat eligibility in Texas + period: 2026 + input: + age: 60 + state_code: TX + output: + snap_state_using_standard_utility_allowance: false + snap_utility_allowance_type: NONE + +- name: SNAP-excluded elderly members do not confer the heat-and-eat exception + period: 2026 + input: + people: + adult: + age: 40 + excluded_elder: + age: 65 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [adult, excluded_elder] + households: + household: + members: [adult, excluded_elder] + state_code: PA + output: + has_usda_elderly_disabled: true + has_snap_elderly_disabled_member: false + snap_utility_allowance_type: NONE From a101a1d9ca9e87c852d6cf74daa5c42224a2270b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 09:48:50 -0400 Subject: [PATCH 02/10] Apply federal elderly or disabled requirement to SNAP heat-and-eat SUA --- .../hub-pb-snap-heat-eat-1009.fixed.md | 1 + ...eat_and_eat_requires_elderly_disabled.yaml | 21 +++++++++++++++++++ .../tests/core/test_snap_heat_and_eat.py | 12 +++++++++++ .../snap_utility_allowance_type.yaml | 4 ++++ .../shelter/snap_utility_allowance_type.py | 17 ++++++++++++--- 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 changelog.d/hub-pb-snap-heat-eat-1009.fixed.md create mode 100644 policyengine_us/parameters/gov/usda/snap/income/deductions/utility/heat_and_eat_requires_elderly_disabled.yaml diff --git a/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md b/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md new file mode 100644 index 00000000000..74cb41e2fbc --- /dev/null +++ b/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md @@ -0,0 +1 @@ +Restrict SNAP heat-and-eat standard utility allowances to households with an elderly or disabled SNAP member under P.L. 119-21, while preserving eligibility based on actual heating or cooling expenses. diff --git a/policyengine_us/parameters/gov/usda/snap/income/deductions/utility/heat_and_eat_requires_elderly_disabled.yaml b/policyengine_us/parameters/gov/usda/snap/income/deductions/utility/heat_and_eat_requires_elderly_disabled.yaml new file mode 100644 index 00000000000..c0cf7c542d3 --- /dev/null +++ b/policyengine_us/parameters/gov/usda/snap/income/deductions/utility/heat_and_eat_requires_elderly_disabled.yaml @@ -0,0 +1,21 @@ +description: The Department of Agriculture requires an elderly or disabled SNAP household member for heating and cooling standard utility allowance eligibility based on energy assistance under the Supplemental Nutrition Assistance Program. + +# Section 10103 took effect upon enactment on July 4, 2025. Monthly SNAP +# formulas evaluate policy at month start, so August 2025 is the first full +# affected model month. Individual certification/recertification dates and +# state administrative implementation dates are not modeled here. +values: + 0000-01-01: false + 2025-07-04: true + +metadata: + unit: bool + period: month + label: SNAP heat-and-eat SUA elderly or disabled requirement + reference: + - title: 7 U.S.C. 2014(e)(6)(C)(iv)(I) - Availability of allowance to recipients of energy assistance + href: https://www.law.cornell.edu/uscode/text/7/2014#e_6_C_iv_I + - title: Public Law 119-21, Section 10103(a) - Standard Utility Allowance + href: https://www.govinfo.gov/content/pkg/PLAW-119publ21/html/PLAW-119publ21.htm + - title: "USDA FNS August 29, 2025 implementation memo - Treatment of Energy Assistance Payments #page 1-3" + href: https://fns-prod.azureedge.us/sites/default/files/resource-files/OBBB-LIHEAP-Implementation-Memo.pdf#page=1 diff --git a/policyengine_us/tests/core/test_snap_heat_and_eat.py b/policyengine_us/tests/core/test_snap_heat_and_eat.py index e651aca0883..7c8599fc716 100644 --- a/policyengine_us/tests/core/test_snap_heat_and_eat.py +++ b/policyengine_us/tests/core/test_snap_heat_and_eat.py @@ -137,6 +137,18 @@ def path(date): state_deeming = fixed.calculate( "snap_state_using_standard_utility_allowance", month ).astype(bool) + np.testing.assert_array_equal( + state_deeming, + legacy.calculate("snap_state_using_standard_utility_allowance", month), + ) + # Check the fixture's SNAP status independently of the allowance so + # eligibility or disability-definition changes cannot hide the defect. + np.testing.assert_array_equal( + fixed.calculate("has_snap_elderly_disabled_member", month), ~ordinary + ) + np.testing.assert_array_equal( + fixed.calculate("is_snap_excluded_member", month), False + ) # The former rule grants SUA whenever the state deems a heating cost. np.testing.assert_array_equal(legacy_type[state_deeming], "SUA") diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml index 69bb3f1921b..dbe2930ba3c 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml @@ -17,6 +17,7 @@ period: 2026 input: age: 40 + weekly_hours_worked: 30 state_code: PA output: snap_utility_allowance_type: NONE @@ -52,6 +53,7 @@ period: 2026 input: age: 59 + weekly_hours_worked: 30 state_code: PA output: has_snap_elderly_disabled_member: false @@ -61,6 +63,7 @@ period: 2026 input: age: 60 + weekly_hours_worked: 30 state_code: PA output: has_snap_elderly_disabled_member: true @@ -130,6 +133,7 @@ people: adult: age: 40 + weekly_hours_worked: 30 excluded_elder: age: 65 immigration_status: UNDOCUMENTED diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_utility_allowance_type.py b/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_utility_allowance_type.py index f5d5dab6bd7..678b76fe7f1 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_utility_allowance_type.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_utility_allowance_type.py @@ -17,17 +17,28 @@ class snap_utility_allowance_type(Variable): documentation = "The type of utility allowance that is eligible for the SPM unit" definition_period = MONTH reference = ( - "https://www.ecfr.gov/current/title-7/section-273.9#p-273.9(d)(6)(iii)(A)(3)" + "https://www.ecfr.gov/current/title-7/section-273.9#p-273.9(d)(6)(iii)(A)(3)", + "https://www.law.cornell.edu/uscode/text/7/2014#e_6_C_iv_I", + "https://www.govinfo.gov/content/pkg/PLAW-119publ21/html/PLAW-119publ21.htm", + # PDF pages 1-3. + "https://fns-prod.azureedge.us/sites/default/files/resource-files/OBBB-LIHEAP-Implementation-Memo.pdf#page=1", ) def formula(spm_unit, period, parameters): # The utility count and incurrence facts are YEAR-defined stocks # read at this MONTH period, so they are carried as is (no รท12). distinct_utility_bills = spm_unit("count_distinct_utility_expenses", period) - lua = parameters(period).gov.usda.snap.income.deductions.utility.limited + p = parameters(period).gov.usda.snap.income.deductions.utility + lua = p.limited region = spm_unit.household("snap_utility_region_str", period) always_sua = spm_unit("snap_state_using_standard_utility_allowance", period) has_heating_cooling = spm_unit("has_heating_cooling_expense", period) + # P.L. 119-21 section 10103(a) restricts energy-assistance deeming, + # while actual heating or cooling expenses still qualify on their own. + deemed_sua = always_sua + if p.heat_and_eat_requires_elderly_disabled: + elderly_disabled = spm_unit("has_snap_elderly_disabled_member", period) + deemed_sua = always_sua & elderly_disabled lua_is_defined = lua.active[region].astype(bool) # Under 7 CFR 273.9(d)(6)(iii)(A)(3), including telephone in the LUA # is a state option; where the state excludes it, a phone bill does @@ -39,7 +50,7 @@ def formula(spm_unit, period, parameters): ) return select( [ - has_heating_cooling | always_sua, + has_heating_cooling | deemed_sua, lua_is_defined & (lua_qualifying_bills >= 2), distinct_utility_bills > 0, ], From d2e5c89ea273b07a4549eb65f5e8a194bb132435 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 09:56:24 -0400 Subject: [PATCH 03/10] Match SNAP regression leaf inputs to country Simulation adapters --- .../snap/income/deductions/heat_and_eat/snap.yaml | 14 +++++++------- .../heat_and_eat/snap_utility_allowance_type.yaml | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml index 12a10ffb125..c4a2a496547 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml @@ -1,5 +1,5 @@ -# Exact PolicyBench scenario facts and conversion defaults. Expected SNAP values -# are the HUB_BRIEF audited targets, not results verified against current gh/main. +# PolicyBench snapshot facts and takeup defaults. Leaf input aliases mirror +# the US Simulation adapter, which Core's YAML SimulationBuilder bypasses. - name: PolicyBench scenario_080 PA household receives SNAP without the heat-and-eat SUA period: 2026 @@ -9,7 +9,7 @@ head: age: '2026': 41 - employment_income: + employment_income_before_lsr: '2026': 0.0 bank_account_assets: '2026': 19827.646484375 @@ -21,7 +21,7 @@ '2026': true is_tax_unit_spouse: '2026': false - long_term_capital_gains: + long_term_capital_gains_before_response: '2026': 924.0 other_medical_expenses: '2026': 12.0 @@ -84,7 +84,7 @@ head: age: '2026': 46 - employment_income: + employment_income_before_lsr: '2026': 5914.720703125 bank_account_assets: '2026': 2800.0 @@ -129,7 +129,7 @@ child1: age: '2026': 6 - employment_income: + employment_income_before_lsr: '2026': 0.0 has_esi: '2026': true @@ -144,7 +144,7 @@ child2: age: '2026': 5 - employment_income: + employment_income_before_lsr: '2026': 0.0 is_tax_unit_head: '2026': false diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml index dbe2930ba3c..27a580b6603 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml @@ -17,7 +17,7 @@ period: 2026 input: age: 40 - weekly_hours_worked: 30 + weekly_hours_worked_before_lsr: 30 state_code: PA output: snap_utility_allowance_type: NONE @@ -53,7 +53,7 @@ period: 2026 input: age: 59 - weekly_hours_worked: 30 + weekly_hours_worked_before_lsr: 30 state_code: PA output: has_snap_elderly_disabled_member: false @@ -63,7 +63,7 @@ period: 2026 input: age: 60 - weekly_hours_worked: 30 + weekly_hours_worked_before_lsr: 30 state_code: PA output: has_snap_elderly_disabled_member: true @@ -133,7 +133,7 @@ people: adult: age: 40 - weekly_hours_worked: 30 + weekly_hours_worked_before_lsr: 30 excluded_elder: age: 65 immigration_status: UNDOCUMENTED From cdce303792c67135d87357a7d597bb05fa92e21a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 12:30:41 -0400 Subject: [PATCH 04/10] Preserve state deeming in vectorized SNAP invariant controls --- .../tests/core/test_snap_heat_and_eat.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/policyengine_us/tests/core/test_snap_heat_and_eat.py b/policyengine_us/tests/core/test_snap_heat_and_eat.py index 7c8599fc716..b8b085d8a46 100644 --- a/policyengine_us/tests/core/test_snap_heat_and_eat.py +++ b/policyengine_us/tests/core/test_snap_heat_and_eat.py @@ -81,11 +81,6 @@ def annual(value): spm_unit["pre_subsidy_electricity_expense"] = annual(600) spm_unit["water_expense"] = annual(300) if reference: - # Matched control: derive expense-based fallback through the - # existing allowance formulas without duplicating those formulas. - spm_unit["snap_state_using_standard_utility_allowance"] = { - month: False for month in PERIODS - } fallback_indices[(state, bills)] = index situation["spm_units"][key] = spm_unit situation["households"][key] = { @@ -124,8 +119,27 @@ def path(date): ordinary = np.array([status == "ordinary" for _, status, _, _, _ in facts]) heating = np.array([heat for _, _, heat, _, _ in facts]) bill_style = np.array([bills for _, _, _, bills, _ in facts]) + reference = np.array([control for _, _, _, _, control in facts]) for month in PERIODS: + state_deeming = fixed.calculate( + "snap_state_using_standard_utility_allowance", month + ).astype(bool) + np.testing.assert_array_equal( + state_deeming, + legacy.calculate("snap_state_using_standard_utility_allowance", month), + ) + # Derive matched expense-only controls from the existing formulas. + # Sparse inputs default the entire vector to False, so preserve the + # computed state option for every primary case in a full input array. + # Set this before calculating dependent utility types or amounts. + state_deeming[reference] = False + for simulation in (fixed, legacy): + simulation.set_input( + "snap_state_using_standard_utility_allowance", + month, + state_deeming.copy(), + ) legacy_type = legacy.calculate( "snap_utility_allowance_type", month ).decode_to_str() @@ -134,13 +148,6 @@ def path(date): ).decode_to_str() legacy_allowance = legacy.calculate("snap_utility_allowance", month) fixed_allowance = fixed.calculate("snap_utility_allowance", month) - state_deeming = fixed.calculate( - "snap_state_using_standard_utility_allowance", month - ).astype(bool) - np.testing.assert_array_equal( - state_deeming, - legacy.calculate("snap_state_using_standard_utility_allowance", month), - ) # Check the fixture's SNAP status independently of the allowance so # eligibility or disability-definition changes cannot hide the defect. np.testing.assert_array_equal( From 15f8d0cd62d50eb3d29bc734898fffef3657a421 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 9 Oct 2026 17:31:56 -0400 Subject: [PATCH 05/10] Update partner pins for the heat-and-eat SUA restriction - Update 33 values in 22 partner contract cases (Amplifi, CA and federal signatures, CA/MA SNAP edge cases). The 11 eligibility flips also take snap to 0. Approved through the partner-test gate; partners notified. - Remove the FNS memo URL, which now returns 404, from the parameter and variable references. - Replace per-year input keys in heat_and_eat/snap.yaml with plain values and standard case/person naming; inputs and results are unchanged. Co-Authored-By: Claude Opus 5.5 --- ...eat_and_eat_requires_elderly_disabled.yaml | 2 - .../income/deductions/heat_and_eat/snap.yaml | 264 ++++++------------ .../baseline/partners/amplifi/2026.yaml | 2 +- .../edge_cases/federal/nutrition/snap/ca.yaml | 4 +- .../edge_cases/federal/nutrition/snap/ma.yaml | 2 +- .../analytics_coverage/signatures/ca.yaml | 26 +- .../signatures/federal.yaml | 32 +-- .../shelter/snap_utility_allowance_type.py | 2 - 8 files changed, 121 insertions(+), 213 deletions(-) diff --git a/policyengine_us/parameters/gov/usda/snap/income/deductions/utility/heat_and_eat_requires_elderly_disabled.yaml b/policyengine_us/parameters/gov/usda/snap/income/deductions/utility/heat_and_eat_requires_elderly_disabled.yaml index c0cf7c542d3..d366ab61aee 100644 --- a/policyengine_us/parameters/gov/usda/snap/income/deductions/utility/heat_and_eat_requires_elderly_disabled.yaml +++ b/policyengine_us/parameters/gov/usda/snap/income/deductions/utility/heat_and_eat_requires_elderly_disabled.yaml @@ -17,5 +17,3 @@ metadata: href: https://www.law.cornell.edu/uscode/text/7/2014#e_6_C_iv_I - title: Public Law 119-21, Section 10103(a) - Standard Utility Allowance href: https://www.govinfo.gov/content/pkg/PLAW-119publ21/html/PLAW-119publ21.htm - - title: "USDA FNS August 29, 2025 implementation memo - Treatment of Energy Assistance Payments #page 1-3" - href: https://fns-prod.azureedge.us/sites/default/files/resource-files/OBBB-LIHEAP-Implementation-Memo.pdf#page=1 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml index c4a2a496547..9da9145379e 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml @@ -1,210 +1,122 @@ # PolicyBench snapshot facts and takeup defaults. Leaf input aliases mirror # the US Simulation adapter, which Core's YAML SimulationBuilder bypasses. -- name: PolicyBench scenario_080 PA household receives SNAP without the heat-and-eat - SUA +- name: Case 1, PolicyBench scenario_080 PA household receives SNAP without the heat-and-eat SUA. period: 2026 absolute_error_margin: 0.01 input: people: - head: - age: - '2026': 41 - employment_income_before_lsr: - '2026': 0.0 - bank_account_assets: - '2026': 19827.646484375 - financial_assistance: - '2026': 3600.0 - is_disabled: - '2026': true - is_tax_unit_head: - '2026': true - is_tax_unit_spouse: - '2026': false - long_term_capital_gains_before_response: - '2026': 924.0 - other_medical_expenses: - '2026': 12.0 - over_the_counter_health_expenses: - '2026': 100.0 - qualified_dividend_income: - '2026': 16.0 - takes_up_medicaid_if_eligible: - '2026': true - takes_up_ssi_if_eligible: - '2026': true + person1: + age: 41 + employment_income_before_lsr: 0 + bank_account_assets: 19_827.646484375 + financial_assistance: 3_600 + is_disabled: true + is_tax_unit_head: true + is_tax_unit_spouse: false + long_term_capital_gains_before_response: 924 + other_medical_expenses: 12 + over_the_counter_health_expenses: 100 + qualified_dividend_income: 16 + takes_up_medicaid_if_eligible: true + takes_up_ssi_if_eligible: true marital_units: marital_unit_1: - members: - - head + members: [person1] tax_units: tax_unit: - members: - - head - first_home_mortgage_balance: - '2026': 93000.0 - takes_up_aca_if_eligible: - '2026': true - takes_up_dc_ptc: - '2026': true - takes_up_eitc: - '2026': true - would_file_if_eligible_for_refundable_credit: - '2026': true - would_file_taxes_voluntarily: - '2026': true + members: [person1] + first_home_mortgage_balance: 93_000 + takes_up_aca_if_eligible: true + takes_up_dc_ptc: true + takes_up_eitc: true + would_file_if_eligible_for_refundable_credit: true + would_file_taxes_voluntarily: true spm_units: spm_unit: - members: - - head - spm_unit_energy_subsidy: - '2026': 2000.0 - takes_up_snap_if_eligible: - '2026': true + members: [person1] + spm_unit_energy_subsidy: 2_000 + takes_up_snap_if_eligible: true families: family: - members: - - head + members: [person1] households: household: - members: - - head - state_code: - '2026': PA - household_vehicles_value: - '2026': 3570.0 + members: [person1] + state_code: PA + household_vehicles_value: 3_570 output: - snap: 3270 -- name: PolicyBench scenario_100 MT household receives SNAP without the heat-and-eat - SUA + snap: 3_270 + +- name: Case 2, PolicyBench scenario_100 MT household receives SNAP without the heat-and-eat SUA. period: 2026 absolute_error_margin: 0.01 input: people: - head: - age: - '2026': 46 - employment_income_before_lsr: - '2026': 5914.720703125 - bank_account_assets: - '2026': 2800.0 - employer_sponsored_insurance_premiums: - '2026': 20643.525390625 - fsla_overtime_premium: - '2026': 709.7664794921875 - has_esi: - '2026': true - health_insurance_premiums_without_medicare_part_b: - '2026': 564.0 - hourly_wage: - '2026': 2.319999933242798 - hours_worked_last_week: - '2026': 55.0 - is_disabled: - '2026': true - is_tax_unit_head: - '2026': true - is_tax_unit_spouse: - '2026': false - other_health_insurance_premiums: - '2026': 564.0 - other_medical_expenses: - '2026': 500.0 - over_the_counter_health_expenses: - '2026': 480.0 - roth_401k_contributions_desired: - '2026': 681.0 - roth_ira_contributions_desired: - '2026': 279.67999267578125 - traditional_401k_contributions_desired: - '2026': 3859.0 - traditional_ira_contributions_desired: - '2026': 180.32000732421875 - weekly_hours_worked_before_lsr: - '2026': 55.0 - takes_up_medicaid_if_eligible: - '2026': true - takes_up_ssi_if_eligible: - '2026': true - child1: - age: - '2026': 6 - employment_income_before_lsr: - '2026': 0.0 - has_esi: - '2026': true - is_tax_unit_head: - '2026': false - is_tax_unit_spouse: - '2026': false - takes_up_medicaid_if_eligible: - '2026': true - takes_up_ssi_if_eligible: - '2026': true - child2: - age: - '2026': 5 - employment_income_before_lsr: - '2026': 0.0 - is_tax_unit_head: - '2026': false - is_tax_unit_spouse: - '2026': false - other_medical_expenses: - '2026': 500.0 - takes_up_medicaid_if_eligible: - '2026': true - takes_up_ssi_if_eligible: - '2026': true + person1: + age: 46 + employment_income_before_lsr: 5_914.720703125 + bank_account_assets: 2_800 + employer_sponsored_insurance_premiums: 20_643.525390625 + fsla_overtime_premium: 709.7664794921875 + has_esi: true + health_insurance_premiums_without_medicare_part_b: 564 + hourly_wage: 2.319999933242798 + hours_worked_last_week: 55 + is_disabled: true + is_tax_unit_head: true + is_tax_unit_spouse: false + other_health_insurance_premiums: 564 + other_medical_expenses: 500 + over_the_counter_health_expenses: 480 + roth_401k_contributions_desired: 681 + roth_ira_contributions_desired: 279.67999267578125 + traditional_401k_contributions_desired: 3_859 + traditional_ira_contributions_desired: 180.32000732421875 + weekly_hours_worked_before_lsr: 55 + takes_up_medicaid_if_eligible: true + takes_up_ssi_if_eligible: true + person2: + age: 6 + employment_income_before_lsr: 0 + has_esi: true + is_tax_unit_head: false + is_tax_unit_spouse: false + takes_up_medicaid_if_eligible: true + takes_up_ssi_if_eligible: true + person3: + age: 5 + employment_income_before_lsr: 0 + is_tax_unit_head: false + is_tax_unit_spouse: false + other_medical_expenses: 500 + takes_up_medicaid_if_eligible: true + takes_up_ssi_if_eligible: true marital_units: marital_unit_1: - members: - - head + members: [person1] marital_unit_2: - members: - - child1 + members: [person2] marital_unit_3: - members: - - child2 + members: [person3] tax_units: tax_unit: - members: - - head - - child1 - - child2 - takes_up_aca_if_eligible: - '2026': true - takes_up_dc_ptc: - '2026': true - takes_up_eitc: - '2026': true - would_file_if_eligible_for_refundable_credit: - '2026': true - would_file_taxes_voluntarily: - '2026': true + members: [person1, person2, person3] + takes_up_aca_if_eligible: true + takes_up_dc_ptc: true + takes_up_eitc: true + would_file_if_eligible_for_refundable_credit: true + would_file_taxes_voluntarily: true spm_units: spm_unit: - members: - - head - - child1 - - child2 - takes_up_snap_if_eligible: - '2026': true + members: [person1, person2, person3] + takes_up_snap_if_eligible: true families: family: - members: - - head - - child1 - - child2 + members: [person1, person2, person3] households: household: - members: - - head - - child1 - - child2 - state_code: - '2026': MT - household_vehicles_value: - '2026': 1980.88232421875 + members: [person1, person2, person3] + state_code: MT + household_vehicles_value: 1_980.88232421875 output: - snap: 7002 + snap: 7_002 diff --git a/policyengine_us/tests/policy/baseline/partners/amplifi/2026.yaml b/policyengine_us/tests/policy/baseline/partners/amplifi/2026.yaml index 840cbe95b4f..0c4d2523093 100644 --- a/policyengine_us/tests/policy/baseline/partners/amplifi/2026.yaml +++ b/policyengine_us/tests/policy/baseline/partners/amplifi/2026.yaml @@ -260,7 +260,7 @@ spm_unit: ca_tanf: 0.0 ca_tanf_eligible: false - snap: 1917.0 + snap: 1506.0 is_snap_eligible: true lifeline: 228.0 is_lifeline_eligible: true 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 d01e8998b9c..da65546cf34 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 @@ -280,7 +280,7 @@ # 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: 2172.0 + snap: 2160.0 is_snap_eligible: true - name: snap_ca_head_immigration_undocumented_calworks_interaction (ca) period: 2026 @@ -346,5 +346,5 @@ # 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: 3009.0 + snap: 2613.0 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 f77ac275631..2a1aef1730b 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 @@ -206,5 +206,5 @@ output: spm_units: spm_unit: - snap: 3825.0 + snap: 2553.0 is_snap_eligible: true diff --git a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/signatures/ca.yaml b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/signatures/ca.yaml index d696fec62b2..ba2c6a460ad 100644 --- a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/signatures/ca.yaml +++ b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/signatures/ca.yaml @@ -1124,7 +1124,7 @@ la_general_relief: 0.0 la_general_relief_eligible: false lifeline: 228.0 - snap: 7956.0 + snap: 7722.0 people: head: ca_ala_general_assistance_eligible_person: false @@ -1706,7 +1706,7 @@ la_general_relief: 0.0 la_general_relief_eligible: false lifeline: 228.0 - snap: 7956.0 + snap: 7722.0 people: head: ca_ala_general_assistance_eligible_person: false @@ -1903,11 +1903,11 @@ ca_tanf: 0.0 ca_tanf_eligible: false is_lifeline_eligible: true - is_snap_eligible: true + is_snap_eligible: false la_general_relief: 0.0 la_general_relief_eligible: false lifeline: 228.0 - snap: 3729.0 + snap: 0.0 people: head: ca_ala_general_assistance_eligible_person: false @@ -2092,7 +2092,7 @@ la_general_relief: 0.0 la_general_relief_eligible: false lifeline: 228.0 - snap: 7956.0 + snap: 7722.0 people: head: ca_ala_general_assistance_eligible_person: false @@ -2486,7 +2486,7 @@ la_general_relief: 0.0 la_general_relief_eligible: false lifeline: 228.0 - snap: 7956.0 + snap: 7722.0 people: head: ca_ala_general_assistance_eligible_person: false @@ -2674,11 +2674,11 @@ ca_tanf: 0.0 ca_tanf_eligible: false is_lifeline_eligible: true - is_snap_eligible: true + is_snap_eligible: false la_general_relief: 0.0 la_general_relief_eligible: false lifeline: 228.0 - snap: 3729.0 + snap: 0.0 people: head: ca_ala_general_assistance_eligible_person: false @@ -2874,11 +2874,11 @@ ca_tanf: 0.0 ca_tanf_eligible: false is_lifeline_eligible: true - is_snap_eligible: true + is_snap_eligible: false la_general_relief: 0.0 la_general_relief_eligible: false lifeline: 228.0 - snap: 3729.0 + snap: 0.0 people: head: ca_ala_general_assistance_eligible_person: false @@ -3450,7 +3450,7 @@ la_general_relief: 0.0 la_general_relief_eligible: false lifeline: 228.0 - snap: 7956.0 + snap: 7722.0 people: head: ca_ala_general_assistance_eligible_person: false @@ -4795,11 +4795,11 @@ ca_tanf: 0.0 ca_tanf_eligible: false is_lifeline_eligible: true - is_snap_eligible: true + is_snap_eligible: false la_general_relief: 0.0 la_general_relief_eligible: false lifeline: 228.0 - snap: 3729.0 + snap: 0.0 people: head: ca_ala_general_assistance_eligible_person: false diff --git a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/signatures/federal.yaml b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/signatures/federal.yaml index ec2c49094d1..eb5e62d1ebb 100644 --- a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/signatures/federal.yaml +++ b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/signatures/federal.yaml @@ -352,8 +352,8 @@ output: spm_units: spm_unit: - is_snap_eligible: true - snap: 3729.0 + is_snap_eligible: false + snap: 0.0 people: head: is_wic_eligible: false @@ -514,8 +514,8 @@ output: spm_units: spm_unit: - is_snap_eligible: true - snap: 3729.0 + is_snap_eligible: false + snap: 0.0 people: head: is_wic_eligible: false @@ -617,8 +617,8 @@ output: spm_units: spm_unit: - is_snap_eligible: true - snap: 3729.0 + is_snap_eligible: false + snap: 0.0 people: head: is_wic_eligible: false @@ -845,7 +845,7 @@ spm_units: spm_unit: is_snap_eligible: true - snap: 5802.0 + snap: 4659.0 people: head: is_wic_eligible: false @@ -953,7 +953,7 @@ spm_units: spm_unit: is_snap_eligible: true - snap: 5802.0 + snap: 4659.0 people: head: is_wic_eligible: false @@ -1148,8 +1148,8 @@ output: spm_units: spm_unit: - is_snap_eligible: true - snap: 3729.0 + is_snap_eligible: false + snap: 0.0 people: head: is_wic_eligible: false @@ -1348,8 +1348,8 @@ output: spm_units: spm_unit: - is_snap_eligible: true - snap: 3729.0 + is_snap_eligible: false + snap: 0.0 people: head: is_wic_eligible: false @@ -1649,8 +1649,8 @@ output: spm_units: spm_unit: - is_snap_eligible: true - snap: 3729.0 + is_snap_eligible: false + snap: 0.0 people: head: is_wic_eligible: false @@ -1760,8 +1760,8 @@ output: spm_units: spm_unit: - is_snap_eligible: true - snap: 3729.0 + is_snap_eligible: false + snap: 0.0 people: head: is_wic_eligible: false diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_utility_allowance_type.py b/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_utility_allowance_type.py index 678b76fe7f1..b0b74d01125 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_utility_allowance_type.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/shelter/snap_utility_allowance_type.py @@ -20,8 +20,6 @@ class snap_utility_allowance_type(Variable): "https://www.ecfr.gov/current/title-7/section-273.9#p-273.9(d)(6)(iii)(A)(3)", "https://www.law.cornell.edu/uscode/text/7/2014#e_6_C_iv_I", "https://www.govinfo.gov/content/pkg/PLAW-119publ21/html/PLAW-119publ21.htm", - # PDF pages 1-3. - "https://fns-prod.azureedge.us/sites/default/files/resource-files/OBBB-LIHEAP-Implementation-Memo.pdf#page=1", ) def formula(spm_unit, period, parameters): From d511508923b7d1cfcb44b8bd7580c379106a0acb Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 22:36:10 -0400 Subject: [PATCH 06/10] Replace SNAP heat-and-eat grid with focused YAML coverage --- .../tests/core/test_snap_heat_and_eat.py | 197 +----------------- .../snap_utility_allowance_type.yaml | 100 +++++++++ .../snap_utility_allowance_type.yaml | 53 +++++ 3 files changed, 164 insertions(+), 186 deletions(-) diff --git a/policyengine_us/tests/core/test_snap_heat_and_eat.py b/policyengine_us/tests/core/test_snap_heat_and_eat.py index b8b085d8a46..23c6b672e67 100644 --- a/policyengine_us/tests/core/test_snap_heat_and_eat.py +++ b/policyengine_us/tests/core/test_snap_heat_and_eat.py @@ -1,192 +1,17 @@ -"""Reform compatibility invariants for the federal heat-and-eat restriction. +"""Check the legal-day boundary that monthly policy YAML cannot express.""" -YAML covers individual policy examples. This test compares a parameter reform -that restores the former rule against its baseline, across a vectorized grid. -It detects unintended changes to other states and expense-based eligibility, -and checks fallback against matched households with state deeming disabled. -Only one reform simulation and its baseline are constructed for all dates. -""" +from pathlib import Path -from itertools import product +from policyengine_core.parameters import load_parameter_file -import numpy as np -from policyengine_core.periods import instant -from policyengine_core.reforms import Reform -from policyengine_us import Simulation - - -# All SNAP-covered state codes: 50 states, DC, Guam, and the Virgin Islands. -# Other StateCode enum members do not have SNAP utility allowance parameters. -SNAP_STATES = tuple( - "AL AK AZ AR CA CO CT DE FL GA HI ID IL IN IA KS KY LA ME MD MA MI MN " - "MS MO MT NE NV NH NJ NM NY NC ND OH OK OR PA RI SC SD TN TX UT VT VA " - "WA WV WI WY DC GU VI".split() -) -PERIODS = ("2025-06", "2025-07", "2025-08", "2026-01") - - -class LegacyHeatAndEat(Reform): - def apply(self): - def modify(parameters): - parameters.gov.usda.snap.income.deductions.utility.heat_and_eat_requires_elderly_disabled.update( - start=instant("2025-07-04"), - stop=instant("2100-12-31"), - value=False, - ) - return parameters - - self.modify_parameters(modify) - - -def _household_grid(): - situation = { - entity: {} - for entity in ( - "people", - "marital_units", - "tax_units", - "spm_units", - "families", - "households", +def test_heat_and_eat_requirement_takes_effect_on_enactment_date(): + requirement = load_parameter_file( + str( + Path(__file__).resolve().parents[2] + / "parameters/gov/usda/snap/income/deductions/utility" + / "heat_and_eat_requires_elderly_disabled.yaml" ) - } - facts = [] - fallback_indices = {} - - def add(state, status, heating, bills, reference=False): - index = len(facts) - key = f"case_{index}" - members = [key] - - def annual(value): - return {"2025": value, "2026": value} - - person = { - "age": annual(60 if status == "elderly" else 40), - # The broad disability flag alone must not qualify under USDA rules. - "is_disabled": annual(True), - } - if status == "ssdi": - person["social_security_disability"] = annual(1_200) - situation["people"][key] = person - for entity in ("marital_units", "tax_units", "families"): - situation[entity][key] = {"members": members} - spm_unit = {"members": members} - if heating: - spm_unit["heating_cooling_expense"] = annual(1_200) - if bills == "phone": - spm_unit["phone_expense"] = annual(360) - elif bills == "two": - spm_unit["pre_subsidy_electricity_expense"] = annual(600) - spm_unit["water_expense"] = annual(300) - if reference: - fallback_indices[(state, bills)] = index - situation["spm_units"][key] = spm_unit - situation["households"][key] = { - "members": members, - "state_code": annual(state), - } - facts.append((state, status, heating, bills, reference)) - - for state, status, heating, bills in product( - SNAP_STATES, - ("ordinary", "elderly", "ssdi"), - (False, True), - ("none", "phone", "two"), - ): - add(state, status, heating, bills) - for state, bills in product(SNAP_STATES, ("none", "phone", "two")): - add(state, "ordinary", False, bills, reference=True) - controls = np.array( - [fallback_indices[(state, bills)] for state, _, _, bills, _ in facts] ) - return situation, facts, controls - - -def test_heat_and_eat_reform_preserves_other_utility_allowance_paths(): - situation, facts, controls = _household_grid() - legacy = Simulation(situation=situation, reform=LegacyHeatAndEat) - fixed = legacy.baseline - parameters = fixed.tax_benefit_system.parameters - - def path(date): - return parameters(date).gov.usda.snap.income.deductions.utility - - assert not bool(path("2025-07-03").heat_and_eat_requires_elderly_disabled) - assert bool(path("2025-07-04").heat_and_eat_requires_elderly_disabled) - - ordinary = np.array([status == "ordinary" for _, status, _, _, _ in facts]) - heating = np.array([heat for _, _, heat, _, _ in facts]) - bill_style = np.array([bills for _, _, _, bills, _ in facts]) - reference = np.array([control for _, _, _, _, control in facts]) - - for month in PERIODS: - state_deeming = fixed.calculate( - "snap_state_using_standard_utility_allowance", month - ).astype(bool) - np.testing.assert_array_equal( - state_deeming, - legacy.calculate("snap_state_using_standard_utility_allowance", month), - ) - # Derive matched expense-only controls from the existing formulas. - # Sparse inputs default the entire vector to False, so preserve the - # computed state option for every primary case in a full input array. - # Set this before calculating dependent utility types or amounts. - state_deeming[reference] = False - for simulation in (fixed, legacy): - simulation.set_input( - "snap_state_using_standard_utility_allowance", - month, - state_deeming.copy(), - ) - legacy_type = legacy.calculate( - "snap_utility_allowance_type", month - ).decode_to_str() - fixed_type = fixed.calculate( - "snap_utility_allowance_type", month - ).decode_to_str() - legacy_allowance = legacy.calculate("snap_utility_allowance", month) - fixed_allowance = fixed.calculate("snap_utility_allowance", month) - # Check the fixture's SNAP status independently of the allowance so - # eligibility or disability-definition changes cannot hide the defect. - np.testing.assert_array_equal( - fixed.calculate("has_snap_elderly_disabled_member", month), ~ordinary - ) - np.testing.assert_array_equal( - fixed.calculate("is_snap_excluded_member", month), False - ) - - # The former rule grants SUA whenever the state deems a heating cost. - np.testing.assert_array_equal(legacy_type[state_deeming], "SUA") - if month in ("2025-06", "2025-07"): - # July is evaluated at the start of the month; the legal parameter - # changes on July 4, making August the first full affected month. - np.testing.assert_array_equal(fixed_type, legacy_type) - np.testing.assert_allclose(fixed_allowance, legacy_allowance) - continue - - affected = state_deeming & ordinary & ~heating - assert affected.any() - preserved = ~affected - np.testing.assert_array_equal(fixed_type[preserved], legacy_type[preserved]) - np.testing.assert_allclose( - fixed_allowance[preserved], legacy_allowance[preserved] - ) - - # Affected households match expense-only controls, including states - # with and without a two-utility LUA. Removing deeming never raises SUA. - np.testing.assert_array_equal( - fixed_type[affected], fixed_type[controls[affected]] - ) - np.testing.assert_allclose( - fixed_allowance[affected], fixed_allowance[controls[affected]] - ) - np.testing.assert_array_equal( - fixed_type[affected & (bill_style == "none")], "NONE" - ) - np.testing.assert_array_equal( - fixed_type[affected & (bill_style == "phone")], "IUA" - ) - assert set(fixed_type[affected]) == {"NONE", "IUA", "LUA"} - assert np.all(fixed_allowance <= legacy_allowance + 1e-6) + assert requirement("2025-07-03") is False + assert requirement("2025-07-04") is True diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml index 27a580b6603..5508245ea88 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap_utility_allowance_type.yaml @@ -78,6 +78,8 @@ output: has_snap_elderly_disabled_member: true snap_utility_allowance_type: SUA + snap_utility_allowance: + 2026-01: 799 - name: General disability alone does not meet the USDA definition period: 2026 @@ -98,6 +100,8 @@ output: has_snap_elderly_disabled_member: false snap_utility_allowance_type: SUA + snap_utility_allowance: + 2026-01: 857 - name: Two non-heating utility bills qualify for Pennsylvania LUA period: 2026 @@ -148,3 +152,99 @@ has_usda_elderly_disabled: true has_snap_elderly_disabled_member: false snap_utility_allowance_type: NONE + +# Each adjacent pair has the same expenses, with state deeming enabled +# in the first household and disabled in its expense-only control. +# The YAML builder gives each person a separate SPM unit and household. +- name: Restricted heat-and-eat households match expense-only utility controls + period: 2026 + input: + age: + 2025: [40, 40, 40, 40, 40, 40, 40, 40] + 2026: [40, 40, 40, 40, 40, 40, 40, 40] + weekly_hours_worked_before_lsr: + 2025: [30, 30, 30, 30, 30, 30, 30, 30] + 2026: [30, 30, 30, 30, 30, 30, 30, 30] + state_code: + 2025: [PA, PA, PA, PA, PA, PA, MI, MI] + 2026: [PA, PA, PA, PA, PA, PA, MI, MI] + # No bills, phone only, PA electricity + water, MI electricity + water. + phone_expense: + 2025: [0, 0, 360, 360, 0, 0, 0, 0] + 2026: [0, 0, 360, 360, 0, 0, 0, 0] + pre_subsidy_electricity_expense: + 2025: [0, 0, 0, 0, 600, 600, 600, 600] + 2026: [0, 0, 0, 0, 600, 600, 600, 600] + water_expense: + 2025: [0, 0, 0, 0, 300, 300, 300, 300] + 2026: [0, 0, 0, 0, 300, 300, 300, 300] + # Full vectors preserve the state option in primary households. + snap_state_using_standard_utility_allowance: + 2025-08: [true, false, true, false, true, false, true, false] + 2026-01: [true, false, true, false, true, false, true, false] + output: + has_snap_elderly_disabled_member: + 2025-08: [false, false, false, false, false, false, false, false] + 2026-01: [false, false, false, false, false, false, false, false] + is_snap_excluded_member: + 2025-08: [false, false, false, false, false, false, false, false] + 2026-01: [false, false, false, false, false, false, false, false] + snap_utility_allowance_type: + 2025-08: [NONE, NONE, IUA, IUA, LUA, LUA, IUA, IUA] + 2026-01: [NONE, NONE, IUA, IUA, LUA, LUA, IUA, IUA] + snap_utility_allowance: + # FY2025: PA phone $34, LUA $402; MI $176 electric + $115 water. + 2025-08: [0, 0, 34, 34, 402, 402, 291, 291] + # FY2026: PA phone $107, LUA $488; MI $181 electric + $119 water. + 2026-01: [0, 0, 107, 107, 488, 488, 300, 300] + +# Dotted parameter inputs create an inline Core reform. Keep reform +# behavior in YAML rather than constructing a 53-state Python grid. +- name: Restoring the former heat-and-eat rule preserves expense and qualifying-member paths + period: 2026 + input: + gov.usda.snap.income.deductions.utility.heat_and_eat_requires_elderly_disabled: false + # PA: no bills, phone, two bills, heating, elderly, SSDI; then TX elderly. + age: + 2025: [40, 40, 40, 40, 60, 40, 60] + 2026: [40, 40, 40, 40, 60, 40, 60] + weekly_hours_worked_before_lsr: + 2025: [30, 30, 30, 30, 30, 30, 30] + 2026: [30, 30, 30, 30, 30, 30, 30] + state_code: + 2025: [PA, PA, PA, PA, PA, PA, TX] + 2026: [PA, PA, PA, PA, PA, PA, TX] + social_security_disability: + 2025: [0, 0, 0, 0, 0, 1_200, 0] + 2026: [0, 0, 0, 0, 0, 1_200, 0] + heating_cooling_expense: + 2025: [0, 0, 0, 1_200, 0, 0, 0] + 2026: [0, 0, 0, 1_200, 0, 0, 0] + phone_expense: + 2025: [0, 360, 0, 0, 0, 0, 0] + 2026: [0, 360, 0, 0, 0, 0, 0] + pre_subsidy_electricity_expense: + 2025: [0, 0, 600, 0, 0, 0, 0] + 2026: [0, 0, 600, 0, 0, 0, 0] + water_expense: + 2025: [0, 0, 300, 0, 0, 0, 0] + 2026: [0, 0, 300, 0, 0, 0, 0] + output: + snap_state_using_standard_utility_allowance: + 2025-07: [true, true, true, true, true, true, false] + 2025-08: [true, true, true, true, true, true, false] + 2026-01: [true, true, true, true, true, true, false] + has_snap_elderly_disabled_member: + 2025-08: [false, false, false, false, true, true, true] + 2026-01: [false, false, false, false, true, true, true] + is_snap_excluded_member: + 2025-08: [false, false, false, false, false, false, false] + 2026-01: [false, false, false, false, false, false, false] + snap_utility_allowance_type: + 2025-07: [SUA, SUA, SUA, SUA, SUA, SUA, NONE] + 2025-08: [SUA, SUA, SUA, SUA, SUA, SUA, NONE] + 2026-01: [SUA, SUA, SUA, SUA, SUA, SUA, NONE] + snap_utility_allowance: + 2025-07: [758, 758, 758, 758, 758, 758, 0] + 2025-08: [758, 758, 758, 758, 758, 758, 0] + 2026-01: [857, 857, 857, 857, 857, 857, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_utility_allowance_type.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_utility_allowance_type.yaml index 38f11db742c..6ead2af8600 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_utility_allowance_type.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_utility_allowance_type.yaml @@ -309,3 +309,56 @@ # households, so the SUA applies before the two-utility LUA count # (legacy plus water) is ever reached. snap_utility_allowance_type: SUA + +- name: Michigan heat-and-eat household with two non-heating bills receives IUA because Michigan has no LUA + period: 2026-01 + input: + age: + 2026: 40 + weekly_hours_worked_before_lsr: + 2026: 30 + state_code: + 2026: MI + pre_subsidy_electricity_expense: + 2026: 600 + water_expense: + 2026: 300 + output: + has_snap_elderly_disabled_member: false + is_snap_excluded_member: false + snap_state_using_standard_utility_allowance: true + has_heating_cooling_expense: false + count_distinct_utility_expenses: 2 + snap_utility_allowance_type: IUA + snap_utility_allowance: 300 # $181 electricity + $119 water. + +- name: Pennsylvania SPM units sharing a household qualify for heat-and-eat independently + period: 2026-01 + input: + people: + elder: + age: + 2026: 60 + weekly_hours_worked_before_lsr: + 2026: 30 + adult: + age: + 2026: 40 + weekly_hours_worked_before_lsr: + 2026: 30 + spm_units: + elderly_unit: + members: [elder] + ordinary_unit: + members: [adult] + households: + household: + members: [elder, adult] + state_code: + 2026: PA + output: + has_snap_elderly_disabled_member: [true, false] + is_snap_excluded_member: [false, false] + snap_state_using_standard_utility_allowance: [true, true] + snap_utility_allowance_type: [SUA, NONE] + snap_utility_allowance: [857, 0] From cd2b83929b503c59d6922000cd0cffad9154de4c Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 05:48:24 -0400 Subject: [PATCH 07/10] Correct Massachusetts SNAP integration expectations after heat-and-eat restriction --- .../eligibility/immigration/integration.yaml | 21 +++++++++++-------- .../ineligible_members/integration.yaml | 8 ++++++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/integration.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/integration.yaml index 8a4973b49ee..8ff88c98a85 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/integration.yaml @@ -130,15 +130,18 @@ snap_earned_income: 1_333.33 snap_earned_income_deduction: 266.67 snap_standard_deduction: 209 - # Massachusetts standard utility allowance $914 - 50% of the $857.67 - # net income before the shelter deduction ($428.83) = $485.17. - snap_utility_allowance: 914 - snap_excess_shelter_expense_deduction: 485.17 - # $857.67 - $485.17 = $372.50, rounded to $373. - snap_net_income: 373 - # 30% of $373 = $111.90, rounded up to $112; $546 - $112 = $434. - snap_expected_contribution: 112 - snap: 434 + # No utility costs or elderly/disabled member: no actual-cost SUA or + # Massachusetts H-EAT SUA under P.L. 119-21 section 10103(a). + # https://www.govinfo.gov/content/pkg/PLAW-119publ21/html/PLAW-119publ21.htm + # https://dtapolicyguide.qa.eohhs.mass.gov/SNAP/ExpensesDeductions/HEAT.htm + snap_utility_allowance: 0 + # No housing costs or SUA, so there is no excess shelter deduction. + snap_excess_shelter_expense_deduction: 0 + # $1,333.33 - $266.67 - $209 = $857.67, rounded to $858. + snap_net_income: 858 + # 30% of $858 = $257.40, rounded up to $258; $546 - $258 = $288. + snap_expected_contribution: 258 + snap: 288 - name: Case 4, LPR earner inside the waiting period is prorated under both tests in a hybrid gross-test state. period: 2026-01 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 index 1bf45126855..46a7f70a59a 100644 --- 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 @@ -108,7 +108,13 @@ snap_unit_size: 2 # Massachusetts counts all of the undocumented member's income. snap_earned_income: 2_000 - snap: 194 + # No utility costs or elderly/disabled member: no actual-cost SUA or + # Massachusetts H-EAT SUA under P.L. 119-21 section 10103(a). + # https://www.govinfo.gov/content/pkg/PLAW-119publ21/html/PLAW-119publ21.htm + # https://dtapolicyguide.qa.eohhs.mass.gov/SNAP/ExpensesDeductions/HEAT.htm + # $2,000 - $400 earned income deduction - $209 standard deduction + # = $1,391; $546 - ceil(30% x $1,391) = $546 - $418 = $128. + snap: 128 - name: Case 5, undocumented earner in a hybrid gross-test state. period: 2026-01 From 6e30cef6a01b27522aa3fbba1d00f7402d3355a9 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 06:13:52 -0400 Subject: [PATCH 08/10] Preserve California small-household MCE eligibility without heat-and-eat SUA --- .../hub-pb-snap-heat-eat-1009.fixed.md | 1 + .../net_income_exempt_household_size.yaml | 17 +++ .../meets_tanf_non_cash_net_income_test.yaml | 11 +- .../income/deductions/heat_and_eat/snap.yaml | 104 ++++++++++++++++++ .../meets_tanf_non_cash_net_income_test.py | 9 ++ 5 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/bbce/net_income_exempt_household_size.yaml diff --git a/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md b/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md index 74cb41e2fbc..d47339a7dc1 100644 --- a/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md +++ b/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md @@ -1 +1,2 @@ Restrict SNAP heat-and-eat standard utility allowances to households with an elderly or disabled SNAP member under P.L. 119-21, while preserving eligibility based on actual heating or cooling expenses. +Preserve California modified categorical eligibility and minimum SNAP allotments for one- and two-person households above the net income limit when they lose the heat-and-eat allowance. diff --git a/policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/bbce/net_income_exempt_household_size.yaml b/policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/bbce/net_income_exempt_household_size.yaml new file mode 100644 index 00000000000..3fbe74e1cea --- /dev/null +++ b/policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/bbce/net_income_exempt_household_size.yaml @@ -0,0 +1,17 @@ +description: California exempts modified categorically eligible CalFresh households with up to this many members from the net income limit, preserving their minimum allotment. + +values: + 0000-01-01: 0 + 2011-02-01: 2 + +metadata: + unit: person + period: month + label: California CalFresh MCE net income exemption household size + reference: + - title: CDSS All County Letter 11-11 + href: https://www.cdss.ca.gov/lettersnotices/entres/getinfo/acl/2011/11-11.pdf#page=1 + - title: CDSS All County Letter 12-62 + href: https://www.cdss.ca.gov/lettersnotices/entres/getinfo/acl/2012/12-62.pdf#page=4 + - title: CDSS All County Welfare Directors Letter, June 30, 2014 + href: https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf#page=3 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.yaml index 8b0d0459554..3b1eb370f8f 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.yaml @@ -1,11 +1,12 @@ -- name: Default household meets California's BBCE net income test (100%). +- name: Default small household is exempt from California's BBCE net income test. period: 2022 output: meets_tanf_non_cash_net_income_test: true -- name: California household with net income exactly at the standard meets the net income test. +- name: California three-person household with net income exactly at the standard meets the net income test. period: 2022 input: + snap_unit_size: 3 snap_fpg: 12_000 snap_net_income: 12_000 output: @@ -13,18 +14,20 @@ # per month is exactly at it. meets_tanf_non_cash_net_income_test: true -- name: California household with net income above the standard fails the net income test. +- name: California three-person household with net income above the standard fails the net income test. period: 2022 input: + snap_unit_size: 3 snap_fpg: 12_000 snap_net_income: 12_120 output: # Net income of 1,010 per month exceeds the 1,000 standard. meets_tanf_non_cash_net_income_test: false -- name: California household at the rounded-up whole-dollar standard meets the net income test. +- name: California three-person household at the rounded-up whole-dollar standard meets the net income test. period: 2022 input: + snap_unit_size: 3 # Monthly poverty guideline of 2,220.83 yields a whole-dollar standard # of ceil(2,220.83) = 2,221, so a rounded net income of exactly 2,221 # still passes; a raw ratio comparison (2,221 / 2,220.83 > 1) would diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml index 9da9145379e..20b7fbc88b8 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml @@ -120,3 +120,107 @@ household_vehicles_value: 1_980.88232421875 output: snap: 7_002 + +- name: California one-person MCE household keeps the minimum allotment without heat-and-eat SUA. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + head: + age: 44 + employment_income: 36_275.59 + employment_income_before_lsr: 36_275.59 + weekly_hours_worked_before_lsr: 40 + child_support_expense: 5_200 + pre_subsidy_rent: 10_560 + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: CA + output: + # P.L. 119-21 section 10103(a): no elderly/disabled member or actual + # heating/cooling expense, so the SUAS payment cannot confer SUA. + # https://www.cdss.ca.gov/Portals/9/Additional-Resources/Letters-and-Notices/ACLs/2025/25-68.pdf#page=6 + has_snap_elderly_disabled_member: false + has_heating_cooling_expense: false + snap_utility_allowance_type: NONE + snap_utility_allowance: 0 + # Gross ($36,275.59 - $5,200) / 12 = $2,589.63 < $2,610 MCE limit. + # Net $2,589.63 - $604.59 - $209 = $1,776.04; $880 rent is below + # half that amount ($888.02), so there is no excess shelter deduction. + snap_gross_income: 2_589.63 + snap_excess_shelter_expense_deduction: 0 + snap_net_income: 1_776 + meets_snap_net_income_test: false + # CDSS ACWDL June 30, 2014, page 3: one/two-person MCE households + # retain the minimum even above the net limit. $298 - ceil(0.3 x + # $1,776) = -$235, so the $24 minimum applies independently of SUA. + # https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf#page=3 + meets_tanf_non_cash_net_income_test: true + meets_snap_categorical_eligibility: true + is_snap_eligible: true + snap: 24 + +- name: California two-person MCE household keeps the minimum allotment without heat-and-eat SUA. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 35 + employment_income: 40_000 + employment_income_before_lsr: 40_000 + weekly_hours_worked_before_lsr: 40 + child: + age: 8 + spm_units: + spm_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: CA + output: + snap_unit_size: 2 + snap_utility_allowance_type: NONE + snap_utility_allowance: 0 + # $3,333.33 gross < $3,526 MCE limit. Net is $3,333.33 - $666.67 + # - $209 = $2,457.67 -> $2,458, above the $1,763 net standard. + snap_net_income: 2_458 + meets_snap_net_income_test: false + # $546 - ceil(0.3 x $2,458) = -$192, floored to $24 under the + # same one/two-person MCE rule as the preceding case. + # https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf#page=3 + meets_snap_categorical_eligibility: true + is_snap_eligible: true + snap: 24 + +- name: California small-household MCE net exemption still requires the gross income test. + period: 2026-01 + input: + people: + head: + age: 44 + employment_income: 32_000 + employment_income_before_lsr: 32_000 + weekly_hours_worked_before_lsr: 40 + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: CA + output: + # $32,000 / 12 = $2,666.67 > the $2,610 one-person MCE gross limit. + # CDSS ACWDL June 30, 2014, pages 1-3: the minimum allotment rule + # applies only after MCE has been conferred. + # https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf + meets_tanf_non_cash_net_income_test: true + meets_tanf_non_cash_gross_income_test: false + meets_snap_categorical_eligibility: false + is_snap_eligible: false + snap: 0 diff --git a/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.py b/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.py index 280c64d16bc..7abd7d9cb24 100644 --- a/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.py +++ b/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.py @@ -13,6 +13,7 @@ class meets_tanf_non_cash_net_income_test(Variable): reference = ( "https://www.law.cornell.edu/cfr/text/7/273.2#j_2", "https://www.law.cornell.edu/cfr/text/7/273.9#a_3_ii", + "https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf#page=3", ) def formula(spm_unit, period, parameters): @@ -24,6 +25,14 @@ def formula(spm_unit, period, parameters): net_limit_applies = where( hheod, applies.hheod[state], applies.non_hheod[state] ).astype(bool) + # CDSS ACWDL June 30, 2014: one- and two-person MCE households + # retain the minimum allotment even above the net income standard. + # Gross income and the other BBCE conditions still apply. + ca = parameters(period).gov.states.ca.cdss.snap.eligibility.bbce + ca_small_household = (state == "CA") & ( + spm_unit("snap_unit_size", period) <= ca.net_income_exempt_household_size + ) + net_limit_applies = net_limit_applies & ~ca_small_household net_income = spm_unit("snap_net_income", period) net_limit = parameters(period).gov.usda.snap.income.limit.net # The state's BBCE net test uses the federal SNAP net standard: the From 71b1fe550f4fb2b7951b410a3380f543c98d40be Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 09:46:11 -0400 Subject: [PATCH 09/10] Exclude SNAP benefits from California CARE and FERA income --- .../hub-pb-snap-heat-eat-1009.fixed.md | 1 + .../gov/states/ca/cpuc/income_sources.yaml | 10 ++++-- .../ca/cpuc/ca_cpuc_countable_income.yaml | 32 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md b/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md index d47339a7dc1..266459ba1f2 100644 --- a/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md +++ b/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md @@ -1,2 +1,3 @@ Restrict SNAP heat-and-eat standard utility allowances to households with an elderly or disabled SNAP member under P.L. 119-21, while preserving eligibility based on actual heating or cooling expenses. Preserve California modified categorical eligibility and minimum SNAP allotments for one- and two-person households above the net income limit when they lose the heat-and-eat allowance. +Exclude SNAP benefits from California CARE and FERA countable income under 7 U.S.C. 2017(b). diff --git a/policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml b/policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml index 066d595d891..aacf6a2b6e7 100644 --- a/policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml +++ b/policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml @@ -11,7 +11,11 @@ description: >- # employment-related, non-cash income. The same footnote appears on the # CPUC-filed tariff sheet referenced below. # -# Housing subsidies are the one exception. D.14-08-030, issued August 14, +# SNAP benefits are excluded in both dated lists: 7 U.S.C. 2017(b) prohibits +# treating their value as income or resources under any federal, state or +# local law, including California's CARE and FERA programs. +# +# Separately, D.14-08-030, issued August 14, # 2014, holds in Section 6.2 that the income definition remains unchanged # except that housing subsidies will not be considered income, and Ordering # Paragraph 40(3) adopts "Housing subsidies are not counted as income". The @@ -67,7 +71,6 @@ values: # Public assistance payments - tanf - ssi - - snap - wic # Social Security - social_security @@ -110,7 +113,6 @@ values: # Public assistance payments - tanf - ssi - - snap - wic # Social Security - social_security @@ -137,6 +139,8 @@ metadata: period: year label: CA CPUC CARE/FERA income sources reference: + - title: 7 U.S.C. 2017(b), exclusion of SNAP benefits from income and resources + href: https://www.govinfo.gov/content/pkg/USCODE-2024-title7/html/USCODE-2024-title7-chap51-sec2017.htm - title: PG&E Form 01-9077, CARE/FERA Program Application (Rev. 6.26) href: https://www.pge.com/assets/pge/localized/en/docs/account/billing-and-assistance/care-fera-application.pdf - title: >- diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/cpuc/ca_cpuc_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/cpuc/ca_cpuc_countable_income.yaml index b95a58c4a11..f5786bf4c18 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/cpuc/ca_cpuc_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/cpuc/ca_cpuc_countable_income.yaml @@ -112,3 +112,35 @@ spm_unit_capped_housing_subsidy: 25 output: ca_cpuc_countable_income: 1_000 + +- name: SNAP is excluded before the housing assistance exclusion. + period: 2013 + input: + age: 40 + state_code: CA + employment_income: 1_000 + housing_assistance: 500 + snap: 1_200 + tanf: 0 + ssi: 0 + wic: 0 + output: + # $1,000 wages + $500 housing; SNAP is excluded under 7 U.S.C. 2017(b). + # https://www.govinfo.gov/content/pkg/USCODE-2024-title7/html/USCODE-2024-title7-chap51-sec2017.htm + ca_cpuc_countable_income: 1_500 + +- name: SNAP cannot push income at the CARE limit above income eligibility. + period: 2026 + input: + age: 40 + state_code: CA + employment_income: 43_280 + snap: 1_200 + ca_care_categorically_eligible: false + output: + # CARE uses the two-person poverty line for a one-person household: + # 200% * ($15,960 + $5,680) = $43,280. SNAP adds $0 to countable income + # under 7 U.S.C. 2017(b), so income remains exactly at the limit. + # https://www.govinfo.gov/content/pkg/USCODE-2024-title7/html/USCODE-2024-title7-chap51-sec2017.htm + ca_cpuc_countable_income: 43_280 + ca_care_income_eligible: true From e6818a33d59f1585e694fb427681a5dfcbc6212a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 20:22:43 -0400 Subject: [PATCH 10/10] Split California corrections from federal SNAP heat-and-eat change --- .../hub-pb-snap-heat-eat-1009.fixed.md | 2 - .../net_income_exempt_household_size.yaml | 17 --- .../gov/states/ca/cpuc/income_sources.yaml | 10 +- .../meets_tanf_non_cash_net_income_test.yaml | 11 +- .../ca/cpuc/ca_cpuc_countable_income.yaml | 32 ------ .../income/deductions/heat_and_eat/snap.yaml | 104 ------------------ .../snap/snap_child_support_integration.yaml | 14 ++- .../meets_tanf_non_cash_net_income_test.py | 9 -- 8 files changed, 18 insertions(+), 181 deletions(-) delete mode 100644 policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/bbce/net_income_exempt_household_size.yaml diff --git a/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md b/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md index 266459ba1f2..74cb41e2fbc 100644 --- a/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md +++ b/changelog.d/hub-pb-snap-heat-eat-1009.fixed.md @@ -1,3 +1 @@ Restrict SNAP heat-and-eat standard utility allowances to households with an elderly or disabled SNAP member under P.L. 119-21, while preserving eligibility based on actual heating or cooling expenses. -Preserve California modified categorical eligibility and minimum SNAP allotments for one- and two-person households above the net income limit when they lose the heat-and-eat allowance. -Exclude SNAP benefits from California CARE and FERA countable income under 7 U.S.C. 2017(b). diff --git a/policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/bbce/net_income_exempt_household_size.yaml b/policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/bbce/net_income_exempt_household_size.yaml deleted file mode 100644 index 3fbe74e1cea..00000000000 --- a/policyengine_us/parameters/gov/states/ca/cdss/snap/eligibility/bbce/net_income_exempt_household_size.yaml +++ /dev/null @@ -1,17 +0,0 @@ -description: California exempts modified categorically eligible CalFresh households with up to this many members from the net income limit, preserving their minimum allotment. - -values: - 0000-01-01: 0 - 2011-02-01: 2 - -metadata: - unit: person - period: month - label: California CalFresh MCE net income exemption household size - reference: - - title: CDSS All County Letter 11-11 - href: https://www.cdss.ca.gov/lettersnotices/entres/getinfo/acl/2011/11-11.pdf#page=1 - - title: CDSS All County Letter 12-62 - href: https://www.cdss.ca.gov/lettersnotices/entres/getinfo/acl/2012/12-62.pdf#page=4 - - title: CDSS All County Welfare Directors Letter, June 30, 2014 - href: https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml b/policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml index aacf6a2b6e7..066d595d891 100644 --- a/policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml +++ b/policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml @@ -11,11 +11,7 @@ description: >- # employment-related, non-cash income. The same footnote appears on the # CPUC-filed tariff sheet referenced below. # -# SNAP benefits are excluded in both dated lists: 7 U.S.C. 2017(b) prohibits -# treating their value as income or resources under any federal, state or -# local law, including California's CARE and FERA programs. -# -# Separately, D.14-08-030, issued August 14, +# Housing subsidies are the one exception. D.14-08-030, issued August 14, # 2014, holds in Section 6.2 that the income definition remains unchanged # except that housing subsidies will not be considered income, and Ordering # Paragraph 40(3) adopts "Housing subsidies are not counted as income". The @@ -71,6 +67,7 @@ values: # Public assistance payments - tanf - ssi + - snap - wic # Social Security - social_security @@ -113,6 +110,7 @@ values: # Public assistance payments - tanf - ssi + - snap - wic # Social Security - social_security @@ -139,8 +137,6 @@ metadata: period: year label: CA CPUC CARE/FERA income sources reference: - - title: 7 U.S.C. 2017(b), exclusion of SNAP benefits from income and resources - href: https://www.govinfo.gov/content/pkg/USCODE-2024-title7/html/USCODE-2024-title7-chap51-sec2017.htm - title: PG&E Form 01-9077, CARE/FERA Program Application (Rev. 6.26) href: https://www.pge.com/assets/pge/localized/en/docs/account/billing-and-assistance/care-fera-application.pdf - title: >- diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.yaml index 3b1eb370f8f..8b0d0459554 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.yaml @@ -1,12 +1,11 @@ -- name: Default small household is exempt from California's BBCE net income test. +- name: Default household meets California's BBCE net income test (100%). period: 2022 output: meets_tanf_non_cash_net_income_test: true -- name: California three-person household with net income exactly at the standard meets the net income test. +- name: California household with net income exactly at the standard meets the net income test. period: 2022 input: - snap_unit_size: 3 snap_fpg: 12_000 snap_net_income: 12_000 output: @@ -14,20 +13,18 @@ # per month is exactly at it. meets_tanf_non_cash_net_income_test: true -- name: California three-person household with net income above the standard fails the net income test. +- name: California household with net income above the standard fails the net income test. period: 2022 input: - snap_unit_size: 3 snap_fpg: 12_000 snap_net_income: 12_120 output: # Net income of 1,010 per month exceeds the 1,000 standard. meets_tanf_non_cash_net_income_test: false -- name: California three-person household at the rounded-up whole-dollar standard meets the net income test. +- name: California household at the rounded-up whole-dollar standard meets the net income test. period: 2022 input: - snap_unit_size: 3 # Monthly poverty guideline of 2,220.83 yields a whole-dollar standard # of ceil(2,220.83) = 2,221, so a rounded net income of exactly 2,221 # still passes; a raw ratio comparison (2,221 / 2,220.83 > 1) would diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/cpuc/ca_cpuc_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/cpuc/ca_cpuc_countable_income.yaml index f5786bf4c18..b95a58c4a11 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/cpuc/ca_cpuc_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/cpuc/ca_cpuc_countable_income.yaml @@ -112,35 +112,3 @@ spm_unit_capped_housing_subsidy: 25 output: ca_cpuc_countable_income: 1_000 - -- name: SNAP is excluded before the housing assistance exclusion. - period: 2013 - input: - age: 40 - state_code: CA - employment_income: 1_000 - housing_assistance: 500 - snap: 1_200 - tanf: 0 - ssi: 0 - wic: 0 - output: - # $1,000 wages + $500 housing; SNAP is excluded under 7 U.S.C. 2017(b). - # https://www.govinfo.gov/content/pkg/USCODE-2024-title7/html/USCODE-2024-title7-chap51-sec2017.htm - ca_cpuc_countable_income: 1_500 - -- name: SNAP cannot push income at the CARE limit above income eligibility. - period: 2026 - input: - age: 40 - state_code: CA - employment_income: 43_280 - snap: 1_200 - ca_care_categorically_eligible: false - output: - # CARE uses the two-person poverty line for a one-person household: - # 200% * ($15,960 + $5,680) = $43,280. SNAP adds $0 to countable income - # under 7 U.S.C. 2017(b), so income remains exactly at the limit. - # https://www.govinfo.gov/content/pkg/USCODE-2024-title7/html/USCODE-2024-title7-chap51-sec2017.htm - ca_cpuc_countable_income: 43_280 - ca_care_income_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml index 20b7fbc88b8..9da9145379e 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/heat_and_eat/snap.yaml @@ -120,107 +120,3 @@ household_vehicles_value: 1_980.88232421875 output: snap: 7_002 - -- name: California one-person MCE household keeps the minimum allotment without heat-and-eat SUA. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - head: - age: 44 - employment_income: 36_275.59 - employment_income_before_lsr: 36_275.59 - weekly_hours_worked_before_lsr: 40 - child_support_expense: 5_200 - pre_subsidy_rent: 10_560 - spm_units: - spm_unit: - members: [head] - households: - household: - members: [head] - state_code: CA - output: - # P.L. 119-21 section 10103(a): no elderly/disabled member or actual - # heating/cooling expense, so the SUAS payment cannot confer SUA. - # https://www.cdss.ca.gov/Portals/9/Additional-Resources/Letters-and-Notices/ACLs/2025/25-68.pdf#page=6 - has_snap_elderly_disabled_member: false - has_heating_cooling_expense: false - snap_utility_allowance_type: NONE - snap_utility_allowance: 0 - # Gross ($36,275.59 - $5,200) / 12 = $2,589.63 < $2,610 MCE limit. - # Net $2,589.63 - $604.59 - $209 = $1,776.04; $880 rent is below - # half that amount ($888.02), so there is no excess shelter deduction. - snap_gross_income: 2_589.63 - snap_excess_shelter_expense_deduction: 0 - snap_net_income: 1_776 - meets_snap_net_income_test: false - # CDSS ACWDL June 30, 2014, page 3: one/two-person MCE households - # retain the minimum even above the net limit. $298 - ceil(0.3 x - # $1,776) = -$235, so the $24 minimum applies independently of SUA. - # https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf#page=3 - meets_tanf_non_cash_net_income_test: true - meets_snap_categorical_eligibility: true - is_snap_eligible: true - snap: 24 - -- name: California two-person MCE household keeps the minimum allotment without heat-and-eat SUA. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 35 - employment_income: 40_000 - employment_income_before_lsr: 40_000 - weekly_hours_worked_before_lsr: 40 - child: - age: 8 - spm_units: - spm_unit: - members: [parent, child] - households: - household: - members: [parent, child] - state_code: CA - output: - snap_unit_size: 2 - snap_utility_allowance_type: NONE - snap_utility_allowance: 0 - # $3,333.33 gross < $3,526 MCE limit. Net is $3,333.33 - $666.67 - # - $209 = $2,457.67 -> $2,458, above the $1,763 net standard. - snap_net_income: 2_458 - meets_snap_net_income_test: false - # $546 - ceil(0.3 x $2,458) = -$192, floored to $24 under the - # same one/two-person MCE rule as the preceding case. - # https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf#page=3 - meets_snap_categorical_eligibility: true - is_snap_eligible: true - snap: 24 - -- name: California small-household MCE net exemption still requires the gross income test. - period: 2026-01 - input: - people: - head: - age: 44 - employment_income: 32_000 - employment_income_before_lsr: 32_000 - weekly_hours_worked_before_lsr: 40 - spm_units: - spm_unit: - members: [head] - households: - household: - members: [head] - state_code: CA - output: - # $32,000 / 12 = $2,666.67 > the $2,610 one-person MCE gross limit. - # CDSS ACWDL June 30, 2014, pages 1-3: the minimum allotment rule - # applies only after MCE has been conferred. - # https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf - meets_tanf_non_cash_net_income_test: true - meets_tanf_non_cash_gross_income_test: false - meets_snap_categorical_eligibility: false - is_snap_eligible: false - snap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_child_support_integration.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_child_support_integration.yaml index a83935e7ac0..1b8a19ece66 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_child_support_integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/snap_child_support_integration.yaml @@ -54,9 +54,17 @@ snap_child_support_gross_income_deduction: 433.33 snap_gross_income: 2_589.63 snap_child_support_deduction: 0 - meets_snap_categorical_eligibility: true - is_snap_eligible: true - snap: 24 + # Without deemed SUA, net is $2,589.63 - $604.59 - $209 = $1,776.04. + # Rent $880 is below half-net $888.02, so there is no shelter deduction. + # Main's California net screen denies SNAP at $1,776 > $1,305. + # The independent California MCE correction is tracked in #10036. + snap_utility_allowance: 0 + snap_excess_shelter_expense_deduction: 0 + snap_net_income: 1_776 + meets_tanf_non_cash_net_income_test: false + meets_snap_categorical_eligibility: false + is_snap_eligible: false + snap: 0 - name: New York excludes child support from gross income and does not deduct it again from net income. period: 2026-01 diff --git a/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.py b/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.py index 7abd7d9cb24..280c64d16bc 100644 --- a/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.py +++ b/policyengine_us/variables/gov/hhs/tanf/non_cash/meets_tanf_non_cash_net_income_test.py @@ -13,7 +13,6 @@ class meets_tanf_non_cash_net_income_test(Variable): reference = ( "https://www.law.cornell.edu/cfr/text/7/273.2#j_2", "https://www.law.cornell.edu/cfr/text/7/273.9#a_3_ii", - "https://www.cdss.ca.gov/lettersnotices/entres/getinfo/coletters/calfreshmodifiedcategoricaleligibility.pdf#page=3", ) def formula(spm_unit, period, parameters): @@ -25,14 +24,6 @@ def formula(spm_unit, period, parameters): net_limit_applies = where( hheod, applies.hheod[state], applies.non_hheod[state] ).astype(bool) - # CDSS ACWDL June 30, 2014: one- and two-person MCE households - # retain the minimum allotment even above the net income standard. - # Gross income and the other BBCE conditions still apply. - ca = parameters(period).gov.states.ca.cdss.snap.eligibility.bbce - ca_small_household = (state == "CA") & ( - spm_unit("snap_unit_size", period) <= ca.net_income_exempt_household_size - ) - net_limit_applies = net_limit_applies & ~ca_small_household net_income = spm_unit("snap_net_income", period) net_limit = parameters(period).gov.usda.snap.income.limit.net # The state's BBCE net test uses the federal SNAP net standard: the