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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/snap-assets-stock.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix asset stock variables being divided across months in SNAP categorical eligibility calculations.
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ RI: # *
SC: # *
2015-10-01: .inf
TX:
2015-10-01: 5_000 # Excludes 1 vehicle up to $15,000 & includes excess vehicle value (not modeled).
2015-10-01: 5_000 # Excludes 1 vehicle up to $15,000 & includes excess vehicle value.
UT:
2015-10-01: -.inf # Non-BBCE.
VT:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
description: Texas excludes up to this value for each additional vehicle after the first vehicle from the TANF non-cash asset test used for SNAP broad-based categorical eligibility.

values:
2015-10-01: 0
2023-09-01: 8_700

metadata:
unit: currency-USD
period: year
label: Texas TANF non-cash additional vehicle exemption for SNAP BBCE
reference:
- title: 88(R) HB 1287 - Enrolled version
href: https://capitol.texas.gov/tlodocs/88R/billtext/html/HB01287F.htm
- title: Texas Works Handbook 24-3, A-1210 General Policy
href: https://www.hhs.texas.gov/sites/default/files/documents/twh_24-3.pdf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
description: Texas excludes up to this value for one vehicle from the TANF non-cash asset test used for SNAP broad-based categorical eligibility.

values:
2015-10-01: 15_000
2023-09-01: 22_500

metadata:
unit: currency-USD
period: year
label: Texas TANF non-cash vehicle exemption for SNAP BBCE
reference:
- title: 88(R) HB 1287 - Enrolled version
href: https://capitol.texas.gov/tlodocs/88R/billtext/html/HB01287F.htm
- title: Texas Works Handbook 24-3, A-1210 General Policy
href: https://www.hhs.texas.gov/sites/default/files/documents/twh_24-3.pdf
- title: USDA Broad-based Categorical Eligibility
href: https://www.fns.usda.gov/snap/broad-based-categorical-eligibility
- title: USDA ERS SNAP Policy Database (bbce_asset/bbce_a_amt columns, Jan 1996 - Dec 2020)
href: https://www.ers.usda.gov/data-products/snap-policy-data-sets
232 changes: 232 additions & 0 deletions policyengine_us/tests/core/test_vectorized_reductions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import ast
from pathlib import Path

import pytest

from policyengine_us import Simulation


def _two_person_two_household_situation(
person_a: dict,
person_b: dict,
*,
state: str = "CA",
) -> dict:
return {
"people": {
"a": person_a,
"b": person_b,
},
"tax_units": {
"tax_unit_a": {"members": ["a"]},
"tax_unit_b": {"members": ["b"]},
},
"spm_units": {
"spm_unit_a": {"members": ["a"]},
"spm_unit_b": {"members": ["b"]},
},
"families": {
"family_a": {"members": ["a"]},
"family_b": {"members": ["b"]},
},
"households": {
"household_a": {"members": ["a"], "state_code": {"2026": state}},
"household_b": {"members": ["b"], "state_code": {"2026": state}},
},
}


def test_is_usda_disabled_reduces_per_person_not_across_simulation():
simulation = Simulation(
situation=_two_person_two_household_situation(
person_a={"age": {"2026": 30}},
person_b={
"age": {"2026": 40},
"social_security_disability": {"2026": 100},
},
)
)

assert simulation.calculate("is_usda_disabled", 2026).tolist() == [
False,
True,
]


def test_medicaid_medically_needy_category_reduces_per_person():
simulation = Simulation(
situation=_two_person_two_household_situation(
person_a={"age": {"2026": 30}},
person_b={"age": {"2026": 70}},
)
)

assert simulation.calculate(
"is_in_medicaid_medically_needy_category", 2026
).tolist() == [
False,
True,
]


def test_has_qdiv_or_ltcg_reduces_per_tax_unit_not_across_simulation():
simulation = Simulation(
situation=_two_person_two_household_situation(
person_a={"age": {"2026": 30}},
person_b={
"age": {"2026": 40},
"qualified_dividend_income": {"2026": 100},
},
)
)

