From 7dc16148615bf60123da1a92e8911d90bdd85781 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 13 Nov 2026 19:50:59 -0500 Subject: [PATCH] Use exact FY2017 SNAP poverty-guideline income standards --- changelog.d/history-snap-fpg-2017.fixed.md | 1 + .../meets_snap_gross_income_test.yaml | 62 +++++++++++++++++++ .../meets_snap_net_income_test.yaml | 31 ++++++++++ .../gov/usda/snap/income/snap_fpg.yaml | 60 ++++++++++++++++++ .../gov/usda/snap/income/snap_fpg.py | 23 ++++++- .../income/snap_income_standard_helpers.py | 26 +++++--- 6 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 changelog.d/history-snap-fpg-2017.fixed.md diff --git a/changelog.d/history-snap-fpg-2017.fixed.md b/changelog.d/history-snap-fpg-2017.fixed.md new file mode 100644 index 00000000000..9b590b2996a --- /dev/null +++ b/changelog.d/history-snap-fpg-2017.fixed.md @@ -0,0 +1 @@ +Corrected SNAP gross and net income standards for October 2016 through September 2017 using HHS's published household-size table. diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_gross_income_test.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_gross_income_test.yaml index 81e93ed54bc..7f0672e99ac 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_gross_income_test.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_gross_income_test.yaml @@ -310,3 +310,65 @@ 2026: GU output: meets_snap_gross_income_test: true + +# USDA FY 2017 Income Eligibility Standards, PDF pages 1-2. +# https://fns-prod.azureedge.us/sites/default/files/resource-files/FY17-Income-Eligibility-Standards.pdf +# Effective October 1, 2016 through September 30, 2017; ordinary gross/net +# tables and separately rounded additional-person increments. HHS raw rows: +# https://www.govinfo.gov/content/pkg/FR-2016-01-25/pdf/2016-01450.pdf#page=1 +# The actual-wage case checks only the ordinary gross predicate, not final SNAP. +- name: Case 19, FY 2017 published gross standards are inclusive in all guideline geographies + and above size eight. + period: 2017-01 + input: + snap_unit_size: [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 12, 12, 1, 1, 2, + 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 12, 12, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, + 6, 7, 7, 8, 8, 9, 9, 12, 12] + state_group_str: + 2017: [CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, + CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, + CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, + CONTIGUOUS_US, CONTIGUOUS_US, AK, AK, AK, AK, AK, AK, AK, AK, AK, AK, AK, AK, AK, + AK, AK, AK, AK, AK, AK, AK, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, + HI, HI, HI, HI, HI, HI] + snap_gross_test_income: [1287, 1288, 1736, 1737, 2184, 2185, 2633, 2634, 3081, 3082, 3530, + 3531, 3980, 3981, 4430, 4431, 4881, 4882, 6234, 6235, 1608, 1609, 2169, 2170, 2730, + 2731, 3292, 3293, 3853, 3854, 4414, 4415, 4975, 4976, 5538, 5539, 6102, 6103, 7794, + 7795, 1481, 1482, 1997, 1998, 2513, 2514, 3028, 3029, 3544, 3545, 4060, 4061, 4575, + 4576, 5093, 5094, 5611, 5612, 7165, 7166] + has_snap_elderly_disabled_member: [false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false] + output: + meets_snap_gross_income_test: [true, false, true, false, true, false, true, false, true, + false, true, false, true, false, true, false, true, false, true, false, true, false, + true, false, true, false, true, false, true, false, true, false, true, false, true, + false, true, false, true, false, true, false, true, false, true, false, true, false, + true, false, true, false, true, false, true, false, true, false, true, false] +- name: Case 20, January 2017 wages one dollar above the three-person gross standard fail + the ordinary test. + period: 2017-01 + input: + people: + person1: + age: {2017: 35} + employment_income: {2017: 26220} + weekly_hours_worked_before_lsr: {2017: 40} + person2: + age: {2017: 8} + is_tax_unit_dependent: {2017: true} + person3: + age: {2017: 4} + is_tax_unit_dependent: {2017: true} + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: {2017: TX} + output: {snap_unit_size: 3, snap_gross_test_income: 2185, has_snap_elderly_disabled_member: false, + meets_snap_gross_income_test: false} diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_net_income_test.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_net_income_test.yaml index 34bbdf75bd9..7262fdb877c 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_net_income_test.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/meets_snap_net_income_test.yaml @@ -354,3 +354,34 @@ 2026: VI output: meets_snap_net_income_test: true + +# USDA FY 2017 Income Eligibility Standards, PDF pages 1-2. +# https://fns-prod.azureedge.us/sites/default/files/resource-files/FY17-Income-Eligibility-Standards.pdf +# Effective October 1, 2016 through September 30, 2017; ordinary gross/net +# tables and separately rounded additional-person increments. HHS raw rows: +# https://www.govinfo.gov/content/pkg/FR-2016-01-25/pdf/2016-01450.pdf#page=1 +- name: Case 21, FY 2017 published net standards are inclusive in all guideline geographies + and above size eight. + period: 2017-01 + input: + snap_unit_size: [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 12, 12, 1, 1, 2, + 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 12, 12, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, + 6, 7, 7, 8, 8, 9, 9, 12, 12] + state_group_str: + 2017: [CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, + CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, + CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, + CONTIGUOUS_US, CONTIGUOUS_US, AK, AK, AK, AK, AK, AK, AK, AK, AK, AK, AK, AK, AK, + AK, AK, AK, AK, AK, AK, AK, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, + HI, HI, HI, HI, HI, HI] + snap_net_income: [990, 991, 1335, 1336, 1680, 1681, 2025, 2026, 2370, 2371, 2715, 2716, + 3061, 3062, 3408, 3409, 3755, 3756, 4796, 4797, 1237, 1238, 1669, 1670, 2100, 2101, + 2532, 2533, 2964, 2965, 3395, 3396, 3827, 3828, 4260, 4261, 4694, 4695, 5996, 5997, + 1140, 1141, 1536, 1537, 1933, 1934, 2330, 2331, 2726, 2727, 3123, 3124, 3520, 3521, + 3918, 3919, 4317, 4318, 5514, 5515] + output: + meets_snap_net_income_test: [true, false, true, false, true, false, true, false, true, + false, true, false, true, false, true, false, true, false, true, false, true, false, + true, false, true, false, true, false, true, false, true, false, true, false, true, + false, true, false, true, false, true, false, true, false, true, false, true, false, + true, false, true, false, true, false, true, false, true, false, true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/snap_fpg.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/snap_fpg.yaml index 13773341433..c2dcdaeebac 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/snap_fpg.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/snap_fpg.yaml @@ -60,3 +60,63 @@ output: # ($15,960 + 2 x $5,680) / 12. snap_fpg: 2_276.67 + +# USDA FY 2017 Income Eligibility Standards, PDF pages 1-2. +# https://fns-prod.azureedge.us/sites/default/files/resource-files/FY17-Income-Eligibility-Standards.pdf +# Effective October 1, 2016 through September 30, 2017; ordinary gross/net +# tables and separately rounded additional-person increments. HHS raw rows: +# https://www.govinfo.gov/content/pkg/FR-2016-01-25/pdf/2016-01450.pdf#page=1 +- name: Case 5, FY 2017 uses the published nonuniform HHS rows and preserves zero-size fallback. + period: 2017-01 + absolute_error_margin: 0.01 + input: + snap_unit_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 12, 0, 0, 0] + state_group_str: + 2017: [CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, + CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, CONTIGUOUS_US, AK, AK, AK, AK, AK, AK, + AK, AK, AK, AK, HI, HI, HI, HI, HI, HI, HI, HI, HI, HI, CONTIGUOUS_US, AK, HI] + output: + snap_fpg: [990.0, 1335.0, 1680.0, 2025.0, 2370.0, 2715.0, 3060.8333333333335, 3407.5, + 3754.1666666666665, 4794.166666666667, 1236.6666666666667, 1668.3333333333333, 2100.0, + 2531.6666666666665, 2963.3333333333335, 3395.0, 3826.6666666666665, 4260.0, 4693.333333333333, + 5993.333333333333, 1139.1666666666667, 1535.8333333333333, 1932.5, 2329.1666666666665, + 2725.8333333333335, 3122.5, 3519.1666666666665, 3917.5, 4315.833333333333, 5510.833333333333, + 643.3333333333334, 803.3333333333334, 740.8333333333334] +- name: Case 6, FY 2016 compatibility before the table window. + period: 2016-09 + absolute_error_margin: 0.01 + input: + snap_unit_size: 3 + state_group_str: {2016: CONTIGUOUS_US} + output: {snap_fpg: 1674.1666666666667} +- name: Case 7, FY 2017 begins in October 2016. + period: 2016-10 + absolute_error_margin: 0.01 + input: + snap_unit_size: 3 + state_group_str: {2016: CONTIGUOUS_US} + output: {snap_fpg: 1680.0} +- name: Case 8, FY 2017 retains the 2016 table through September. + period: 2017-09 + absolute_error_margin: 0.01 + input: + snap_unit_size: 3 + state_group_str: {2017: CONTIGUOUS_US} + output: {snap_fpg: 1680.0} +- name: Case 9, FY 2018 compatibility resumes the 2017 linear guideline. + period: 2017-10 + absolute_error_margin: 0.01 + input: + snap_unit_size: 3 + state_group_str: {2017: CONTIGUOUS_US} + output: {snap_fpg: 1701.6666666666667} +- name: Case 10, Guam and Virgin Islands use the FY 2017 contiguous guideline. + period: 2016-10 + absolute_error_margin: 0.01 + input: + snap_unit_size: [3, 3] + state_group_str: + 2016: [GU, VI] + output: + snap_fpg: [1680, 1680] diff --git a/policyengine_us/variables/gov/usda/snap/income/snap_fpg.py b/policyengine_us/variables/gov/usda/snap/income/snap_fpg.py index 74738d2963d..a689fa0d123 100644 --- a/policyengine_us/variables/gov/usda/snap/income/snap_fpg.py +++ b/policyengine_us/variables/gov/usda/snap/income/snap_fpg.py @@ -1,5 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg from policyengine_us.variables.gov.usda.snap.income.snap_income_standard_helpers import ( + _snap_fpg_context, snap_monthly_fpg_amounts, ) @@ -14,9 +16,28 @@ class snap_fpg(Variable): reference = ( "https://www.law.cornell.edu/cfr/text/7/273.9#a_3", "https://www.law.cornell.edu/uscode/text/7/2014#c", + # PDF pages 1-2. + "https://fns-prod.azureedge.us/sites/default/files/resource-files/FY17-Income-Eligibility-Standards.pdf#page=1", ) def formula(spm_unit, period, parameters): n = spm_unit("snap_unit_size", period) p1, pn = snap_monthly_fpg_amounts(spm_unit, period, parameters) - return p1 + pn * (n - 1) + linear_monthly_fpg = p1 + pn * (n - 1) + state_group, guideline_date = _snap_fpg_context(spm_unit, period) + table = parameters(guideline_date).gov.hhs.fpg_table + if not table.in_effect: + return linear_monthly_fpg + + use_table = ( + np.isin(state_group, list(table.amount)) + & np.isfinite(n) + & (n >= table.min_household_size) + & (n == np.floor(n)) + ) + # Keep the original monthly arithmetic outside HHS's source domain. + return where( + use_table, + fpg(n, state_group, guideline_date, parameters) / MONTHS_IN_YEAR, + linear_monthly_fpg, + ) diff --git a/policyengine_us/variables/gov/usda/snap/income/snap_income_standard_helpers.py b/policyengine_us/variables/gov/usda/snap/income/snap_income_standard_helpers.py index 17eca74ecff..136f0a5b190 100644 --- a/policyengine_us/variables/gov/usda/snap/income/snap_income_standard_helpers.py +++ b/policyengine_us/variables/gov/usda/snap/income/snap_income_standard_helpers.py @@ -5,16 +5,8 @@ SNAP_CONTIGUOUS_US_GUIDELINE_AREAS = ["GU", "VI"] -def snap_monthly_fpg_amounts(spm_unit, period, parameters): - """Monthly poverty guideline for the first person and for each additional - person, from the guidelines SNAP adopts each October 1 (7 CFR 273.9(a)(3)). - - Guam and the Virgin Islands use the guidelines for the 48 contiguous - States and the District of Columbia (7 CFR 273.9(a)(1)(i) and (a)(2)(i)), - so they read the contiguous-US guideline rather than their own zero - placeholders. This mapping is specific to SNAP income standards; the - households keep their own state group elsewhere. - """ +def _snap_fpg_context(spm_unit, period): + """Return SNAP's guideline area and October reference date.""" state_group = spm_unit.household("state_group_str", period.this_year) state_group = np.where( np.isin(state_group, SNAP_CONTIGUOUS_US_GUIDELINE_AREAS), @@ -27,6 +19,20 @@ def snap_monthly_fpg_amounts(spm_unit, period, parameters): instant_str = f"{year}-10-01" else: instant_str = f"{year - 1}-10-01" + return state_group, instant_str + + +def snap_monthly_fpg_amounts(spm_unit, period, parameters): + """Monthly poverty guideline for the first person and for each additional + person, from the guidelines SNAP adopts each October 1 (7 CFR 273.9(a)(3)). + + Guam and the Virgin Islands use the guidelines for the 48 contiguous + States and the District of Columbia (7 CFR 273.9(a)(1)(i) and (a)(2)(i)), + so they read the contiguous-US guideline rather than their own zero + placeholders. This mapping is specific to SNAP income standards; the + households keep their own state group elsewhere. + """ + state_group, instant_str = _snap_fpg_context(spm_unit, period) p_fpg = parameters(instant_str).gov.hhs.fpg first_person = p_fpg.first_person[state_group] / MONTHS_IN_YEAR additional_person = p_fpg.additional_person[state_group] / MONTHS_IN_YEAR