From 0ca225faa6c5b74491757bf2a27056e202e5f2c9 Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 11 Nov 2026 23:41:22 -0500 Subject: [PATCH 1/2] Combine an SSI couple's income by each month's facility status ssi_marital_earned_income and ssi_marital_unearned_income were YEAR-defined and read the monthly ssi_couple_computation_applies for the year, which Core returns at December. ssi_countable_income and ky_ssp_countable_income split by the month's own status, so a part-year medical facility stay gave wrong SSI in the months whose status differs from December's (20 CFR 416.414(b)(3), 416.1802(b)(1), 416.420). Make both variables MONTH-defined through a shared helper, _ssi_couple_income. The annual Maryland PAA and Medicaid optional pathway formulas call the same helper for the year, so their results are unchanged; their Medicaid test inputs move from ssi_marital_* to the underlying SSI income inputs. Add cases for the months before and during a stay, including the POMS SI 02005.050 D.1 example. Fixes #10818 Co-Authored-By: Claude Opus 5.5 --- .../ssi-part-year-couple-income.fixed.md | 1 + ...l_senior_or_disabled_countable_income.yaml | 32 +++++++-------- .../ssi/income/ssi_marital_earned_income.yaml | 31 ++++++++++++++ .../income/ssi_marital_unearned_income.yaml | 33 +++++++++++++++ .../gov/ssa/ssi/ssi_countable_income.yaml | 40 +++++++++++++++++++ ...nal_senior_or_disabled_countable_income.py | 28 ++++++++----- .../marital/ssi_marital_earned_income.py | 15 +++---- .../marital/ssi_marital_unearned_income.py | 15 +++---- .../resources/deemed/_ssi_spouses.py | 17 ++++++++ .../md/dhs/fia/paa/md_paa_countable_income.py | 23 ++++++----- 10 files changed, 186 insertions(+), 49 deletions(-) create mode 100644 changelog.d/ssi-part-year-couple-income.fixed.md diff --git a/changelog.d/ssi-part-year-couple-income.fixed.md b/changelog.d/ssi-part-year-couple-income.fixed.md new file mode 100644 index 00000000000..6cacaa777f9 --- /dev/null +++ b/changelog.d/ssi-part-year-couple-income.fixed.md @@ -0,0 +1 @@ +Combine an SSI couple's income by each month's facility status, so a part-year medical facility stay no longer changes SSI in the other months of the year. diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.yaml index 75514720c77..5edfc042ca9 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.yaml @@ -33,7 +33,7 @@ absolute_error_margin: 0.01 input: age: 70 - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: CT output: medicaid_optional_senior_or_disabled_countable_income: 5_580 @@ -98,7 +98,7 @@ absolute_error_margin: 0.01 input: age: 70 - ssi_marital_unearned_income: 13_821 + ssi_unearned_income: 13_821 state_code: MO output: # Monthly, 1,151.75 - 20 = 1,131.75, rounded down to 1,131. Without @@ -112,7 +112,7 @@ input: age: 70 medicare_enrolled: false - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: CA output: # DHCS ACWDL 20-24, no Part B or other premiums: (1,000 - 20) x 12 = 11,760. @@ -123,7 +123,7 @@ absolute_error_margin: 0.01 input: age: 70 - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: CT output: # (1,000 - 562) x 12 = 5,256. @@ -134,7 +134,7 @@ absolute_error_margin: 0.01 input: age: 70 - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: SC output: # (1,000 - 50) x 12 = 11,400. @@ -146,7 +146,7 @@ input: age: 70 medicare_enrolled: true - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: CA output: # DHCS ACWDL 20-24 deducts Part B regardless of how it is paid; the @@ -161,7 +161,7 @@ age: 70 medicare_enrolled: true medicare_part_b_enrolled: false - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: CA output: # No Part B enrollment, so no Part B premium to disregard: @@ -250,7 +250,7 @@ input: age: 70 medicare_enrolled: false - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: CA output: # ACWDL 20-24 ended the 230 disregard on December 1, 2020, which first @@ -263,7 +263,7 @@ input: age: 70 medicare_enrolled: true - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: CA output: # ACWDL 00-57 MC 176 lines 8 and 16. 2020 reads January 1, 2020: the 230 @@ -307,7 +307,7 @@ input: age: 70 medicare_enrolled: false - ssi_marital_earned_income: 12_000 + ssi_earned_income: 12_000 state_code: CA output: # ACWDL 00-57 MC 176 lines 10-16: the unused 20-dollar deduction joins the @@ -347,7 +347,7 @@ input: age: 70 medicare_enrolled: false - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 health_insurance_premiums_without_medicare_part_b: 1_800 state_code: CA output: @@ -360,7 +360,7 @@ input: age: 70 medicare_enrolled: true - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: CA output: # The December 2020 Part B disregard first applies in 2021 annual @@ -374,7 +374,7 @@ input: age: 70 medicare_enrolled: false - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 health_insurance_premiums: 1_200 state_code: CA output: @@ -468,7 +468,7 @@ age: 70 medicare_enrolled: true health_insurance_premiums_without_medicare_part_b: 3_000 - ssi_marital_unearned_income: 1_200 + ssi_unearned_income: 1_200 state_code: CA output: # (100 - 20) x 12 = 960, less 2,434.80 Part B and 3,000 premiums, floored at 0. @@ -512,7 +512,7 @@ age: 70 medicare_enrolled: true health_insurance_premiums_without_medicare_part_b: 1_200 - ssi_marital_unearned_income: 12_000 + ssi_unearned_income: 12_000 state_code: NC output: # Only California deducts premiums: (1,000 - 20) x 12 = 11,760. @@ -523,7 +523,7 @@ absolute_error_margin: 0.01 input: age: 70 - ssi_marital_earned_income: 12_000 + ssi_earned_income: 12_000 state_code: MN output: # Minn. Stat. 256B.056, subd. 1b: no $20, so none moves to earned income. diff --git a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_earned_income.yaml index 9b45bfaa00d..12f2f4e8c6f 100644 --- a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_earned_income.yaml @@ -61,3 +61,34 @@ members: [head, spouse] output: ssi_marital_earned_income: [10, 10] + +- name: Earned income is combined only in the months before one spouse enters a medical facility + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + ssi_earned_income: 12_000 + person2: + age: 62 + meets_ssi_disability_criteria: true + ssi_lives_in_medical_treatment_facility: + 2026-07: true + ssi_medicaid_pays_majority_of_care: + 2026-07: true + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # 20 CFR 416.1802(b)(1) combines an eligible couple's income through + # June, so person1's 1,000 a month of wages counts for both spouses. + # 20 CFR 416.414(b)(3) counts each spouse's own income from July, the + # first month person2 spends in the facility. + ssi_marital_earned_income: + 2026-06: [1_000, 1_000] + 2026-07: [1_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_unearned_income.yaml index 2f29c8d7fbd..98d4d8f69fe 100644 --- a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_unearned_income.yaml @@ -61,3 +61,36 @@ members: [head, spouse] output: ssi_marital_unearned_income: [10, 10] + +- name: Unearned income is combined only in the months before one spouse enters a medical facility + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + ssi_unearned_income: 12_000 + person2: + age: 62 + meets_ssi_disability_criteria: true + ssi_unearned_income: 18_000 + ssi_lives_in_medical_treatment_facility: + 2026-07: true + ssi_medicaid_pays_majority_of_care: + 2026-07: true + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # 20 CFR 416.1802(b)(1) combines an eligible couple's income through + # June: 1,000 + 1,500 = 2,500 a month for each spouse. + # 20 CFR 416.414(b)(3) counts each spouse's own income from July, the + # first month person2 spends in the facility. The stay runs through + # December, and the months before it are still combined. + ssi_marital_unearned_income: + 2026-06: [2_500, 2_500] + 2026-07: [1_000, 1_500] diff --git a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_countable_income.yaml index af96ba13287..d7e3d2891a6 100644 --- a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_countable_income.yaml @@ -191,3 +191,43 @@ # countable income stays 0. ssi_countable_income: [25_370, 0] ssi: [0, 0] + +- name: A couple is paid as a couple until one spouse enters a Medicaid facility and as individuals from that month. + period: 2000 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + ssi_unearned_income: 4_800 + ssi_lives_in_medical_treatment_facility: + 2000-07: true + ssi_medicaid_pays_majority_of_care: + 2000-07: true + person2: + age: 70 + ssi_unearned_income: 2_400 + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # POMS SI 02005.050 D.1: Allen (person1) has $400 a month of Social + # Security and Martha (person2) a $200 spouse's benefit. Allen enters a + # Title XIX facility on June 10, 2000 and stays. + # June, "the last month for which payment is computed for a couple": + # 400 + 200 - 20 = 580 of countable income against the 769 couple rate + # leaves 189, or 94.50 each. + # July, "computed as if Allen and Martha are individuals": Allen has + # 30 - 380, so no payment. Martha has 513 - 180 = 333. POMS shows 332 + # from the 512 individual rate first set for 2000; SSA's rate table has + # 513 for 2000 after Public Law 106-554. + ssi_countable_income: + 2000-06: [290, 290] + 2000-07: [380, 180] + ssi: + 2000-06: [94.5, 94.5] + 2000-07: [0, 333] diff --git a/policyengine_us/variables/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.py b/policyengine_us/variables/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.py index c90f8fc349b..9daad4aed2b 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.py +++ b/policyengine_us/variables/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.py @@ -4,6 +4,7 @@ ) from policyengine_us.variables.gov.ssa.ssi.eligibility.resources.deemed._ssi_spouses import ( _ssi_couple_budget_total, + _ssi_couple_income, ) @@ -32,7 +33,15 @@ class medicaid_optional_senior_or_disabled_countable_income(Variable): ) def formula(person, period, parameters): - pre_reduction_earned_income = person("ssi_marital_earned_income", period) + # NOTE: this annual formula applies one couple status to the whole + # year (couple_computation 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. + pre_reduction_earned_income = _ssi_couple_income( + person, period, "ssi_earned_income" + ) blind_disabled_working_student_income = person( "ssi_blind_or_disabled_working_student_exclusion", period ) @@ -41,14 +50,13 @@ def formula(person, period, parameters): 0, ) - total_unearned_income = add( - person, - period, - [ - "ssi_marital_unearned_income", + total_unearned_income = ( + _ssi_couple_income(person, period, "ssi_unearned_income") + + person( "medicaid_optional_senior_or_disabled_unearned_income_deemed_from_ineligible_parent", - "ssi_in_kind_support_and_maintenance", - ], + period, + ) + + person("ssi_in_kind_support_and_maintenance", period) ) couple_computation = person("ssi_couple_computation_applies", period) @@ -99,8 +107,8 @@ def formula(person, period, parameters): # person in the family. A couple's budget takes both spouses' # premiums and those of unmarried tax dependents in their marital # unit and tax unit. It leaves out another couple's dependent and an - # unrelated adult, whose income also stays out of - # ssi_marital_unearned_income when a situation omits marital units. + # unrelated adult, whose income also stays out of the couple's + # combined income above when a situation omits marital units. premiums = where( couple_computation, _ssi_couple_budget_total(person, period, own_premiums), diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_earned_income.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_earned_income.py index f2d1eca5834..c3d8cb8a49b 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_earned_income.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_earned_income.py @@ -1,6 +1,6 @@ from policyengine_us.model_api import * from policyengine_us.variables.gov.ssa.ssi.eligibility.resources.deemed._ssi_spouses import ( - _ssi_spouse_index, + _ssi_couple_income, ) @@ -8,15 +8,16 @@ class ssi_marital_earned_income(Variable): value_type = float entity = Person label = "Combined SSI earned income of the two spouses" - definition_period = YEAR + definition_period = MONTH reference = ( - "https://www.ecfr.gov/current/title-20/section-416.1802#p-416.1802(b)(1)" + "https://www.ecfr.gov/current/title-20/section-416.1802#p-416.1802(b)(1)", + "https://www.ecfr.gov/current/title-20/section-416.414#p-416.414(b)(3)", ) def formula(person, period, parameters): # 416.1802(b)(1): with an eligible spouse 'we will count your combined # income'. Combine the two spouses, not every member of the unit. - couple_computation = person("ssi_couple_computation_applies", period) - earned_income = person("ssi_earned_income", period) - spouse = np.maximum(_ssi_spouse_index(person, period), 0) - return earned_income + where(couple_computation, earned_income[spouse], 0) + # 416.414(b)(3) counts 'each individual's own countable income' in a + # month one spouse spends in a medical treatment facility, so each + # month follows its own status. + return _ssi_couple_income(person, period, "ssi_earned_income") diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_unearned_income.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_unearned_income.py index ebfe4088eb1..e8d7b0837cb 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_unearned_income.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_unearned_income.py @@ -1,6 +1,6 @@ from policyengine_us.model_api import * from policyengine_us.variables.gov.ssa.ssi.eligibility.resources.deemed._ssi_spouses import ( - _ssi_spouse_index, + _ssi_couple_income, ) @@ -8,15 +8,16 @@ class ssi_marital_unearned_income(Variable): value_type = float entity = Person label = "Combined SSI unearned income of the two spouses" - definition_period = YEAR + definition_period = MONTH reference = ( - "https://www.ecfr.gov/current/title-20/section-416.1802#p-416.1802(b)(1)" + "https://www.ecfr.gov/current/title-20/section-416.1802#p-416.1802(b)(1)", + "https://www.ecfr.gov/current/title-20/section-416.414#p-416.414(b)(3)", ) def formula(person, period, parameters): # 416.1802(b)(1): with an eligible spouse 'we will count your combined # income'. Combine the two spouses, not every member of the unit. - couple_computation = person("ssi_couple_computation_applies", period) - unearned_income = person("ssi_unearned_income", period) - spouse = np.maximum(_ssi_spouse_index(person, period), 0) - return unearned_income + where(couple_computation, unearned_income[spouse], 0) + # 416.414(b)(3) counts 'each individual's own countable income' in a + # month one spouse spends in a medical treatment facility, so each + # month follows its own status. + return _ssi_couple_income(person, period, "ssi_unearned_income") diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/deemed/_ssi_spouses.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/deemed/_ssi_spouses.py index 960575324bb..61268f9cbdb 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/deemed/_ssi_spouses.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/deemed/_ssi_spouses.py @@ -113,6 +113,23 @@ def _ssi_couple_total(person, period, values): return values + where(spouse >= 0, values[np.maximum(spouse, 0)], 0) +def _ssi_couple_income(person, period, variable): + """A person's income, plus the spouse's when the couple computation applies. + + 416.1802(b)(1) counts an eligible couple's combined income. 416.414(b)(3) + counts 'each individual's own countable income' in a month one spouse + spends in a medical treatment facility. The status and the income are + both read for ``period``, so a monthly formula follows that month's + status. An annual formula applies one status to the whole year, the + status of the year's last month, which is how Core reads a monthly status + over a year. + """ + couple_computation = person("ssi_couple_computation_applies", period) + income = person(variable, period) + spouse = np.maximum(_ssi_spouse_index(person, period.this_year), 0) + return income + where(couple_computation, income[spouse], 0) + + def _ssi_couple_budget_total(person, period, values): """A couple's total: both spouses' values plus their dependents'. 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 b32ee9f66b5..d5760a1b635 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 @@ -2,6 +2,9 @@ from policyengine_us.variables.gov.ssa.ssi.eligibility.income._apply_ssi_exclusions import ( _apply_ssi_exclusions, ) +from policyengine_us.variables.gov.ssa.ssi.eligibility.resources.deemed._ssi_spouses import ( + _ssi_couple_income, +) class md_paa_countable_income(Variable): @@ -23,19 +26,21 @@ 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. - earned = person("ssi_marital_earned_income", period) + # 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. + earned = _ssi_couple_income(person, period, "ssi_earned_income") student_exclusion = person( "ssi_blind_or_disabled_working_student_exclusion", period ) adjusted_earned = max_(earned - student_exclusion, 0) - unearned = add( - person, - period, - [ - "ssi_marital_unearned_income", - "ssi_unearned_income_deemed_from_ineligible_parent", - "ssi_in_kind_support_and_maintenance", - ], + unearned = ( + _ssi_couple_income(person, period, "ssi_unearned_income") + + person("ssi_unearned_income_deemed_from_ineligible_parent", period) + + person("ssi_in_kind_support_and_maintenance", period) ) personal_countable = _apply_ssi_exclusions( adjusted_earned, From 27687ea37f578bded38b15f73ef252b485bd02c1 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 12 Nov 2026 13:22:55 -0500 Subject: [PATCH 2/2] Pin the annual readers' part-year treatment and name the couple test issue Add a part-year facility case to the Medicaid optional pathway and to Maryland PAA countable income: each applies December's status to the whole year, so reverting either to the monthly marital variables fails. Note in ssi_couple_computation_applies that the couple eligibility test during a temporary absence is not modeled (POMS SI 02005.050 B.3, issue 10851). Say "throughout" where 416.414(a)(1) defines a facility month, and cite it beside 416.414(b)(3). Co-Authored-By: Claude Opus 5.5 --- ...l_senior_or_disabled_countable_income.yaml | 41 ++++++++++++++++++ .../ssi/income/ssi_marital_earned_income.yaml | 2 +- .../income/ssi_marital_unearned_income.yaml | 5 ++- .../gov/ssa/ssi/ssi_countable_income.yaml | 2 +- .../dhs/fia/paa/md_paa_countable_income.yaml | 43 +++++++++++++++++++ .../marital/ssi_marital_earned_income.py | 4 +- .../marital/ssi_marital_unearned_income.py | 4 +- .../resources/deemed/_ssi_spouses.py | 3 +- .../ssa/ssi/ssi_couple_computation_applies.py | 5 +++ 9 files changed, 102 insertions(+), 7 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.yaml index 5edfc042ca9..27d2f4cdedc 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_optional_senior_or_disabled_countable_income.yaml @@ -1098,3 +1098,44 @@ # The spouse's own countable income stays 0 to avoid counting it twice. medicaid_optional_senior_or_disabled_countable_income: [25_370, 0] is_optional_senior_or_disabled_income_eligible: [false, false] + +- name: A spouse's facility stay from July counts by December's status for the whole year, as an annual approximation. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 72 + is_tax_unit_head: true + ssi_unearned_income: 12_000 + person2: + age: 70 + is_tax_unit_spouse: true + ssi_unearned_income: 6_000 + ssi_lives_in_medical_treatment_facility: + 2025-01: false + 2025-07: true + ssi_medicaid_pays_majority_of_care: + 2025-01: false + 2025-07: true + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NC + output: + # person2 is in a Medicaid-funded facility from July through December, + # so the couple computation applies through June and not from July. + 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: + # (1,000 - 20) x 12 = 11,760 and (500 - 20) x 12 = 5,760. + # Summing the monthly ssi_marital_unearned_income instead would combine + # 1,500 a month through June: 15,000 - 240 = 14,760 and + # 12,000 - 240 = 11,760. + medicaid_optional_senior_or_disabled_countable_income: [11_760, 5_760] diff --git a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_earned_income.yaml index 12f2f4e8c6f..220474026fc 100644 --- a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_earned_income.yaml @@ -88,7 +88,7 @@ # 20 CFR 416.1802(b)(1) combines an eligible couple's income through # June, so person1's 1,000 a month of wages counts for both spouses. # 20 CFR 416.414(b)(3) counts each spouse's own income from July, the - # first month person2 spends in the facility. + # first month person2 spends throughout in the facility (416.414(a)(1)). ssi_marital_earned_income: 2026-06: [1_000, 1_000] 2026-07: [1_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_unearned_income.yaml index 98d4d8f69fe..12947143741 100644 --- a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/income/ssi_marital_unearned_income.yaml @@ -89,8 +89,9 @@ # 20 CFR 416.1802(b)(1) combines an eligible couple's income through # June: 1,000 + 1,500 = 2,500 a month for each spouse. # 20 CFR 416.414(b)(3) counts each spouse's own income from July, the - # first month person2 spends in the facility. The stay runs through - # December, and the months before it are still combined. + # first month person2 spends throughout in the facility + # (416.414(a)(1)). The stay runs through December, and the months + # before it are still combined. ssi_marital_unearned_income: 2026-06: [2_500, 2_500] 2026-07: [1_000, 1_500] diff --git a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_countable_income.yaml index d7e3d2891a6..d6bc16ed890 100644 --- a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/ssi_countable_income.yaml @@ -192,7 +192,7 @@ ssi_countable_income: [25_370, 0] ssi: [0, 0] -- name: A couple is paid as a couple until one spouse enters a Medicaid facility and as individuals from that month. +- name: A couple is paid as a couple through the month one spouse enters a Medicaid facility and as individuals from the first full month. period: 2000 absolute_error_margin: 0.01 input: 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 b66a6235b4a..4f99f3871c6 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 @@ -45,3 +45,46 @@ # $2,500 monthly SSDI - $20 general exclusion. 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. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 72 + is_tax_unit_head: true + ssi_unearned_income: 12_000 + person2: + age: 70 + is_tax_unit_spouse: true + ssi_unearned_income: 6_000 + ssi_lives_in_medical_treatment_facility: + 2025-01: false + 2025-07: true + ssi_medicaid_pays_majority_of_care: + 2025-01: false + 2025-07: true + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MD + output: + # person2 is in a Medicaid-funded facility from July through December, + # so the couple computation applies through June and not from July. + 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: + # (1,000 - 20) x 12 = 11,760 and (500 - 20) x 12 = 5,760. + # Both spouses are aged, so neither is an ineligible spouse and no + # income is deemed. + # Summing the monthly ssi_marital_unearned_income instead would combine + # 1,500 a month through June: 15,000 - 240 = 14,760 and + # 12,000 - 240 = 11,760. + md_paa_countable_income: [11_760, 5_760] diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_earned_income.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_earned_income.py index c3d8cb8a49b..5500e88fb10 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_earned_income.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_earned_income.py @@ -11,6 +11,7 @@ class ssi_marital_earned_income(Variable): definition_period = MONTH reference = ( "https://www.ecfr.gov/current/title-20/section-416.1802#p-416.1802(b)(1)", + "https://www.ecfr.gov/current/title-20/section-416.414#p-416.414(a)(1)", "https://www.ecfr.gov/current/title-20/section-416.414#p-416.414(b)(3)", ) @@ -18,6 +19,7 @@ def formula(person, period, parameters): # 416.1802(b)(1): with an eligible spouse 'we will count your combined # income'. Combine the two spouses, not every member of the unit. # 416.414(b)(3) counts 'each individual's own countable income' in a - # month one spouse spends in a medical treatment facility, so each + # month one spouse spends throughout in a medical treatment facility, + # from the start of the month to its end (416.414(a)(1)), so each # month follows its own status. return _ssi_couple_income(person, period, "ssi_earned_income") diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_unearned_income.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_unearned_income.py index e8d7b0837cb..a077b41655d 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_unearned_income.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/income/marital/ssi_marital_unearned_income.py @@ -11,6 +11,7 @@ class ssi_marital_unearned_income(Variable): definition_period = MONTH reference = ( "https://www.ecfr.gov/current/title-20/section-416.1802#p-416.1802(b)(1)", + "https://www.ecfr.gov/current/title-20/section-416.414#p-416.414(a)(1)", "https://www.ecfr.gov/current/title-20/section-416.414#p-416.414(b)(3)", ) @@ -18,6 +19,7 @@ def formula(person, period, parameters): # 416.1802(b)(1): with an eligible spouse 'we will count your combined # income'. Combine the two spouses, not every member of the unit. # 416.414(b)(3) counts 'each individual's own countable income' in a - # month one spouse spends in a medical treatment facility, so each + # month one spouse spends throughout in a medical treatment facility, + # from the start of the month to its end (416.414(a)(1)), so each # month follows its own status. return _ssi_couple_income(person, period, "ssi_unearned_income") diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/deemed/_ssi_spouses.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/deemed/_ssi_spouses.py index 61268f9cbdb..2ebc57e3be2 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/deemed/_ssi_spouses.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/deemed/_ssi_spouses.py @@ -118,7 +118,8 @@ def _ssi_couple_income(person, period, variable): 416.1802(b)(1) counts an eligible couple's combined income. 416.414(b)(3) counts 'each individual's own countable income' in a month one spouse - spends in a medical treatment facility. The status and the income are + spends throughout in a medical treatment facility, from the start of the + month to its end (416.414(a)(1)). The status and the income are both read for ``period``, so a monthly formula follows that month's status. An annual formula applies one status to the whole year, the status of the year's last month, which is how Core reads a monthly status diff --git a/policyengine_us/variables/gov/ssa/ssi/ssi_couple_computation_applies.py b/policyengine_us/variables/gov/ssa/ssi/ssi_couple_computation_applies.py index a960bb7ea94..df6da06e3b9 100644 --- a/policyengine_us/variables/gov/ssa/ssi/ssi_couple_computation_applies.py +++ b/policyengine_us/variables/gov/ssa/ssi/ssi_couple_computation_applies.py @@ -30,6 +30,11 @@ def formula(person, period, parameters): # permanent, and PolicyEngine does not currently model intent to # return. We therefore scope this variable to the benefit- # computation rule that 416.414(b) applies in either case. + # NOTE: Without intent to return, the model also omits SSI's couple + # eligibility test during a temporary absence: eligibility subtracts + # the couple's combined countable income from the couple rate, and + # only then is each spouse paid on their own income + # (POMS SI 02005.050 B.3; PolicyEngine/policyengine-us#10851). arrangement = person("ssi_federal_living_arrangement", period) in_facility = ( arrangement == SSIFederalLivingArrangement.MEDICAL_TREATMENT_FACILITY