assert simulation.calculate("has_qdiv_or_ltcg", 2026).tolist() == [
False,
True,
]


def test_numpy_any_all_outputs_specify_axis_or_stay_in_control_flow():
variables_dir = Path(__file__).parents[2] / "variables"
offenders = []

for path in variables_dir.rglob("*.py"):
tree = ast.parse(path.read_text())
parents = {}
for parent in ast.walk(tree):
for child in ast.iter_child_nodes(parent):
parents[child] = parent

for node in ast.walk(tree):
if not (
isinstance(node, ast.Call)
and isinstance(node.func, ast.Attribute)
and node.func.attr in {"any", "all"}
and isinstance(node.func.value, ast.Name)
and node.func.value.id == "np"
):
continue

if any(keyword.arg == "axis" for keyword in node.keywords):
continue

parent = parents.get(node)
in_control_flow_test = False
while parent is not None:
if isinstance(parent, (ast.If, ast.While)):
in_control_flow_test = node in ast.walk(parent.test)
break
parent = parents.get(parent)

if not in_control_flow_test:
offenders.append(f"{path.relative_to(variables_dir)}:{node.lineno}")

assert offenders == []


def test_tanf_non_cash_asset_test_does_not_mutate_snap_assets():
simulation = Simulation(
situation=_two_person_two_household_situation(
person_a={"age": {"2026": 30}},
person_b={"age": {"2026": 40}},
state="TX",
)
)
simulation.set_input("snap_assets", 2026, [4_000, 4_000])
simulation.set_input("household_vehicles_value", 2026, [24_000, 0])

simulation.calculate("meets_tanf_non_cash_asset_test", "2026-01")

assert simulation.calculate("snap_assets", "2026-01").tolist() == [
4_000,
4_000,
]


def test_tanf_non_cash_asset_test_applies_texas_additional_vehicle_exemption():
simulation = Simulation(
situation=_two_person_two_household_situation(
person_a={"age": {"2026": 30}},
person_b={"age": {"2026": 40}},
state="TX",
)
)
simulation.set_input("snap_assets", 2026, [4_000, 4_000])
simulation.set_input("household_vehicles_value", 2026, [31_200, 32_300])
simulation.set_input("household_vehicles_owned", 2026, [2, 2])

assert simulation.calculate(
"meets_tanf_non_cash_asset_test", "2026-01"
).tolist() == [
True,
False,
]


def test_county_mixed_known_and_missing_fips_preserves_known_rows():
simulation = Simulation(
situation=_two_person_two_household_situation(
person_a={"age": {"2025": 30}},
person_b={"age": {"2025": 40}},
)
)
simulation.set_input("state_code", 2025, ["NY", "CA"])
simulation.set_input("county_fips", 2025, ["36059", ""])

assert simulation.calculate("county_str", 2025).tolist() == [
"NASSAU_COUNTY_NY",
"ALAMEDA_COUNTY_CA",
]


def test_county_all_missing_fips_uses_state_fallback():
simulation = Simulation(
situation=_two_person_two_household_situation(
person_a={"age": {"2025": 30}},
person_b={"age": {"2025": 40}},
)
)
simulation.set_input("state_code", 2025, ["NY", "CA"])
simulation.set_input("county_fips", 2025, ["", ""])

assert simulation.calculate("county_str", 2025).tolist() == [
"ALBANY_COUNTY_NY",
"ALAMEDA_COUNTY_CA",
]


def test_il_aabd_utility_allowance_uses_elementwise_caps():
simulation = Simulation(
situation=_two_person_two_household_situation(
person_a={"age": {"2022": 30}},
person_b={"age": {"2022": 40}},
state="IL",
)
)
simulation.set_input("state_code", 2022, ["IL", "IL"])
simulation.set_input("county_str", 2022, ["COOK_COUNTY_IL", "BOND_COUNTY_IL"])
simulation.set_input("water_expense", 2022, [60, 0])
simulation.set_input("coal_expense", 2022, [0, 144])

assert simulation.calculate(
"il_aabd_utility_allowance", "2022-01"
).tolist() == pytest.approx([3.8, 11.1])


