diff --git a/changelog.d/statute-bugs-ccap-tanf-snap-ssi.fixed.md b/changelog.d/statute-bugs-ccap-tanf-snap-ssi.fixed.md new file mode 100644 index 00000000000..d91628fe4c7 --- /dev/null +++ b/changelog.d/statute-bugs-ccap-tanf-snap-ssi.fixed.md @@ -0,0 +1 @@ +Follow the rules on four benefit calculations: Missouri TANF payments are lowered to the whole dollar (13 CSR 40-2.310(14)); SNAP income limits in Guam and the Virgin Islands use the contiguous-state poverty guidelines (7 CFR 273.9(a)); Hawaii CCAP counts Hawaii TANF payments from August 6, 2021 (HAR 17-798.3-10(b)(11)); and SSI stops counting food as in-kind support and maintenance from September 30, 2024, with the ISM chain and countable income computed monthly. diff --git a/policyengine_us/parameters/gov/ssa/ssi/income/ism/food_counts.yaml b/policyengine_us/parameters/gov/ssa/ssi/income/ism/food_counts.yaml index 1b71f32aebd..b52ce4968fe 100644 --- a/policyengine_us/parameters/gov/ssa/ssi/income/ism/food_counts.yaml +++ b/policyengine_us/parameters/gov/ssa/ssi/income/ism/food_counts.yaml @@ -1,13 +1,13 @@ description: SSA uses this indicator to determine whether food counts in SSI in-kind support and maintenance calculations. -# NOTE: This parameter flips on 2024-10-01, but the consuming variables -# (ssi_pmv_applies, ssi_in_kind_support_and_maintenance, etc.) are -# YEAR-defined, so parameters(period) resolves to January 1 of the year. -# In practice the change takes effect in 2025, not October 2024. -# Converting the ISM chain to MONTH would fix this. +# The final rule removing food from in-kind support and maintenance took +# effect September 30, 2024 (89 FR 21199, DATES). The consuming variables +# are MONTH-defined and read this value at the start of each month; +# ssi_pmv_applies also reads it at the end of the month to find the month in +# which food stops counting. values: 1975-01-01: true - 2024-10-01: false + 2024-09-30: false metadata: unit: bool @@ -16,5 +16,5 @@ metadata: reference: - title: 20 CFR § 416.1130 - Introduction to in-kind support and maintenance (as amended) href: https://www.law.cornell.edu/cfr/text/20/416.1130 - - title: 89 FR 21199 - Omitting Food from In-Kind Support and Maintenance Calculations - href: https://www.federalregister.gov/documents/2024/03/27/2024-06464/omitting-food-from-in-kind-support-and-maintenance-calculations + - title: "89 FR 21199, Omitting Food from In-Kind Support and Maintenance Calculations #page 1,11" + href: https://www.govinfo.gov/content/pkg/FR-2024-03-27/pdf/2024-06464.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/hi/bessd/ccap/income/countable_income/sources.yaml b/policyengine_us/parameters/gov/states/hi/bessd/ccap/income/countable_income/sources.yaml index ed5add6b1a1..a8114aeb1bd 100644 --- a/policyengine_us/parameters/gov/states/hi/bessd/ccap/income/countable_income/sources.yaml +++ b/policyengine_us/parameters/gov/states/hi/bessd/ccap/income/countable_income/sources.yaml @@ -33,17 +33,41 @@ values: # - royalties (item (17)) # - free rent converted to cost compensation (item (19)); # we don't track in-kind rent-for-activity at the moment - # HAR 17-798.2-10(b)(11) counts Hawaii public assistance (TANF), but we do - # NOT add `tanf` here to avoid the CCAP<->TANF circular dependency (CCAP - # income -> tanf -> TANF dependent-care deduction -> childcare_expenses -> - # child_care_subsidies -> CCAP). TANF families fall well below the lowest - # copay band, so the omission does not change their subsidy in practice. + # Hawaii public assistance (item (11)) is counted from August 6, 2021, + # under the replacement HAR 17-798.3-10(b)(11) (see the entry below). + # The predecessor 17-798.2-10(b)(11) lists it too, but the edition in + # force from August 2017 was not established, so this period keeps the + # earlier list. # Items (10) out-of-state public assistance and (12) adoption assistance # have no clean PolicyEngine income input. # Excluded per HAR 17-798.2-11 (omitted from the list above): capital # gains, SNAP, WIC, USDA donated foods, EITC, Title IV HEA aid, lump sums, # and others. The half-time+ student-minor earnings exclusion (item (8)) is # applied as a formula filter in hi_ccap_countable_income. + 2021-08-06: + # Same sources as above, plus Hawaii TANF payments before any + # overpayment-recovery reduction (HAR 17-798.3-10(b)(11), eff. + # 2021-08-06). hi_ccap_tanf_income explains why it reads hi_tanf + # rather than tanf. + - employment_income + - self_employment_income + - sstb_self_employment_income + - social_security_retirement + - social_security_disability + - social_security_survivors + - ssi + - dividend_income + - interest_income + - rental_income + - pension_income + - veterans_benefits + - military_retirement_income + - total_unemployment_compensation + - strike_benefits + - workers_compensation + - alimony_income + - child_support_received + - hi_ccap_tanf_income metadata: unit: list @@ -56,6 +80,8 @@ metadata: # and unearned income. - title: HAR 17-798.3-10(b) and 17-798.3-11 href: https://humanservices.hawaii.gov/bessd/files/2021/09/CHAPTER-17-798.3-Child-Care-Payments.pdf#page=24 + - title: HAR 17-798.3-10(b)(11), Hawaii public assistance payments + href: https://www.law.cornell.edu/regulations/hawaii/Haw-Code-R-SS-17-798-3-10 - title: HAR 17-798.2-10(b) href: https://humanservices.hawaii.gov/bessd/files/2013/01/HAR-17-798.2-Child-Care-Services-Rules.pdf#page=19 - title: HAR 17-798.2-11 diff --git a/policyengine_us/parameters/gov/states/hi/dhs/tanf/deductions/dependent_care/amount.yaml b/policyengine_us/parameters/gov/states/hi/dhs/tanf/deductions/dependent_care/amount.yaml index ea7849163c4..acaec185182 100644 --- a/policyengine_us/parameters/gov/states/hi/dhs/tanf/deductions/dependent_care/amount.yaml +++ b/policyengine_us/parameters/gov/states/hi/dhs/tanf/deductions/dependent_care/amount.yaml @@ -1,25 +1,25 @@ -description: Hawaii deducts up to this amount for dependent care expenses based on employment hours under the Temporary Assistance for Needy Families program. +description: Hawaii deducts up to this amount for the actual cost of care of each incapacitated adult in the household, based on the applicant's weekly hours of employment, under the Temporary Assistance for Needy Families program. metadata: type: single_amount threshold_unit: hour amount_unit: currency-USD period: month - label: Hawaii TANF dependent care deduction amount + label: Hawaii TANF incapacitated adult care deduction amount reference: + - title: "HAR 17-676-54.1(c)(4) and (d)(4); HAR 17-676-2 full-time employed #page 6,36-37" + href: https://humanservices.hawaii.gov/wp-content/uploads/2019/03/HAR-17-676-INCOME.pdf#page=6 - title: Hawaii TANF State Plan 10.2.2 (Effective October 1, 2023) href: https://humanservices.hawaii.gov/wp-content/uploads/2024/12/Hawaii_TANF_State_Plan_Signed_Certified-Eff_20231001.pdf#page=20 - - title: HAR 17-676-2 Earned income (1) - href: https://humanservices.hawaii.gov/wp-content/uploads/2019/03/HAR-17-676-INCOME.pdf#page=6 -# Full-time = 32+ hours/week per HAR 17-676 -# Part-time = less than 32 hours/week +# Full time = employed 32 or more hours a week (HAR 17-676-2). +# Less than full time = under 32 hours a week. brackets: - threshold: 2007-07-01: 0 amount: - 2007-07-01: 165 # Part-time + 2007-07-01: 165 # Less than full time - threshold: 2007-07-01: 32 amount: - 2007-07-01: 175 # Full-time + 2007-07-01: 175 # Full time diff --git a/policyengine_us/parameters/gov/states/mo/dss/tanf/minimum_payment.yaml b/policyengine_us/parameters/gov/states/mo/dss/tanf/minimum_payment.yaml index 546e356614e..629b468d621 100644 --- a/policyengine_us/parameters/gov/states/mo/dss/tanf/minimum_payment.yaml +++ b/policyengine_us/parameters/gov/states/mo/dss/tanf/minimum_payment.yaml @@ -9,5 +9,7 @@ metadata: period: month label: Missouri TANF minimum payment reference: + - title: 13 CSR 40-2.310(14) + href: https://www.sos.mo.gov/cmsimages/adrules/csr/current/13csr/13c40-2.pdf#page=36 - title: Missouri DSS Manual 0210.010.15 - Payment Determination href: https://dssmanuals.mo.gov/temporary-assistance-case-management/0210-010-15/ diff --git a/policyengine_us/tests/code_health/test_source_conflicts.py b/policyengine_us/tests/code_health/test_source_conflicts.py index 5cb01c89cf6..81aae94e3e6 100644 --- a/policyengine_us/tests/code_health/test_source_conflicts.py +++ b/policyengine_us/tests/code_health/test_source_conflicts.py @@ -122,14 +122,12 @@ "policyengine_us/parameters/gov/irs/credits/residential_clean_energy/fuel_cell_cap_per_kw.yaml": 1, "policyengine_us/parameters/gov/irs/income/exemption/overtime/hce_salary_threshold.yaml": 1, "policyengine_us/parameters/gov/irs/income/exemption/overtime/salary_basis_threshold.yaml": 1, - "policyengine_us/parameters/gov/ssa/ssi/income/ism/food_counts.yaml": 1, "policyengine_us/parameters/gov/states/ar/ade/oec/sra/rates/no_copay_smi_threshold.yaml": 1, "policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml": 1, "policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/fpl_limit.yaml": 1, "policyengine_us/parameters/gov/states/co/doi/premium_assistance/amount/additional_member.yaml": 1, "policyengine_us/parameters/gov/states/co/doi/premium_assistance/amount/first_member.yaml": 1, "policyengine_us/parameters/gov/states/fl/commerce/liheap/payment/base_amount.yaml": 1, - "policyengine_us/parameters/gov/states/hi/bessd/ccap/income/countable_income/sources.yaml": 1, "policyengine_us/parameters/gov/states/ma/hic/connector_care/fpl_floor.yaml": 1, "policyengine_us/parameters/gov/states/mo/tax/income/deductions/federal_income_tax/ignored_credits.yaml": 1, "policyengine_us/parameters/gov/states/mt/tax/income/subtractions/disability_income/cap.yaml": 1, diff --git a/policyengine_us/tests/core/test_mo_tanf_dependent_parent_properties.py b/policyengine_us/tests/core/test_mo_tanf_dependent_parent_properties.py index 5312218b384..b75ffb32e8b 100644 --- a/policyengine_us/tests/core/test_mo_tanf_dependent_parent_properties.py +++ b/policyengine_us/tests/core/test_mo_tanf_dependent_parent_properties.py @@ -60,7 +60,8 @@ PERIOD = "2026-01" YEAR = 2026 -# 13 CSR 40-2.310(13) standard of need and (12) payment percentage. +# 13 CSR 40-2.310(13) standard of need and (12) payment percentage; (14) +# lowers the payment to the whole dollar (see _payment). STANDARD_OF_NEED = {1: 393, 2: 678, 3: 846, 4: 990, 5: 1_123, 6: 1_247} PAYMENT_PERCENTAGE = 0.34526 @@ -210,7 +211,8 @@ def _calc(sim, variable): def _payment(size): - return STANDARD_OF_NEED.get(int(size), 0) * PAYMENT_PERCENTAGE + # 13 CSR 40-2.310(14) lowers the payment to the whole dollar. + return np.floor(STANDARD_OF_NEED.get(int(size), 0) * PAYMENT_PERCENTAGE) def _adult_is_dependent_child(h): diff --git a/policyengine_us/tests/core/test_mo_tanf_non_parent_caretaker_properties.py b/policyengine_us/tests/core/test_mo_tanf_non_parent_caretaker_properties.py index dc5ce09318e..d3570cdcb4f 100644 --- a/policyengine_us/tests/core/test_mo_tanf_non_parent_caretaker_properties.py +++ b/policyengine_us/tests/core/test_mo_tanf_non_parent_caretaker_properties.py @@ -44,7 +44,8 @@ YEAR = 2026 N = 480 SEED = 20260928 -# 13 CSR 40-2.310(13) standard of need and (12) payment percentage. +# 13 CSR 40-2.310(13) standard of need and (12) payment percentage; (14) +# lowers the payment to the whole dollar (see _payment). STANDARD_OF_NEED = {1: 393, 2: 678, 3: 846, 4: 990, 5: 1_123} PAYMENT_PERCENTAGE = 0.34526 WORK_EXPENSE = 90 @@ -179,7 +180,8 @@ def _person_index(): def _payment(size): - return STANDARD_OF_NEED.get(size, 0) * PAYMENT_PERCENTAGE + # 13 CSR 40-2.310(14) lowers the payment to the whole dollar. + return np.floor(STANDARD_OF_NEED.get(size, 0) * PAYMENT_PERCENTAGE) def test_unflagged_households_include_dependent_parents(unflagged_sim): diff --git a/policyengine_us/tests/core/test_ssi_monthly_ism_properties.py b/policyengine_us/tests/core/test_ssi_monthly_ism_properties.py new file mode 100644 index 00000000000..e363009721b --- /dev/null +++ b/policyengine_us/tests/core/test_ssi_monthly_ism_properties.py @@ -0,0 +1,188 @@ +"""Invariants of the monthly SSI in-kind support and maintenance chain. + +The ISM chain and SSI countable income are computed per month so that the +food exclusion that took effect September 30, 2024 (89 FR 21199) reaches only +the months it applies to. Everything else is supplied for the whole year, so +nothing else may vary between months. One simulation holds randomized aged +or disabled people in several states with a supplement, with and without +shelter or food support; each check says which months it compares: + +1. In 2023 and 2025 every month gives the same countable income, ISM, SSI + and state supplement, and the annual countable income is twelve times + one month: annual inputs reach each month as one-twelfth. +2. In 2024 the same holds for everyone without food-only support. With + food-only support, January to September match one another, October to + December match one another and count no ISM, and dropping the food ISM + never lowers SSI. +3. An annual countable income input reaches every month as one-twelfth. +4. For an eligible individual with no other income, a month's countable + income is that month's ISM less the $20 general exclusion. + +Comparisons against the pre-monthly model on randomized households are +reported on the pull request; they cannot run here, where only one version +of the model exists. +""" + +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEARS = (2023, 2024, 2025) +STATES = ("CA", "CO", "FL", "IN", "KY", "LA", "MO", "MT", "NY", "OK") +SUPPLEMENTS = ( + "co_state_supplement", + "fl_oss", + "in_ssp", + "ky_ssp", + "la_oss", + "mo_ssp", + "mt_ssp", + "ny_ssp", + "ok_ssp", +) +N = 60 + + +def _households(seed=2024): + rng = np.random.default_rng(seed) + people, households, food_only, no_income = {}, {}, [], [] + for i in range(N): + food = bool(rng.random() < 0.5) + shelter = bool(rng.random() < 0.4) + person = { + "age": int(rng.choice([40, 66, 75])), + "is_disabled": bool(rng.random() < 0.6), + "employment_income": float(rng.choice([0, 0, 2_400, 9_600])), + "social_security": float(rng.choice([0, 3_600, 7_200])), + "ssi_receives_food_from_others": food, + "ssi_receives_outside_shelter_support": shelter, + } + if shelter and rng.random() < 0.3: + person["ssi_shelter_support_value"] = float(rng.choice([1_200, 3_000])) + people[f"p{i}"] = { + k: {str(year): v for year in YEARS} for k, v in person.items() + } + households[f"h{i}"] = { + "members": [f"p{i}"], + "state_code": {str(year): STATES[i % len(STATES)] for year in YEARS}, + } + food_only.append(food and not shelter) + no_income.append( + person["employment_income"] == 0 and person["social_security"] == 0 + ) + # Give each person their own unit of every group entity, as the + # households are, so no income is shared between people. + groups = { + group: {f"{group}_{name}": {"members": [name]} for name in people} + for group in ("tax_units", "spm_units", "families", "marital_units") + } + situation = {"people": people, "households": households, **groups} + return situation, np.array(food_only), np.array(no_income) + + +@pytest.fixture(scope="module") +def results(): + situation, food_only, no_income = _households() + sim = Simulation(situation=situation) + out = {} + for year in YEARS: + for month in range(1, 13): + period = f"{year}-{month:02d}" + values = { + name: sim.calculate(name, period) + for name in ( + "ssi_countable_income", + "ssi_in_kind_support_and_maintenance", + "ssi_pmv_amount", + "ssi", + "is_ssi_eligible_individual", + ) + } + values["supplement"] = sum(sim.calculate(s, period) for s in SUPPLEMENTS) + out[period] = values + out[year] = sim.calculate("ssi_countable_income", year) + return out, food_only, no_income + + +def _assert_constant(values): + np.testing.assert_allclose( + values, np.broadcast_to(values[0], values.shape), atol=1e-6 + ) + + +def _months(out, year, name, months=range(1, 13)): + return np.stack([out[f"{year}-{m:02d}"][name] for m in months])[:, :N] + + +@pytest.mark.parametrize("year", [2023, 2025]) +def test_months_match_when_nothing_changes_within_the_year(results, year): + out, _, _ = results + for name in ("ssi_countable_income", "ssi_in_kind_support_and_maintenance"): + values = _months(out, year, name) + _assert_constant(values) + for name in ("ssi", "supplement"): + values = _months(out, year, name) + _assert_constant(values) + np.testing.assert_allclose( + out[year][:N], 12 * _months(out, year, "ssi_countable_income")[0], rtol=1e-6 + ) + # ISM is capped by the PMV, a monthly amount (one-third of the federal + # benefit rate plus $20, well under $600), not an annual one. + ism = _months(out, year, "ssi_in_kind_support_and_maintenance") + pmv = _months(out, year, "ssi_pmv_amount") + assert np.all(ism <= pmv + 1e-6) + assert np.all(pmv < 600) + + +def test_2024_changes_only_food_only_support_from_october(results): + out, food_only, _ = results + before = range(1, 10) + after = range(10, 13) + for name in ("ssi_countable_income", "ssi", "supplement"): + values = _months(out, 2024, name) + others = values[:, ~food_only] + _assert_constant(others) + early = _months(out, 2024, name, before)[:, food_only] + late = _months(out, 2024, name, after)[:, food_only] + _assert_constant(early) + _assert_constant(late) + late_ism = _months(out, 2024, "ssi_in_kind_support_and_maintenance", after) + assert not late_ism[:, food_only].any() + ssi_sep = _months(out, 2024, "ssi", [9])[0] + ssi_oct = _months(out, 2024, "ssi", [10])[0] + assert np.all(ssi_oct >= ssi_sep - 1e-6) + + +def test_annual_countable_income_input_reaches_each_month(): + # A separate simulation: an input sets the variable for every person. + sim = Simulation( + situation={ + "people": { + "p": {"age": {"2025": 70}, "ssi_countable_income": {"2025": 6_000}} + }, + "households": {"h": {"members": ["p"], "state_code": {"2025": "FL"}}}, + } + ) + for month in range(1, 13): + assert sim.calculate("ssi_countable_income", f"2025-{month:02d}")[ + 0 + ] == pytest.approx(500) + + +@pytest.mark.parametrize("year", YEARS) +def test_month_countable_income_is_that_months_ism_without_other_income(results, year): + # The $20 general exclusion applies first to unearned income, which ISM + # is (20 CFR 416.1124, as _apply_ssi_exclusions_monthly applies it). With + # no other income, each month's countable income is its ISM less $20. + out, _, no_income = results + ism = _months(out, year, "ssi_in_kind_support_and_maintenance") + countable = _months(out, year, "ssi_countable_income") + # Countable income is computed only for an eligible individual or spouse; + # these units are single people. + eligible = _months(out, year, "is_ssi_eligible_individual").astype(bool) + checked = eligible & no_income + assert (ism[checked] > 20).any() + np.testing.assert_allclose( + countable[checked], np.maximum(ism[checked] - 20, 0), atol=1e-4 + ) diff --git a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/eligibility/income/ism/ssi_in_kind_support_and_maintenance.yaml b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/eligibility/income/ism/ssi_in_kind_support_and_maintenance.yaml index 195518591b6..48a393c495c 100644 --- a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/eligibility/income/ism/ssi_in_kind_support_and_maintenance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/eligibility/income/ism/ssi_in_kind_support_and_maintenance.yaml @@ -69,3 +69,63 @@ ssi_medicaid_pays_majority_of_care: true output: ssi_in_kind_support_and_maintenance: 0 + +- name: Case 6, food-only support does not trigger PMV in October 2024. + period: 2024-10 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + ssi_federal_living_arrangement: OWN_HOUSEHOLD + ssi_receives_food_from_others: true + ssi_receives_outside_shelter_support: false + ssi_receives_shelter_from_others_in_household: false + output: + # Food stopped counting as ISM on September 30, 2024 (89 FR 21199). + ssi_pmv_applies: false + ssi_in_kind_support_and_maintenance: 0 + +- name: Case 7, food-only support triggers PMV before the exclusion. + period: 2024-08 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + ssi_federal_living_arrangement: OWN_HOUSEHOLD + ssi_receives_food_from_others: true + ssi_receives_outside_shelter_support: false + ssi_receives_shelter_from_others_in_household: false + ssi_pmv_amount: 334.33 + output: + # $943 / 3 + $20 = $334.33 monthly PMV (20 CFR 416.1140(a)(1)). + ssi_pmv_applies: true + ssi_in_kind_support_and_maintenance: 334.33 + +- name: Case 8, shelter support continues to trigger PMV after the exclusion. + period: 2024-10 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + ssi_federal_living_arrangement: OWN_HOUSEHOLD + ssi_receives_food_from_others: false + ssi_receives_outside_shelter_support: true + ssi_receives_shelter_from_others_in_household: false + ssi_pmv_amount: 334.33 + output: + ssi_pmv_applies: true + ssi_in_kind_support_and_maintenance: 334.33 + +- name: Case 9, monthly PMV amount for an individual in 2024. + period: 2024-10 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + output: + # $943 / 3 + $20, a monthly amount. + ssi_pmv_amount: 334.33 diff --git a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_living_arrangement_integration.yaml b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_living_arrangement_integration.yaml index ebf2ccf7c70..cda76a0eb43 100644 --- a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_living_arrangement_integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_living_arrangement_integration.yaml @@ -196,8 +196,8 @@ is_disabled: true meets_ssi_disability_criteria: true ssi_receives_outside_shelter_support: true - # Actual annual shelter value $1,200 ($100/month electric bill) - ssi_shelter_support_value: 1_200 + # Actual monthly shelter value $100 (electric bill) + ssi_shelter_support_value: 100 spm_units: spm_unit: members: [person1] @@ -207,9 +207,8 @@ output: ssi_federal_living_arrangement: OWN_HOUSEHOLD ssi_pmv_applies: true - # PMV annual = 342.33 * 12 = 4,108 - # Actual = 1,200 < PMV → use actual - # ISM monthly = 1,200 / 12 = 100 + # PMV monthly = 342.33 + # Actual = 100 < PMV → use actual ssi_in_kind_support_and_maintenance: 100 # Countable = (100 - 20) = 80 (after $20 general exclusion) ssi_countable_income: 80 @@ -245,6 +244,146 @@ uncapped_ssi: 628.67 ssi: 628.67 +- name: Case 9b, food-only support in October 2024 no longer triggers PMV. + period: 2024-10 + absolute_error_margin: 0.01 + input: + people: + person1: + # Year-defined inputs are keyed to the calendar year so that a + # non-January test month still reads them. + age: + 2024: 40 + is_disabled: + 2024: true + meets_ssi_disability_criteria: + 2024: true + ssi_receives_food_from_others: true + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + output: + ssi_federal_living_arrangement: OWN_HOUSEHOLD + # Food stopped counting as ISM on September 30, 2024 (89 FR 21199). + # Before this change the 2024 exclusion waited until January 2025. + ssi_pmv_applies: false + ssi_in_kind_support_and_maintenance: 0 + ssi_countable_income: 0 + uncapped_ssi: 943 + ssi: 943 + +- name: Case 9c, food-only support in September 2024 still triggers PMV. + period: 2024-09 + absolute_error_margin: 0.01 + input: + people: + person1: + # Year-defined inputs are keyed to the calendar year so that a + # non-January test month still reads them. + age: + 2024: 40 + is_disabled: + 2024: true + meets_ssi_disability_criteria: + 2024: true + ssi_receives_food_from_others: true + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + output: + # Food received from September 1 to 29 still counts; the PMV is not + # prorated by day. + ssi_pmv_applies: true + ssi_in_kind_support_and_maintenance: 334.33 + ssi_countable_income: 314.33 + ssi: 628.67 + +- name: Case 9d, September 2024 food received only on or after September 30 does not trigger PMV. + period: 2024-09 + absolute_error_margin: 0.01 + input: + people: + person1: + # Year-defined inputs are keyed to the calendar year so that a + # non-January test month still reads them. + age: + 2024: 40 + is_disabled: + 2024: true + meets_ssi_disability_criteria: + 2024: true + ssi_receives_food_from_others: true + ssi_food_from_others_only_on_or_after_food_exclusion: true + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + output: + ssi_pmv_applies: false + ssi_in_kind_support_and_maintenance: 0 + ssi_countable_income: 0 + ssi: 943 + +- name: Case 9e, the September 30 flag does not affect a month after the exclusion starts. + period: 2024-08 + absolute_error_margin: 0.01 + input: + people: + person1: + # Year-defined inputs are keyed to the calendar year so that a + # non-January test month still reads them. + age: + 2024: 40 + is_disabled: + 2024: true + meets_ssi_disability_criteria: + 2024: true + ssi_receives_food_from_others: true + ssi_food_from_others_only_on_or_after_food_exclusion: true + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + output: + # August 2024 ends before September 30, so its food counts. + ssi_pmv_applies: true + ssi_in_kind_support_and_maintenance: 334.33 + +- name: Case 9f, a year of food-only support counts ISM for January through September 2024. + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_receives_food_from_others: true + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + output: + # 9 x ($943 / 3 + $20) = 9 x 334.333 = 3,009; before this change the + # model counted all 12 months ($4,012). + ssi_in_kind_support_and_maintenance: 3_009 + # 9 x 314.333 = 2,829 + ssi_countable_income: 2_829 + # 9 x 628.667 + 3 x 943 = 5,658 + 2,829 = 8,487 + ssi: 8_487 + - name: Case 10, food-only support after 9/30/2024 does not trigger PMV. period: 2025-01 absolute_error_margin: 0.01 @@ -355,8 +494,8 @@ is_disabled: true meets_ssi_disability_criteria: true ssi_receives_outside_shelter_support: true - # Actual annual shelter value $6,000 exceeds PMV ($4,108) - ssi_shelter_support_value: 6_000 + # Actual monthly shelter value $500 exceeds PMV ($342.33) + ssi_shelter_support_value: 500 spm_units: spm_unit: members: [person1] @@ -366,8 +505,8 @@ output: ssi_federal_living_arrangement: OWN_HOUSEHOLD ssi_pmv_applies: true - # PMV annual = 342.33 * 12 = 4,108 - # Actual = 6,000 > PMV → capped at PMV + # PMV monthly = 342.33 + # Actual = 500 > PMV → capped at PMV # ISM monthly = 342.33 (PMV, not 500) ssi_in_kind_support_and_maintenance: 342.33 # Countable = 342.33 - 20 = 322.33 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/capi/ca_capi_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/capi/ca_capi_person.yaml index d5abc2efdd6..ad6bb0abf5a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/capi/ca_capi_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/capi/ca_capi_person.yaml @@ -13,12 +13,12 @@ # test, so the case supplies it. The couple's countable income is # 420 - 20 general exclusion = 400 per month, split equally as SSI # does for a couple: 200 per month (2,400 per year) each. - ssi_countable_income: 2_400 + ssi_countable_income: {2026: 2_400} person2: age: 68 immigration_status: LEGAL_PERMANENT_RESIDENT ssi_qualifying_quarters_earnings: 39 - ssi_countable_income: 2_400 + ssi_countable_income: {2026: 2_400} marital_units: marital_unit: members: [person1, person2] @@ -50,7 +50,7 @@ # deemed because person1 has no income of her own. is_ssi_spousal_deeming_applies: true ssi_income_deemed_from_ineligible_spouse: 2_490 - ssi_countable_income: 2_490 + ssi_countable_income: {2026: 2_490} person2: age: 50 employment_income: 6_000 @@ -86,7 +86,7 @@ # month (22,692 per year), all deemed. is_ssi_spousal_deeming_applies: true ssi_income_deemed_from_ineligible_spouse: 22_692 - ssi_countable_income: 22_692 + ssi_countable_income: {2026: 22_692} person2: age: 50 employment_income: 46_404 @@ -122,7 +122,7 @@ # month (25,092 per year), all deemed. is_ssi_spousal_deeming_applies: true ssi_income_deemed_from_ineligible_spouse: 25_092 - ssi_countable_income: 25_092 + ssi_countable_income: {2026: 25_092} person2: age: 42 employment_income: 51_204 @@ -164,7 +164,7 @@ # (15,210 per year). is_ssi_spousal_deeming_applies: true ssi_income_deemed_from_ineligible_spouse: 11_610 - ssi_countable_income: 15_210 + ssi_countable_income: {2026: 15_210} person2: age: 50 employment_income: 24_000 @@ -201,7 +201,7 @@ # these, since the model's split of the couple's SSI and income is # being reworked separately. ssi: 994 - ssi_countable_income: 0 + ssi_countable_income: {2026: 0} person2: age: 68 immigration_status: LEGAL_PERMANENT_RESIDENT @@ -209,7 +209,7 @@ # 420 per month foreign pension; countable income is 420 - 20 = 400 # per month (4,800 per year), supplied as in Case 1. pension_income: 5_040 - ssi_countable_income: 4_800 + ssi_countable_income: {2026: 4_800} marital_units: marital_unit: members: [person1, person2] @@ -250,7 +250,7 @@ # 587.50 per month (7,050 per year). is_ssi_spousal_deeming_applies: true ssi_income_deemed_from_ineligible_spouse: 6_810 - ssi_countable_income: 7_050 + ssi_countable_income: {2026: 7_050} person2: age: 50 employment_income: 14_400 @@ -376,12 +376,12 @@ # supplement 239.94. pension_income: 5_040 ssi: 594 - ssi_countable_income: 4_800 + ssi_countable_income: {2026: 4_800} person2: age: 68 immigration_status: LEGAL_PERMANENT_RESIDENT ssi_qualifying_quarters_earnings: 39 - ssi_countable_income: 0 + ssi_countable_income: {2026: 0} marital_units: marital_unit: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/edge_cases.yaml b/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/edge_cases.yaml index 42aa9265acd..f35069cd233 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/edge_cases.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/edge_cases.yaml @@ -23,7 +23,7 @@ uncapped_ssi: -940 # $1,045.40/month * 12 = $12,544.80/year. # Formula: countable_income ($1,045.40) <= income_standard ($1,045.40) → true. - ssi_countable_income: 12_544.80 + ssi_countable_income: {2025: 12_544.80} households: household: members: [person1] @@ -49,7 +49,7 @@ # ($1,045.40 + $0.01)/month * 12 = $12,544.92/year. # Formula: countable_income ($1,045.41) > income_standard ($1,045.40) → false. # Also uncapped_ssi < 0 → not SSI recipient. Both groups fail → ineligible. - ssi_countable_income: 12_544.92 + ssi_countable_income: {2025: 12_544.92} households: household: members: [person1] @@ -72,7 +72,7 @@ uncapped_ssi: -930 # ($1,045.40 - $0.01)/month * 12 = $12,544.68/year. # Formula: countable_income ($1,045.39) <= income_standard ($1,045.40) → true. - ssi_countable_income: 12_544.68 + ssi_countable_income: {2025: 12_544.68} households: household: members: [person1] @@ -102,7 +102,7 @@ uncapped_ssi: 12 # $1,151.40/month * 12 = $13,816.80/year. # Formula: max(0, $1,151.40 - $1,151.40) = $0. - ssi_countable_income: 13_816.80 + ssi_countable_income: {2025: 13_816.80} households: household: members: [person1] @@ -131,7 +131,7 @@ uncapped_ssi: 12 # $967/month * 12 = $11,604/year. # Formula: min(max(0, $1,151.40 - $967), $184.40) = min($184.40, $184.40) = $184.40. - ssi_countable_income: 11_604 + ssi_countable_income: {2025: 11_604} households: household: members: [person1] @@ -154,7 +154,7 @@ uncapped_ssi: 12 # ($967 + $0.01)/month * 12 = $11,604.12/year. # Formula: min(max(0, $1,151.40 - $967.01), $184.40) = min($184.39, $184.40) = $184.39. - ssi_countable_income: 11_604.12 + ssi_countable_income: {2025: 11_604.12} households: household: members: [person1] @@ -179,7 +179,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 11_604 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} households: household: members: [person1] @@ -202,7 +202,7 @@ is_ssi_eligible: true ssi_claim_is_joint: true uncapped_ssi: 10_000 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} person2: fl_oss_community_care_type: AFCH fl_oss_program_track: REDESIGN @@ -210,7 +210,7 @@ is_ssi_eligible: true ssi_claim_is_joint: true uncapped_ssi: 10_000 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} marital_units: marital_unit: members: [person1, person2] @@ -246,7 +246,7 @@ # Still an SSI recipient (Group 1) but income very high. uncapped_ssi: 12 # $5,000/month * 12 = $60,000/year. - ssi_countable_income: 60_000 + ssi_countable_income: {2025: 60_000} households: household: members: [person1] @@ -274,7 +274,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 2_916 - ssi_countable_income: 8_400 + ssi_countable_income: {2024: 8_400} households: household: members: [person1] @@ -302,7 +302,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 2_004 - ssi_countable_income: 8_400 + ssi_countable_income: {2025: 8_400} households: household: members: [person1] @@ -336,7 +336,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 2_916 - ssi_countable_income: 8_400 + ssi_countable_income: {2022: 8_400} households: household: members: [person1] @@ -359,7 +359,7 @@ is_ssi_eligible: true ssi_claim_is_joint: true uncapped_ssi: 2_000 - ssi_countable_income: 7_200 + ssi_countable_income: {2022: 7_200} person2: fl_oss_community_care_type: MHRTF fl_oss_program_track: PROTECTED @@ -367,7 +367,7 @@ is_ssi_eligible: true ssi_claim_is_joint: true uncapped_ssi: 2_000 - ssi_countable_income: 7_200 + ssi_countable_income: {2022: 7_200} marital_units: marital_unit: members: [person1, person2] @@ -399,7 +399,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 11_604 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} households: household: members: [person1] @@ -433,7 +433,7 @@ uncapped_ssi: -200 # $1,152/month * 12 = $13,824/year. # countable_income ($1,152) <= income_standard ($1,152) → Group 2 eligible. - ssi_countable_income: 13_824 + ssi_countable_income: {2025: 13_824} households: household: members: [person1] @@ -460,7 +460,7 @@ # ($1,152 + $0.01)/month * 12 = $13,824.12/year. # countable_income ($1,152.01) > income_standard ($1,152) → Group 2 fails. # Also uncapped_ssi < 0 → not Group 1. Ineligible. - ssi_countable_income: 13_824.12 + ssi_countable_income: {2025: 13_824.12} households: household: members: [person1] @@ -485,7 +485,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 11_604 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} households: household: members: [person1] @@ -506,7 +506,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 11_604 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} households: household: members: [person1] @@ -531,7 +531,7 @@ is_ssi_eligible: true ssi_claim_is_joint: true uncapped_ssi: 10_000 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} person2: fl_oss_community_care_type: MHRTF fl_oss_program_track: PROTECTED @@ -539,7 +539,7 @@ is_ssi_eligible: true ssi_claim_is_joint: true uncapped_ssi: 10_000 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} marital_units: marital_unit: members: [person1, person2] @@ -583,7 +583,7 @@ # Per-person max couple OSS = $368.80/2 = $184.40. # Target income = total_needs - max = $1,102.90 - $184.40 = $918.50/month. # $918.50/month * 12 = $11,022/year. - ssi_countable_income: 11_022 + ssi_countable_income: {2025: 11_022} person2: fl_oss_community_care_type: AFCH fl_oss_program_track: REDESIGN @@ -591,7 +591,7 @@ is_ssi_eligible: true ssi_claim_is_joint: true uncapped_ssi: 12 - ssi_countable_income: 11_022 + ssi_countable_income: {2025: 11_022} marital_units: marital_unit: members: [person1, person2] @@ -625,7 +625,7 @@ ssi_claim_is_joint: true uncapped_ssi: -100 # $996.90/month * 12 = $11,962.80/year. - ssi_countable_income: 11_962.80 + ssi_countable_income: {2025: 11_962.80} person2: fl_oss_community_care_type: AFCH fl_oss_program_track: REDESIGN @@ -633,7 +633,7 @@ is_ssi_eligible: true ssi_claim_is_joint: true uncapped_ssi: -100 - ssi_countable_income: 11_962.80 + ssi_countable_income: {2025: 11_962.80} marital_units: marital_unit: members: [person1, person2] @@ -667,7 +667,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 2_088 - ssi_countable_income: 6_000 + ssi_countable_income: {2011: 6_000} households: household: members: [person1] @@ -697,7 +697,7 @@ uncapped_ssi: 12 # $1,050/month * 12 = $12,600/year. # Income standard = $1,045.40, but Group 1 (SSI recipient) so still eligible. - ssi_countable_income: 12_600 + ssi_countable_income: {2025: 12_600} households: household: members: [person1] @@ -728,7 +728,7 @@ # Income = $1,050/month > income standard $1,045.40 (would fail Group 2). # But uncapped_ssi > 0 → SSI recipient → Group 1 eligible. uncapped_ssi: 12 - ssi_countable_income: 12_600 + ssi_countable_income: {2025: 12_600} households: household: members: [person1] @@ -757,7 +757,7 @@ is_ssi_eligible: false ssi_claim_is_joint: false uncapped_ssi: 0 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} households: household: members: [person1] @@ -781,7 +781,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 2_004 - ssi_countable_income: 9_600 + ssi_countable_income: {2025: 9_600} households: household: members: [person1] @@ -805,7 +805,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 2_004 - ssi_countable_income: 9_600 + ssi_countable_income: {2025: 9_600} households: household: members: [person1] @@ -831,7 +831,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 1_200 - ssi_countable_income: 0 + ssi_countable_income: {2024: 0} households: household: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/fl_oss.yaml b/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/fl_oss.yaml index 87b16b4e08b..931951343d5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/fl_oss.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/fl_oss.yaml @@ -17,7 +17,7 @@ # Annual: $167 * 12 = $2,004. uncapped_ssi: 2_004 # Annual countable income = $800 * 12 = $9,600. - ssi_countable_income: 9_600 + ssi_countable_income: {2025: 9_600} households: household: members: [person1] @@ -43,7 +43,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 2_004 - ssi_countable_income: 9_600 + ssi_countable_income: {2025: 9_600} households: household: members: [person1] @@ -73,7 +73,7 @@ # $1,200/month countable income = $14,400/year. # Total needs Redesign (I) = $13,816.80/year. # $14,400 > $13,816.80, so benefit = 0. - ssi_countable_income: 14_400 + ssi_countable_income: {2025: 14_400} households: household: members: [person1] @@ -93,7 +93,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 11_604 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} households: household: members: [person1] @@ -126,7 +126,7 @@ # $13,200 > $12,544.80 so Group 2 check fails. # Also uncapped_ssi < 0 so not Group 1. # Person is INELIGIBLE (income above income standard for Group 2). - ssi_countable_income: 13_200 + ssi_countable_income: {2025: 13_200} households: household: members: [person1] @@ -151,7 +151,7 @@ # $1,000/month = $12,000/year. # Income standard (Redesign, I) = $1,045.40/month = $12,544.80/year. # $12,000 < $12,544.80 → Group 2 eligible. - ssi_countable_income: 12_000 + ssi_countable_income: {2025: 12_000} households: household: members: [person1] @@ -177,7 +177,7 @@ is_ssi_eligible: true ssi_claim_is_joint: false uncapped_ssi: 60_000_000 - ssi_countable_income: -60_000_000 + ssi_countable_income: {2025: -60_000_000} households: household: members: [person1] @@ -200,7 +200,7 @@ ssi_claim_is_joint: false uncapped_ssi: 1 # > 0 -> Group 1 (receives SSI) # $1,050/month countable income = $12,600/year. - ssi_countable_income: 12_600 + ssi_countable_income: {2025: 12_600} households: household: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/fl_oss_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/fl_oss_eligible.yaml index 9358453d5a6..18532b29979 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/fl_oss_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/fl/dcf/oss/fl_oss_eligible.yaml @@ -87,7 +87,7 @@ # ssi_countable_income is YEAR-defined. # Redesign income standard (I) 2025 = $991.40 + $54 = $1,045.40/month # = $12,544.80/year. Set income below that. - ssi_countable_income: 12_000 + ssi_countable_income: {2025: 12_000} households: household: members: [person1] @@ -106,7 +106,7 @@ is_ssi_eligible: true uncapped_ssi: -500 # Income well above income standard of $12,544.80/year. - ssi_countable_income: 15_000 + ssi_countable_income: {2025: 15_000} households: household: members: [person1] @@ -124,7 +124,7 @@ age: 70 is_ssi_eligible: true ssi: 0 - ssi_countable_income: 5_000 + ssi_countable_income: {2025: 5_000} receives_ssi: true households: household: diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/copay/hi_ccap_copay.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/copay/hi_ccap_copay.yaml index 65ff2be8a04..3f834da616a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/copay/hi_ccap_copay.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/copay/hi_ccap_copay.yaml @@ -12,7 +12,7 @@ people: person1: age: 30 - employment_income: 56_000 # 4,666.67/mo, FPG ratio 2.30+ -> 9% + employment_income_before_lsr: 56_000 # 4,666.67/mo, FPG ratio 2.30+ -> 9% immigration_status: CITIZEN person2: age: 4 @@ -41,7 +41,7 @@ people: person1: age: 30 - employment_income: 40_800 # 3,400/mo, FPG ratio 1.68 -> 5% + employment_income_before_lsr: 40_800 # 3,400/mo, FPG ratio 1.68 -> 5% immigration_status: CITIZEN person2: age: 1 # < 36 months -> INFANT_TODDLER rate @@ -70,7 +70,7 @@ people: person1: age: 30 - employment_income: 12_000 # 1,000/mo, FPG ratio 0.49 -> 0% + employment_income_before_lsr: 12_000 # 1,000/mo, FPG ratio 0.49 -> 0% immigration_status: CITIZEN person2: age: 4 @@ -101,7 +101,7 @@ people: person1: age: 25 - employment_income: 43_584 # 3,632/mo + employment_income_before_lsr: 43_584 # 3,632/mo immigration_status: CITIZEN tax_units: tax_unit: @@ -130,7 +130,7 @@ people: person1: age: 30 - employment_income: 56_000 # 4,666.67/mo, far above 230% FPG -> 9% + employment_income_before_lsr: 56_000 # 4,666.67/mo, far above 230% FPG -> 9% immigration_status: CITIZEN person2: age: 4 @@ -159,7 +159,7 @@ people: person1: age: 30 - employment_income: 56_000 # 4,666.67/mo, far above 230% FPG -> 9% + employment_income_before_lsr: 56_000 # 4,666.67/mo, far above 230% FPG -> 9% immigration_status: CITIZEN person2: age: 4 @@ -193,7 +193,7 @@ people: person1: age: 30 - employment_income: 56_000 # 4,666.67/mo, FPG ratio 2.79 -> 9% + employment_income_before_lsr: 56_000 # 4,666.67/mo, FPG ratio 2.79 -> 9% immigration_status: CITIZEN person2: age: 4 @@ -235,7 +235,7 @@ people: person1: age: 30 - employment_income: 66_000 # 5,500/mo, FPG ratio 2.61 -> 9% + employment_income_before_lsr: 66_000 # 5,500/mo, FPG ratio 2.61 -> 9% immigration_status: CITIZEN person2: age: 1 # < 36 months -> INFANT_TODDLER rate @@ -271,7 +271,7 @@ people: person1: age: 30 - employment_income: 66_000 # 5,500/mo, FPG ratio 2.49 -> 9% + employment_income_before_lsr: 66_000 # 5,500/mo, FPG ratio 2.49 -> 9% immigration_status: CITIZEN person2: age: 1 # < 36 months -> INFANT_TODDLER rate @@ -299,3 +299,39 @@ hi_ccap_maximum_monthly_rate: [0, 1_733, 795] hi_ccap_copay_rate: 0.09 hi_ccap_copay: 495 + +- name: Counted Hawaii TANF payments can move a family out of the zero co-payment band. + period: 2025-01 + absolute_error_margin: 0.001 + input: + people: + parent: + age: 30 + employment_income_before_lsr: 30_000 # 2,500/month + spouse: + age: 30 + is_disabled: true + child: + age: 5 + hi_ccap_maximum_monthly_rate: 1_000 + spm_units: + unit: + members: [parent, spouse, child] + hi_tanf: 153.64 # monthly + spm_unit_fpg: 30_650 + spm_unit_pre_subsidy_childcare_expenses: 6_000 + hi_ccap_eligible: true + households: + household: + members: [parent, spouse, child] + state_code: HI + output: + # Countable income = $2,500 + $153.64 Hawaii TANF = $2,653.64 + # (HAR 17-798.3-10(b)(11)). Monthly FPG = $30,650 / 12 = $2,554.17, so + # income is about 103.9% of FPG: the 1% band. Without the TANF income, + # $2,500 is under 100% of FPG and the co-payment is zero. + hi_ccap_countable_income: 2_653.64 + hi_ccap_copay_rate: 0.01 + hi_ccap_copay: 26.5364 + # min($6,000 / 12, $1,000) - $26.5364 = $473.4636 + hi_ccap: 473.4636 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/copay/hi_ccap_copay_rate.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/copay/hi_ccap_copay_rate.yaml index 8906ff01a9c..d776ad2ac65 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/copay/hi_ccap_copay_rate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/copay/hi_ccap_copay_rate.yaml @@ -16,7 +16,7 @@ people: person1: age: 30 - employment_income: 12_000 # 1,000/mo, ratio 0.49 + employment_income_before_lsr: 12_000 # 1,000/mo, ratio 0.49 immigration_status: CITIZEN person2: age: 4 @@ -41,7 +41,7 @@ people: person1: age: 30 - employment_income: 24_320 # 2,026.67/mo, ratio exactly 1.00 + employment_income_before_lsr: 24_320 # 2,026.67/mo, ratio exactly 1.00 immigration_status: CITIZEN person2: age: 4 @@ -67,7 +67,7 @@ people: person1: age: 30 - employment_income: 26_280 # 2,190/mo, ratio 1.08 + employment_income_before_lsr: 26_280 # 2,190/mo, ratio 1.08 immigration_status: CITIZEN person2: age: 4 @@ -92,7 +92,7 @@ people: person1: age: 30 - employment_income: 30_000 # 2,500/mo, ratio 1.23 + employment_income_before_lsr: 30_000 # 2,500/mo, ratio 1.23 immigration_status: CITIZEN person2: age: 4 @@ -117,7 +117,7 @@ people: person1: age: 30 - employment_income: 34_080 # 2,840/mo, ratio 1.40 + employment_income_before_lsr: 34_080 # 2,840/mo, ratio 1.40 immigration_status: CITIZEN person2: age: 4 @@ -142,7 +142,7 @@ people: person1: age: 30 - employment_income: 37_200 # 3,100/mo, ratio 1.53 + employment_income_before_lsr: 37_200 # 3,100/mo, ratio 1.53 immigration_status: CITIZEN person2: age: 4 @@ -167,7 +167,7 @@ people: person1: age: 30 - employment_income: 40_800 # 3,400/mo, ratio 1.68 + employment_income_before_lsr: 40_800 # 3,400/mo, ratio 1.68 immigration_status: CITIZEN person2: age: 4 @@ -192,7 +192,7 @@ people: person1: age: 30 - employment_income: 44_400 # 3,700/mo, ratio 1.83 + employment_income_before_lsr: 44_400 # 3,700/mo, ratio 1.83 immigration_status: CITIZEN person2: age: 4 @@ -217,7 +217,7 @@ people: person1: age: 30 - employment_income: 48_000 # 4,000/mo, ratio 1.97 + employment_income_before_lsr: 48_000 # 4,000/mo, ratio 1.97 immigration_status: CITIZEN person2: age: 4 @@ -242,7 +242,7 @@ people: person1: age: 30 - employment_income: 53_040 # 4,420/mo, ratio 2.18 + employment_income_before_lsr: 53_040 # 4,420/mo, ratio 2.18 immigration_status: CITIZEN person2: age: 4 @@ -267,7 +267,7 @@ people: person1: age: 30 - employment_income: 56_000 # 4,666.67/mo, ratio 2.30 (just above) + employment_income_before_lsr: 56_000 # 4,666.67/mo, ratio 2.30 (just above) immigration_status: CITIZEN person2: age: 4 @@ -293,7 +293,7 @@ people: person1: age: 30 - employment_income: 58_320 # 4,860/mo, ratio 2.40 + employment_income_before_lsr: 58_320 # 4,860/mo, ratio 2.40 immigration_status: CITIZEN person2: age: 4 @@ -320,7 +320,7 @@ people: person1: age: 30 - employment_income: 0 # ratio 0.00 -> 0% band + employment_income_before_lsr: 0 # ratio 0.00 -> 0% band immigration_status: CITIZEN person2: age: 4 @@ -331,6 +331,9 @@ spm_units: spm_unit: members: [person1, person2] + # Without earnings the family would qualify for Hawaii TANF, which + # CCAP counts (HAR 17-798.3-10(b)(11)); this case has no TANF. + takes_up_tanf_if_eligible: false households: household: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/eligibility/hi_ccap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/eligibility/hi_ccap_income_eligible.yaml index bb332b42a48..822ec799ad0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/eligibility/hi_ccap_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/eligibility/hi_ccap_income_eligible.yaml @@ -12,7 +12,7 @@ people: person1: age: 30 - employment_income: 72_000 # 6,000/month + employment_income_before_lsr: 72_000 # 6,000/month immigration_status: CITIZEN person2: age: 4 @@ -37,7 +37,7 @@ people: person1: age: 30 - employment_income: 78_000 # 6,500/month + employment_income_before_lsr: 78_000 # 6,500/month immigration_status: CITIZEN person2: age: 4 @@ -62,7 +62,7 @@ people: person1: age: 35 - employment_income: 200_000 # 16,666.67/month, far above limit + employment_income_before_lsr: 200_000 # 16,666.67/month, far above limit immigration_status: CITIZEN person2: age: 6 @@ -88,7 +88,7 @@ people: person1: age: 35 - employment_income: 200_000 # 16,666.67/month + employment_income_before_lsr: 200_000 # 16,666.67/month immigration_status: CITIZEN person2: age: 6 @@ -138,7 +138,7 @@ person1: age: 30 # 74,624/yr = 6,218.667/mo (~3.5 cents under the 6,218.7017 ceiling) - employment_income: 74_624 + employment_income_before_lsr: 74_624 immigration_status: CITIZEN person2: age: 4 @@ -164,7 +164,7 @@ person1: age: 30 # 74,648.42/yr = 6,220.7017/mo (= ceiling + 2/mo, clearly over) - employment_income: 74_648.42 + employment_income_before_lsr: 74_648.42 immigration_status: CITIZEN person2: age: 4 @@ -191,7 +191,7 @@ age: 30 # Family-of-1 ceiling = 129,108 x 0.52 x 0.85 / 12 = 4,755.4780/mo. # 57,060/yr = 4,755.00/mo (just under the ceiling) -> eligible - employment_income: 57_060 + employment_income_before_lsr: 57_060 immigration_status: CITIZEN spm_units: spm_unit: @@ -212,7 +212,7 @@ # Family-of-8 ceiling = 129,108 x 1.38 x 0.85 / 12 = 12,620.3070/mo. # adj = 0.52 + 0.16x5 + 0.03x2 = 1.38 (the 7th/8th use 0.03 each). # 151,000/yr = 12,583.33/mo, just under the ceiling -> eligible. - employment_income: 151_000 + employment_income_before_lsr: 151_000 immigration_status: CITIZEN person2: age: 38 @@ -255,7 +255,7 @@ person1: age: 40 # 152,000/yr = 12,666.67/mo > 12,620.31 ceiling -> ineligible - employment_income: 152_000 + employment_income_before_lsr: 152_000 immigration_status: CITIZEN person2: age: 38 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/hi_ccap.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/hi_ccap.yaml index 6ed40870741..f879cdbb800 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/hi_ccap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/hi_ccap.yaml @@ -10,7 +10,7 @@ people: person1: age: 30 - employment_income: 12_000 # low income -> 0% copay + employment_income_before_lsr: 12_000 # low income -> 0% copay weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN person2: @@ -42,7 +42,7 @@ people: person1: age: 30 - employment_income: 12_000 + employment_income_before_lsr: 12_000 weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN person2: @@ -73,7 +73,7 @@ people: person1: age: 30 - employment_income: 56_000 # 4,666.67/mo, FPG ratio 2.30+ -> 9% + employment_income_before_lsr: 56_000 # 4,666.67/mo, FPG ratio 2.30+ -> 9% weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN person2: @@ -105,7 +105,7 @@ people: person1: age: 30 - employment_income: 12_000 + employment_income_before_lsr: 12_000 weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN person2: @@ -173,7 +173,7 @@ people: person1: age: 30 - employment_income: 0 + employment_income_before_lsr: 0 weekly_hours_worked_before_lsr: 0 is_full_time_student: false is_disabled: false @@ -208,7 +208,7 @@ people: person1: age: 30 - employment_income: 56_000 # 4,666.67/mo, FPG ratio 2.30+ -> 9% + employment_income_before_lsr: 56_000 # 4,666.67/mo, FPG ratio 2.30+ -> 9% weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN person2: @@ -241,7 +241,7 @@ people: person1: age: 30 - employment_income: 56_000 # 4,666.67/mo, FPG ratio 2.30+ -> 9% + employment_income_before_lsr: 56_000 # 4,666.67/mo, FPG ratio 2.30+ -> 9% weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN person2: @@ -273,7 +273,7 @@ people: person1: age: 30 - employment_income: 12_000 + employment_income_before_lsr: 12_000 weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN spm_units: @@ -298,7 +298,7 @@ people: person1: age: 30 - employment_income: 12_000 # low income -> 0% copay + employment_income_before_lsr: 12_000 # low income -> 0% copay weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN person2: @@ -332,7 +332,7 @@ people: person1: age: 30 - employment_income: 12_000 # low income -> 0% copay + employment_income_before_lsr: 12_000 # low income -> 0% copay weekly_hours_worked_before_lsr: 10 # 10 x 52/12 = 43.33 monthly immigration_status: CITIZEN person2: @@ -371,7 +371,7 @@ people: person1: age: 30 - employment_income: 12_000 # low income -> 0% copay + employment_income_before_lsr: 12_000 # low income -> 0% copay weekly_hours_worked_before_lsr: 10 # 10 x 52/12 = 43.33 monthly immigration_status: CITIZEN person2: @@ -411,7 +411,7 @@ people: person1: age: 30 - employment_income: 56_000 # 4,666.67/mo + employment_income_before_lsr: 56_000 # 4,666.67/mo weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN person2: diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/hi_ccap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/hi_ccap_countable_income.yaml index 3737656552e..238588f2b22 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/hi_ccap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/hi_ccap_countable_income.yaml @@ -6,6 +6,8 @@ # # hi_ccap_countable_income is MONTH-defined. YEAR-defined sources are entered # annually (auto-divided to monthly); SSI is MONTH-defined and entered monthly. +# Earnings are entered as employment_income_before_lsr, the input Hawaii TANF +# reads, so the TANF grant CCAP counts from 2021-08-06 sees the same earnings. - name: Case 1, employment income is counted. period: 2025-01 @@ -14,7 +16,7 @@ people: person1: age: 30 - employment_income: 24_000 # 2,000/month + employment_income_before_lsr: 24_000 # 2,000/month spm_units: spm_unit: members: [person1] @@ -68,7 +70,7 @@ people: person1: age: 40 - employment_income: 24_000 # 2,000/month + employment_income_before_lsr: 24_000 # 2,000/month ssi: 500 # 500/month social_security_retirement: 12_000 # 1,000/month spm_units: @@ -113,10 +115,10 @@ people: person1: age: 30 - employment_income: 24_000 # parent, 2,000/month (counted) + employment_income_before_lsr: 24_000 # parent, 2,000/month (counted) person2: age: 16 # minor (< 18) - employment_income: 12_000 # 1,000/month, excluded as minor student + employment_income_before_lsr: 12_000 # 1,000/month, excluded as minor student is_full_time_student: true spm_units: spm_unit: @@ -136,10 +138,10 @@ people: person1: age: 30 - employment_income: 24_000 # parent, 2,000/month + employment_income_before_lsr: 24_000 # parent, 2,000/month person2: age: 16 # minor but not a student -> earnings counted - employment_income: 12_000 # 1,000/month + employment_income_before_lsr: 12_000 # 1,000/month is_full_time_student: false spm_units: spm_unit: @@ -159,10 +161,10 @@ people: person1: age: 30 - employment_income: 24_000 # parent, 2,000/month + employment_income_before_lsr: 24_000 # parent, 2,000/month person2: age: 20 # adult (>= 18); exclusion is minors only - employment_income: 12_000 # 1,000/month, counted + employment_income_before_lsr: 12_000 # 1,000/month, counted is_full_time_student: true spm_units: spm_unit: @@ -174,3 +176,102 @@ output: # 2,000 + 1,000 = 3,000 (adult student not excluded) hi_ccap_countable_income: 3_000 + +- name: Case 9, Hawaii TANF payments are counted from August 6, 2021. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income_before_lsr: 12_000 # 1,000/month + child: + age: 5 + spm_units: + unit: + members: [parent, child] + hi_tanf: 100 # monthly + households: + household: + members: [parent, child] + state_code: HI + output: + # HAR 17-798.3-10(b)(11): $1,000 earnings + $100 Hawaii TANF. + hi_ccap_tanf_income: 100 + hi_ccap_countable_income: 1_100 + +- name: Case 10, TANF the family does not take up is not counted. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income_before_lsr: 12_000 + child: + age: 5 + spm_units: + unit: + members: [parent, child] + hi_tanf: 100 + takes_up_tanf_if_eligible: false + households: + household: + members: [parent, child] + state_code: HI + output: + hi_ccap_tanf_income: 0 + hi_ccap_countable_income: 1_000 + +- name: Case 11, Hawaii TANF is not added before the August 6, 2021 rules. + period: 2021-01 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income_before_lsr: 12_000 + child: + age: 5 + spm_units: + unit: + members: [parent, child] + hi_tanf: 100 + households: + household: + members: [parent, child] + state_code: HI + output: + # The predecessor HAR 17-798.2 edition in force before then was not + # established, so the earlier source list applies. + hi_ccap_countable_income: 1_000 + +- name: Case 12, a computed Hawaii TANF grant flows into CCAP income. + period: 2024-01 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income_before_lsr: 18_000 # 1,500/month + weekly_hours_worked_before_lsr: 40 + child: + age: 5 + is_tax_unit_dependent: true + spm_units: + unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: HI + output: + # Hawaii TANF: countable earnings (1,500 x 0.8 - 200) x 0.45 = 450, with + # no incapacitated adult care deduction; 485.76 - 450 = 35.76. The grant + # reaches CCAP income without a circular dependency. + hi_tanf_dependent_care_deduction: 0 + hi_tanf: 35.76 + hi_ccap_countable_income: 1_535.76 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/integration.yaml index 9445909227f..45bb22f4b55 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/bessd/ccap/integration.yaml @@ -18,7 +18,7 @@ people: person1: age: 30 - employment_income: 18_000 # 1,500/mo + employment_income_before_lsr: 18_000 # 1,500/mo weekly_hours_worked_before_lsr: 40 is_tax_unit_head_or_spouse: true immigration_status: CITIZEN @@ -66,7 +66,7 @@ people: person1: age: 32 - employment_income: 60_000 # 5,000/mo + employment_income_before_lsr: 60_000 # 5,000/mo weekly_hours_worked_before_lsr: 40 is_tax_unit_head_or_spouse: true immigration_status: CITIZEN @@ -108,7 +108,7 @@ people: person1: age: 35 - employment_income: 200_000 # far above income limit + employment_income_before_lsr: 200_000 # far above income limit weekly_hours_worked_before_lsr: 40 is_tax_unit_head_or_spouse: true immigration_status: CITIZEN @@ -152,7 +152,7 @@ people: person1: age: 45 - employment_income: 30_000 # 2,500/mo + employment_income_before_lsr: 30_000 # 2,500/mo weekly_hours_worked_before_lsr: 40 is_tax_unit_head_or_spouse: true immigration_status: CITIZEN @@ -197,7 +197,7 @@ people: person1: age: 33 - employment_income: 36_000 # 3,000/mo + employment_income_before_lsr: 36_000 # 3,000/mo weekly_hours_worked_before_lsr: 40 is_tax_unit_head_or_spouse: true immigration_status: CITIZEN @@ -249,13 +249,13 @@ people: person1: age: 35 - employment_income: 30_000 + employment_income_before_lsr: 30_000 weekly_hours_worked_before_lsr: 40 is_tax_unit_head_or_spouse: true immigration_status: CITIZEN person2: age: 33 - employment_income: 0 + employment_income_before_lsr: 0 weekly_hours_worked_before_lsr: 0 is_full_time_student: false is_disabled: false @@ -291,7 +291,7 @@ people: person1: age: 35 - employment_income: 78_000 # 6,500/mo > 6,218.70 limit + employment_income_before_lsr: 78_000 # 6,500/mo > 6,218.70 limit weekly_hours_worked_before_lsr: 40 is_tax_unit_head_or_spouse: true immigration_status: CITIZEN @@ -324,7 +324,7 @@ people: person1: age: 30 - employment_income: 18_000 + employment_income_before_lsr: 18_000 weekly_hours_worked_before_lsr: 40 immigration_status: CITIZEN person2: diff --git a/policyengine_us/tests/policy/baseline/gov/states/in/fssa/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/in/fssa/ssp/integration.yaml index 835705f335f..114ef7c4c0b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/in/fssa/ssp/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/in/fssa/ssp/integration.yaml @@ -80,7 +80,7 @@ ssi_claim_is_joint: false ssi_lives_in_medical_treatment_facility: true ssi_medicaid_pays_majority_of_care: true - ssi_countable_income: 120 + ssi_countable_income: {2025: 120} households: household: members: [person1] @@ -106,7 +106,7 @@ is_ssi_aged_blind_disabled: true is_medicaid_eligible: false ssi: 750 - ssi_countable_income: 2_400 + ssi_countable_income: {2025: 2_400} in_resides_in_licensed_residential_facility: true households: household: diff --git a/policyengine_us/tests/policy/baseline/gov/states/la/hhs/oss/la_oss.yaml b/policyengine_us/tests/policy/baseline/gov/states/la/hhs/oss/la_oss.yaml index a29cf8405d0..cf4a7d8f6fb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/la/hhs/oss/la_oss.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/la/hhs/oss/la_oss.yaml @@ -18,7 +18,7 @@ person1: la_oss_eligible: true ssi: 30 # full institutional FBR - ssi_countable_income: 0 + ssi_countable_income: {2024: 0} households: household: members: [person1] @@ -47,7 +47,7 @@ person1: la_oss_eligible: true ssi: 30 # full institutional FBR - ssi_countable_income: 0 + ssi_countable_income: {2026: 0} households: household: members: [person1] @@ -65,7 +65,7 @@ person1: la_oss_eligible: true ssi: 25 # reduced institutional SSI - ssi_countable_income: 60 + ssi_countable_income: {2024: 60} households: household: members: [person1] @@ -82,7 +82,7 @@ person1: la_oss_eligible: true ssi: 0 - ssi_countable_income: 1_200 + ssi_countable_income: {2024: 1_200} households: household: members: [person1] @@ -99,7 +99,7 @@ person1: la_oss_eligible: true ssi: 0 - ssi_countable_income: 360 + ssi_countable_income: {2024: 360} households: household: members: [person1] @@ -116,7 +116,7 @@ person1: la_oss_eligible: true ssi: 0 - ssi_countable_income: 372 + ssi_countable_income: {2024: 372} households: household: members: [person1] @@ -133,7 +133,7 @@ person1: la_oss_eligible: true ssi: 0 - ssi_countable_income: 456 + ssi_countable_income: {2024: 456} households: household: members: [person1] @@ -150,7 +150,7 @@ person1: la_oss_eligible: true ssi: 0 - ssi_countable_income: -1_000_000 + ssi_countable_income: {2024: -1_000_000} households: household: members: [person1] @@ -166,7 +166,7 @@ person1: la_oss_eligible: true ssi: 30 - ssi_countable_income: 0 + ssi_countable_income: {2025: 0} households: household: members: [person1] @@ -181,7 +181,7 @@ person1: la_oss_eligible: true ssi: 0 - ssi_countable_income: -1_000_000 + ssi_countable_income: {2026: -1_000_000} households: household: members: [person1] @@ -199,7 +199,7 @@ person1: la_oss_eligible: true ssi: 0 - ssi_countable_income: 540 + ssi_countable_income: {2026: 540} households: household: members: [person1] @@ -216,7 +216,7 @@ person1: la_oss_eligible: true ssi: 0 - ssi_countable_income: 360 + ssi_countable_income: {2026: 360} households: household: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/ssp/mo_sab_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/ssp/mo_sab_countable_income.yaml index 7ac9f990845..c4b550c61da 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/ssp/mo_sab_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/ssp/mo_sab_countable_income.yaml @@ -245,7 +245,7 @@ people: person1: age: 50 - ssi_in_kind_support_and_maintenance: 3_000 + ssi_in_kind_support_and_maintenance: {2026: 3_000} households: household: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/integration.yaml index a0147f3f836..503d73c29ff 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/integration.yaml @@ -29,7 +29,7 @@ mo_tanf_income_eligible: true mo_tanf_resources_eligible: true mo_tanf_eligible: true - mo_tanf: 234.086 + mo_tanf: 234 - name: Family of 3 with earned income below maximum period: 2024 @@ -90,9 +90,9 @@ mo_tanf_income_eligible: true mo_tanf_eligible: true # Monthly: $250 earned - ($90 + $30 + $130 / 3) deductions leaves - # $86.67 countable; $292.09 - $86.67 = $205.42 benefit; * 12 = - # $2,465.08. - mo_tanf: 2_465.08 + # $86.67 countable; $292.09 - $86.67 = $205.42, lowered to $205 + # (13 CSR 40-2.310(14)); * 12 = $2,460. + mo_tanf: 2_460 - name: Family of 3 income at exact 185% limit period: 2024 @@ -145,7 +145,7 @@ output: mo_tanf_resources_eligible: true mo_tanf_eligible: true - mo_tanf: 2_809.035 + mo_tanf: 2_808 - name: Family with resources exceeding $1000 limit period: 2024 @@ -248,7 +248,7 @@ mo_tanf_percentage_of_need_test: true mo_tanf_income_eligible: true mo_tanf_eligible: true - mo_tanf: 1_052.724 + mo_tanf: 1_044 - name: Benefit calculation period: 2024 @@ -275,7 +275,7 @@ mo_tanf_standard_of_need: 8_136 mo_tanf_maximum_benefit: 2_809.035 mo_tanf_eligible: true - mo_tanf: 2_809.035 + mo_tanf: 2_808 - name: A 19-year-old non-student household member is excluded from the unit period: 2024-01 @@ -307,7 +307,7 @@ mo_tanf_assistance_unit_size: 2 mo_tanf_standard_of_need: 678 mo_tanf_maximum_benefit: 234.086 - mo_tanf: 234.086 + mo_tanf: 234 - name: Pregnancy alone does not create eligibility without a dependent child period: 2024-01 @@ -357,7 +357,7 @@ mo_tanf_gross_earned_income: 0 mo_tanf_gross_income_eligible: true mo_tanf_eligible: true - mo_tanf: 234.086 + mo_tanf: 234 - name: Student teen parent's earnings do not affect the grant period: 2024-01 @@ -386,7 +386,7 @@ # who is a full-time secondary-school student. mo_tanf_gross_earned_income: 0 mo_tanf_eligible: true - mo_tanf: 234.086 + mo_tanf: 234 - name: An SSI-recipient parent is excluded from needs and income period: 2024-01 @@ -420,7 +420,7 @@ mo_tanf_countable_income: 0 mo_tanf_maximum_benefit: 135.687 mo_tanf_eligible: true - mo_tanf: 135.687 + mo_tanf: 135 - name: A family whose only child receives SSI gets a caretaker-only grant period: 2024-01 @@ -451,7 +451,7 @@ # parent"). mo_tanf_assistance_unit_size: 1 mo_tanf_eligible: true - mo_tanf: 135.69 + mo_tanf: 135 - name: An SSI child's own savings do not disqualify the caretaker-only unit period: 2024-01 @@ -484,7 +484,7 @@ mo_tanf_resources_eligible: true mo_tanf_assistance_unit_size: 1 mo_tanf_eligible: true - mo_tanf: 135.69 + mo_tanf: 135 - name: A family whose child and caretaker both receive SSI has no payable unit period: 2024-01 @@ -543,7 +543,7 @@ # size-2 payment level while the SSI child is excluded. mo_tanf_assistance_unit_size: 2 mo_tanf_eligible: true - mo_tanf: 234.09 + mo_tanf: 234 - name: An SSI child with an eligible sibling stays out of the unit period: 2024-01 @@ -576,7 +576,7 @@ # (13 CSR 40-2.310(1)(F); DSS Manual 0210.005.10). mo_tanf_assistance_unit_size: 2 mo_tanf_eligible: true - mo_tanf: 234.09 + mo_tanf: 234 - name: A caretaker's own assets can still fail the resource test period: 2024-01 @@ -636,12 +636,13 @@ output: # Per earner: $90 + $30 + ($200 - $120) / 3 = $146.67; x2 = $293.33 # (13 CSR 40-2.310(9)(A) applies the disregards to each participant). - # Countable: $400 - $293.33 = $106.67; benefit: $292.09 - $106.67. + # Countable: $400 - $293.33 = $106.67; benefit: $292.09 - $106.67 + # = $185.42, lowered to $185. mo_tanf_gross_earned_income: 400 mo_tanf_earned_income_deductions: 293.33 mo_tanf_countable_income: 106.67 mo_tanf_eligible: true - mo_tanf: 185.42 + mo_tanf: 185 - name: Two enrolled earners end to end period: 2024-01 @@ -677,7 +678,7 @@ mo_tanf_earned_income_deductions: 400 mo_tanf_countable_income: 0 mo_tanf_eligible: true - mo_tanf: 292.09 + mo_tanf: 292 - name: A self-employment loss does not offset another earner's income period: 2024-01 @@ -708,11 +709,12 @@ # The loss earner's -$300 deduction cancels their -$300 gross, so # countable income equals the wage earner's own result: $480 - $240 # = $240 (13 CSR 40-2.310(9)(A) applies the disregards per - # participant; no cross-earner loss offset). Benefit: $292.09 - $240. + # participant; no cross-earner loss offset). Benefit: $292.09 - $240 + # = $52.09, lowered to $52. mo_tanf_gross_earned_income: 180 mo_tanf_earned_income_deductions: -60 mo_tanf_countable_income: 240 - mo_tanf: 52.09 + mo_tanf: 52 - name: A self-employment loss does not offset an enrolled earner's income period: 2024-01 @@ -745,4 +747,4 @@ # work exemption = $410; the loss earner's -$300 cancels their own # gross, leaving countable income $480 - $410 = $70. mo_tanf_countable_income: 70 - mo_tanf: 222.09 + mo_tanf: 222 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf.yaml index 3bf6eb8dbb4..ef45b7ba43c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf.yaml @@ -16,7 +16,9 @@ mo_tanf_standard_of_need: 846 mo_tanf_countable_income: 0 output: - mo_tanf: 292.09 + # 846 x 0.34526 = 292.08996, lowered to the whole dollar under + # 13 CSR 40-2.310(14). + mo_tanf: 292 - name: Eligible household with countable income receives reduced benefit period: 2024-01 @@ -26,7 +28,8 @@ mo_tanf_standard_of_need: 846 mo_tanf_countable_income: 100 output: - mo_tanf: 192.09 + # 292.08996 - 100 = 192.08996, lowered to 192. + mo_tanf: 192 - name: Benefit below $10 minimum threshold is not paid period: 2024-01 @@ -36,7 +39,7 @@ mo_tanf_standard_of_need: 846 mo_tanf_countable_income: 285 output: - # $292.09 - $285 = $7.09, but minimum payment is $10 + # $292.08996 - $285 = $7.08996, lowered to $7, below the $10 minimum. mo_tanf: 0 - name: Benefit of $10 @@ -47,7 +50,8 @@ mo_tanf_standard_of_need: 846 mo_tanf_countable_income: 282 output: - mo_tanf: 10.09 + # $292.08996 - $282 = $10.08996, lowered to $10. + mo_tanf: 10 - name: Countable income exceeds maximum benefit results in zero period: 2024-01 @@ -58,3 +62,47 @@ mo_tanf_countable_income: 300 output: mo_tanf: 0 + +- name: A size-two zero-income grant is lowered to whole dollars. + period: 2026-01 + input: + state_code: MO + mo_tanf_eligible: true + mo_tanf_standard_of_need: 678 + mo_tanf_countable_income: 0 + output: + # 678 x 0.34526 = 234.08628; 13 CSR 40-2.310(14) lowers it to 234. + mo_tanf: 234 + +- name: A payable grant just above ten dollars is lowered to ten. + period: 2026-01 + input: + state_code: MO + mo_tanf_eligible: true + mo_tanf_standard_of_need: 678 + mo_tanf_countable_income: 223.25 + output: + # 234.08628 - 223.25 = 10.83628, lowered to 10. + mo_tanf: 10 + +- name: A grant below ten dollars remains unpaid. + period: 2026-01 + input: + state_code: MO + mo_tanf_eligible: true + mo_tanf_standard_of_need: 678 + mo_tanf_countable_income: 224.10 + output: + # 234.08628 - 224.10 = 9.98628, lowered to 9, below the $10 minimum. + mo_tanf: 0 + +- name: A grant of exactly ten dollars is paid. + period: 2026-01 + input: + state_code: MO + mo_tanf_eligible: true + mo_tanf_maximum_benefit: 234 + mo_tanf_countable_income: 224 + output: + # 234 - 224 = 10, not less than the $10 minimum. + mo_tanf: 10 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_countable_resources.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_countable_resources.yaml index 2188605a31f..e2b632fe128 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_countable_resources.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_countable_resources.yaml @@ -215,7 +215,7 @@ # The needy grandparent is included. mo_tanf_non_parent_caretaker_included: true mo_tanf_countable_resources: 0 - mo_tanf: 234.086 + mo_tanf: 234 - name: An included non-parent caretaker's resources count period: 2026-01 @@ -243,4 +243,4 @@ # included in the assistance group, count the NPCR's resources." mo_tanf_non_parent_caretaker_included: true mo_tanf_countable_resources: 800 - mo_tanf: 234.086 + mo_tanf: 234 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_gross_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_gross_unearned_income.yaml index 2c9f0329b60..a7d890b6188 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_gross_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_gross_unearned_income.yaml @@ -82,7 +82,7 @@ mo_tanf_gross_unearned_income: 200.0 mo_tanf_assistance_unit_size: 2 mo_tanf_eligible: true - mo_tanf: 34.08628 + mo_tanf: 34 - name: Unemployment remains countable period: 2026-01 @@ -112,7 +112,7 @@ mo_tanf_gross_unearned_income: 200.0 mo_tanf_assistance_unit_size: 2 mo_tanf_eligible: true - mo_tanf: 34.08628 + mo_tanf: 34 - name: Mixed workers compensation and unemployment are counted once period: 2026-01 @@ -142,7 +142,7 @@ mo_tanf_gross_unearned_income: 200.0 mo_tanf_assistance_unit_size: 2 mo_tanf_eligible: true - mo_tanf: 34.08628 + mo_tanf: 34 - name: No income receives the maximum grant period: 2026-01 @@ -172,7 +172,7 @@ mo_tanf_gross_unearned_income: 0.0 mo_tanf_assistance_unit_size: 2 mo_tanf_eligible: true - mo_tanf: 234.08628 + mo_tanf: 234 - name: Workers compensation above the payment standard makes the unit ineligible period: 2026-01 @@ -235,7 +235,7 @@ mo_tanf_is_assistance_unit_member: [true, true, false] mo_tanf_gross_unearned_income: 50 mo_tanf_assistance_unit_size: 2 - mo_tanf: 184.08628 + mo_tanf: 184 - name: Workers compensation of an SSI recipient does not count period: 2026-01 @@ -264,7 +264,7 @@ mo_tanf_is_assistance_unit_member: [false, true] mo_tanf_gross_unearned_income: 0 mo_tanf_assistance_unit_size: 1 - mo_tanf: 135.68718 + mo_tanf: 135 - name: Taxable and tax-exempt interest are excluded from Missouri TANF income period: 2026-01 @@ -295,7 +295,7 @@ mo_tanf_gross_unearned_income: 0 mo_tanf_countable_income: 0 mo_tanf_eligible: true - mo_tanf: 234.08628 + mo_tanf: 234 - name: Interest is excluded while dividends and workers compensation count period: 2026-01 @@ -329,7 +329,7 @@ mo_tanf_gross_unearned_income: 200 mo_tanf_countable_income: 200 mo_tanf_eligible: true - mo_tanf: 34.08628 + mo_tanf: 34 - name: Disability payments and strike benefits count alongside workers compensation period: 2026-01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_is_assistance_unit_member.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_is_assistance_unit_member.yaml index c723459a5e5..0d760a76cb9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_is_assistance_unit_member.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/mo_tanf_is_assistance_unit_member.yaml @@ -1,8 +1,10 @@ # Parents in the Missouri TANF assistance unit when someone else claims them # as a tax dependent: 13 CSR 40-2.300(5)(C), 13 CSR 40-2.310(1)(F) and # DSS Manual 0210.005.05, 0210.005.10 and 0210.005.30. -# Standard of need: size 1 $393, size 2 $678, size 3 $846. Payment is 34.526% -# of the standard: size 1 $135.687, size 2 $234.086, size 3 $292.090. +# Standard of need: size 1 $393, size 2 $678, size 3 $846. The maximum is +# 34.526% of the standard (size 1 $135.687, size 2 $234.086, size 3 +# $292.090), and 13 CSR 40-2.310(14) lowers the payment to the whole +# dollar: size 1 $135, size 2 $234, size 3 $292. - name: A mother claimed as her own mother's tax dependent is the caretaker for her child period: 2026-01 @@ -35,7 +37,7 @@ mo_tanf_non_parent_caretaker: [false, false, false] mo_tanf_is_assistance_unit_member: [false, true, true] mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: The dependent mother's earnings count and the excluded grandparent's do not period: 2026-01 @@ -67,12 +69,12 @@ # 0210.005.05: "Include the needs and income of the following persons". # The mother's $300 less the $90 work expense, $30 and one-third of the # remaining $180 leaves $120 (13 CSR 40-2.310(9)(A)2-3): - # 234.086 - 120 = 114.086. The mother is 20, so no minor-parent deeming + # 234.086 - 120 = 114.086, lowered to 114. The mother is 20, so no minor-parent deeming # of the grandparent's income applies (13 CSR 40-2.310(8)(B)1.F). mo_tanf_is_assistance_unit_member: [false, true, true] mo_tanf_gross_earned_income: 300 mo_tanf_countable_income: 120 - mo_tanf: 114.086 + mo_tanf: 114 - name: An 18-year-old mother out of school is the caretaker, not a child period: 2026-01 @@ -105,7 +107,7 @@ mo_tanf_dependent_child: [false, false, true] mo_tanf_is_assistance_unit_member: [false, true, true] mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: An 18-year-old mother in high school is a cash-eligible child, grouped with her own mother under one reading period: 2026-01 @@ -148,7 +150,7 @@ mo_tanf_is_assistance_unit_member: [true, true, true] mo_tanf_gross_earned_income: 0 mo_tanf_assistance_unit_size: 3 - mo_tanf: 292.090 + mo_tanf: 292 - name: A 16-year-old mother, her baby and her mother form one three-generation group period: 2026-01 @@ -179,7 +181,7 @@ # assistance group". The minor mother is a member as an eligible child. mo_tanf_is_assistance_unit_member: [true, true, true] mo_tanf_assistance_unit_size: 3 - mo_tanf: 292.090 + mo_tanf: 292 - name: An unmarried father claimed as the mother's dependent is a member with their child period: 2026-01 @@ -210,7 +212,7 @@ # household with everyone included." mo_tanf_is_assistance_unit_member: [true, true, true] mo_tanf_assistance_unit_size: 3 - mo_tanf: 292.090 + mo_tanf: 292 - name: Two dependent parents living with the child's grandparents are both members period: 2026-01 @@ -247,7 +249,7 @@ mo_tanf_non_parent_caretaker: [false, false, false, false, false] mo_tanf_is_assistance_unit_member: [false, false, true, true, true] mo_tanf_assistance_unit_size: 3 - mo_tanf: 292.090 + mo_tanf: 292 - name: A dependent adult who is a parent, but not of an eligible child, is not a member period: 2026-01 @@ -282,7 +284,7 @@ mo_tanf_is_assistance_unit_member: [false, true, true] mo_tanf_assistance_unit_size: 2 mo_tanf_gross_unearned_income: 0 - mo_tanf: 234.086 + mo_tanf: 234 - name: Without the input, a grandmother young enough to be the child's parent is counted as one period: 2026-01 @@ -347,7 +349,7 @@ # parent and child, so her own child in the home must be someone else. mo_tanf_is_parent_of_dependent_child: [false, false, false] mo_tanf_is_assistance_unit_member: [false, true, true] - mo_tanf: 234.086 + mo_tanf: 234 - name: The default rules out a dependent too young to be the child's parent period: 2026-01 @@ -377,7 +379,7 @@ # 19 - 9 = 10 years, fewer than the 12 the default requires. mo_tanf_is_parent_of_dependent_child: [false, false, false] mo_tanf_is_assistance_unit_member: [true, false, true] - mo_tanf: 234.086 + mo_tanf: 234 - name: A dependent parent on SSI is not a member but still excludes the non-parent caretaker period: 2026-01 @@ -411,7 +413,7 @@ mo_tanf_non_parent_caretaker: [false, false, false] mo_tanf_is_assistance_unit_member: [false, false, true] mo_tanf_assistance_unit_size: 1 - mo_tanf: 135.687 + mo_tanf: 135 - name: A grandparent marked as a non-parent stays out even when their own adult child is counted period: 2026-01 @@ -446,7 +448,7 @@ mo_tanf_is_parent_of_dependent_child: [true, true, false] mo_tanf_is_assistance_unit_member: [false, true, true] mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: A mandatory dependent parent's income can eliminate the children's grant period: 2026-01 @@ -524,15 +526,15 @@ members: [grandparent, mother, child1, child2, child3] state_code: MO output: - # 990 * 0.34526 = 341.8074, $49.71744 more than the three children's - # $292.08996 without their mother. + # 990 * 0.34526 = 341.8074, lowered to $341, $49 more than the three + # children's $292 without their mother. mo_tanf_is_assistance_unit_member: [false, true, true, true, true] mo_tanf_assistance_unit_size: 4 mo_tanf_gross_earned_income: 0 mo_tanf_gross_unearned_income: 0 mo_tanf_countable_income: 0 mo_tanf_standard_of_need_test: true - mo_tanf: 341.8074 + mo_tanf: 341 - name: An explicit parent flag requires a dependent child in that person's tax unit period: 2026-01 @@ -567,7 +569,7 @@ # size 3 and 846 * 0.34526 = 292.08996. mo_tanf_is_assistance_unit_member: [true, true, false] mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.08628 + mo_tanf: 234 - name: A real second parent filing separately stays excluded despite an explicit true parent flag period: 2026-01 @@ -607,4 +609,4 @@ mo_tanf_is_parent_of_dependent_child: [false, false, true] mo_tanf_is_assistance_unit_member: [true, true, false] mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.08628 + mo_tanf: 234 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/non_parent_caretaker/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/non_parent_caretaker/integration.yaml index fc30561c8db..0e922dc1ea2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/non_parent_caretaker/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/non_parent_caretaker/integration.yaml @@ -1,8 +1,10 @@ # Missouri TANF non-parent caretaker relative (NPCR) rules: 13 CSR 40-2.300(5)(D), # 13 CSR 40-2.310(3) and (8)(B)1.C, and DSS Manual 0210.005.05, 0210.005.10, # 0210.005.15, 0210.005.35, 0210.005.40 and 0210.010.10. -# Standard of need: size 1 $393, size 2 $678, size 3 $846. Payment is 34.526% -# of the standard: size 1 $135.687, size 2 $234.086, size 3 $292.090. +# Standard of need: size 1 $393, size 2 $678, size 3 $846. The maximum is +# 34.526% of the standard (size 1 $135.687, size 2 $234.086, size 3 +# $292.090), and 13 CSR 40-2.310(14) lowers the payment to the whole +# dollar: size 1 $135, size 2 $234, size 3 $292. - name: Grandparent with no spouse and $100 unemployment is left out because inclusion lowers the grant period: 2026-01 @@ -30,18 +32,18 @@ state_code: MO output: # No spouse in the home, so the NPCR is needy without a budget - # (DSS Manual 0210.005.35). Included: 292.090 - 100 = 192.090. - # Excluded: the two children alone get 234.086. DSS Manual 0210.005.15 + # (DSS Manual 0210.005.35). Included: 292.090 - 100 = 192.090, paid as 192. + # Excluded: the two children alone get 234. DSS Manual 0210.005.15 # forbids an optional inclusion that reduces the grant. mo_tanf_non_parent_caretaker: [true, false, false] mo_tanf_non_parent_caretaker_needy: true - mo_tanf_if_non_parent_caretaker_included: 192.090 - mo_tanf_if_non_parent_caretaker_excluded: 234.086 + mo_tanf_if_non_parent_caretaker_included: 192 + mo_tanf_if_non_parent_caretaker_excluded: 234 mo_tanf_non_parent_caretaker_included: false mo_tanf_is_assistance_unit_member: [false, true, true] mo_tanf_assistance_unit_size: 2 mo_tanf_gross_unearned_income: 0 - mo_tanf: 234.086 + mo_tanf: 234 - name: The same household without the non-parent input keeps the grandparent as a caretaker period: 2026-01 @@ -70,7 +72,7 @@ mo_tanf_non_parent_caretaker: [false, false, false] mo_tanf_non_parent_caretaker_needy: false mo_tanf_assistance_unit_size: 3 - mo_tanf: 192.090 + mo_tanf: 192 - name: Grandparent with $700 unemployment and a $0 spouse is not needy and is excluded period: 2026-01 @@ -112,7 +114,7 @@ mo_tanf_is_assistance_unit_member: [false, false, true, true] mo_tanf_assistance_unit_size: 2 mo_tanf_eligible: true - mo_tanf: 234.086 + mo_tanf: 234 - name: The same household without the non-parent input stays ineligible at size four period: 2026-01 @@ -169,12 +171,12 @@ state_code: MO output: mo_tanf_non_parent_caretaker_needy: true - mo_tanf_if_non_parent_caretaker_included: 292.090 - mo_tanf_if_non_parent_caretaker_excluded: 234.086 + mo_tanf_if_non_parent_caretaker_included: 292 + mo_tanf_if_non_parent_caretaker_excluded: 234 mo_tanf_non_parent_caretaker_included: true mo_tanf_is_assistance_unit_member: [true, true, true] mo_tanf_assistance_unit_size: 3 - mo_tanf: 292.090 + mo_tanf: 292 - name: A needy grandparent who opts out is excluded even though inclusion would raise the grant period: 2026-01 @@ -206,7 +208,7 @@ mo_tanf_non_parent_caretaker_needy: true mo_tanf_non_parent_caretaker_included: false mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: Grandparent with a $0 spouse is needy through the budget and is included period: 2026-01 @@ -241,7 +243,7 @@ mo_tanf_non_parent_caretaker_included: true mo_tanf_is_assistance_unit_member: [true, false, true, true] mo_tanf_assistance_unit_size: 3 - mo_tanf: 292.090 + mo_tanf: 292 - name: A spouse with $700 of income makes the grandparent not needy period: 2026-01 @@ -274,7 +276,7 @@ mo_tanf_non_parent_caretaker_countable_income: 700 mo_tanf_non_parent_caretaker_needy: false mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: The same spouse on SSI makes the grandparent needy without a budget period: 2026-01 @@ -312,7 +314,7 @@ mo_tanf_non_parent_caretaker_included: true mo_tanf_is_assistance_unit_member: [true, false, true, true] mo_tanf_assistance_unit_size: 3 - mo_tanf: 292.090 + mo_tanf: 292 - name: When the head receives SSI the spouse is the caretaker relative and is needy period: 2026-01 @@ -347,7 +349,7 @@ mo_tanf_non_parent_caretaker_included: true mo_tanf_is_assistance_unit_member: [false, true, true] mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: A grandparent with $800 and no spouse is needy but excluded because inclusion makes the unit ineligible period: 2026-01 @@ -381,7 +383,7 @@ mo_tanf_if_non_parent_caretaker_included: 0 mo_tanf_non_parent_caretaker_included: false mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: A needy grandparent whose only grandchild receives SSI receives a caretaker-only grant period: 2026-01 @@ -411,7 +413,7 @@ mo_tanf_non_parent_caretaker_included: true mo_tanf_is_assistance_unit_member: [true, false] mo_tanf_assistance_unit_size: 1 - mo_tanf: 135.687 + mo_tanf: 135 - name: A grandparent is not a non-parent caretaker when the grandchild's adult mother lives in the home period: 2026-01 @@ -448,7 +450,7 @@ mo_tanf_non_parent_caretaker_needy: false mo_tanf_is_assistance_unit_member: [false, true, true] mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: A mother who is herself a dependent child does not block her aunt as caretaker relative period: 2026-01 @@ -481,7 +483,7 @@ mo_tanf_non_parent_caretaker: [true, false, false] mo_tanf_non_parent_caretaker_included: true mo_tanf_assistance_unit_size: 3 - mo_tanf: 292.090 + mo_tanf: 292 - name: A stepparent marked by mistake as a non-parent is still excluded when the parent is in the home period: 2026-01 @@ -538,8 +540,8 @@ members: [grandparent, grandchild1, grandchild2] state_code: MO output: - # 12 months of the children-only grant: 12 x 234.086. - mo_tanf: 2_809.04 + # 12 months of the children-only grant: 12 x 234. + mo_tanf: 2_808 - name: An excluded caretaker's resources do not count period: 2026-01 @@ -573,4 +575,4 @@ mo_tanf_non_parent_caretaker_included: false mo_tanf_countable_resources: 0 mo_tanf_resources_eligible: true - mo_tanf: 234.086 + mo_tanf: 234 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/non_parent_caretaker/mo_tanf_non_parent_caretaker.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/non_parent_caretaker/mo_tanf_non_parent_caretaker.yaml index aeb93628404..f37e1f625fc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/non_parent_caretaker/mo_tanf_non_parent_caretaker.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/dss/tanf/non_parent_caretaker/mo_tanf_non_parent_caretaker.yaml @@ -74,7 +74,7 @@ mo_tanf_non_parent_caretaker_needy: true mo_tanf_non_parent_caretaker_included: true mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: A non-parent caretaker who receives SSI is never the caretaker relative period: 2026-01 @@ -101,7 +101,7 @@ mo_tanf_non_parent_caretaker: [false, false] mo_tanf_non_parent_caretaker_needy: false mo_tanf_is_assistance_unit_member: [false, true] - mo_tanf: 135.687 + mo_tanf: 135 - name: A parent on SSI in the home still excludes a non-parent caretaker period: 2026-01 @@ -198,7 +198,7 @@ mo_tanf_non_parent_caretaker: [true, false, false] mo_tanf_non_parent_caretaker_included: true mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 - name: Without the override the default rules out a great-grandparent too old to be the child's parent period: 2026-01 @@ -233,5 +233,5 @@ mo_tanf_non_parent_caretaker: [true, false, false] mo_tanf_non_parent_caretaker_included: true mo_tanf_assistance_unit_size: 2 - mo_tanf: 234.086 + mo_tanf: 234 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml index 4b4635f8e8e..911e6599cb4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml @@ -71,7 +71,7 @@ mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME # 13,055.88 / 12 = 1,087.99 countable a month, so federal SSI is 0 and # the supplement is 94 - (1,087.99 - 994) = 0.01, raised to $1. - ssi_countable_income: 13_055.88 + ssi_countable_income: {2026: 13_055.88} output: ssi: 0 mt_ssp: 1 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 1aa82f7f1c2..c2e7577590d 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 @@ -222,3 +222,87 @@ state_code: PA output: meets_snap_gross_income_test: true + +- name: Guam two-person household passes the FY 2027 gross income test + period: 2026-10 + input: + people: + person1: {} + person2: {} + spm_units: + spm_unit: + members: [person1, person2] + snap_unit_size: 2 + has_snap_elderly_disabled_member: false + # 7 CFR 273.9(a)(1)(i): Guam uses 130% of the poverty guidelines + # for the 48 contiguous States. FY 2027 uses the 2026 guidelines + # ($15,960 + $5,680 = $21,640 for two people); 130% x $21,640 / 12 + # = $2,344.33, rounded up to $2,345. + snap_gross_test_income: 1_000 + households: + household: + members: [person1, person2] + state_code: GU + output: + meets_snap_gross_income_test: true + +- name: Virgin Islands two-person household at the FY 2027 gross limit passes + period: 2026-10 + input: + people: + person1: {} + person2: {} + spm_units: + spm_unit: + members: [person1, person2] + snap_unit_size: 2 + has_snap_elderly_disabled_member: false + # $2,345 is the two-person contiguous-state gross limit, which the + # Virgin Islands also use (7 CFR 273.9(a)(1)(i)). + snap_gross_test_income: 2_345 + households: + household: + members: [person1, person2] + state_code: VI + output: + meets_snap_gross_income_test: true + +- name: Virgin Islands two-person household just above the FY 2027 gross limit fails + period: 2026-10 + input: + people: + person1: {} + person2: {} + spm_units: + spm_unit: + members: [person1, person2] + snap_unit_size: 2 + has_snap_elderly_disabled_member: false + snap_gross_test_income: 2_346 + households: + household: + members: [person1, person2] + state_code: VI + output: + meets_snap_gross_income_test: false + +- name: Ten-person Guam household at the FY 2027 gross limit passes + period: 2026-10 + input: + people: + person1: {} + spm_units: + spm_unit: + members: [person1] + snap_unit_size: 10 + has_snap_elderly_disabled_member: false + # Guam uses the contiguous-state standard and its separately rounded + # increment: $6,037 for eight people plus 2 x $616 gives $7,269 + # (7 CFR 273.9(a)(1)(i), (a)(3)(i)). + snap_gross_test_income: 7_269 + households: + household: + members: [person1] + state_code: GU + output: + meets_snap_gross_income_test: true 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 85238ffc712..2ccef2565eb 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 @@ -272,3 +272,81 @@ state_code: HI output: meets_snap_net_income_test: true + +- name: Guam two-person household passes the FY 2027 net income test + period: 2026-10 + input: + people: + person1: {} + person2: {} + spm_units: + spm_unit: + members: [person1, person2] + snap_unit_size: 2 + # 7 CFR 273.9(a)(2)(i): Guam uses the poverty guidelines for the 48 + # contiguous States. FY 2027 uses the 2026 guidelines ($15,960 + + # $5,680 = $21,640 for two people); $21,640 / 12 = $1,803.33, + # rounded up to $1,804. + snap_net_income: 365 + households: + household: + members: [person1, person2] + state_code: GU + output: + meets_snap_net_income_test: true + +- name: Virgin Islands two-person household at the FY 2027 net limit passes + period: 2026-10 + input: + people: + person1: {} + person2: {} + spm_units: + spm_unit: + members: [person1, person2] + snap_unit_size: 2 + snap_net_income: 1_804 + households: + household: + members: [person1, person2] + state_code: VI + output: + meets_snap_net_income_test: true + +- name: Virgin Islands two-person household just above the FY 2027 net limit fails + period: 2026-10 + input: + people: + person1: {} + person2: {} + spm_units: + spm_unit: + members: [person1, person2] + snap_unit_size: 2 + snap_net_income: 1_805 + households: + household: + members: [person1, person2] + state_code: VI + output: + meets_snap_net_income_test: false + +- name: Nine-person Virgin Islands household at the FY 2027 net limit passes + period: 2026-10 + input: + people: + person1: {} + spm_units: + spm_unit: + members: [person1] + snap_unit_size: 9 + # The contiguous-state eight-person standard of $4,644 plus the + # separately rounded $474 increment gives $5,118 + # (7 CFR 273.9(a)(2)(i), (a)(3)(ii)). + snap_net_income: 5_118 + households: + household: + members: [person1] + state_code: VI + output: + meets_snap_net_income_test: true 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 c924c743ae7..2d8daeed80d 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 @@ -19,3 +19,42 @@ output: spm_unit_fpg: 13590 + 4720 * 2 snap_fpg: (12880 + 4540 * 2) / 12 * 9 + (13590 + 4720 * 2) / 12 * 3 + +- name: Guam uses the contiguous-state SNAP poverty guideline in FY 2027 + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + person1: {} + person2: {} + spm_units: + spm_unit: + members: [person1, person2] + snap_unit_size: 2 + households: + household: + members: [person1, person2] + state_code: GU + output: + # 7 CFR 273.9(a)(1)(i), (a)(2)(i): ($15,960 + $5,680) / 12. + snap_fpg: 1_803.33 + +- name: The Virgin Islands use the contiguous-state SNAP poverty guideline in FY 2027 + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + person1: {} + person2: {} + person3: {} + spm_units: + spm_unit: + members: [person1, person2, person3] + snap_unit_size: 3 + households: + household: + members: [person1, person2, person3] + state_code: VI + output: + # ($15,960 + 2 x $5,680) / 12. + snap_fpg: 2_276.67 diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/_apply_ssi_exclusions.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/_apply_ssi_exclusions.py index 0b5de7a0f16..c60a0f8965f 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/_apply_ssi_exclusions.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/_apply_ssi_exclusions.py @@ -1,7 +1,7 @@ from policyengine_us.model_api import * -def _apply_ssi_exclusions( +def _apply_ssi_exclusions_monthly( earned_income: ArrayLike, unearned_income: ArrayLike, parameters: ParameterNode, @@ -9,35 +9,51 @@ def _apply_ssi_exclusions( general_exclusion: ArrayLike = None, ) -> ArrayLike: """ - Applies standard SSI income exclusions to convert total earned/unearned income - into countable income. This follows 20 CFR §§ 416.1112 and 416.1124: + Applies standard SSI income exclusions to one month's earned and unearned + income, returning that month's countable income. This follows + 20 CFR §§ 416.1112 and 416.1124: 1) Subtract $20 'general' exclusion from unearned (or from earned if unearned < 20). 2) Subtract $65 'earned' exclusion from any remaining earned income. 3) Exclude 50% of the remainder of earned income. - - All amounts are stored as annual but exclusions are monthly amounts, so we convert - from annual -> monthly, apply monthly exclusions, then go back to annual. """ - # Convert from annual to monthly - earned_monthly = earned_income / MONTHS_IN_YEAR - unearned_monthly = unearned_income / MONTHS_IN_YEAR - p = parameters(period).gov.ssa.ssi.income.exclusions general_exclusion = p.general if general_exclusion is None else general_exclusion # Step 1: Subtract general exclusion from unearned first - applied_general = min_(general_exclusion, unearned_monthly) - countable_unearned = unearned_monthly - applied_general + applied_general = min_(general_exclusion, unearned_income) + countable_unearned = unearned_income - applied_general # The remainder of the general exclusion (if unearned < 20) applies to earned leftover_general = general_exclusion - applied_general total_earned_excl = p.earned + leftover_general # Step 2 & 3: Apply the total earned exclusion, then exclude 50% of the rest - earned_after_flat = max_(earned_monthly - total_earned_excl, 0) + earned_after_flat = max_(earned_income - total_earned_excl, 0) # The 'earned_share' parameter is the fraction excluded above the flat. Typically 0.5 => 50% excluded. # So the fraction that remains is (1 - earned_share). countable_earned = earned_after_flat * (1.0 - p.earned_share) - # Convert back to annual - return (countable_unearned + countable_earned) * MONTHS_IN_YEAR + return countable_unearned + countable_earned + + +def _apply_ssi_exclusions( + earned_income: ArrayLike, + unearned_income: ArrayLike, + parameters: ParameterNode, + period: Period, + general_exclusion: ArrayLike = None, +) -> ArrayLike: + """ + Applies the standard SSI income exclusions to annual earned and unearned + income, treating each month as one-twelfth of the year, and returns + annual countable income. The exclusions are monthly amounts, so this + converts annual -> monthly, applies them, then goes back to annual. + """ + monthly = _apply_ssi_exclusions_monthly( + earned_income / MONTHS_IN_YEAR, + unearned_income / MONTHS_IN_YEAR, + parameters, + period, + general_exclusion, + ) + return monthly * MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_in_kind_support_and_maintenance.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_in_kind_support_and_maintenance.py index 993b20fe33b..7e87a1d5e5b 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_in_kind_support_and_maintenance.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_in_kind_support_and_maintenance.py @@ -6,7 +6,7 @@ class ssi_in_kind_support_and_maintenance(Variable): entity = Person label = "SSI in-kind support and maintenance" unit = USD - definition_period = YEAR + definition_period = MONTH reference = ( "https://www.law.cornell.edu/cfr/text/20/416.1130", "https://www.law.cornell.edu/cfr/text/20/416.1140", @@ -14,7 +14,7 @@ class ssi_in_kind_support_and_maintenance(Variable): def formula(person, period, parameters): # ISM is counted as unearned income for own-household recipients - # who receive shelter (or food, pre-10/2024) support. + # who receive shelter support (or food, before September 30, 2024). # The PMV rule caps ISM at the presumed maximum value. # If the recipient provides an actual shelter value (rebuttal), # ISM is the lesser of the actual value and the PMV. diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_pmv_amount.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_pmv_amount.py index 18c70e2a9be..101b7aa6b11 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_pmv_amount.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_pmv_amount.py @@ -6,7 +6,7 @@ class ssi_pmv_amount(Variable): entity = Person label = "SSI presumed maximum value amount" unit = USD - definition_period = YEAR + definition_period = MONTH reference = ( "https://www.law.cornell.edu/cfr/text/20/416.1140", "https://secure.ssa.gov/poms.nsf/lnx/0500835300", @@ -34,5 +34,4 @@ def formula(person, period, parameters): pmv_monthly = ( applicable_fbr * p.income.ism.pmv_fbr_fraction + p.income.exclusions.general ) - pmv_monthly = where(deeming_applies, pmv_monthly / 2, pmv_monthly) - return pmv_monthly * MONTHS_IN_YEAR + return where(deeming_applies, pmv_monthly / 2, pmv_monthly) diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_pmv_applies.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_pmv_applies.py index afad7780207..be38891ec2e 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_pmv_applies.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_pmv_applies.py @@ -8,10 +8,11 @@ class ssi_pmv_applies(Variable): value_type = bool entity = Person label = "SSI presumed maximum value rule applies" - definition_period = YEAR + definition_period = MONTH reference = ( "https://www.law.cornell.edu/cfr/text/20/416.1140", "https://www.law.cornell.edu/cfr/text/20/416.1141", + "https://www.govinfo.gov/content/pkg/FR-2024-03-27/pdf/2024-06464.pdf#page=1", ) def formula(person, period, parameters): @@ -20,9 +21,19 @@ def formula(person, period, parameters): receives_shelter = person( "ssi_receives_outside_shelter_support", period ) | person("ssi_receives_shelter_from_others_in_household", period) - # Before 9/30/2024, food also counted as ISM under the PMV rule. - # After 9/30/2024, only shelter triggers PMV (89 FR 21210). - food_counts = parameters(period).gov.ssa.ssi.income.ism.food_counts + # Food stopped counting as ISM on September 30, 2024 (89 FR 21199). + # Food still counts in a month that starts before that date, except + # when all of the month's food from others was received or paid for + # on or after it. The PMV is not prorated by day. + food_counts = bool(parameters(period).gov.ssa.ssi.income.ism.food_counts) + food_counts_at_month_end = bool( + parameters(period.stop).gov.ssa.ssi.income.ism.food_counts + ) + exclusion_starts_this_month = food_counts and not food_counts_at_month_end + food_only_after_exclusion = person( + "ssi_food_from_others_only_on_or_after_food_exclusion", period + ) + late_food_excluded = exclusion_starts_this_month & food_only_after_exclusion receives_food = person("ssi_receives_food_from_others", period) - receives_ism_support = receives_shelter | (receives_food & food_counts) - return is_own_household & receives_ism_support + receives_counted_food = receives_food & food_counts & ~late_food_excluded + return is_own_household & (receives_shelter | receives_counted_food) diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_shelter_support_value.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_shelter_support_value.py index d7d9ca3d72e..e8c73bef159 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_shelter_support_value.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ism/ssi_shelter_support_value.py @@ -4,9 +4,9 @@ class ssi_shelter_support_value(Variable): value_type = float entity = Person - label = "Actual annual value of shelter support received for SSI ISM purposes" + label = "Actual monthly value of shelter support received for SSI ISM purposes" unit = USD - definition_period = YEAR + definition_period = MONTH reference = ( "https://www.law.cornell.edu/cfr/text/20/416.1140", "https://secure.ssa.gov/poms.nsf/lnx/0500835300", diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ssi_countable_income.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ssi_countable_income.py index 1d268ec5b6c..f946a4da8c9 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ssi_countable_income.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/ssi_countable_income.py @@ -1,6 +1,6 @@ from policyengine_us.model_api import * from policyengine_us.variables.gov.ssa.ssi.eligibility.income._apply_ssi_exclusions import ( - _apply_ssi_exclusions, + _apply_ssi_exclusions_monthly, ) @@ -9,7 +9,7 @@ class ssi_countable_income(Variable): entity = Person label = "SSI countable income" unit = USD - definition_period = YEAR + definition_period = MONTH reference = "https://www.law.cornell.edu/uscode/text/42/1382a#b" documentation = """ Calculates total countable income for SSI: @@ -23,9 +23,15 @@ class ssi_countable_income(Variable): ISM is added to unearned income BEFORE exclusions per 20 CFR § 416.1140. The PMV formula includes $20 so that after the general exclusion applies, the net countable ISM equals 1/3 FBR (matching POMS SI 00835.901 values). + + Countable income is computed for each month, so a change in ISM within a + year (such as food ceasing to count on September 30, 2024) reaches the + months it applies to. Annual queries sum the months. """ def formula(person, period, parameters): + # YEAR-defined income amounts read for a month are one-twelfth of the + # annual amount; ISM is MONTH-defined and read for the month itself. # The individual's earned income, after the blind/disabled student exclusion pre_reduction_earned_income = person("ssi_marital_earned_income", period) blind_disabled_working_student_income = person( @@ -56,7 +62,7 @@ def formula(person, period, parameters): total_unearned = unearned_income + parent_deemed + ism # Apply standard SSI exclusions to the individual's own income - personal_countable = _apply_ssi_exclusions( + personal_countable = _apply_ssi_exclusions_monthly( earned_income, total_unearned, parameters, diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/status/ssi_food_from_others_only_on_or_after_food_exclusion.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/status/ssi_food_from_others_only_on_or_after_food_exclusion.py new file mode 100644 index 00000000000..24d61dde203 --- /dev/null +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/status/ssi_food_from_others_only_on_or_after_food_exclusion.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class ssi_food_from_others_only_on_or_after_food_exclusion(Variable): + value_type = bool + entity = Person + label = ( + "All food from others this month came on or after the SSI food exclusion date" + ) + documentation = ( + "Whether all of the food this person received from others in the " + "month was received or paid for on or after the date food stopped " + "counting as in-kind support and maintenance (September 30, 2024). " + "Only matters in the month that date falls in." + ) + definition_period = MONTH + default_value = False + reference = ( + "https://www.govinfo.gov/content/pkg/FR-2024-03-27/pdf/2024-06464.pdf#page=1", + "https://secure.ssa.gov/apps10/referencearchive.nsf/links/09202024102751AM", + ) diff --git a/policyengine_us/variables/gov/states/hi/bessd/ccap/hi_ccap_tanf_income.py b/policyengine_us/variables/gov/states/hi/bessd/ccap/hi_ccap_tanf_income.py new file mode 100644 index 00000000000..fcc8726efc0 --- /dev/null +++ b/policyengine_us/variables/gov/states/hi/bessd/ccap/hi_ccap_tanf_income.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class hi_ccap_tanf_income(Variable): + value_type = float + entity = SPMUnit + label = "Hawaii TANF payments counted as Hawaii CCAP income" + unit = USD + definition_period = MONTH + defined_for = StateCode.HI + reference = ( + # HAR 17-798.3-10(b)(11), eff. 2021-08-06. + "https://www.law.cornell.edu/regulations/hawaii/Haw-Code-R-SS-17-798-3-10", + ) + + def formula(spm_unit, period, parameters): + # HAR 17-798.3-10(b)(11) counts "Hawaii public assistance payments + # prior to any reduction of the monthly assistance payment due to + # overpayment recovery". The model has no overpayment recovery, so + # the payment is the Hawaii TANF grant the family takes up. + # Reading tanf here would be circular: tanf sums every state's TANF + # program, several of which deduct childcare_expenses, which nets + # out child_care_subsidies, which includes Hawaii CCAP. hi_tanf under + # the TANF take-up gate equals tanf for Hawaii households. + if parameters(period).gov.hhs.tanf.abolish_tanf: + return 0 + takes_up = spm_unit("takes_up_tanf_if_eligible", period.this_year) + return spm_unit("hi_tanf", period) * takes_up diff --git a/policyengine_us/variables/gov/states/mo/dss/tanf/mo_tanf.py b/policyengine_us/variables/gov/states/mo/dss/tanf/mo_tanf.py index e80e86666d5..8357c2522ee 100644 --- a/policyengine_us/variables/gov/states/mo/dss/tanf/mo_tanf.py +++ b/policyengine_us/variables/gov/states/mo/dss/tanf/mo_tanf.py @@ -9,6 +9,9 @@ class mo_tanf(Variable): definition_period = MONTH reference = ( "https://www.law.cornell.edu/regulations/missouri/13-CSR-40-2-310", + # 13 CSR 40-2.310(14): payments lowered to the whole dollar; no + # payment below $10. + "https://www.sos.mo.gov/cmsimages/adrules/csr/current/13csr/13c40-2.pdf#page=36", "https://dssmanuals.mo.gov/temporary-assistance-case-management/0210-010-15/", ) defined_for = "mo_tanf_eligible" @@ -19,4 +22,8 @@ def formula(spm_unit, period, parameters): benefit = max_(maximum_benefit - countable_income, 0) capped_benefit = min_(benefit, maximum_benefit) p = parameters(period).gov.states.mo.dss.tanf - return where(capped_benefit >= p.minimum_payment, capped_benefit, 0) + # 13 CSR 40-2.310(14): "In the payment of TA benefits, the amount + # shall always be lowered to the nearest dollar interval", and a + # grant below $10 is not paid. + payment = np.floor(capped_benefit) + return where(payment >= p.minimum_payment, payment, 0) 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 3f51fc266e3..17eca74ecff 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 @@ -1,11 +1,26 @@ from policyengine_us.model_api import * +# 7 CFR 273.9(a)(1)(i), (a)(2)(i): +# https://www.govinfo.gov/content/pkg/CFR-2025-title7-vol4/pdf/CFR-2025-title7-vol4-sec273-9.pdf#page=1 +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. """ state_group = spm_unit.household("state_group_str", period.this_year) + state_group = np.where( + np.isin(state_group, SNAP_CONTIGUOUS_US_GUIDELINE_AREAS), + "CONTIGUOUS_US", + state_group, + ) year = period.start.year month = period.start.month if month >= 10: