diff --git a/changelog.d/ma-connectorcare-exact-fpl-ratio.fixed.md b/changelog.d/ma-connectorcare-exact-fpl-ratio.fixed.md new file mode 100644 index 00000000000..def0a650264 --- /dev/null +++ b/changelog.d/ma-connectorcare-exact-fpl-ratio.fixed.md @@ -0,0 +1 @@ +Decide Massachusetts ConnectorCare's income ceiling and Plan Type bands on the exact ratio of ACA MAGI to the prior-year poverty guideline instead of the truncated `aca_magi_fraction`. 956 CMR 12.04(3)(b) puts income "at or below 150%" in Plan Type 2A, "over 150% and not over 200%" in 2B, and so on up to 3C, "not over 400%"; truncation put incomes from 150.003% to 150.994% (and the same slivers above 200%, 250% and 300%) in the cheaper band below. The 400% ceiling changes results only under a reform that extends the federal credit above 400%. diff --git a/policyengine_us/parameters/gov/states/ma/hic/connector_care/enrollee_premium.yaml b/policyengine_us/parameters/gov/states/ma/hic/connector_care/enrollee_premium.yaml index 6ce38ca508e..2cbf3a4b104 100644 --- a/policyengine_us/parameters/gov/states/ma/hic/connector_care/enrollee_premium.yaml +++ b/policyengine_us/parameters/gov/states/ma/hic/connector_care/enrollee_premium.yaml @@ -16,41 +16,44 @@ metadata: href: https://www.mahealthconnector.org/wp-content/uploads/amended-956CMR12.00.pdf#page=13 brackets: - # Household income measured as ACA MAGI as a share of the federal poverty line. - # Band boundaries are lower-exclusive / upper-inclusive per 956 CMR 12.04(3)(b), - # so thresholds use the "x.0001" convention matching the published "150.1%" - # banding. The schedule is a flat dollar step function decoupled from the - # affordability percent-of-income schedule (Board-set, 956 CMR 12.12(9)); every - # band is $0 before the PY2026 effective date. - # Plan Type 2A, 100-150% FPL -> $0 per person per month. + # Household income measured as ACA MAGI as a share of the prior-year federal + # poverty guideline, the exact ratio. Band boundaries are lower-exclusive / + # upper-inclusive per 956 CMR 12.04(3)(b) ("at or below 150%", "over 150% and + # not over 200%"), so each threshold is the band's lower edge and + # ma_connector_care evaluates the scale with right-closed brackets: a ratio + # exactly at a threshold stays in the band below it. The schedule is a flat + # dollar step function decoupled from the affordability percent-of-income + # schedule (Board-set, 956 CMR 12.12(9)); every band is $0 before the PY2026 + # effective date. + # Plan Type 2A, 100% to 150% FPL -> $0 per person per month. - threshold: 2026-01-01: 1 amount: 0000-01-01: 0 2026-01-01: 0 - # Plan Type 2B, 150.1-200% FPL -> $53. + # Plan Type 2B, over 150% to 200% FPL -> $53. - threshold: - 2026-01-01: 1.5001 + 2026-01-01: 1.5 amount: 0000-01-01: 0 2026-01-01: 53 - # Plan Type 3A, 200.1-250% FPL -> $103. + # Plan Type 3A, over 200% to 250% FPL -> $103. - threshold: - 2026-01-01: 2.0001 + 2026-01-01: 2 amount: 0000-01-01: 0 2026-01-01: 103 - # Plan Type 3B, 250.1-300% FPL -> $152. + # Plan Type 3B, over 250% to 300% FPL -> $152. - threshold: - 2026-01-01: 2.5001 + 2026-01-01: 2.5 amount: 0000-01-01: 0 2026-01-01: 152 - # Plan Type 3C, 300.1-400% FPL -> $235. This tier is a pilot extended through + # Plan Type 3C, over 300% to 400% FPL -> $235. This tier is a pilot extended through # FY2026 in the state budget and may lapse in out-years; it is carried forward # without end-dating as a current-law assumption. - threshold: - 2026-01-01: 3.0001 + 2026-01-01: 3 amount: 0000-01-01: 0 2026-01-01: 235 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/integration.yaml index f3f18d6f8c4..aea4744dc8b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/integration.yaml @@ -1,12 +1,14 @@ # Integration tests for Massachusetts ConnectorCare (PY2026 premium wrap). # -# These exercise the full chain: aca_magi_fraction (income gate) and +# These exercise the full chain: the exact ratio of ACA MAGI to the prior-year +# guideline (income gate and Plan Type band) and # is_aca_ptc_eligible (member gate) -> ma_connector_care_member_eligible -> # ma_connector_care_eligible -> ma_connector_care (residual subsidy) -> # healthcare_benefit_value (household resource aggregation). # -# In Scenarios 1-5 slcsp, aca_ptc, and aca_magi_fraction are supplied directly -# (slcsp as an annual value) so every expected value is fully hand-verifiable. +# In Scenarios 1-5 slcsp and aca_ptc are supplied directly (slcsp as an annual +# value), and income is aca_magi against a prior-year tax_unit_fpg pinned to +# $10,000, so every expected value is fully hand-verifiable. # slcsp is a MONTH variable; supplying it at the YEAR period sets the annual sum. # Scenario 6 instead derives aca_magi_fraction, aca_ptc, and is_aca_ptc_eligible # from raw employment_income (only slcsp is supplied), per the Code Integrity note @@ -20,12 +22,12 @@ # # Amount formula recap: # n_members = count of ma_connector_care_member_eligible (= is_aca_ptc_eligible) -# per_person_mo = enrollee_premium.calc(aca_magi_fraction) # single_amount bracket +# per_person_mo = enrollee_premium.calc(ACA MAGI / prior-year FPG, right=True) # contribution = 12 * per_person_mo * n_members # subsidy = max(0, slcsp_annual - aca_ptc - contribution) # amount = where(ma_connector_care_eligible, subsidy, 0) -# Bracket (per person per month): [1.0,1.5001)->0, [1.5001,2.0001)->53, -# [2.0001,2.5001)->103, [2.5001,3.0001)->152, [3.0001,inf)->235. +# Bracket (per person per month, right-closed): [1.0,1.5]->0, (1.5,2.0]->53, +# (2.0,2.5]->103, (2.5,3.0]->152, (3.0,inf)->235. - name: Scenario 1, single adult at 175% FPL, band 2B ($53/mo), positive residual. # n_members=1, band 2B -> per_person 53. contribution = 12*53 = 636. @@ -40,7 +42,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.75 + aca_magi: 17_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 7_200 aca_ptc: 5_000 assigned_aca_ptc: 5_000 @@ -68,7 +72,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.4 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 6_000 aca_ptc: 5_500 assigned_aca_ptc: 5_500 @@ -100,7 +106,9 @@ tax_units: tax_unit: members: [person1, person2] - aca_magi_fraction: 1.75 + aca_magi: 17_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 7_200 aca_ptc: 5_000 assigned_aca_ptc: 5_000 @@ -129,7 +137,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 3.5 + aca_magi: 35_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 4_000 assigned_aca_ptc: 4_000 @@ -159,7 +169,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 3.5 + aca_magi: 35_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 5_000 aca_ptc: 3_000 assigned_aca_ptc: 3_000 @@ -221,7 +233,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.75 + aca_magi: 17_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 7_200 aca_ptc: 5_000 assigned_aca_ptc: 5_000 @@ -261,7 +275,9 @@ tax_units: tax_unit: members: [person1, person2, person3, person4, person5] - aca_magi_fraction: 3.5 + aca_magi: 35_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 20_000 aca_ptc: 0 assigned_aca_ptc: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care.yaml index cf046cd4d31..a3866f9f00f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care.yaml @@ -2,24 +2,31 @@ # # ma_connector_care (TaxUnit, YEAR, USD, defined_for StateCode.MA): # n_members = count of ma_connector_care_member_eligible (= is_aca_ptc_eligible) -# per_person_month = enrollee_premium.calc(aca_magi_fraction) # single_amount bracket +# ratio = max(aca_magi, 0) / tax_unit_fpg(prior year) # exact, not truncated +# per_person_month = enrollee_premium.calc(ratio, right=True) # single_amount bracket # contribution = MONTHS_IN_YEAR (12) * per_person_month * n_members # slcsp_annual = add(tax_unit, period, ["slcsp"]) # MONTH var summed to year # subsidy = max(0, slcsp_annual - aca_ptc - contribution) # amount = where(ma_connector_care_eligible, subsidy, 0) # -# All of slcsp / aca_ptc / aca_magi_fraction / is_aca_ptc_eligible are supplied -# directly so each expected value is hand-verifiable. slcsp is a MONTH variable; +# slcsp / aca_ptc / is_aca_ptc_eligible are supplied directly, and income is +# aca_magi against a prior-year tax_unit_fpg pinned to $10,000, so each expected +# value is hand-verifiable. Income-derived cases at the end use the actual 2025 +# one-person guideline, $15,650. slcsp is a MONTH variable; # supplying it at the YEAR period sets the annual total, so the values below are # annual SLCSP. # -# Per-person monthly enrollee premium bracket (single_amount, lower-exclusive): -# [1.0, 1.5001) -> $0 (Plan Type 2A) -# [1.5001, 2.0001) -> $53 (Plan Type 2B) -# [2.0001, 2.5001) -> $103 (Plan Type 3A) -# [2.5001, 3.0001) -> $152 (Plan Type 3B) -# [3.0001, ...) -> $235 (Plan Type 3C) -# So at EXACTLY 150%/200%/250%/300% FPL the household is still in the LOWER band. +# Per-person monthly enrollee premium bracket (single_amount, evaluated +# right-closed; 956 CMR 12.04(3)(b): "at or below 150%", "over 150% and not over +# 200%", ...): +# [1.0, 1.5] -> $0 (Plan Type 2A) +# (1.5, 2.0] -> $53 (Plan Type 2B) +# (2.0, 2.5] -> $103 (Plan Type 3A) +# (2.5, 3.0] -> $152 (Plan Type 3B) +# (3.0, 4.0] -> $235 (Plan Type 3C) +# So at EXACTLY 150%/200%/250%/300% FPL the household is still in the LOWER band, +# and any income above an edge is in the higher band, even below the next whole +# percentage point. # --- Each band exercised with a positive residual, single member (n=1) --- @@ -35,7 +42,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.4 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 6_000 aca_ptc: 5_500 households: @@ -57,7 +66,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.75 + aca_magi: 17_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 7_200 aca_ptc: 5_000 households: @@ -79,7 +90,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.25 + aca_magi: 22_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 9_000 aca_ptc: 5_000 households: @@ -101,7 +114,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.75 + aca_magi: 27_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 9_000 aca_ptc: 4_000 households: @@ -123,7 +138,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 3.5 + aca_magi: 35_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 4_000 households: @@ -149,7 +166,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.5 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 12_000 aca_ptc: 0 households: @@ -171,7 +190,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.51 + aca_magi: 15_100 + tax_unit_fpg: + 2025: 10_000 slcsp: 12_000 aca_ptc: 0 households: @@ -193,7 +214,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.0 + aca_magi: 20_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 12_000 aca_ptc: 0 households: @@ -215,7 +238,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.01 + aca_magi: 20_100 + tax_unit_fpg: + 2025: 10_000 slcsp: 12_000 aca_ptc: 0 households: @@ -237,7 +262,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.5 + aca_magi: 25_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 12_000 aca_ptc: 0 households: @@ -259,7 +286,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.51 + aca_magi: 25_100 + tax_unit_fpg: + 2025: 10_000 slcsp: 12_000 aca_ptc: 0 households: @@ -281,7 +310,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 3.0 + aca_magi: 30_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 12_000 aca_ptc: 0 households: @@ -303,7 +334,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 3.01 + aca_magi: 30_100 + tax_unit_fpg: + 2025: 10_000 slcsp: 12_000 aca_ptc: 0 households: @@ -331,7 +364,9 @@ tax_units: tax_unit: members: [person1, person2] - aca_magi_fraction: 1.75 + aca_magi: 17_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 7_200 aca_ptc: 5_000 households: @@ -357,7 +392,9 @@ tax_units: tax_unit: members: [person1, person2] - aca_magi_fraction: 2.25 + aca_magi: 22_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 9_000 aca_ptc: 5_000 households: @@ -380,7 +417,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 3.5 + aca_magi: 35_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 5_000 aca_ptc: 3_000 households: @@ -402,7 +441,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.0 + aca_magi: 20_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 9_000 aca_ptc: 0 households: @@ -424,7 +465,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.4 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 6_000 aca_ptc: 0 households: @@ -446,7 +489,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.4 + aca_magi: 14_000 + tax_unit_fpg: + 2024: 10_000 slcsp: 6_000 aca_ptc: 0 households: @@ -472,7 +517,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.4 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 0 aca_ptc: 0 households: @@ -497,7 +544,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.75 + aca_magi: 17_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 5_636 aca_ptc: 5_000 households: @@ -533,7 +582,9 @@ tax_units: tax_unit: members: [person1, person2, person3, person4, person5] - aca_magi_fraction: 3.5 + aca_magi: 35_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 20_000 aca_ptc: 0 households: @@ -557,7 +608,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.0 + aca_magi: 10_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 6_000 aca_ptc: 5_500 households: @@ -566,3 +619,210 @@ state_code: MA output: ma_connector_care: 500 + +- name: Case 24, 150% of the 2025 guideline ($23,475) is Plan Type 2A ($0/mo). + # contribution = 12*0*1 = 0. subsidy = 9_000 - 5_000 - 0 = 4_000. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 23_475 + slcsp: 9_000 + aca_ptc: 5_000 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 1.50 + ma_connector_care: 4_000 + +- name: Case 25, 150.003% of the 2025 guideline ($23,475.47) is Plan Type 2B ($53/mo). + # contribution = 12*53*1 = 636. subsidy = 9_000 - 5_000 - 636 = 3_364. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 23_475.47 + slcsp: 9_000 + aca_ptc: 5_000 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 1.50 + ma_connector_care: 3_364 + +- name: Case 26, 150.994% of the 2025 guideline ($23,630.56) is Plan Type 2B ($53/mo). + # contribution = 12*53*1 = 636. subsidy = 9_000 - 5_000 - 636 = 3_364. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 23_630.56 + slcsp: 9_000 + aca_ptc: 5_000 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 1.50 + ma_connector_care: 3_364 + +- name: Case 27, 200% of the 2025 guideline ($31,300) is Plan Type 2B ($53/mo). + # contribution = 12*53*1 = 636. subsidy = 9_000 - 5_000 - 636 = 3_364. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 31_300 + slcsp: 9_000 + aca_ptc: 5_000 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 2.00 + ma_connector_care: 3_364 + +- name: Case 28, 200.003% of the 2025 guideline ($31,300.47) is Plan Type 3A ($103/mo). + # contribution = 12*103*1 = 1_236. subsidy = 9_000 - 5_000 - 1_236 = 2_764. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 31_300.47 + slcsp: 9_000 + aca_ptc: 5_000 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 2.00 + ma_connector_care: 2_764 + +- name: Case 29, 250% of the 2025 guideline ($39,125) is Plan Type 3A ($103/mo). + # contribution = 12*103*1 = 1_236. subsidy = 9_000 - 5_000 - 1_236 = 2_764. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 39_125 + slcsp: 9_000 + aca_ptc: 5_000 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 2.50 + ma_connector_care: 2_764 + +- name: Case 30, 250.003% of the 2025 guideline ($39,125.47) is Plan Type 3B ($152/mo). + # contribution = 12*152*1 = 1_824. subsidy = 9_000 - 5_000 - 1_824 = 2_176. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 39_125.47 + slcsp: 9_000 + aca_ptc: 5_000 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 2.50 + ma_connector_care: 2_176 + +- name: Case 31, 300% of the 2025 guideline ($46,950) is Plan Type 3B ($152/mo). + # contribution = 12*152*1 = 1_824. subsidy = 9_000 - 5_000 - 1_824 = 2_176. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 46_950 + slcsp: 9_000 + aca_ptc: 5_000 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 3.00 + ma_connector_care: 2_176 + +- name: Case 32, 300.003% of the 2025 guideline ($46,950.47) is Plan Type 3C ($235/mo). + # contribution = 12*235*1 = 2_820. subsidy = 9_000 - 5_000 - 2_820 = 1_180. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 46_950.47 + slcsp: 9_000 + aca_ptc: 5_000 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 3.00 + ma_connector_care: 1_180 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care_eligible.yaml index 0ce81de5955..09774b0c94f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care_eligible.yaml @@ -2,18 +2,23 @@ # # ma_connector_care_eligible (TaxUnit, YEAR, bool, defined_for StateCode.MA) is: # in_effect -# & (aca_magi_fraction >= fpl_floor) # fpl_floor = 1.0 for PY2026 -# & (aca_magi_fraction <= fpl_limit) # fpl_limit = 4.0 for PY2026 +# & (ratio >= fpl_floor) # fpl_floor = 1.0 for PY2026 +# & (ratio <= fpl_limit) # fpl_limit = 4.0 for PY2026 (956 CMR 12.04(3)(b): +# # Plan Type 3C is "not over 400% of the FPL") +# where ratio = max(aca_magi, 0) / tax_unit_fpg(prior year), the exact ratio, not +# the truncated aca_magi_fraction. # & (count of ma_connector_care_member_eligible members > 0) # # ma_connector_care_member_eligible = is_aca_ptc_eligible (Person, YEAR). # in_effect is false before 2026-01-01, true from 2026-01-01 open-ended. -# aca_magi_fraction is supplied directly so the income band is exact. +# Supplied-income cases pin the prior-year guideline to $10,000 and set aca_magi +# to the target ratio; income-derived cases use the 2025 one-person guideline, +# $15,650. # --- Income-band boundaries at the 100% FPL floor (>= comparison) --- - name: Case 1, one APTC-eligible member just below the 100% FPL floor is ineligible. - # aca_magi_fraction 0.99 < fpl_floor 1.0 -> income_eligible false. + # ratio 0.99 < fpl_floor 1.0 -> income_eligible false. period: 2026 input: people: @@ -23,7 +28,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 0.99 + aca_magi: 9_900 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -32,7 +39,7 @@ ma_connector_care_eligible: false - name: Case 2, one APTC-eligible member exactly at the 100% FPL floor is eligible. - # aca_magi_fraction 1.0 >= fpl_floor 1.0 (inclusive) and <= 4.0 -> eligible. + # ratio 1.0 >= fpl_floor 1.0 (inclusive) and <= 4.0 -> eligible. period: 2026 input: people: @@ -42,7 +49,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.0 + aca_magi: 10_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -53,7 +62,7 @@ # --- Income-band boundaries at the 400% FPL limit (<= comparison) --- - name: Case 3, one APTC-eligible member exactly at the 400% FPL limit is eligible. - # aca_magi_fraction 4.0 <= fpl_limit 4.0 (inclusive) -> eligible. + # ratio 4.0 <= fpl_limit 4.0 (inclusive) -> eligible. period: 2026 input: people: @@ -63,7 +72,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 4.0 + aca_magi: 40_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -72,7 +83,7 @@ ma_connector_care_eligible: true - name: Case 4, one APTC-eligible member just above the 400% FPL limit is ineligible. - # aca_magi_fraction 4.01 > fpl_limit 4.0 -> income_eligible false. + # ratio 4.01 > fpl_limit 4.0 -> income_eligible false. period: 2026 input: people: @@ -82,7 +93,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 4.01 + aca_magi: 40_100 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -93,7 +106,7 @@ # --- Member and residency gates --- - name: Case 5, in-band household with no APTC-eligible member is ineligible. - # aca_magi_fraction 2.0 is in band but has_eligible_member is false. + # ratio 2.0 is in band but has_eligible_member is false. period: 2026 input: people: @@ -103,7 +116,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.0 + aca_magi: 20_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -122,7 +137,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.0 + aca_magi: 20_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -143,7 +160,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.0 + aca_magi: 20_000 + tax_unit_fpg: + 2024: 10_000 households: household: members: [person1] @@ -162,10 +181,96 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.0 + aca_magi: 20_000 + tax_unit_fpg: + 2026: 10_000 households: household: members: [person1] state_code: MA output: ma_connector_care_eligible: true + +- name: Case 9, $62,600 (exactly 400% of the 2025 guideline) is eligible. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 62_600 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 4.00 + ma_connector_care_eligible: true + +- name: Case 10, $62,600.47 (400.003%) is ineligible though truncated to 4.00. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 62_600.47 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 4.00 + ma_connector_care_eligible: false + +- name: Case 11, $62,755.56 (400.994%) is ineligible though truncated to 4.00. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 62_755.56 + households: + household: + members: [person1] + state_code: MA + output: + aca_magi_fraction: 4.00 + ma_connector_care_eligible: false + +- name: Case 12, with the federal credit extended above 400%, 400.003% is still ineligible. + # A reform that keeps the premium tax credit above 400% FPL makes the member + # APTC-eligible at $62,600.47, so the ConnectorCare ceiling alone decides. + period: 2026 + input: + gov.aca.ptc_income_eligibility[2].amount: true + people: + person1: + age: 40 + is_medicaid_eligible: false + is_chip_eligible: false + is_basic_health_program_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person1] + aca_magi: 62_600.47 + filing_status: SINGLE + households: + household: + members: [person1] + state_code: MA + output: + is_aca_ptc_eligible: [true] + ma_connector_care_eligible: false diff --git a/policyengine_us/variables/gov/states/ma/hic/connector_care/ma_connector_care.py b/policyengine_us/variables/gov/states/ma/hic/connector_care/ma_connector_care.py index 154b1faf3d2..6d57f10d1bc 100644 --- a/policyengine_us/variables/gov/states/ma/hic/connector_care/ma_connector_care.py +++ b/policyengine_us/variables/gov/states/ma/hic/connector_care/ma_connector_care.py @@ -45,8 +45,14 @@ class ma_connector_care(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.ma.hic.connector_care n_members = add(tax_unit, period, ["ma_connector_care_member_eligible"]) - magi_fraction = tax_unit("aca_magi_fraction", period) - per_person_monthly = p.enrollee_premium.calc(magi_fraction) + # 956 CMR 12.04(3)(b) assigns Plan Types by exact income bands that + # include their upper edge: "at or below 150%" is 2A, "over 150% and + # not over 200%" is 2B, and so on. Look up the exact ratio, not the + # truncated aca_magi_fraction (which put 150.00%-150.99% in 2A), with + # each bracket closed on the right. + magi = max_(tax_unit("aca_magi", period), 0) + exact_magi_frac = magi / tax_unit("tax_unit_fpg", period.last_year) + per_person_monthly = p.enrollee_premium.calc(exact_magi_frac, right=True) enrollee_contribution = MONTHS_IN_YEAR * per_person_monthly * n_members # SLCSP is a MONTH variable summed to the year to proxy the benchmark # ConnectorCare plan; the full APTC is netted per 956 CMR 12.04(3)(c). diff --git a/policyengine_us/variables/gov/states/ma/hic/connector_care/ma_connector_care_eligible.py b/policyengine_us/variables/gov/states/ma/hic/connector_care/ma_connector_care_eligible.py index a138a04a40e..3ce513c376b 100644 --- a/policyengine_us/variables/gov/states/ma/hic/connector_care/ma_connector_care_eligible.py +++ b/policyengine_us/variables/gov/states/ma/hic/connector_care/ma_connector_care_eligible.py @@ -16,8 +16,10 @@ class ma_connector_care_eligible(Variable): documentation = ( "A tax unit is eligible for ConnectorCare when the program is in " "effect, household income (ACA MAGI per 26 U.S.C. 36B(d)(2)) as a " - "share of the federal poverty line is at or above the 100% FPL floor " - "and at or below the 400% FPL limit (the operational 2026 band), and " + "share of the prior-year federal poverty guideline, measured as the " + "exact ratio rather than the truncated aca_magi_fraction, is at or " + "above the 100% FPL floor and at or below the 400% FPL limit (the " + "operational 2026 band), and " "at least one member is eligible for the federal ACA premium tax " "credit. Massachusetts residency is enforced by defined_for. The " "amended 956 CMR still defines a sub-100% Plan Type 1 and a 400.1-500% " @@ -29,9 +31,14 @@ class ma_connector_care_eligible(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.ma.hic.connector_care in_effect = p.in_effect - magi_fraction = tax_unit("aca_magi_fraction", period) - income_eligible = (magi_fraction >= p.fpl_floor) & ( - magi_fraction <= p.fpl_limit + # 956 CMR 12.04(3)(b) bounds Plan Type 3C by income "not over 400% of + # the FPL". Test the exact ratio, not the truncated aca_magi_fraction: + # truncation maps 400.00%-400.99% to 4.00, which would admit incomes + # above 400%. The floor ("at least 100%") is unaffected by truncation. + magi = max_(tax_unit("aca_magi", period), 0) + exact_magi_frac = magi / tax_unit("tax_unit_fpg", period.last_year) + income_eligible = (exact_magi_frac >= p.fpl_floor) & ( + exact_magi_frac <= p.fpl_limit ) has_eligible_member = ( add(tax_unit, period, ["ma_connector_care_member_eligible"]) > 0