def test_co_ccap_unknown_county_fallback_is_row_specific():
simulation = Simulation(
situation=_two_person_two_household_situation(
person_a={"age": {"2023": 30}},
person_b={"age": {"2023": 40}},
state="CO",
)
)
simulation.set_input("state_code", 2023, ["CO", "CO"])
simulation.set_input("county_str", 2023, ["BACA_COUNTY_CO", "UNKNOWN"])
simulation.set_input("co_ccap_countable_income", 2023, [30 * 12, 0])
simulation.set_input("spm_unit_fpg", 2023, [12 * 12, 12 * 12])

assert simulation.calculate("co_ccap_fpg_eligible", "2023-01").tolist() == [
False,
True,
]
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
output:
meets_tanf_non_cash_asset_test: true

- name: Texas asset test is $5,000, monthly assets below limit
- name: Texas asset test is $5,000, asset stock below limit
period: 2022
input:
state_code_str: TX
snap_assets: 16_000 # $1,333.33 monthly
snap_assets: 4_000
output:
meets_tanf_non_cash_asset_test: true

- name: Texas asset test is $5,000, monthly assets above limit
- name: Texas asset test is $5,000, asset stock above limit
period: 2025
input:
state_code_str: TX
snap_assets: 61_200 # $5,100 monthly
snap_assets: 5_100
output:
meets_tanf_non_cash_asset_test: false

Expand All @@ -31,14 +31,78 @@
period: 2024
input:
state_code_str: IN
snap_assets: 48_000 # $4,000 monthly
snap_assets: 4_000
output:
meets_tanf_non_cash_asset_test: true

- name: IN asset above $5K limit fails
period: 2024
input:
state_code_str: IN
snap_assets: 72_000 # $6,000 monthly
snap_assets: 6_000
output:
meets_tanf_non_cash_asset_test: false

- name: Texas asset test uses annual asset stock in monthly formula
period: 2026
input:
state_code_str: TX
snap_assets: 15_000
output:
meets_tanf_non_cash_asset_test: false

- name: Texas BBCE ignores one vehicle up to $22,500 from September 2023
period: 2026
input:
state_code_str: TX
snap_assets: 4_000
household_vehicles_value: 22_500
output:
meets_tanf_non_cash_asset_test: true

- name: Texas BBCE counts excess vehicle value above $22,500 from September 2023
period: 2026
input:
state_code_str: TX
snap_assets: 4_000
household_vehicles_value: 24_000
output:
meets_tanf_non_cash_asset_test: false

- name: Texas BBCE excludes first and additional vehicles from September 2023
period: 2026
input:
state_code_str: TX
snap_assets: 4_000
household_vehicles_value: 31_200
household_vehicles_owned: 2
output:
meets_tanf_non_cash_asset_test: true

- name: Texas BBCE counts excess above first and additional vehicle exemptions
period: 2026
input:
state_code_str: TX
snap_assets: 4_000
household_vehicles_value: 32_300
household_vehicles_owned: 2
output:
meets_tanf_non_cash_asset_test: false

- name: Texas BBCE counted excess vehicle value above $15,000 before September 2023
period: 2022
input:
state_code_str: TX
snap_assets: 4_000
household_vehicles_value: 18_000
output:
meets_tanf_non_cash_asset_test: false

- name: Non-Texas BBCE asset tests do not use the Texas vehicle rule
period: 2026
input:
state_code_str: IN
snap_assets: 4_000
household_vehicles_value: 18_000
output:
meets_tanf_non_cash_asset_test: true
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,13 @@
spm_unit_fpg: 12
output:
co_ccap_fpg_eligible: false

- name: Unknown county fallback does not overwrite valid county rows
period: 2023-01
input:
state_code_str: [CO, CO]
co_ccap_countable_income: [2.3, 0]
county_str: [BACA_COUNTY_CO, UNKNOWN]
spm_unit_fpg: [12, 12]
output:
co_ccap_fpg_eligible: [false, true]
Loading
Loading