diff --git a/changelog.d/ssi-monthly-ism-state-readers.fixed.md b/changelog.d/ssi-monthly-ism-state-readers.fixed.md new file mode 100644 index 00000000000..4d67fa4f3bf --- /dev/null +++ b/changelog.d/ssi-monthly-ism-state-readers.fixed.md @@ -0,0 +1 @@ +Count each month's SSI in-kind support and maintenance in Maryland PAA income and in California's supplement for a couple with one SSI and one CAPI spouse. Read each month's SSI couple status and income deemed from an ineligible spouse in Maryland PAA income. diff --git a/policyengine_us/tests/core/test_ssi_monthly_state_readers.py b/policyengine_us/tests/core/test_ssi_monthly_state_readers.py new file mode 100644 index 00000000000..271c224362e --- /dev/null +++ b/policyengine_us/tests/core/test_ssi_monthly_state_readers.py @@ -0,0 +1,265 @@ +"""Check Maryland PAA and California SSP against changes to one month's ISM.""" + +import numpy as np +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st + +from policyengine_core.periods import period as make_period +from policyengine_us import Simulation +from policyengine_us.system import system +from policyengine_us.variables.gov.ssa.ssi.eligibility.income._apply_ssi_exclusions import ( + _apply_ssi_exclusions, +) + + +YEAR = "2024" +MONTHS = tuple(f"{YEAR}-{month:02d}" for month in range(1, 13)) +VARIABLES = ( + "md_paa_countable_income", + "md_paa_imputed_federal_ssi", + "md_paa", + "ca_state_supplement_person", +) + + +def _situation(profiles, annual_earned, annual_unearned): + situation = { + group: {} + for group in ( + "people", + "households", + "tax_units", + "marital_units", + "families", + "spm_units", + ) + } + rows = {} + for name, profile in profiles.items(): + for state, citizenship in ( + ("MD", "CITIZEN"), + ("MD", "LEGAL_PERMANENT_RESIDENT"), + ("CA", "CITIZEN"), + ): + unit = f"{name}_{state}_{citizenship}" + person = f"{unit}_head" + members = [person] + rows[name, state, citizenship] = len(situation["people"]) + values = { + "age": {YEAR: 70}, + "is_tax_unit_head": {YEAR: True}, + "immigration_status": {YEAR: citizenship}, + "is_ssi_qualified_noncitizen": {YEAR: False}, + "ssi_earned_income": {YEAR: annual_earned}, + "ssi_unearned_income": {YEAR: annual_unearned}, + "ssi_in_kind_support_and_maintenance": dict(zip(MONTHS, profile)), + } + if state == "MD": + values.update( + md_paa_eligible={YEAR: True}, + md_paa_total_cost_of_care={YEAR: 18_000}, + ) + else: + # Hold federal SSI and the couple's income fixed to isolate + # the supplement's own-income reader for a CAPI spouse. + values.update( + ssi={YEAR: 7_200}, + ssi_countable_income={YEAR: 0}, + ) + situation["people"][person] = values + if state == "CA": + spouse = f"{unit}_spouse" + members.append(spouse) + situation["people"][spouse] = { + "age": {YEAR: 68}, + "is_tax_unit_spouse": {YEAR: True}, + "immigration_status": {YEAR: "LEGAL_PERMANENT_RESIDENT"}, + "ssi_qualifying_quarters_earnings": {YEAR: 39}, + "ssi_countable_income": {YEAR: 0}, + } + for group in ( + "households", + "tax_units", + "marital_units", + "families", + "spm_units", + ): + situation[group][unit] = {"members": members} + situation["households"][unit].update( + state_code={YEAR: state}, + living_arrangements_allow_for_food_preparation={YEAR: True}, + ) + return situation, rows + + +def _monthly(simulation, variable): + return np.stack([simulation.calculate(variable, month) for month in MONTHS]) + + +@settings(max_examples=8, deadline=None, database=None, derandomize=True) +@given( + ism=st.integers(20, 200), + extra=st.integers(1, 100), + annual_earned=st.integers(0, 1_200), + annual_unearned=st.integers(0, 2_400), +) +def test_state_readers_keep_a_december_ism_change_in_december( + ism, extra, annual_earned, annual_unearned +): + situation, rows = _situation( + {"constant": [ism] * 12, "changed": [ism] * 11 + [ism + extra]}, + annual_earned, + annual_unearned, + ) + simulation = Simulation(tax_benefit_system=system, situation=situation) + results = {name: _monthly(simulation, name) for name in VARIABLES} + + for name, values in results.items(): + assert np.all(values >= 0) + np.testing.assert_allclose( + simulation.calculate(name, YEAR), values.sum(axis=0), atol=0.01 + ) + for state, citizenship in ( + ("MD", "CITIZEN"), + ("MD", "LEGAL_PERMANENT_RESIDENT"), + ("CA", "CITIZEN"), + ): + constant = rows["constant", state, citizenship] + changed = rows["changed", state, citizenship] + np.testing.assert_allclose( + values[:11, constant], values[:11, changed], atol=0.01 + ) + + for citizenship in ("CITIZEN", "LEGAL_PERMANENT_RESIDENT"): + constant = rows["constant", "MD", citizenship] + changed = rows["changed", "MD", citizenship] + countable = results["md_paa_countable_income"][-1] + # ISM already uses the whole $20 exclusion, so each extra dollar + # increases this month's countable income by one dollar. + assert countable[changed] - countable[constant] == pytest.approx( + extra, abs=0.01 + ) + imputed = results["md_paa_imputed_federal_ssi"][-1] + payment = results["md_paa"][-1] + if citizenship == "CITIZEN": + assert imputed[constant] - imputed[changed] == pytest.approx( + extra, abs=0.01 + ) + # Federal SSI offsets the income change while both amounts + # remain below the federal benefit rate. + assert payment[changed] == pytest.approx(payment[constant], abs=0.01) + else: + assert imputed[constant] == imputed[changed] == 0 + assert payment[constant] - payment[changed] == pytest.approx( + extra, abs=0.01 + ) + + constant = rows["constant", "CA", "CITIZEN"] + changed = rows["changed", "CA", "CITIZEN"] + payment = results["ca_state_supplement_person"][-1] + assert payment[constant] - payment[changed] == pytest.approx(extra, abs=0.011) + + +@settings(max_examples=6, deadline=None, database=None, derandomize=True) +@given( + ism=st.integers(0, 400), + annual_earned=st.integers(0, 24_000), + annual_unearned=st.integers(0, 12_000), +) +def test_constant_ism_keeps_the_annual_exclusion_result( + ism, annual_earned, annual_unearned +): + situation, rows = _situation( + {"constant": [ism] * 12}, annual_earned, annual_unearned + ) + simulation = Simulation(tax_benefit_system=system, situation=situation) + # Constant monthly income must retain the existing annual computation. + annual_countable = _apply_ssi_exclusions( + np.array([annual_earned]), + np.array([annual_unearned + 12 * ism]), + simulation.tax_benefit_system.parameters, + make_period(YEAR), + )[0] + countable = annual_countable / 12 + for citizenship in ("CITIZEN", "LEGAL_PERMANENT_RESIDENT"): + index = rows["constant", "MD", citizenship] + np.testing.assert_allclose( + _monthly(simulation, "md_paa_countable_income")[:, index], + countable, + atol=0.01, + ) + + index = rows["constant", "CA", "CITIZEN"] + payment = _monthly(simulation, "ca_state_supplement_person")[:, index] + # POMS SI 01415.056 B.2.a gives the 2024 aged standard of $1,182.94. + # Federal administration rounds a positive balance and pays at least $1. + # https://secure.ssa.gov/apps10/poms.nsf/lnx/0501415056 + balance = max(1_182.94 - 600 - countable, 0) + if balance == 0: + np.testing.assert_allclose(payment, 0, atol=0.01) + else: + assert np.all(payment >= min(balance, 1)) + np.testing.assert_allclose(payment, max(balance, 1), atol=0.011) + + +def _maryland_reference_countable(monthly_earned, monthly_unearned): + # Maryland DHS deducts $20 from unearned income, then $65 and one-half + # of the remaining earned income. When unearned income falls short of + # $20, DHS applies the unused part to earnings (PAA Manual 900). Under + # COMAR 07.03.07.08(A), DHS takes these disregards "in any month". + # https://dhs.maryland.gov/documents/FIA/Manuals/Public%20Assistance%20to%20Adults%20%28PAA%29%20Manual/PAA%20900%20Calculation%20of%20Benefits%20rev%2011.22.docx + # https://regs.maryland.gov/us/md/exec/comar/07.03.07.08 + general = min(20, monthly_unearned) + return monthly_unearned - general + max(monthly_earned - 65 - (20 - general), 0) / 2 + + +@settings(max_examples=5, deadline=None, database=None, derandomize=True) +@given( + ism=st.integers(0, 19), + extra=st.integers(1, 19), + annual_earned=st.integers(0, 2_400), +) +def test_state_readers_keep_a_small_ism_exclusion_in_its_month( + ism, extra, annual_earned +): + # With ISM under $20 and no other unearned income, the unused part of + # the $20 exclusion reduces that month's earnings and no other month's. + profiles = {"constant": [ism] * 12, "changed": [ism] * 11 + [ism + extra]} + situation, rows = _situation(profiles, annual_earned, 0) + simulation = Simulation(tax_benefit_system=system, situation=situation) + countable = _monthly(simulation, "md_paa_countable_income") + supplement = _monthly(simulation, "ca_state_supplement_person") + + for name, profile in profiles.items(): + expected = [ + _maryland_reference_countable(annual_earned / 12, monthly_ism) + for monthly_ism in profile + ] + for citizenship in ("CITIZEN", "LEGAL_PERMANENT_RESIDENT"): + np.testing.assert_allclose( + countable[:, rows[name, "MD", citizenship]], expected, atol=0.01 + ) + + constant = rows["constant", "CA", "CITIZEN"] + changed = rows["changed", "CA", "CITIZEN"] + np.testing.assert_allclose( + supplement[:11, constant], supplement[:11, changed], atol=0.01 + ) + # SSA applies the same exclusions to this person's own income in each + # month (20 CFR 416.1112(c)). In December, as in every other month, it + # pays the 2024 aged standard of $1,182.94, less $600 of SSI and that + # month's countable income, rounded up to the cent. For example, $10 of + # ISM and $200 of earnings leave 0 + (200 - 65 - 10) / 2 = $62.50 + # countable and a $520.44 payment; a December ISM of $25 leaves + # (25 - 20) + (200 - 65) / 2 = $72.50 and a $510.44 payment. + for name, profile in profiles.items(): + expected = [ + 1_182.94 + - 600 + - _maryland_reference_countable(annual_earned / 12, monthly_ism) + for monthly_ism in profile + ] + np.testing.assert_allclose( + supplement[:, rows[name, "CA", "CITIZEN"]], expected, atol=0.011 + ) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/state_supplement/ca_state_supplement.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/state_supplement/ca_state_supplement.yaml index 85dc3480892..130345be6dd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/state_supplement/ca_state_supplement.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/cdss/state_supplement/ca_state_supplement.yaml @@ -833,3 +833,115 @@ # 1,233.94 - 0 - (800.0833 - 20) = 453.8567, which rounds up to 453.86 # (POMS SI 02001.010 A.2). ca_state_supplement_person: [453.86, 0] + +- name: California counts January's SSI in-kind support for the SSI member of a split SSI and CAPI couple. + period: 2024-01 + absolute_error_margin: 0.01 + input: + people: + citizen: + age: + 2024: 72 + immigration_status: + 2024: CITIZEN + ssi_receives_food_from_others: + 2024: true + noncitizen: + age: + 2024: 68 + immigration_status: + 2024: LEGAL_PERMANENT_RESIDENT + ssi_qualifying_quarters_earnings: + 2024: 39 + marital_units: + couple: + members: [citizen, noncitizen] + households: + household: + members: [citizen, noncitizen] + state_code: + 2024: CA + living_arrangements_allow_for_food_preparation: + 2024: true + output: + ca_capi_eligible_person: [false, true] + # Couple PMV = 1,415 / 3 + 20 = 491.666667 for the recipient. + ssi_in_kind_support_and_maintenance: [491.666667, 0] + # SSI = 1,415 / 2 - (491.666667 - 20) / 2 = 471.666667. + ssi: [471.666667, 0] + # Own income = 491.666667 - 20 = 471.666667. + # SSP = ceil_cent(1,182.94 - 471.666667 - 471.666667) = 239.61. + ca_state_supplement_person: [239.61, 0] + +- name: California counts November's SSI in-kind support in the split couple's November supplement. + period: 2024-11 + absolute_error_margin: 0.01 + input: + people: + citizen: + age: + 2024: 72 + immigration_status: + 2024: CITIZEN + ssi_receives_food_from_others: + 2024: true + noncitizen: + age: + 2024: 68 + immigration_status: + 2024: LEGAL_PERMANENT_RESIDENT + ssi_qualifying_quarters_earnings: + 2024: 39 + marital_units: + couple: + members: [citizen, noncitizen] + households: + household: + members: [citizen, noncitizen] + state_code: + 2024: CA + living_arrangements_allow_for_food_preparation: + 2024: true + output: + ca_capi_eligible_person: [false, true] + # November uses September food; the January arithmetic still applies. + ssi_in_kind_support_and_maintenance: [491.666667, 0] + ssi: [471.666667, 0] + ca_state_supplement_person: [239.61, 0] + +- name: California counts no SSI in-kind support in the split couple's December 2024 supplement. + period: 2024-12 + absolute_error_margin: 0.01 + input: + people: + citizen: + age: + 2024: 72 + immigration_status: + 2024: CITIZEN + ssi_receives_food_from_others: + 2024: true + noncitizen: + age: + 2024: 68 + immigration_status: + 2024: LEGAL_PERMANENT_RESIDENT + ssi_qualifying_quarters_earnings: + 2024: 39 + marital_units: + couple: + members: [citizen, noncitizen] + households: + household: + members: [citizen, noncitizen] + state_code: + 2024: CA + living_arrangements_allow_for_food_preparation: + 2024: true + output: + ca_capi_eligible_person: [false, true] + # October food no longer counts; SSI = 1,415 / 2 = 707.50. + ssi_in_kind_support_and_maintenance: [0, 0] + ssi: [707.50, 0] + # SSP = 1,182.94 - 707.50 - 0 = 475.44. + ca_state_supplement_person: [475.44, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/dhs/fia/paa/md_paa_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/dhs/fia/paa/md_paa_countable_income.yaml index cd9a5f71e76..9cd1f185d58 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/md/dhs/fia/paa/md_paa_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/md/dhs/fia/paa/md_paa_countable_income.yaml @@ -46,7 +46,7 @@ ssi_countable_income: 0 md_paa_countable_income: 2_480 -- name: Case 4, a spouse's facility stay from July counts by December's status for the whole year, as an annual approximation. +- name: Case 4, a spouse's facility stay from July separates the couple's income from July. period: 2025 absolute_error_margin: 0.01 input: @@ -79,16 +79,161 @@ ssi_couple_computation_applies: 2025-06: [true, true] 2025-07: [false, false] - # This annual formula applies December's status, separate, to the whole - # year, so each spouse counts their own income in all twelve months, - # including January to June, when SSI combines the couple's income. - # person1: (1,000 - 20) + (1,000 - 65) / 2 = 980 + 467.50 = 1,447.50, - # x 12 = 17,370. person2: (500 - 20) x 12 = 5,760. + # January to June each spouse counts half of the couple's combined income + # (20 CFR 416.1802(b)(1)): earned 1,000 and unearned 1,000 + 500 = 1,500 a + # month, so (1,500 - 20) + (1,000 - 65) / 2 = 1,480 + 467.50 = 1,947.50, + # and 1,947.50 / 2 = 973.75 each. + # July to December each spouse counts their own income (20 CFR + # 416.414(b)(3)): person1 (1,000 - 20) + (1,000 - 65) / 2 = 1,447.50; + # person2 500 - 20 = 480. # Both spouses are aged, so neither is an ineligible spouse and no # income is deemed. - # Summing the monthly ssi_marital_* variables instead would combine the - # couple's income through June. Unearned, 1,500 a month: person1 - # 15,000 - 240 + 5,610 = 20,370 and person2 12,000 - 240 = 11,760. - # Earned, 1,000 a month: person2 gets 6,000 a year, so - # 5,760 + (500 - 65) / 2 x 12 = 8,370. - md_paa_countable_income: [17_370, 5_760] + # Year: person1 6 x 973.75 + 6 x 1,447.50 = 5,842.50 + 8,685 = 14,527.50; + # person2 6 x 973.75 + 6 x 480 = 5,842.50 + 2,880 = 8,722.50. The two sum + # to 23,250 = 6 x 1,947.50 + 6 x (1,447.50 + 480), the couple's income + # net of exclusions. + md_paa_countable_income: + 2025-06: [973.75, 973.75] + 2025-07: [1_447.50, 480] + 2025: [14_527.50, 8_722.50] + +- name: Maryland counts January's SSI in-kind support in January PAA income. + period: 2024-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: + 2024: 70 + ssi_receives_food_from_others: + 2024: true + md_paa_living_arrangement: + 2024: CARE_HOME_LEVEL_C + households: + household: + members: [person] + state_code: + 2024: MD + output: + # ISM = 943 / 3 + 20; countable = ISM - 20 = 314.333333. + ssi_in_kind_support_and_maintenance: 334.333333 + md_paa_countable_income: 314.333333 + # Imputed SSI = 943 - 314.333333 = 628.666667. + md_paa_imputed_federal_ssi: 628.666667 + # PAA = 1,173 + 98 - 314.333333 - 628.666667 = 328. + md_paa: 328 + +- name: Maryland counts November's SSI in-kind support in November PAA income. + period: 2024-11 + absolute_error_margin: 0.01 + input: + people: + person: + age: + 2024: 70 + ssi_receives_food_from_others: + 2024: true + md_paa_living_arrangement: + 2024: CARE_HOME_LEVEL_C + households: + household: + members: [person] + state_code: + 2024: MD + output: + # November retains food ISM from September under SSI's payment timing. + ssi_in_kind_support_and_maintenance: 334.333333 + md_paa_countable_income: 314.333333 + md_paa_imputed_federal_ssi: 628.666667 + # PAA = 1,173 + 102 - 943 = 332 after the July PNA increase. + md_paa: 332 + +- name: Maryland counts no SSI in-kind support in December 2024 PAA income. + period: 2024-12 + absolute_error_margin: 0.01 + input: + people: + person: + age: + 2024: 70 + ssi_receives_food_from_others: + 2024: true + md_paa_living_arrangement: + 2024: CARE_HOME_LEVEL_C + households: + household: + members: [person] + state_code: + 2024: MD + output: + # October food no longer counts, so December ISM and countable income = 0. + ssi_in_kind_support_and_maintenance: 0 + md_paa_countable_income: 0 + md_paa_imputed_federal_ssi: 943 + # PAA = 1,173 + 102 - 0 - 943 = 332. + md_paa: 332 + +- name: Maryland sums monthly countable income without carrying December's unused exclusion. + period: 2024 + absolute_error_margin: 0.01 + input: + age: 70 + state_code: MD + ssi_receives_food_from_others: true + output: + # 11 * (943 / 3 + 20) = 3,677.666667 gross ISM. + ssi_in_kind_support_and_maintenance: 3_677.666667 + # 11 * (943 / 3) + 0 = 3,457.666667 countable income. + md_paa_countable_income: 3_457.666667 + +- name: Maryland adds the month's income deemed from an ineligible spouse to that month's PAA income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + ssi_receives_outside_shelter_support: + 2026-01: false + 2026-07: true + 2026-08: false + spouse: + age: 60 + employment_income: 24_000 + tax_units: + couple: + members: [claimant, spouse] + marital_units: + couple: + members: [claimant, spouse] + households: + home: + members: [claimant, spouse] + state_code: MD + output: + # The spouse's $2,000 of monthly wages exceeds 1,491 - 994 = 497, so SSA + # combines the spouses' incomes (20 CFR 416.1163(d)(2)). The claimant + # receives shelter support in July alone, at the presumed maximum value + # of 1,491 / 6 + 10 = 258.50 (POMS SI 01320.150 B.1.a). + # https://secure.ssa.gov/poms.nsf/lnx/0501320150 + # The deemed income is the couple's countable income less the + # claimant's own. + # January: the couple counts (2,000 - 20 - 65) / 2 = 957.50 and the + # claimant alone counts 0, which leaves 957.50. + # July: the couple counts (258.50 - 20) + (2,000 - 65) / 2 = 238.50 + + # 967.50 = 1,206 and the claimant alone counts 258.50 - 20 = 238.50, + # which leaves 967.50. + ssi_income_deemed_from_ineligible_spouse_monthly: + 2026-01: [957.50, 0] + 2026-07: [967.50, 0] + # The claimant counts own income plus that month's deemed income: + # 0 + 957.50 = 957.50 in January and 238.50 + 967.50 = 1,206 in July. + # One-twelfth of the year's deemed income, (11 x 957.50 + 967.50) / 12 = + # 958.33, would give 958.33 in January and 1,196.83 in July. + # The spouse counts own wages: (2,000 - 20 - 65) / 2 = 957.50 a month. + # Year: claimant 11 x 957.50 + 1,206 = 11,738.50; spouse 12 x 957.50 = + # 11,490. + md_paa_countable_income: + 2026-01: [957.50, 957.50] + 2026-07: [1_206, 957.50] + 2026: [11_738.50, 11_490] diff --git a/policyengine_us/variables/gov/states/ca/cdss/state_supplement/ca_state_supplement_person.py b/policyengine_us/variables/gov/states/ca/cdss/state_supplement/ca_state_supplement_person.py index f4aaf8ae212..5e4cf980210 100644 --- a/policyengine_us/variables/gov/states/ca/cdss/state_supplement/ca_state_supplement_person.py +++ b/policyengine_us/variables/gov/states/ca/cdss/state_supplement/ca_state_supplement_person.py @@ -4,7 +4,7 @@ ssi_state_supplement, ) from policyengine_us.variables.gov.ssa.ssi.eligibility.income._apply_ssi_exclusions import ( - _apply_ssi_exclusions, + _apply_ssi_exclusions_monthly, ) from policyengine_us.variables.gov.ssa.ssi.eligibility.resources.deemed._ssi_spouses import ( _ssi_couple_total, @@ -89,12 +89,12 @@ def formula(person, period, parameters): ) # NOTE: in-kind support uses the couple's presumed maximum value here, # because SSI still treats the pair as a couple. - unearned = person("ssi_unearned_income", period.this_year) + person( - "ssi_in_kind_support_and_maintenance", period.this_year + ism = person("ssi_in_kind_support_and_maintenance", period) + unearned = ( + person("ssi_unearned_income", period.this_year) / MONTHS_IN_YEAR + ism ) - # _apply_ssi_exclusions takes and returns annual amounts. - own_countable_income = ( - _apply_ssi_exclusions(earned, unearned, parameters, period) / MONTHS_IN_YEAR + own_countable_income = _apply_ssi_exclusions_monthly( + earned / MONTHS_IN_YEAR, unearned, parameters, period ) ssi_countable_income = person("ssi_countable_income", period) couple_income = _ssi_couple_total( diff --git a/policyengine_us/variables/gov/states/md/dhs/fia/paa/md_paa_countable_income.py b/policyengine_us/variables/gov/states/md/dhs/fia/paa/md_paa_countable_income.py index d5760a1b635..1cec1b9fe3f 100644 --- a/policyengine_us/variables/gov/states/md/dhs/fia/paa/md_paa_countable_income.py +++ b/policyengine_us/variables/gov/states/md/dhs/fia/paa/md_paa_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, ) from policyengine_us.variables.gov.ssa.ssi.eligibility.resources.deemed._ssi_spouses import ( _ssi_couple_income, @@ -12,7 +12,7 @@ class md_paa_countable_income(Variable): entity = Person label = "Maryland PAA countable income" unit = USD - definition_period = YEAR + definition_period = MONTH defined_for = StateCode.MD reference = ( "https://regs.maryland.gov/us/md/exec/comar/07.03.07.07", @@ -26,12 +26,9 @@ def formula(person, period, parameters): # exclusions, but it must count income for every PAA eligibility # pathway. Using ssi_countable_income would zero out SSDI-only # recipients unless an input also marks them as SSI-disabled. - # NOTE: this annual formula applies one couple status to the whole - # year (both_eligible below), so it combines the spouses' annual - # income by that same status. ssi_marital_earned_income and - # ssi_marital_unearned_income are monthly and follow each month's - # status; their annual totals would not match it in a year with a - # part-year facility stay. + # _ssi_couple_income and both_eligible read the same month's couple + # status, so a month in which one spouse stays in a medical facility + # counts each spouse's own income (20 CFR 416.414(b)(3)). earned = _ssi_couple_income(person, period, "ssi_earned_income") student_exclusion = person( "ssi_blind_or_disabled_working_student_exclusion", period @@ -42,14 +39,14 @@ def formula(person, period, parameters): + person("ssi_unearned_income_deemed_from_ineligible_parent", period) + person("ssi_in_kind_support_and_maintenance", period) ) - personal_countable = _apply_ssi_exclusions( + personal_countable = _apply_ssi_exclusions_monthly( adjusted_earned, unearned, parameters, period, ) - both_eligible = person("ssi_couple_computation_applies", period.this_year) + both_eligible = person("ssi_couple_computation_applies", period) # COMAR 07.03.07.07 counts the individual's own income and has no # spousal deeming, so people who fail SSI's citizen or # qualified-noncitizen test count their own income alone, including @@ -61,9 +58,12 @@ def formula(person, period, parameters): is_citizen = immigration_status == immigration_status.possible_values.CITIZEN is_qualified_noncitizen = person("is_ssi_qualified_noncitizen", period) meets_ssi_immigration_test = is_citizen | is_qualified_noncitizen + # The deemed amount comes from the same month as the income above: + # ssi_income_deemed_from_ineligible_spouse_monthly counts that month's + # in-kind support in the couple's budget and in the claimant's. spousal_deemed = where( meets_ssi_immigration_test, - person("ssi_income_deemed_from_ineligible_spouse", period), + person("ssi_income_deemed_from_ineligible_spouse_monthly", period), 0, ) return where(