From 8a04efdedb325f689aec582d0dacf3d51476da28 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 2 Nov 2026 17:01:33 -0500 Subject: [PATCH 1/2] Count the income of an aged, blind or disabled spouse not on Medicare in MSP Co-Authored-By: Claude Opus 5.5 --- changelog.d/msp-abd-spouse-income.fixed.md | 1 + .../income/msp_countable_income.yaml | 83 +++++++++++++++++++ .../income/msp_countable_income.py | 25 ++++-- 3 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 changelog.d/msp-abd-spouse-income.fixed.md diff --git a/changelog.d/msp-abd-spouse-income.fixed.md b/changelog.d/msp-abd-spouse-income.fixed.md new file mode 100644 index 00000000000..e01881c839c --- /dev/null +++ b/changelog.d/msp-abd-spouse-income.fixed.md @@ -0,0 +1 @@ +Count the income of an aged, blind or disabled spouse who is not on Medicare in Medicare Savings Program countable income. diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_countable_income.yaml index fb81c53bf64..bfa58cbc37d 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_countable_income.yaml @@ -456,3 +456,86 @@ # (1,000 - 20) + (1,666.67 - 65) / 2 = 980 + 800.83 = 1,780.83. is_ssi_spousal_deeming_applies: [true, false] msp_countable_income: [1_780.83, 790.83] + +- name: Case 21, income of a disabled spouse who is not on Medicare counts for the couple. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 12_000 + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + ssi_unearned_income: 18_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + output: + # (1,000 + 1,500) - 20 = 2,480, above 135% of the two-person guideline: + # 1.35 x 1,803.33 = 2,434.50. + is_medicare_eligible: [true, false] + ssi_couple_computation_applies: [true, true] + msp_countable_income: [2_480, 2_480] + msp_category: [NONE, NONE] + +- name: Case 22, Illinois gives one exemption when only one spouse is on Medicare. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 12_000 + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + ssi_unearned_income: 18_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: IL + output: + # (1,000 + 1,500) - 25 = 2,475. + msp_countable_income: [2_475, 2_475] + +- name: Case 23, a disabled spouse living apart in a facility is not counted. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 12_000 + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + ssi_unearned_income: 18_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + output: + # 20 CFR 416.414(b)(3) computes the spouses separately: 1,000 - 20 = 980. + ssi_couple_computation_applies: [false, false] + msp_countable_income: [980, 1_480] diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py index 99b10f12989..a8fb624a502 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py @@ -12,6 +12,7 @@ class msp_countable_income(Variable): definition_period = MONTH reference = ( "https://www.law.cornell.edu/uscode/text/42/1396d#p", + "https://www.law.cornell.edu/cfr/text/20/416.412", "https://www.law.cornell.edu/cfr/text/20/416.1163", "https://secure.ssa.gov/apps10/poms.nsf/lnx/0501715010", "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", @@ -27,11 +28,13 @@ class msp_countable_income(Variable): 2. $65 earned income exclusion 3. 50% of remaining earned income excluded - For married couples where both spouses are Medicare-eligible, SSI couple - rules apply per 20 CFR 416.1160: both spouses' incomes are combined before - exclusions are applied, and the $20 exclusion is taken once on the combined - income. The combined countable income is then compared against the couple FPL - in the eligibility variables (is_qmb_eligible, is_slmb_eligible, is_qi_eligible). + For married couples where both spouses are Medicare-eligible, or where SSI + computes both as a couple because both are aged, blind or disabled, SSI couple + rules apply per 20 CFR 416.412 and 416.1160: both spouses' incomes are combined + before exclusions are applied, and the $20 exclusion is taken once on the + combined income. The combined countable income is then compared against the + couple FPL in the eligibility variables (is_qmb_eligible, is_slmb_eligible, + is_qi_eligible). Under 42 U.S.C. 1396a(r)(2) a state may exclude more than the $20 SSI general income exclusion; Illinois and Mississippi use their larger @@ -91,7 +94,9 @@ def formula(person, period, parameters): per_spouse, max_( ssi_general, - person.marital_unit.sum(min_(state_exclusion, qualifying_income)), + person.marital_unit.sum( + min_(state_exclusion, qualifying_income) * is_medicare_eligible + ), ), general_exclusion, ) @@ -134,7 +139,13 @@ def formula(person, period, parameters): deeming_applies, deemed_countable, personal_countable_income ) + # A spouse who is aged, blind or disabled but not on Medicare is neither + # an applying spouse nor an ineligible spouse whose income is deemed, so + # the SSI couple computation is what brings in their income. + couple_computation = person("ssi_couple_computation_applies", period) annual_countable = where( - both_medicare_eligible, couple_countable, single_countable + both_medicare_eligible | couple_computation, + couple_countable, + single_countable, ) return annual_countable / MONTHS_IN_YEAR From 862c092c92ace657c798947377b328315b95ff3c Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 2 Nov 2026 22:18:32 -0500 Subject: [PATCH 2/2] Follow SSI's ineligible spouse rule for MSP couples A spouse who is aged, blind or disabled but not eligible for SSI is an ineligible spouse (20 CFR 416.1801(c), 416.1160(d)), so child allocations and the rate difference in 416.1163 decide whether their income counts. A person tested alone now gets the one-person guideline, month by month. Illinois gives one $25 when only one spouse is on Medicare (PM 15-04-03-a). Co-Authored-By: Claude Opus 5.5 --- changelog.d/msp-abd-spouse-income.fixed.md | 2 +- .../income/msp_countable_income.yaml | 334 +++++++++++++++++- .../savings_programs/income/msp_fpg.yaml | 84 ++++- .../savings_programs/integration.yaml | 7 +- .../income/_msp_spouse_income.py | 42 +++ .../income/msp_countable_income.py | 81 +++-- .../savings_programs/income/msp_fpg.py | 13 +- .../income/msp_tests_as_couple.py | 48 +++ source_conflicts/candidates.yaml | 83 +++++ 9 files changed, 648 insertions(+), 46 deletions(-) create mode 100644 policyengine_us/variables/gov/hhs/medicare/savings_programs/income/_msp_spouse_income.py create mode 100644 policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_tests_as_couple.py diff --git a/changelog.d/msp-abd-spouse-income.fixed.md b/changelog.d/msp-abd-spouse-income.fixed.md index e01881c839c..f51776f4faa 100644 --- a/changelog.d/msp-abd-spouse-income.fixed.md +++ b/changelog.d/msp-abd-spouse-income.fixed.md @@ -1 +1 @@ -Count the income of an aged, blind or disabled spouse who is not on Medicare in Medicare Savings Program countable income. +Apply SSI's eligible-couple and spouse deeming rules to Medicare Savings Program countable income and the poverty guideline when only one spouse is on Medicare, including a spouse who is aged, blind or disabled. diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_countable_income.yaml index bfa58cbc37d..8b63696d1bd 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_countable_income.yaml @@ -339,8 +339,9 @@ output: # PM 15-04-03-a: one applicant, so one $25. Deemed: unearned 600 - 25 = 575; # earned (2,500 - 65) / 2 = 1,217.50; 575 + 1,217.50 = 1,792.50. - # The spouse without Medicare: (2,500 - 25 - 65) / 2 = 1,205. - msp_countable_income: [1_792.5, 1_205] + # The spouse without Medicare has person1's $600 deemed the same way, since + # 600 is above the 497 rate difference. + msp_countable_income: [1_792.5, 1_792.5] - name: Case 16, Illinois caps a spouse's 25 dollar exemption at that spouse's income. period: 2026-01 @@ -454,8 +455,9 @@ # SSI methodology deems the spouse's wages whether or not the person # meets SSI's noncitizen test: 20,000 > (1,450 - 967) x 12 = 5,796. # (1,000 - 20) + (1,666.67 - 65) / 2 = 980 + 800.83 = 1,780.83. + # person2 has person1's income deemed the same way: 12,000 > 5,796. is_ssi_spousal_deeming_applies: [true, false] - msp_countable_income: [1_780.83, 790.83] + msp_countable_income: [1_780.83, 1_780.83] - name: Case 21, income of a disabled spouse who is not on Medicare counts for the couple. period: 2026-01 @@ -539,3 +541,329 @@ # 20 CFR 416.414(b)(3) computes the spouses separately: 1,000 - 20 = 980. ssi_couple_computation_applies: [false, false] msp_countable_income: [980, 1_480] + +- name: Case 24, allocations for ineligible children come off the spouse's income before deeming. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 12_000 + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + ssi_unearned_income: 18_000 + person3: + age: 12 + person4: + age: 10 + tax_units: + tax_unit: + members: [person1, person2, person3, person4] + marital_units: + marital_unit: + members: [person1, person2] + child1_marital_unit: + members: [person3] + child2_marital_unit: + members: [person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: TX + output: + # 20 CFR 416.1163(b): two allocations of 1,491 - 994 = 497 total 994. + # person1: 1,500 - 994 = 506, above 497, so it is deemed (416.1163(d)(2)): + # 1,000 + 506 - 20 = 1,486, at or below the two-person 21,640 / 12 = 1,803.33. + # person2: 1,000 - 994 = 6, not above 497, so 1,500 - 20 = 1,480 alone. + msp_countable_income: [1_486, 1_480, 0, 0] + msp_fpg: [1_803.33, 1_330, 1_330, 1_330] + msp_category: [QMB, NONE, NONE, NONE] + +- name: Case 25, a disabled spouse's income at or below the rate difference is not deemed. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 20_400 + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + ssi_unearned_income: 5_400 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # 20 CFR 416.1163(d)(1): person2's 450 is not above 497, so person1 is tested + # alone: 1,700 - 20 = 1,680 against the one-person 15,960 / 12 = 1,330. + # 120% is 1,596 and 135% is 1,795.50, so QI. + # person2 has person1's 1,700 deemed: 450 + 1,700 - 20 = 2,130. + msp_countable_income: [1_680, 2_130] + msp_fpg: [1_330, 1_803.33] + msp_category: [QI, NONE] + +- name: Case 26, an eligible SSI couple is tested together when one spouse is on Medicare. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 16_800 + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # 20 CFR 416.1801(c): 1,400 - 20 = 1,380 is below the 1,491 couple rate, so each + # spouse is eligible for SSI: (1,491 - 1,380) / 2 = 55.50. + # 1,380 is at or below the two-person 1,803.33, so QMB. Tested on the one-person + # 1,330 instead, person1 would be SLMB. + uncapped_ssi: [55.50, 55.50] + msp_countable_income: [1_380, 1_380] + msp_fpg: [1_803.33, 1_803.33] + msp_category: [QMB, NONE] + +- name: Case 27, income at or below the rate difference is not deemed from a spouse who is not aged, blind or disabled. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 20_160 + person2: + age: 50 + is_tax_unit_spouse: true + ssi_unearned_income: 5_400 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # 20 CFR 416.1163(d)(1): person2's 450 is not above 497, so person1 is tested + # alone: 1,680 - 20 = 1,660 against the one-person 1,330. + # 120% is 1,596 and 135% is 1,795.50, so QI. The two-person 1,803.33 would be QMB. + # person2 has person1's 1,680 deemed: 450 + 1,680 - 20 = 2,110. + msp_countable_income: [1_660, 2_110] + msp_fpg: [1_330, 1_803.33] + msp_category: [QI, NONE] + +- name: Case 28, an undocumented spouse's income counts whether or not that spouse can enroll in Medicare. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_tax_unit_head: true + ssi_unearned_income: 12_000 + person2: + age: 70 + is_tax_unit_spouse: true + immigration_status: UNDOCUMENTED + ssi_unearned_income: 18_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # With both on Medicare the incomes combine. With person2 off Medicare, each + # spouse's income is above 497 and is deemed (20 CFR 416.1163(d)(2)). Either way: + # 1,000 + 1,500 - 20 = 2,480, above 135% of the two-person guideline: + # 1.35 x 1,803.33 = 2,434.50. + msp_countable_income: [2_480, 2_480] + msp_fpg: [1_803.33, 1_803.33] + msp_category: [NONE, NONE] + +- name: Case 29, Illinois gives one 25 dollar exemption to a spouse on Medicare who has no income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + ssi_unearned_income: 21_900 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: IL + output: + # PM 15-04-03-a: person2's 1,825 is above 497 and is deemed: 1,825 - 25 = 1,800, + # at or below the two-person 1,803.33, so QMB. The SSI 20 would leave 1,805: SLMB. + # person2 is tested alone, since person1 has no income: 1,825 - 25 = 1,800. + msp_countable_income: [1_800, 1_800] + msp_fpg: [1_803.33, 1_330] + msp_category: [QMB, NONE] + +- name: Case 30, a minor's wages are not counted as a spouse's when marital units are omitted. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + social_security_retirement: 20_400 + person2: + age: 16 + employment_income_before_lsr: 10_000 + households: + household: + members: [person1, person2] + state_code: TX + output: + # Omitted marital units put both people in one default unit, but a parent and the + # child they claim are not spouses, so each is tested alone. + # person1: 1,700 - 20 = 1,680 against the one-person 1,330. + # 120% is 1,596 and 135% is 1,795.50, and that Social Security income is too high + # for Texas Medicaid as a parent, so QI. + # person2: (10,000 / 12 - 20 - 65) / 2 = 374.17. + msp_countable_income: [1_680, 374.17] + msp_fpg: [1_330, 1_330] + msp_category: [QI, NONE] + +- name: Case 31, a spouse's income exactly at the rate difference is not deemed. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 20_160 + person2: + age: 50 + is_tax_unit_spouse: true + ssi_unearned_income: 5_964 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # 20 CFR 416.1163(d)(1): person2's 5,964 / 12 = 497 is not above 1,491 - 994 = 497, + # so person1 is tested alone: 1,680 - 20 = 1,660 against the one-person 1,330. + # 120% is 1,596 and 135% is 1,795.50, so QI. + # person2 has person1's 1,680 deemed: 497 + 1,680 - 20 = 2,157. + msp_countable_income: [1_660, 2_157] + msp_fpg: [1_330, 1_803.33] + msp_category: [QI, NONE] + +- name: Case 32, a spouse's income one dollar above the rate difference is deemed. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 20_160 + person2: + age: 50 + is_tax_unit_spouse: true + ssi_unearned_income: 5_976 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # 20 CFR 416.1163(d)(2): person2's 5,976 / 12 = 498 is above 497, so it is deemed: + # 1,680 + 498 - 20 = 2,158 against the two-person 1,803.33. + # 120% is 2,164, so SLMB. + msp_countable_income: [2_158, 2_158] + msp_fpg: [1_803.33, 1_803.33] + msp_category: [SLMB, NONE] + +- name: Case 33, Illinois gives an eligible SSI couple one 25 dollar exemption when one spouse is on Medicare. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 1_200 + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + ssi_unearned_income: 15_600 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: IL + output: + # 20 CFR 416.1801(c): 100 + 1,300 - 20 = 1,380 is below the 1,491 couple rate, so + # each spouse is eligible for SSI: (1,491 - 1,380) / 2 = 55.50. + # PM 15-04-03-a: only person1 applies, so one $25: 100 + 1,300 - 25 = 1,375. + # A $25 for each spouse would give 1,350. + uncapped_ssi: [55.50, 55.50] + msp_countable_income: [1_375, 1_375] + msp_fpg: [1_803.33, 1_803.33] + msp_category: [QMB, NONE] diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_fpg.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_fpg.yaml index 14acc4c3fb0..0ffd2931b33 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_fpg.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/income/msp_fpg.yaml @@ -12,11 +12,89 @@ # Individual FPL = $15,060/year, monthly = $1,255 msp_fpg: 1_255 -- name: Case 2, married couple gets couple FPL. +- name: Case 2, married couple both on Medicare gets couple FPL. period: 2024-01 absolute_error_margin: 0.01 input: - spm_unit_is_married: true + people: + person1: + age: 68 + person2: + age: 66 + marital_units: + marital_unit: + members: [person1, person2] output: # Couple FPL = $20,440/year, monthly = $1,703.33 - msp_fpg: 1_703.33 + msp_fpg: [1_703.33, 1_703.33] + +- name: Case 3, spouses separated by a facility each keep the one-person guideline. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 12_000 + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + ssi_unearned_income: 18_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + output: + # 20 CFR 416.414(b)(3) computes the spouses separately, though each one's income + # is above the 497 rate difference. One-person guideline: 15,960 / 12 = 1,330. + msp_fpg: [1_330, 1_330] + +- name: Case 4, the guideline and income test change in the month a spouse enters a facility. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + is_tax_unit_head: true + ssi_unearned_income: 12_000 + person2: + age: 62 + is_tax_unit_spouse: true + meets_ssi_disability_criteria: true + ssi_lives_in_medical_treatment_facility: + 2026-07: true + ssi_medicaid_pays_majority_of_care: + 2026-07: true + ssi_unearned_income: 18_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # June, both at home: each spouse's income is above 497 and is deemed: + # 1,000 + 1,500 - 20 = 2,480, above 1.35 x 21,640 / 12 = 2,434.50. + # July, person2 in a facility (20 CFR 416.414(b)(3)): each is tested alone. + # person1: 1,000 - 20 = 980, at or below 15,960 / 12 = 1,330, so QMB. + # person2: 1,500 - 20 = 1,480, and not on Medicare. + msp_fpg: + 2026-06: [1_803.33, 1_803.33] + 2026-07: [1_330, 1_330] + msp_countable_income: + 2026-06: [2_480, 2_480] + 2026-07: [980, 1_480] + msp_category: + 2026-06: [NONE, NONE] + 2026-07: [QMB, NONE] diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/integration.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/integration.yaml index c177b3f7b3a..fc59c8d7f50 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/integration.yaml @@ -1,5 +1,6 @@ # MSP uses SSI income methodology per 42 U.S.C. 1396d(p)(1)(B) -# For married couples, MSP uses couple FPL = $20,440 for 2024 +# A married person tested as a couple (msp_tests_as_couple) uses the two-person +# FPL = $20,440 for 2024; a married person tested alone uses the one-person FPL. - name: MSP integration - married couple, both QMB eligible period: 2024-01 @@ -111,8 +112,8 @@ # Edge case: only one spouse is Medicare-eligible. # person.marital_unit.sum(is_medicare_eligible) == 1, not 2, so full couple # aggregation does not apply. With no spouse income to deem, spouse1 is - # tested on their own income ($10,000) against the married MSP threshold. - # ($10,000 - $240) / 12 = $813.33/month < $1,703.33 -> QMB. + # tested on their own income ($10,000) against the one-person guideline. + # ($10,000 - $240) / 12 = $813.33/month < $1,255 -> QMB. # Spouse2 is not Medicare-eligible and cannot qualify for MSP. input: people: diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/_msp_spouse_income.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/_msp_spouse_income.py new file mode 100644 index 00000000000..d599a79bc19 --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/_msp_spouse_income.py @@ -0,0 +1,42 @@ +from policyengine_us.model_api import * + + +def msp_spouse_value(values: ArrayLike, spouse: ArrayLike) -> ArrayLike: + # Each person's spouse's value, or zero for a person with no spouse. The + # spouse is the row from _ssi_spouse_index, not the rest of the marital unit, + # because a situation that omits marital units puts everyone in one. + return values[np.maximum(spouse, 0)] * (spouse >= 0) + + +def msp_spouses_combine_income( + person: Population, period: Period, spouse: ArrayLike +) -> ArrayLike: + # Spouses who are both on Medicare are tested together. So is an eligible + # couple: each spouse must be eligible for SSI (20 CFR 416.1801(c); POMS SI + # 00501.154 A.1), not only aged, blind or disabled, and uncapped_ssi above + # zero adds the income test that is_ssi_eligible leaves out. + is_medicare_eligible = person("is_medicare_eligible", period.this_year) + both_on_medicare = is_medicare_eligible & msp_spouse_value( + is_medicare_eligible, spouse + ) + eligible_for_ssi = person("uncapped_ssi", period) > 0 + eligible_couple = ( + person("ssi_couple_computation_applies", period) + & eligible_for_ssi + & msp_spouse_value(eligible_for_ssi, spouse) + ) + return both_on_medicare | eligible_couple + + +def msp_spouse_income(person: Population, year: Period, spouse: ArrayLike) -> tuple: + # A spouse's annual earned and unearned income after allocations for + # ineligible children, which come off unearned income first (20 CFR + # 416.1163(a) and (b)). Any spouse who is not eligible for SSI is an + # ineligible spouse, whether or not aged, blind or disabled (20 CFR + # 416.1160(d)). + spouse_earned = msp_spouse_value(person("ssi_earned_income", year), spouse) + spouse_unearned = msp_spouse_value(person("ssi_unearned_income", year), spouse) + allocations = add(person.tax_unit, year, ["ssi_ineligible_child_allocation"]) + earned_left = max_(spouse_earned - max_(allocations - spouse_unearned, 0), 0) + unearned_left = max_(spouse_unearned - allocations, 0) + return earned_left, unearned_left diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py index a8fb624a502..196ced94d51 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py @@ -1,7 +1,15 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.medicare.savings_programs.income._msp_spouse_income import ( + msp_spouse_income, + msp_spouse_value, + msp_spouses_combine_income, +) 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_spouse_index, +) class msp_countable_income(Variable): @@ -13,7 +21,11 @@ class msp_countable_income(Variable): reference = ( "https://www.law.cornell.edu/uscode/text/42/1396d#p", "https://www.law.cornell.edu/cfr/text/20/416.412", + "https://www.law.cornell.edu/cfr/text/20/416.414", + "https://www.law.cornell.edu/cfr/text/20/416.1160", "https://www.law.cornell.edu/cfr/text/20/416.1163", + "https://www.law.cornell.edu/cfr/text/20/416.1801", + "https://secure.ssa.gov/poms.nsf/lnx/0500501154", "https://secure.ssa.gov/apps10/poms.nsf/lnx/0501715010", "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", "https://www.law.cornell.edu/uscode/text/42/1396a#r_2", @@ -28,29 +40,40 @@ class msp_countable_income(Variable): 2. $65 earned income exclusion 3. 50% of remaining earned income excluded - For married couples where both spouses are Medicare-eligible, or where SSI - computes both as a couple because both are aged, blind or disabled, SSI couple - rules apply per 20 CFR 416.412 and 416.1160: both spouses' incomes are combined - before exclusions are applied, and the $20 exclusion is taken once on the - combined income. The combined countable income is then compared against the - couple FPL in the eligibility variables (is_qmb_eligible, is_slmb_eligible, - is_qi_eligible). + For married couples where both spouses are Medicare-eligible, or who are an + eligible couple because each is eligible for SSI (20 CFR 416.412 and + 416.1801(c)), both spouses' incomes are combined before exclusions are + applied, and the $20 exclusion is taken once on the combined income. + + Any other spouse is an ineligible spouse under 20 CFR 416.1160(d), even one + who is aged, blind or disabled. Under 20 CFR 416.1163, allocations for + ineligible children come off that spouse's income. A remainder above the + difference between the couple and individual benefit rates is added to the + person's own income before exclusions, and otherwise nothing is deemed. A + medical treatment facility that separates a couple leaves each with their own + income (20 CFR 416.414(b)(3)). The eligibility variables (is_qmb_eligible, + is_slmb_eligible, is_qi_eligible) compare the result with msp_fpg, which is the + couple FPL whenever msp_tests_as_couple is true. Under 42 U.S.C. 1396a(r)(2) a state may exclude more than the $20 SSI general income exclusion; Illinois and Mississippi use their larger exclusion in its place. Mississippi takes it once from a couple's combined income; Illinois gives each applying spouse with income their - own exclusion, up to that spouse's income. Illinois gives its $25 only on - income other than SSI and contributions (alimony, child support, outside - cash help); the $20 SSI exclusion stays the floor. + own exclusion, up to that spouse's income, and one exclusion when only one + spouse is on Medicare. Illinois gives its $25 only on income other than SSI + and contributions (alimony, child support, outside cash help); the $20 SSI + exclusion stays the floor. """ def formula(person, period, parameters): year = period.this_year earned_income = person("ssi_earned_income", year) unearned_income = person("ssi_unearned_income", year) + spouse = _ssi_spouse_index(person, year) is_medicare_eligible = person("is_medicare_eligible", year) - both_medicare_eligible = person.marital_unit.sum(is_medicare_eligible) == 2 + both_medicare_eligible = is_medicare_eligible & msp_spouse_value( + is_medicare_eligible, spouse + ) blind_or_disabled_working_student_exclusion = person( "ssi_blind_or_disabled_working_student_exclusion", year @@ -59,7 +82,6 @@ def formula(person, period, parameters): earned_income - blind_or_disabled_working_student_exclusion, 0, ) - deeming_applies = person("is_ssi_spousal_deeming_applies", year) state = person.household("state_code_str", period) p = parameters(period).gov.hhs.medicare.savings_programs.eligibility.income ssi_general = parameters(period).gov.ssa.ssi.income.exclusions.general @@ -89,23 +111,22 @@ def formula(person, period, parameters): max_(ssi_general, min_(state_exclusion, qualifying_income)), general_exclusion, ) + # Two spouses on Medicare each apply. When only one does, PM 15-04-03-a + # allows one $25 on income in the standard, even if that spouse has no + # income other than SSI or contributions. per_spouse = p.state_general_exclusion_per_spouse[state].astype(bool) + own_exemption = min_(state_exclusion, qualifying_income) couple_general_exclusion = where( - per_spouse, + per_spouse & both_medicare_eligible, max_( ssi_general, - person.marital_unit.sum( - min_(state_exclusion, qualifying_income) * is_medicare_eligible - ), + own_exemption + msp_spouse_value(own_exemption, spouse), ), general_exclusion, ) - spouse_earned_income = person( - "ssi_earned_income_deemed_from_ineligible_spouse", year - ) - spouse_unearned_income = person( - "ssi_unearned_income_deemed_from_ineligible_spouse", year + spouse_earned_income, spouse_unearned_income = msp_spouse_income( + person, year, spouse ) personal_countable_income = _apply_ssi_exclusions( personal_earned_income, @@ -117,11 +138,13 @@ def formula(person, period, parameters): couple_countable = _apply_ssi_exclusions( max_( - person.marital_unit.sum(earned_income) - - person.marital_unit.sum(blind_or_disabled_working_student_exclusion), + earned_income + + msp_spouse_value(earned_income, spouse) + - blind_or_disabled_working_student_exclusion + - msp_spouse_value(blind_or_disabled_working_student_exclusion, spouse), 0, ), - person.marital_unit.sum(unearned_income), + unearned_income + msp_spouse_value(unearned_income, spouse), parameters, year, general_exclusion=couple_general_exclusion, @@ -136,15 +159,13 @@ def formula(person, period, parameters): ) single_countable = where( - deeming_applies, deemed_countable, personal_countable_income + person("msp_tests_as_couple", period), + deemed_countable, + personal_countable_income, ) - # A spouse who is aged, blind or disabled but not on Medicare is neither - # an applying spouse nor an ineligible spouse whose income is deemed, so - # the SSI couple computation is what brings in their income. - couple_computation = person("ssi_couple_computation_applies", period) annual_countable = where( - both_medicare_eligible | couple_computation, + msp_spouses_combine_income(person, period, spouse), couple_countable, single_countable, ) diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_fpg.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_fpg.py index b1cb24efc20..be47fb61f66 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_fpg.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_fpg.py @@ -6,16 +6,18 @@ class msp_fpg(Variable): entity = Person unit = USD label = "MSP Federal Poverty Guideline" - definition_period = YEAR + definition_period = MONTH reference = ( "https://www.law.cornell.edu/uscode/text/42/1396d#p", + "https://www.law.cornell.edu/cfr/text/20/416.1163", "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", ) documentation = """ - The Federal Poverty Guideline used for MSP eligibility is based on + The monthly Federal Poverty Guideline used for MSP eligibility is based on individual or couple status, not the full household/SPM unit size. Per 42 U.S.C. 1396d(p), MSP income limits are based on the federal - poverty line for a family of the size involved. + poverty line for a family of the size involved. The family is two people + when msp_tests_as_couple is true, and one otherwise. Note: Published Medicare.gov limits are $20/month higher than raw FPL because they include the $20 general income exclusion in the threshold. @@ -29,12 +31,11 @@ def formula(person, period, parameters): # Note: These are raw FPL values. Published limits are $20 higher # because we apply the $20 exclusion to income (msp_countable_income) # rather than adding it to the threshold. - married = person.spm_unit("spm_unit_is_married", period.this_year) + tests_as_couple = person("msp_tests_as_couple", period) state_group = person.household("state_group_str", period) p = parameters(period).gov.hhs.fpg p1 = p.first_person[state_group] pn = p.additional_person[state_group] - # 1 person if single, 2 persons if married individual_fpg = p1 couple_fpg = p1 + pn - return where(married, couple_fpg, individual_fpg) + return where(tests_as_couple, couple_fpg, individual_fpg) / MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_tests_as_couple.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_tests_as_couple.py new file mode 100644 index 00000000000..e5c72cec3a1 --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_tests_as_couple.py @@ -0,0 +1,48 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.medicare.savings_programs.income._msp_spouse_income import ( + msp_spouse_income, + msp_spouses_combine_income, +) +from policyengine_us.variables.gov.ssa.ssi.eligibility.resources.deemed._ssi_spouses import ( + _ssi_spouse_index, +) + + +class msp_tests_as_couple(Variable): + value_type = bool + entity = Person + label = "Medicare Savings Program tests this person as a couple" + definition_period = MONTH + reference = ( + "https://www.law.cornell.edu/uscode/text/42/1396d#p", + "https://www.law.cornell.edu/cfr/text/20/416.414", + "https://www.law.cornell.edu/cfr/text/20/416.1160", + "https://www.law.cornell.edu/cfr/text/20/416.1163", + "https://www.law.cornell.edu/cfr/text/20/416.1801", + "https://secure.ssa.gov/poms.nsf/lnx/0500501154", + ) + documentation = """ + Whether SSI's spouse rules count a spouse's income for this person, which also + makes the two-person poverty guideline the income limit. That holds when both + spouses are on Medicare, when they are an eligible couple, or when the spouse's + income after allocations for ineligible children is more than the difference + between the couple and individual benefit rates (20 CFR 416.1163(d)(2)). If it + is not more, nothing is deemed (416.1163(d)(1)). A medical treatment facility + that separates a couple also leaves each spouse tested alone (416.414(b)(3)), + unless both are on Medicare, when the model still tests them together. A person + with no co-resident spouse (_ssi_spouse_index) is always tested alone. + """ + + def formula(person, period, parameters): + year = period.this_year + spouse = _ssi_spouse_index(person, year) + spouse_earned, spouse_unearned = msp_spouse_income(person, year, spouse) + p = parameters(period).gov.ssa.ssi.amount + rate_difference = (p.couple - p.individual) * MONTHS_IN_YEAR + spouse_income_is_deemed = spouse_earned + spouse_unearned > rate_difference + split_by_facility = person("ssi_claim_is_joint", year) & ~person( + "ssi_couple_computation_applies", period + ) + return msp_spouses_combine_income(person, period, spouse) | ( + spouse_income_is_deemed & ~split_by_facility + ) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 025bb5dd42b..b208b7cdc5b 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -2779,6 +2779,43 @@ candidates: state: ruled note: Ruled statute-first in October 2026; verify the incorporated dependency bar and annual questionnaire routing, including the 2026 draft. +- id: us-il:msp-exemption-applicant-without-income + program: medicare-savings-programs + years: + from: 2026 + type: calendar + question: Does an Illinois Medicare Savings Program applicant with no income of their own get the $25 exemption on + income counted from a spouse who is not applying? + sources: + - kind: manual + citation: IDHS Policy Manual PM 15-04-03-a + url: https://www.dhs.state.il.us/page.aspx?item=16387 + - kind: regulation + citation: 89 Ill. Adm. Code 120.335(a)(1) + url: https://www.ilga.gov/ftp/JCAR/AdminCode/089/089001200H03350R.html + - kind: manual + citation: IDHS Policy Manual PM 06-12-01-c + url: https://www.dhs.state.il.us/page.aspx?item=14179 + readings: + statute: 'No statute is supplied; 120.335(a)(1) exempts "the first $25.00 of a client''s earned or unearned income + other than SSI income, or contributions from a spouse or other individual", and PM 06-12-01-c allows "no more + than one income exemption for their own income" and an exemption on a spouse''s income only if the spouse "is + an applicant or client in another MANG case".' + other: 'PM 15-04-03-a: "If the person applying for or receiving AABD Medical has no income other than SSI or + contributions, and another person included in the standard has income, deduct one $25 exemption even if the + other person receives benefits under one of the Family Health Plans."' + example: An Illinois applicant, 68, with no income and a disabled spouse, 62, with $1,825 a month counts $1,800 and + is QMB with one $25, and counts $1,805 and is SLMB with only the SSI $20. + pe_us: + follows: conflicting-source + code: + - policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10633 + state: ruled + note: 'PE-US follows PM 15-04-03-a: one $25 applies when only one spouse is on Medicare, whether or not that spouse + has income of their own. Two spouses on Medicare keep one exemption each, capped at each spouse''s own income. Ruled + by the project lead, 2026-11-02: https://github.com/PolicyEngine/policyengine-us/pull/10633' - id: us-il:msp-income-exemption-contributions program: medicare-savings-programs years: @@ -10473,6 +10510,52 @@ candidates: three places, and floors the result at the federal deduction, a PE safeguard that binds only when the rounded share falls below the exact federal share. Settling the federal reading (rulespec-us#1413) and using it for both would make that floor unnecessary.' +- id: us:msp-eligible-spouse-ssi-definition + program: medicare-savings-programs + years: + from: 2026 + type: calendar + question: Under SSI methodology for the Medicare Savings Programs, is a spouse who is aged, blind or disabled but not + eligible for SSI an eligible spouse, whose income is combined, or an ineligible spouse, whose income is deemed? + sources: + - kind: statute + citation: 42 U.S.C. 1382c(b) and (f)(1) + url: https://www.law.cornell.edu/uscode/text/42/1382c + - kind: regulation + citation: 20 CFR 416.1801(c) + url: https://www.law.cornell.edu/cfr/text/20/416.1801 + - kind: regulation + citation: 20 CFR 416.1160(d) + url: https://www.law.cornell.edu/cfr/text/20/416.1160 + - kind: regulation + citation: 20 CFR 416.1163(b) and (d) + url: https://www.law.cornell.edu/cfr/text/20/416.1163 + - kind: manual + citation: POMS SI 00501.154 A.1 + url: https://secure.ssa.gov/poms.nsf/lnx/0500501154 + readings: + statute: 'Section 1382c(b) defines an eligible spouse as "an aged, blind, or disabled individual" married to and + living with another such individual, and (f)(1) deems income from a spouse who "is not an eligible spouse".' + other: '416.1801(c) defines an eligible spouse as a person "Who is eligible for SSI"; 416.1160(d) defines an + ineligible spouse as someone who "is not eligible for SSI benefits"; POMS SI 00501.154 A.1 applies couple + computation rules if "each is eligible for benefits". 416.1163 then allocates to ineligible children and deems + only a remainder above the rate difference.' + example: A, on Medicare, has $1,000 a month and B, 62 and disabled, has $1,500, with two ineligible children. Combined + income is $2,480 and no tier; under deeming it is $1,000 + ($1,500 - 2 x $497) - $20 = $1,486 and QMB. + pe_us: + follows: conflicting-source + code: + - policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_tests_as_couple.py + - policyengine_us/variables/gov/hhs/medicare/savings_programs/income/_msp_spouse_income.py + - policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_countable_income.py + - policyengine_us/variables/gov/hhs/medicare/savings_programs/income/msp_fpg.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10633 + state: ruled + note: 'PE-US follows the regulations for the Medicare Savings Programs: spouses are an eligible couple only if each + is eligible for SSI, and any other spouse is an ineligible spouse, with income deemed above the rate difference. + The SSI variables (is_ssi_ineligible_spouse, ssi_couple_computation_applies) still classify by aged, blind or + disabled status. Ruled by the project lead, 2026-11-02: https://github.com/PolicyEngine/policyengine-us/pull/10633' - id: us:niit-excess-business-loss-addback program: income-tax years: