From c5ff526bdc9aef8a6507fe1df61b868b6b12bb75 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 2 Nov 2026 17:01:30 -0500 Subject: [PATCH 1/3] Apply immigration tests to Medicare and Medicare Savings Programs Co-Authored-By: Claude Opus 5.5 --- .../medicare-immigration-test.fixed.md | 1 + .../eligibility/permanent_resident_years.yaml | 10 +++ .../eligibility/is_medicare_eligible.yaml | 89 +++++++++++++++++++ .../category/msp_category.yaml | 36 ++++++++ .../eligibility/is_medicare_eligible.py | 33 ++++++- .../category/is_qi_eligible.py | 13 ++- .../category/is_qmb_eligible.py | 9 +- .../category/is_slmb_eligible.py | 9 +- .../msp_part_b_premium_coverage.py | 6 +- 9 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 changelog.d/medicare-immigration-test.fixed.md create mode 100644 policyengine_us/parameters/gov/hhs/medicare/eligibility/permanent_resident_years.yaml diff --git a/changelog.d/medicare-immigration-test.fixed.md b/changelog.d/medicare-immigration-test.fixed.md new file mode 100644 index 00000000000..a880ffa23e5 --- /dev/null +++ b/changelog.d/medicare-immigration-test.fixed.md @@ -0,0 +1 @@ +Apply immigration tests to Medicare (42 U.S.C. 1395o(a)(2) and 8 U.S.C. 1611(b)(3)) and to Medicare Savings Programs (42 U.S.C. 1396b(v)), so undocumented people no longer get either. diff --git a/policyengine_us/parameters/gov/hhs/medicare/eligibility/permanent_resident_years.yaml b/policyengine_us/parameters/gov/hhs/medicare/eligibility/permanent_resident_years.yaml new file mode 100644 index 00000000000..09eff6859c7 --- /dev/null +++ b/policyengine_us/parameters/gov/hhs/medicare/eligibility/permanent_resident_years.yaml @@ -0,0 +1,10 @@ +description: The United States requires lawful permanent residents without Part A entitlement to have this many years of continuous residence to enroll in Medicare at the eligibility age. +metadata: + period: year + unit: year + label: Medicare permanent resident years of continuous residence + reference: + - title: 42 U.S. Code § 1395o(a)(2) + href: https://www.law.cornell.edu/uscode/text/42/1395o#a +values: + 1966-01-01: 5 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/is_medicare_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/is_medicare_eligible.yaml index 79a28836ab3..e78dba582ef 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/is_medicare_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/is_medicare_eligible.yaml @@ -28,3 +28,92 @@ months_receiving_social_security_disability: 24 output: is_medicare_eligible: true + +# 8 U.S.C. 1611(b)(3) limits Medicare benefits to noncitizens lawfully present. +- name: is_medicare_eligible unit test 5 + period: 2026 + input: + age: 70 + immigration_status: UNDOCUMENTED + output: + is_medicare_eligible: false + +# 42 U.S.C. 1395o(a)(2): a permanent resident of five years may enroll at 65 +# without Part A entitlement, and one of fewer than five years may not. +- name: is_medicare_eligible unit test 6 + period: 2026 + input: + age: 70 + immigration_status: LEGAL_PERMANENT_RESIDENT + years_since_us_entry: 5 + medicare_quarters_of_coverage: 0 + output: + is_medicare_eligible: true + +- name: is_medicare_eligible unit test 7 + period: 2026 + input: + age: 70 + immigration_status: LEGAL_PERMANENT_RESIDENT + years_since_us_entry: 4 + medicare_quarters_of_coverage: 0 + output: + is_medicare_eligible: false + +# 42 U.S.C. 1395o(a)(1): entitlement to Part A opens Part B regardless of status. +- name: is_medicare_eligible unit test 8 + period: 2026 + input: + age: 70 + immigration_status: LEGAL_PERMANENT_RESIDENT + years_since_us_entry: 4 + medicare_quarters_of_coverage: 40 + output: + is_medicare_eligible: true + +- name: is_medicare_eligible unit test 9 + period: 2026 + input: + age: 70 + immigration_status: TPS + medicare_quarters_of_coverage: 40 + output: + is_medicare_eligible: true + +- name: is_medicare_eligible unit test 10 + period: 2026 + input: + age: 70 + immigration_status: TPS + medicare_quarters_of_coverage: 39 + output: + is_medicare_eligible: false + +# A citizen without enough quarters can still buy Part A and enroll in Part B. +- name: is_medicare_eligible unit test 11 + period: 2026 + input: + age: 70 + medicare_quarters_of_coverage: 0 + output: + is_medicare_eligible: true + +- name: is_medicare_eligible unit test 12 + period: 2026 + input: + age: 50 + immigration_status: TPS + social_security_disability: 12000 + months_receiving_social_security_disability: 24 + output: + is_medicare_eligible: true + +- name: is_medicare_eligible unit test 13 + period: 2026 + input: + age: 50 + immigration_status: UNDOCUMENTED + social_security_disability: 12000 + months_receiving_social_security_disability: 24 + output: + is_medicare_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/category/msp_category.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/category/msp_category.yaml index c788af80531..0bbd3183b4c 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/category/msp_category.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/category/msp_category.yaml @@ -97,3 +97,39 @@ spm_unit_is_married: false output: msp_category: NONE + +- name: Case 8, undocumented person is not on Medicare and gets no tier. + period: 2026 + input: + age: 70 + immigration_status: UNDOCUMENTED + pension_income: 10_000 + state_code: TX + output: + msp_category: + 2026-01: NONE + +# Medicare comes through Part A work history, but 42 U.S.C. 1396b(v) bars federal +# Medicaid payment, which includes Medicare Savings Program benefits. +- name: Case 9, person with temporary protected status on Medicare gets no tier. + period: 2026 + input: + age: 70 + immigration_status: TPS + pension_income: 10_000 + state_code: TX + output: + msp_category: + 2026-01: NONE + +- name: Case 10, permanent resident inside the five-year bar on Medicare gets no tier. + period: 2026 + input: + age: 70 + immigration_status: LEGAL_PERMANENT_RESIDENT + pension_income: 10_000 + years_since_us_entry: 2 + state_code: TX + output: + msp_category: + 2026-01: NONE diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/is_medicare_eligible.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/is_medicare_eligible.py index 0772dde903c..44eb8b25d2d 100644 --- a/policyengine_us/variables/gov/hhs/medicare/eligibility/is_medicare_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/is_medicare_eligible.py @@ -16,9 +16,10 @@ class is_medicare_eligible(Variable): "(42 U.S.C. 426-1) are not, as the model has no inputs for those " "conditions." ) - reference = dict( - title="CMS, Original Medicare (Part A and B) Eligibility and Enrollment", - href="https://www.cms.gov/medicare/enrollment-renewal/health-plans/original-part-a-b", + reference = ( + "https://www.cms.gov/medicare/enrollment-renewal/health-plans/original-part-a-b", + "https://www.law.cornell.edu/uscode/text/42/1395o#a", + "https://www.law.cornell.edu/uscode/text/8/1611#b_3", ) def formula(person, period, parameters): @@ -29,4 +30,28 @@ def formula(person, period, parameters): is_disability_eligible = gets_ssdi & ( months >= p.min_months_receiving_social_security_disability ) - return is_age_eligible | is_disability_eligible + # 8 U.S.C. 1611(b)(3) limits Medicare benefits for noncitizens to those + # lawfully present, and Part A to wages from authorized employment. + status = person("immigration_status", period) + statuses = status.possible_values + lawfully_present = status != statuses.UNDOCUMENTED + # 42 U.S.C. 1395o(a)(2): at 65, a person without Part A may enroll in + # Part B only as a citizen or a permanent resident of five years. + is_long_term_resident = (status == statuses.LEGAL_PERMANENT_RESIDENT) & ( + person("years_since_us_entry", period) >= p.permanent_resident_years + ) + # 42 U.S.C. 1395o(a)(1): entitlement to Part A opens Part B to anyone else. + has_part_a_work_history = ( + person("medicare_quarters_of_coverage", period) + >= parameters( + period + ).gov.hhs.medicare.part_a.premium_free_quarters_threshold + ) + can_enroll_at_age = ( + (status == statuses.CITIZEN) + | is_long_term_resident + | (lawfully_present & has_part_a_work_history) + ) + return (is_age_eligible & can_enroll_at_age) | ( + is_disability_eligible & lawfully_present + ) diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qi_eligible.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qi_eligible.py index febc8e8d5a8..033e5079943 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qi_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qi_eligible.py @@ -9,6 +9,7 @@ class is_qi_eligible(Variable): reference = ( "https://www.law.cornell.edu/cfr/text/42/435.125", "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", + "https://www.law.cornell.edu/uscode/text/42/1396b#v", ) def formula(person, period, parameters): @@ -17,6 +18,10 @@ def formula(person, period, parameters): p = parameters(period).gov.hhs.medicare.savings_programs.eligibility.income medicare_eligible = person("is_medicare_eligible", period.this_year) + # QMB, SLMB and QI are Medicaid benefits, so 42 U.S.C. 1396b(v) applies. + immigration_eligible = person( + "is_medicaid_immigration_status_eligible", period.this_year + ) asset_eligible = person("msp_asset_eligible", period) fpg = person("msp_fpg", period) @@ -32,4 +37,10 @@ def formula(person, period, parameters): # QI excludes those eligible for other Medicaid coverage medicaid_eligible = person("is_medicaid_eligible", period.this_year) - return medicare_eligible & income_eligible & asset_eligible & ~medicaid_eligible + return ( + medicare_eligible + & immigration_eligible + & income_eligible + & asset_eligible + & ~medicaid_eligible + ) diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qmb_eligible.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qmb_eligible.py index 97628c9ce47..d6d2b6afdc2 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qmb_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qmb_eligible.py @@ -9,6 +9,7 @@ class is_qmb_eligible(Variable): reference = ( "https://www.law.cornell.edu/cfr/text/42/435.123", "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", + "https://www.law.cornell.edu/uscode/text/42/1396b#v", ) def formula(person, period, parameters): @@ -16,6 +17,10 @@ def formula(person, period, parameters): p = parameters(period).gov.hhs.medicare.savings_programs.eligibility.income medicare_eligible = person("is_medicare_eligible", period.this_year) + # QMB, SLMB and QI are Medicaid benefits, so 42 U.S.C. 1396b(v) applies. + immigration_eligible = person( + "is_medicaid_immigration_status_eligible", period.this_year + ) asset_eligible = person("msp_asset_eligible", period) fpg = person("msp_fpg", period) @@ -23,4 +28,6 @@ def formula(person, period, parameters): qmb_income_limit = fpg * p.qmb.fpl_limit income_eligible = countable_income <= qmb_income_limit - return medicare_eligible & income_eligible & asset_eligible + return ( + medicare_eligible & immigration_eligible & income_eligible & asset_eligible + ) diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_slmb_eligible.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_slmb_eligible.py index 9bfe6d615c6..454ec21b9c9 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_slmb_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_slmb_eligible.py @@ -9,6 +9,7 @@ class is_slmb_eligible(Variable): reference = ( "https://www.law.cornell.edu/cfr/text/42/435.124", "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", + "https://www.law.cornell.edu/uscode/text/42/1396b#v", ) def formula(person, period, parameters): @@ -16,6 +17,10 @@ def formula(person, period, parameters): p = parameters(period).gov.hhs.medicare.savings_programs.eligibility.income medicare_eligible = person("is_medicare_eligible", period.this_year) + # QMB, SLMB and QI are Medicaid benefits, so 42 U.S.C. 1396b(v) applies. + immigration_eligible = person( + "is_medicaid_immigration_status_eligible", period.this_year + ) asset_eligible = person("msp_asset_eligible", period) fpg = person("msp_fpg", period) @@ -28,4 +33,6 @@ def formula(person, period, parameters): income_below_slmb = countable_income < slmb_income_limit income_eligible = income_above_qmb & income_below_slmb - return medicare_eligible & income_eligible & asset_eligible + return ( + medicare_eligible & immigration_eligible & income_eligible & asset_eligible + ) diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.py index cac89f2d0d1..4e6d0eb4b17 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.py @@ -28,11 +28,15 @@ def formula(person, period, parameters): monthly_standard_premium = ( person("base_part_b_premium", period) / MONTHS_IN_YEAR ) + # MSP coverage is a Medicaid benefit, so 42 U.S.C. 1396b(v) applies. + immigration_eligible = person("is_medicaid_immigration_status_eligible", period) monthly_coverage = 0 for month in period.get_subperiods(MONTH): income_eligible = person("msp_income_eligible", month) asset_eligible = person("msp_asset_eligible", month) - eligible_for_coverage = enrolled & income_eligible & asset_eligible + eligible_for_coverage = ( + enrolled & income_eligible & asset_eligible & immigration_eligible + ) monthly_coverage += where( eligible_for_coverage, monthly_standard_premium, From 599087056fff5a445b8d273b461a4718fe294898 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 2 Nov 2026 17:47:34 -0500 Subject: [PATCH 2/3] Date the Part A quarters threshold from 1966 and fix a SNAP immigration test input Co-Authored-By: Claude Opus 5.5 --- .../medicare/part_a/premium_free_quarters_threshold.yaml | 6 +++++- .../gov/usda/snap/eligibility/immigration/integration.yaml | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/policyengine_us/parameters/gov/hhs/medicare/part_a/premium_free_quarters_threshold.yaml b/policyengine_us/parameters/gov/hhs/medicare/part_a/premium_free_quarters_threshold.yaml index bc6ab7c2646..232d1c7c8d0 100644 --- a/policyengine_us/parameters/gov/hhs/medicare/part_a/premium_free_quarters_threshold.yaml +++ b/policyengine_us/parameters/gov/hhs/medicare/part_a/premium_free_quarters_threshold.yaml @@ -6,5 +6,9 @@ metadata: reference: - title: Medicare.gov - Part A costs href: https://www.cms.gov/newsroom/fact-sheets/2026-medicare-parts-b-premiums-deductibles + - title: 42 U.S. Code § 426(a) - Entitlement to hospital insurance benefits (fully insured individuals) + href: https://www.law.cornell.edu/uscode/text/42/426 + - title: 42 U.S. Code § 414(a) - Fully insured individual (no more than 40 quarters of coverage required) + href: https://www.law.cornell.edu/uscode/text/42/414 values: - 2000-01-01: 40 + 1966-01-01: 40 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/integration.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/integration.yaml index 8ff88c98a85..ca779075242 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/immigration/integration.yaml @@ -591,6 +591,9 @@ age: 67 immigration_status: LEGAL_PERMANENT_RESIDENT years_since_us_entry: 2 + # Too few quarters for Part A, so inside five years this person has no + # Medicare and no Part B premium to deduct. + medicare_quarters_of_coverage: 0 spm_units: spm_unit: members: [person1] From 885db36c5a420c678cac7bc89f162a6702d1764e Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 2 Nov 2026 22:18:33 -0500 Subject: [PATCH 3/3] Apply the 42 USC 1395mmm status list to Medicare from 2027 Pub. L. 119-21 section 71201 limits Medicare to citizens, nationals, permanent residents, Cuban and Haitian entrants and Compact residents. The model treats everyone as grandfathered through 2026, since no input records enrollment on July 4, 2025. Also pins the MSP and premium gates and corrects citations. Co-Authored-By: Claude Opus 5.5 --- .../medicare-immigration-test.fixed.md | 2 +- .../eligibility/immigration_statuses.yaml | 42 ++++++++++ .../premium_free_quarters_threshold.yaml | 5 +- .../aca/eligibility/is_aca_ptc_eligible.yaml | 17 ++++ .../gov/hhs/medicare/costs/medicare_cost.yaml | 24 ++++++ .../hhs/medicare/costs/medicare_enrolled.yaml | 24 ++++++ .../eligibility/is_medicare_eligible.yaml | 79 ++++++++++++++++++- .../category/msp_category.yaml | 53 ++++++++++++- .../msp_part_b_premium_coverage.yaml | 25 ++++++ .../eligibility/is_medicare_eligible.py | 23 ++++-- .../part_a/medicare_quarters_of_coverage.py | 13 ++- .../category/is_qi_eligible.py | 4 +- .../category/is_qmb_eligible.py | 4 +- .../category/is_slmb_eligible.py | 4 +- .../msp_part_a_premium_coverage.py | 9 ++- .../msp_part_b_premium_coverage.py | 8 +- 16 files changed, 313 insertions(+), 23 deletions(-) create mode 100644 policyengine_us/parameters/gov/hhs/medicare/eligibility/immigration_statuses.yaml diff --git a/changelog.d/medicare-immigration-test.fixed.md b/changelog.d/medicare-immigration-test.fixed.md index a880ffa23e5..254f88eb36a 100644 --- a/changelog.d/medicare-immigration-test.fixed.md +++ b/changelog.d/medicare-immigration-test.fixed.md @@ -1 +1 @@ -Apply immigration tests to Medicare (42 U.S.C. 1395o(a)(2) and 8 U.S.C. 1611(b)(3)) and to Medicare Savings Programs (42 U.S.C. 1396b(v)), so undocumented people no longer get either. +Apply immigration tests to Medicare (42 U.S.C. 1395o(a)(2), 42 U.S.C. 1395mmm and 8 U.S.C. 1611(b)(3)) and to Medicare Savings Programs (42 U.S.C. 1396b(v), 8 U.S.C. 1611(a) and 1613(a)), so undocumented people no longer get either and, from 2027, only citizens, permanent residents and Cuban and Haitian entrants can get Medicare. diff --git a/policyengine_us/parameters/gov/hhs/medicare/eligibility/immigration_statuses.yaml b/policyengine_us/parameters/gov/hhs/medicare/eligibility/immigration_statuses.yaml new file mode 100644 index 00000000000..a0fdf0dda5f --- /dev/null +++ b/policyengine_us/parameters/gov/hhs/medicare/eligibility/immigration_statuses.yaml @@ -0,0 +1,42 @@ +description: The United States limits eligibility to these immigration statuses under the Medicare program. + +# 42 USC 1395mmm(a) limits Medicare to citizens and nationals, permanent residents, +# Cuban and Haitian entrants, and Compact of Free Association residents. Nationals +# and Compact residents have no immigration_status value. +# 42 USC 1395mmm(b)(1) applies that limit on January 4, 2027 to people already +# entitled or enrolled on July 4, 2025. A YEAR variable reads parameters at January 1, +# so the list is dated January 1, 2027; a January 4 date would not apply until 2028. +# 42 USC 1395mmm(a) bars new entrants from July 4, 2025 at once. No input records +# enrollment on July 4, 2025, so the model treats everyone as grandfathered through +# 2026, which overstates eligibility for those new entrants. +# The earlier list is the lawful presence limit in 8 USC 1611(b)(3), applied backward +# to the start of Medicare as an approximation. +values: + 1966-01-01: + - CITIZEN + - LEGAL_PERMANENT_RESIDENT + - REFUGEE + - ASYLEE + - DEPORTATION_WITHHELD + - CUBAN_HAITIAN_ENTRANT + - CONDITIONAL_ENTRANT + - PAROLED_ONE_YEAR + - DACA + - TPS + + 2027-01-01: + - CITIZEN + - LEGAL_PERMANENT_RESIDENT + - CUBAN_HAITIAN_ENTRANT + +metadata: + unit: list + period: year + label: Medicare eligible immigration statuses + reference: + - title: 42 U.S. Code § 1395mmm - Limiting Medicare coverage of certain individuals + href: https://www.law.cornell.edu/uscode/text/42/1395mmm + - title: "Public Law 119-21, section 71201 #page 249-250" + href: https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=249 + - title: 8 U.S. Code § 1611(b)(3) + href: https://www.law.cornell.edu/uscode/text/8/1611#b_3 diff --git a/policyengine_us/parameters/gov/hhs/medicare/part_a/premium_free_quarters_threshold.yaml b/policyengine_us/parameters/gov/hhs/medicare/part_a/premium_free_quarters_threshold.yaml index 232d1c7c8d0..ee25bce16f8 100644 --- a/policyengine_us/parameters/gov/hhs/medicare/part_a/premium_free_quarters_threshold.yaml +++ b/policyengine_us/parameters/gov/hhs/medicare/part_a/premium_free_quarters_threshold.yaml @@ -6,9 +6,12 @@ metadata: reference: - title: Medicare.gov - Part A costs href: https://www.cms.gov/newsroom/fact-sheets/2026-medicare-parts-b-premiums-deductibles - - title: 42 U.S. Code § 426(a) - Entitlement to hospital insurance benefits (fully insured individuals) + - title: 42 U.S. Code § 426(a) - Entitlement to hospital insurance benefits href: https://www.law.cornell.edu/uscode/text/42/426 - title: 42 U.S. Code § 414(a) - Fully insured individual (no more than 40 quarters of coverage required) href: https://www.law.cornell.edu/uscode/text/42/414 +# 40 is the ceiling under 42 USC 414(a), which requires one quarter for each year after +# 1950 and before the year a person reaches 62, and at least 6, so people reaching 62 +# before 1991 needed fewer. It is applied backward to 1966 as an approximation. values: 1966-01-01: 40 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_ptc_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_ptc_eligible.yaml index 0c8789a0467..00ff4c05de5 100644 --- a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_ptc_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_ptc_eligible.yaml @@ -495,3 +495,20 @@ has_qualifying_non_marketplace_health_coverage_at_interview: [true] pays_aca_premium: [false] is_aca_ptc_eligible: [false] + +# 42 U.S.C. 1395o(a)(2): a permanent resident of two years without Part A can't enroll +# in Medicare at 70, so Medicare doesn't block the credit. The five-year bar in +# 8 U.S.C. 1613(a) also rules out Medicaid. +# $30,000 / 2025 individual FPL of $15,650 = 1.91, inside the 1.00 to 4.00 income range. +- name: Permanent resident barred from Medicare at age 70 qualifies for ACA PTC + period: 2026 + input: + age: 70 + immigration_status: LEGAL_PERMANENT_RESIDENT + years_since_us_entry: 2 + medicare_quarters_of_coverage: 0 + taxable_private_pension_income: 30_000 + state_code: TX + output: + is_medicare_eligible: false + is_aca_ptc_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml index b0fb091fce4..d16baca6b2d 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml @@ -69,3 +69,27 @@ gross_medicare_part_b_premium: 3_000 output: medicare_cost: 14_751 # $17,751 spending - $3,000 Part B premium + +- name: unit test 7 - undocumented person is not enrolled and has no cost + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + immigration_status: UNDOCUMENTED + pension_income: 10_000 + state_code: TX + output: + medicare_cost: 0 + +- name: unit test 8 - person with temporary protected status, premium-free Part A + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + immigration_status: TPS + medicare_quarters_of_coverage: 40 + pension_income: 10_000 + state_code: TX + output: + # $18,983 spending - $2,434.80 Part B premium ($202.90 * 12) (2026) + medicare_cost: 16_548.2 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_enrolled.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_enrolled.yaml index 6dcd78c928a..d9651387f3c 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_enrolled.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_enrolled.yaml @@ -35,3 +35,27 @@ medicare_enrolled: true output: medicare_enrolled: true + +# 8 U.S.C. 1611(b)(3) limits Medicare benefits to noncitizens lawfully present. +- name: Undocumented person aged 65 or older is not enrolled. + period: 2026 + input: + age: 70 + immigration_status: UNDOCUMENTED + pension_income: 10_000 + state_code: TX + output: + medicare_enrolled: false + +# Allowed in 2026 because the model treats everyone as grandfathered under +# 42 U.S.C. 1395mmm(b)(1) through 2026. +- name: Person with temporary protected status and Part A work history is enrolled. + period: 2026 + input: + age: 70 + immigration_status: TPS + medicare_quarters_of_coverage: 40 + pension_income: 10_000 + state_code: TX + output: + medicare_enrolled: true diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/is_medicare_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/is_medicare_eligible.yaml index e78dba582ef..968c3b4d0eb 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/is_medicare_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/is_medicare_eligible.yaml @@ -60,7 +60,9 @@ output: is_medicare_eligible: false -# 42 U.S.C. 1395o(a)(1): entitlement to Part A opens Part B regardless of status. +# 42 U.S.C. 1395o(a)(1): entitlement to Part A opens Part B to anyone with an allowed +# status, so the five-year rule doesn't apply. A permanent resident stays allowed from +# 2027 under 42 U.S.C. 1395mmm(a)(2). - name: is_medicare_eligible unit test 8 period: 2026 input: @@ -71,6 +73,8 @@ output: is_medicare_eligible: true +# Temporary protected status is allowed in 2026 only because the model treats everyone +# as grandfathered under 42 U.S.C. 1395mmm(b)(1) through 2026. Unit test 14 covers 2027. - name: is_medicare_eligible unit test 9 period: 2026 input: @@ -98,12 +102,15 @@ output: is_medicare_eligible: true +# The SSDI route also needs an allowed status. Temporary protected status is allowed in +# 2026 only because the model treats everyone as grandfathered under 42 U.S.C. +# 1395mmm(b)(1) through 2026. Unit test 16 covers 2027. - name: is_medicare_eligible unit test 12 period: 2026 input: age: 50 immigration_status: TPS - social_security_disability: 12000 + social_security_disability: 12_000 months_receiving_social_security_disability: 24 output: is_medicare_eligible: true @@ -113,7 +120,73 @@ input: age: 50 immigration_status: UNDOCUMENTED - social_security_disability: 12000 + social_security_disability: 12_000 months_receiving_social_security_disability: 24 output: is_medicare_eligible: false + +# 42 U.S.C. 1395mmm(a) limits Medicare from 2027 to citizens and nationals, permanent +# residents, Cuban and Haitian entrants, and Compact of Free Association residents. +# Part A work history doesn't open Medicare to anyone else. +- name: is_medicare_eligible unit test 14 + period: 2027 + input: + age: 70 + immigration_status: TPS + medicare_quarters_of_coverage: 40 + output: + is_medicare_eligible: false + +- name: is_medicare_eligible unit test 15 + period: 2027 + input: + age: 70 + immigration_status: REFUGEE + medicare_quarters_of_coverage: 40 + output: + is_medicare_eligible: false + +# 42 U.S.C. 1395mmm(a) applies to the SSDI route as well. +- name: is_medicare_eligible unit test 16 + period: 2027 + input: + age: 50 + immigration_status: TPS + social_security_disability: 12_000 + months_receiving_social_security_disability: 24 + output: + is_medicare_eligible: false + +# 42 U.S.C. 1395mmm(a)(3) keeps Cuban and Haitian entrants, and entitlement to Part A +# opens Part B under 42 U.S.C. 1395o(a)(1). +- name: is_medicare_eligible unit test 17 + period: 2027 + input: + age: 70 + immigration_status: CUBAN_HAITIAN_ENTRANT + medicare_quarters_of_coverage: 40 + output: + is_medicare_eligible: true + +# 42 U.S.C. 1395o(a)(2): without Part A, only a citizen or a permanent resident of five +# years may enroll at 65, so an allowed status isn't enough on its own. +- name: is_medicare_eligible unit test 18 + period: 2027 + input: + age: 70 + immigration_status: CUBAN_HAITIAN_ENTRANT + medicare_quarters_of_coverage: 0 + output: + is_medicare_eligible: false + +# 42 U.S.C. 1395mmm(a)(2) keeps permanent residents, and entitlement to Part A opens +# Part B under 42 U.S.C. 1395o(a)(1) inside the five years. +- name: is_medicare_eligible unit test 19 + period: 2027 + input: + age: 70 + immigration_status: LEGAL_PERMANENT_RESIDENT + years_since_us_entry: 2 + medicare_quarters_of_coverage: 40 + output: + is_medicare_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/category/msp_category.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/category/msp_category.yaml index 0bbd3183b4c..8157045bff4 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/category/msp_category.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/category/msp_category.yaml @@ -122,7 +122,7 @@ msp_category: 2026-01: NONE -- name: Case 10, permanent resident inside the five-year bar on Medicare gets no tier. +- name: Case 10, permanent resident on Medicare but inside the five-year Medicaid bar gets no tier. period: 2026 input: age: 70 @@ -133,3 +133,54 @@ output: msp_category: 2026-01: NONE + +# 2026 individual FPL: $15,960 / 12 = $1,330 a month. +# SLMB band: above $1,330 and below 120% = $1,596. +# $18,240 pension: $18,240 / 12 - $20 = $1,500 countable, inside the band. +- name: Case 11, citizen in the SLMB income band gets SLMB. + period: 2026 + input: + age: 70 + pension_income: 18_240 + state_code: TX + output: + msp_category: + 2026-01: SLMB + +# Same income as Case 11, but the Medicaid immigration test bars the SLMB benefit. +- name: Case 12, person with temporary protected status in the SLMB band gets no tier. + period: 2026 + input: + age: 70 + immigration_status: TPS + medicare_quarters_of_coverage: 40 + pension_income: 18_240 + state_code: TX + output: + msp_category: + 2026-01: NONE + +# QI band: at least 120% = $1,596 and below 135% = $1,795.50. +# $20,640 pension: $20,640 / 12 - $20 = $1,700 countable, inside the band. +- name: Case 13, citizen in the QI income band gets QI. + period: 2026 + input: + age: 70 + pension_income: 20_640 + state_code: TX + output: + msp_category: + 2026-01: QI + +# Same income as Case 13, but the Medicaid immigration test bars the QI benefit. +- name: Case 14, person with temporary protected status in the QI band gets no tier. + period: 2026 + input: + age: 70 + immigration_status: TPS + medicare_quarters_of_coverage: 40 + pension_income: 20_640 + state_code: TX + output: + msp_category: + 2026-01: NONE diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.yaml index 0c450945ee9..16519539eb6 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.yaml @@ -27,3 +27,28 @@ base_part_b_premium: 2_220 output: msp_part_b_premium_coverage: 0 + +# $18,240 pension: $18,240 / 12 - $20 = $1,500 countable, below 135% of the 2026 +# individual FPL ($15,960 / 12 * 1.35 = $1,795.50). +- name: MSP part B coverage pays the 2026 standard premium for a citizen under the income limit + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + pension_income: 18_240 + state_code: TX + output: + msp_part_b_premium_coverage: 2_434.8 # $202.90 * 12 + +# Same income, but the Medicaid immigration test bars MSP coverage of the premium. +- name: MSP part B coverage is zero for a person with temporary protected status + period: 2026 + absolute_error_margin: 0.01 + input: + age: 70 + immigration_status: TPS + medicare_quarters_of_coverage: 40 + pension_income: 18_240 + state_code: TX + output: + msp_part_b_premium_coverage: 0 diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/is_medicare_eligible.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/is_medicare_eligible.py index 44eb8b25d2d..5cbb7674512 100644 --- a/policyengine_us/variables/gov/hhs/medicare/eligibility/is_medicare_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/is_medicare_eligible.py @@ -14,12 +14,17 @@ class is_medicare_eligible(Variable): "Only the age-65 and 24-month SSDI routes are modeled. The immediate " "entitlement routes for ALS (42 U.S.C. 426(h)) and ESRD " "(42 U.S.C. 426-1) are not, as the model has no inputs for those " - "conditions." + "conditions. Everyone is treated as grandfathered under " + "42 U.S.C. 1395mmm(b)(1) through 2026, as the model has no input for " + "enrollment on July 4, 2025." ) reference = ( "https://www.cms.gov/medicare/enrollment-renewal/health-plans/original-part-a-b", "https://www.law.cornell.edu/uscode/text/42/1395o#a", "https://www.law.cornell.edu/uscode/text/8/1611#b_3", + "https://www.law.cornell.edu/uscode/text/42/1395mmm", + # PDF pages 249-250 + "https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=249", ) def formula(person, period, parameters): @@ -30,17 +35,21 @@ def formula(person, period, parameters): is_disability_eligible = gets_ssdi & ( months >= p.min_months_receiving_social_security_disability ) - # 8 U.S.C. 1611(b)(3) limits Medicare benefits for noncitizens to those - # lawfully present, and Part A to wages from authorized employment. status = person("immigration_status", period) statuses = status.possible_values - lawfully_present = status != statuses.UNDOCUMENTED + # 42 U.S.C. 1395mmm(a) limits Medicare to the statuses listed from 2027, with + # everyone treated as grandfathered under 1395mmm(b)(1) until then. The earlier + # list follows 8 U.S.C. 1611(b)(3), which requires lawful presence. + allowed = np.isin(status.decode_to_str(), p.immigration_statuses) # 42 U.S.C. 1395o(a)(2): at 65, a person without Part A may enroll in # Part B only as a citizen or a permanent resident of five years. is_long_term_resident = (status == statuses.LEGAL_PERMANENT_RESIDENT) & ( person("years_since_us_entry", period) >= p.permanent_resident_years ) - # 42 U.S.C. 1395o(a)(1): entitlement to Part A opens Part B to anyone else. + # 42 U.S.C. 1395o(a)(1): entitlement to Part A opens Part B to anyone else + # with an allowed status. Quarters stand in for entitlement on the person's + # own or a spouse's record (42 U.S.C. 426(a)). The limit in 8 U.S.C. 1611(b)(3) + # to wages from authorized employment isn't modeled. has_part_a_work_history = ( person("medicare_quarters_of_coverage", period) >= parameters( @@ -50,8 +59,8 @@ def formula(person, period, parameters): can_enroll_at_age = ( (status == statuses.CITIZEN) | is_long_term_resident - | (lawfully_present & has_part_a_work_history) + | (allowed & has_part_a_work_history) ) return (is_age_eligible & can_enroll_at_age) | ( - is_disability_eligible & lawfully_present + is_disability_eligible & allowed ) diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/part_a/medicare_quarters_of_coverage.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_a/medicare_quarters_of_coverage.py index 33125c63ada..f57e3d53105 100644 --- a/policyengine_us/variables/gov/hhs/medicare/eligibility/part_a/medicare_quarters_of_coverage.py +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_a/medicare_quarters_of_coverage.py @@ -11,7 +11,18 @@ class medicare_quarters_of_coverage(Variable): "because survey microdata does not observe work-history quarters " "and about 99% of beneficiaries qualify for premium-free Part A; " "without this default, microsimulation would charge every enrollee " - "the full Part A premium." + "the full Part A premium. For noncitizens outside the citizen and " + "five-year permanent resident routes, this input also decides Medicare " + "eligibility, so it should reflect entitlement on the person's own or a " + "spouse's record (42 U.S.C. 426(a)(2)(A) reaches monthly benefits under " + "42 U.S.C. 402, including spouse benefits in 402(b) and (c)). The limit " + "in 8 U.S.C. 1611(b)(3) to wages from authorized employment is not " + "modeled." + ) + reference = ( + "https://www.law.cornell.edu/uscode/text/42/426#a", + "https://www.law.cornell.edu/uscode/text/42/402", + "https://www.law.cornell.edu/uscode/text/8/1611#b_3", ) default_value = 40 definition_period = YEAR diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qi_eligible.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qi_eligible.py index 033e5079943..70d22c32f7e 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qi_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qi_eligible.py @@ -10,6 +10,7 @@ class is_qi_eligible(Variable): "https://www.law.cornell.edu/cfr/text/42/435.125", "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", "https://www.law.cornell.edu/uscode/text/42/1396b#v", + "https://www.law.cornell.edu/uscode/text/8/1613#a", ) def formula(person, period, parameters): @@ -18,7 +19,8 @@ def formula(person, period, parameters): p = parameters(period).gov.hhs.medicare.savings_programs.eligibility.income medicare_eligible = person("is_medicare_eligible", period.this_year) - # QMB, SLMB and QI are Medicaid benefits, so 42 U.S.C. 1396b(v) applies. + # QMB, SLMB and QI are Medicaid benefits, so the Medicaid immigration test + # (42 U.S.C. 1396b(v), 8 U.S.C. 1611(a) and 1613(a)) applies. immigration_eligible = person( "is_medicaid_immigration_status_eligible", period.this_year ) diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qmb_eligible.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qmb_eligible.py index d6d2b6afdc2..cbdd86e8a32 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qmb_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_qmb_eligible.py @@ -10,6 +10,7 @@ class is_qmb_eligible(Variable): "https://www.law.cornell.edu/cfr/text/42/435.123", "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", "https://www.law.cornell.edu/uscode/text/42/1396b#v", + "https://www.law.cornell.edu/uscode/text/8/1613#a", ) def formula(person, period, parameters): @@ -17,7 +18,8 @@ def formula(person, period, parameters): p = parameters(period).gov.hhs.medicare.savings_programs.eligibility.income medicare_eligible = person("is_medicare_eligible", period.this_year) - # QMB, SLMB and QI are Medicaid benefits, so 42 U.S.C. 1396b(v) applies. + # QMB, SLMB and QI are Medicaid benefits, so the Medicaid immigration test + # (42 U.S.C. 1396b(v), 8 U.S.C. 1611(a) and 1613(a)) applies. immigration_eligible = person( "is_medicaid_immigration_status_eligible", period.this_year ) diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_slmb_eligible.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_slmb_eligible.py index 454ec21b9c9..95f95dbefff 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_slmb_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/category/is_slmb_eligible.py @@ -10,6 +10,7 @@ class is_slmb_eligible(Variable): "https://www.law.cornell.edu/cfr/text/42/435.124", "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", "https://www.law.cornell.edu/uscode/text/42/1396b#v", + "https://www.law.cornell.edu/uscode/text/8/1613#a", ) def formula(person, period, parameters): @@ -17,7 +18,8 @@ def formula(person, period, parameters): p = parameters(period).gov.hhs.medicare.savings_programs.eligibility.income medicare_eligible = person("is_medicare_eligible", period.this_year) - # QMB, SLMB and QI are Medicaid benefits, so 42 U.S.C. 1396b(v) applies. + # QMB, SLMB and QI are Medicaid benefits, so the Medicaid immigration test + # (42 U.S.C. 1396b(v), 8 U.S.C. 1611(a) and 1613(a)) applies. immigration_eligible = person( "is_medicaid_immigration_status_eligible", period.this_year ) diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.py index 8628f4c0361..b456cc52a9c 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.py @@ -20,10 +20,11 @@ class msp_part_a_premium_coverage(Variable): 42 U.S.C. 1396d(p)(3)(A)(i); SLMB and QI cover the Part B premium only. The QMB gate is cycle-safe for SPM MOOP: is_qmb_eligible composes - msp_countable_income (SSI methodology), msp_asset_eligible, and - is_medicare_eligible directly, without the modeled Medicaid exclusion - used in QI eligibility that reaches the medically needy Medicaid - formula and its medical_out_of_pocket_expenses dependency. + msp_countable_income (SSI methodology), msp_asset_eligible, + is_medicare_eligible, and is_medicaid_immigration_status_eligible + directly, without the modeled Medicaid exclusion used in QI eligibility + that reaches the medically needy Medicaid formula and its + medical_out_of_pocket_expenses dependency. """ def formula(person, period, parameters): diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.py index 4e6d0eb4b17..c08bca14598 100644 --- a/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.py +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_b_premium_coverage.py @@ -9,12 +9,15 @@ class msp_part_b_premium_coverage(Variable): definition_period = YEAR reference = ( "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", + "https://www.law.cornell.edu/uscode/text/42/1396b#v", + "https://www.law.cornell.edu/uscode/text/8/1613#a", ) documentation = """ Annual standard Part B premium amount paid on the enrollee's behalf through a Medicare Savings Program-like pathway. - This uses the MSP income and asset rules directly and intentionally avoids the + This uses the MSP income and asset rules and + is_medicaid_immigration_status_eligible directly and intentionally avoids the modeled Medicaid exclusion used in QI eligibility because that path reaches the medically needy Medicaid formula, which depends on medical_out_of_pocket_expenses and would create a cycle in SPM MOOP calculations. @@ -28,7 +31,8 @@ def formula(person, period, parameters): monthly_standard_premium = ( person("base_part_b_premium", period) / MONTHS_IN_YEAR ) - # MSP coverage is a Medicaid benefit, so 42 U.S.C. 1396b(v) applies. + # MSP coverage is a Medicaid benefit, so the Medicaid immigration test + # (42 U.S.C. 1396b(v), 8 U.S.C. 1611(a) and 1613(a)) applies. immigration_eligible = person("is_medicaid_immigration_status_eligible", period) monthly_coverage = 0 for month in period.get_subperiods(MONTH):