diff --git a/changelog.d/medicare-immigration-test.fixed.md b/changelog.d/medicare-immigration-test.fixed.md new file mode 100644 index 00000000000..254f88eb36a --- /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), 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/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/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..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,5 +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 + 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: - 2000-01-01: 40 + 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 79a28836ab3..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 @@ -28,3 +28,165 @@ 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 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: + age: 70 + immigration_status: LEGAL_PERMANENT_RESIDENT + years_since_us_entry: 4 + medicare_quarters_of_coverage: 40 + 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: + 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 + +# 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: 12_000 + 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: 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 c788af80531..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 @@ -97,3 +97,90 @@ 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 on Medicare but inside the five-year Medicaid bar 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 + +# 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/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] 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..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,11 +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 = 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", + "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): @@ -29,4 +35,32 @@ 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 + status = person("immigration_status", period) + statuses = status.possible_values + # 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 + # 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( + period + ).gov.hhs.medicare.part_a.premium_free_quarters_threshold + ) + can_enroll_at_age = ( + (status == statuses.CITIZEN) + | is_long_term_resident + | (allowed & has_part_a_work_history) + ) + return (is_age_eligible & can_enroll_at_age) | ( + 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 febc8e8d5a8..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 @@ -9,6 +9,8 @@ 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", + "https://www.law.cornell.edu/uscode/text/8/1613#a", ) def formula(person, period, parameters): @@ -17,6 +19,11 @@ 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 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 + ) asset_eligible = person("msp_asset_eligible", period) fpg = person("msp_fpg", period) @@ -32,4 +39,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..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 @@ -9,6 +9,8 @@ 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", + "https://www.law.cornell.edu/uscode/text/8/1613#a", ) def formula(person, period, parameters): @@ -16,6 +18,11 @@ 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 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 + ) asset_eligible = person("msp_asset_eligible", period) fpg = person("msp_fpg", period) @@ -23,4 +30,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..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 @@ -9,6 +9,8 @@ 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", + "https://www.law.cornell.edu/uscode/text/8/1613#a", ) def formula(person, period, parameters): @@ -16,6 +18,11 @@ 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 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 + ) asset_eligible = person("msp_asset_eligible", period) fpg = person("msp_fpg", period) @@ -28,4 +35,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_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 cac89f2d0d1..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,11 +31,16 @@ def formula(person, period, parameters): monthly_standard_premium = ( person("base_part_b_premium", period) / MONTHS_IN_YEAR ) + # 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): 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,