diff --git a/changelog.d/ct-covered-connecticut-exact-fpl-ratio.fixed.md b/changelog.d/ct-covered-connecticut-exact-fpl-ratio.fixed.md new file mode 100644 index 00000000000..92c47f8ca10 --- /dev/null +++ b/changelog.d/ct-covered-connecticut-exact-fpl-ratio.fixed.md @@ -0,0 +1 @@ +Decide Covered Connecticut eligibility on the exact ratio of ACA MAGI to the prior-year poverty guideline instead of the truncated `aca_magi_fraction`, so incomes from 175.003% to 175.994% of the poverty line are no longer eligible (C.G.S. ยง 19a-754c(b)(1)(C)(iv): "household income up to one hundred seventy-five per cent of the federal poverty level"; DSS applies it "up to an[d] including 175%"). diff --git a/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut.yaml b/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut.yaml index 8284416c1e3..92ee3608c50 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut.yaml @@ -5,8 +5,9 @@ # ct_covered_connecticut = max(0, slcsp_annual - aca_ptc) # The amount variable is gated by defined_for = "ct_covered_connecticut_eligible", # so any tax unit failing the three eligibility gates (in_effect, APTC, income) -# or the StateCode.CT gate returns 0. slcsp, aca_ptc, aca_magi_fraction and -# is_aca_ptc_eligible are supplied directly so each residual is hand-verifiable. +# or the StateCode.CT gate returns 0. slcsp, aca_ptc, is_aca_ptc_eligible and +# aca_magi against a $10,000 prior-year tax_unit_fpg are supplied directly so +# each residual is hand-verifiable. # ---- Eligible, small residual near the Medicaid floor (~150% FPL) ---- - name: Case 1, 150% FPL eligible residual of 500. @@ -21,7 +22,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 7_500 households: @@ -43,7 +46,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.60 + aca_magi: 16_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 9_000 aca_ptc: 8_700 households: @@ -67,7 +72,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.75 + aca_magi: 17_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 10_000 aca_ptc: 8_800 households: @@ -90,7 +97,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 8_000 households: @@ -113,7 +122,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.76 + aca_magi: 17_600 + tax_unit_fpg: + 2025: 10_000 slcsp: 10_000 aca_ptc: 9_000 households: @@ -135,7 +146,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 7_000 households: @@ -158,7 +171,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 7_000 households: @@ -180,7 +195,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2024: 10_000 slcsp: 8_000 aca_ptc: 7_000 households: @@ -208,7 +225,9 @@ tax_units: tax_unit: members: [person1, person2, person3] - aca_magi_fraction: 1.60 + aca_magi: 16_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 20_000 aca_ptc: 18_500 households: @@ -231,7 +250,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 0 aca_ptc: 0 households: @@ -255,7 +276,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 9_000 households: @@ -280,7 +303,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.38 + aca_magi: 13_800 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_500 aca_ptc: 8_300 households: @@ -312,7 +337,9 @@ tax_units: tax_unit: members: [person1, person2, person3, person4, person5] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 32_000 aca_ptc: 27_000 households: @@ -336,7 +363,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2026: 10_000 slcsp: 8_000 aca_ptc: 7_500 households: @@ -362,7 +391,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.38 + aca_magi: 13_800 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 7_500 households: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.yaml index f46d4188fa1..7771090b0e7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.yaml @@ -3,14 +3,19 @@ # in_effect = parameter gov.states.ct.dss.covered_connecticut.in_effect # (false before 2026, true from 2026 on) # aptc_eligible = tax_unit.any(members is_aca_ptc_eligible) -# income_eligible = aca_magi_fraction <= fpl_limit (1.75) +# income_eligible = aca_magi / prior-year FPG <= fpl_limit (1.75), on the +# exact ratio, not the truncated aca_magi_fraction +# (C.G.S. 19a-754c(b)(1)(C)(iv): "up to one hundred +# seventy-five per cent"; DSS: "up to an[d] including 175%") # defined_for = StateCode.CT gates the whole variable off outside Connecticut. -# aca_magi_fraction and is_aca_ptc_eligible are supplied directly so each case is -# exactly determined. +# is_aca_ptc_eligible is supplied directly. Supplied-income cases pin the +# prior-year guideline to $10,000 and set aca_magi to the target ratio, so each +# case is exactly determined; income-derived cases use the actual 2025 +# one-person guideline, $15,650. # ---- Income boundary: just below the 175% FPL ceiling ---- - name: Case 1, 174% FPL APTC-eligible CT tax unit is eligible. - # aca_magi_fraction 1.74 <= 1.75, APTC-eligible member, in effect (2026) -> eligible. + # 17_400 / 10_000 = 1.74 <= 1.75, APTC-eligible member, in effect (2026) -> eligible. period: 2026 input: people: @@ -19,7 +24,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.74 + aca_magi: 17_400 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -29,7 +36,7 @@ # ---- Income boundary: exactly at the 175% FPL ceiling (inclusive) ---- - name: Case 2, exactly 175% FPL is eligible (ceiling is inclusive). - # aca_magi_fraction 1.75 <= 1.75 (fpl_limit) -> eligible at the inclusive ceiling. + # 17_500 / 10_000 = 1.75 <= 1.75 (fpl_limit) -> eligible at the inclusive ceiling. period: 2026 input: people: @@ -38,7 +45,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.75 + aca_magi: 17_500 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -48,7 +57,7 @@ # ---- Income boundary: just above the 175% FPL ceiling ---- - name: Case 3, 176% FPL above the ceiling is ineligible. - # aca_magi_fraction 1.76 > 1.75 -> income-ineligible -> not eligible. + # 17_600 / 10_000 = 1.76 > 1.75 -> income-ineligible -> not eligible. period: 2026 input: people: @@ -57,7 +66,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.76 + aca_magi: 17_600 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -76,7 +87,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -99,7 +112,9 @@ tax_units: tax_unit: members: [person1, person2, person3] - aca_magi_fraction: 1.60 + aca_magi: 16_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1, person2, person3] @@ -118,7 +133,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -137,7 +154,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2024: 10_000 households: household: members: [person1] @@ -156,7 +175,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2026: 10_000 households: household: members: [person1] @@ -166,7 +187,7 @@ # ---- Income low end: ~138% FPL just above the Medicaid floor is eligible ---- - name: Case 9, 138% FPL just above the Medicaid floor is eligible (no explicit floor). - # aca_magi_fraction 1.38 <= 1.75; CT imposes no minimum-income floor -- the + # 13_800 / 10_000 = 1.38 <= 1.75; CT imposes no minimum-income floor -- the # ~138% effective lower bound is inherited from the federal APTC/Medicaid # split, so an APTC-eligible unit at 138% FPL is eligible. period: 2026 @@ -177,7 +198,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.38 + aca_magi: 13_800 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -187,7 +210,7 @@ # ---- T1 [C1]: tightest epsilon above the inclusive 1.75 ceiling ---- - name: Case 10, 175.01% FPL just above the inclusive ceiling is ineligible. - # aca_magi_fraction 1.7501 > 1.75 (fpl_limit) -> income-ineligible even at the + # 17_501 / 10_000 = 1.7501 > 1.75 (fpl_limit) -> income-ineligible even at the # tightest epsilon above the inclusive ceiling -> not eligible. Complements the # existing 1.75 (eligible) and 1.76 (ineligible) boundary cases. period: 2026 @@ -198,7 +221,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.7501 + aca_magi: 17_501 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -207,6 +232,82 @@ ct_covered_connecticut_eligible: false # ---- T2 [C2]: MFS-driven ineligibility via a real filing_status input ---- +- name: Case 10a, $27,386.56 (174.994% 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: 27_386.56 + households: + household: + members: [person1] + state_code: CT + output: + aca_magi_fraction: 1.74 + ct_covered_connecticut_eligible: true + +- name: Case 10b, $27,387.50 (exactly 175%) is eligible. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 27_387.50 + households: + household: + members: [person1] + state_code: CT + output: + aca_magi_fraction: 1.75 + ct_covered_connecticut_eligible: true + +- name: Case 10c, $27,387.97 (175.003%) is ineligible though truncated to 1.75. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 27_387.97 + households: + household: + members: [person1] + state_code: CT + output: + aca_magi_fraction: 1.75 + ct_covered_connecticut_eligible: false + +- name: Case 10d, $27,543.06 (175.994%) is ineligible though truncated to 1.75. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 27_543.06 + households: + household: + members: [person1] + state_code: CT + output: + aca_magi_fraction: 1.75 + ct_covered_connecticut_eligible: false + - name: Case 11, married-filing-separately drives the derived federal PTC gate to ineligible. # filing_status = SEPARATE is a REAL input; is_aca_ptc_eligible is NOT injected. # The federal PTC formula returns pays_aca_premium & ~separate & income_eligible, @@ -222,7 +323,9 @@ tax_unit: members: [person1] filing_status: SEPARATE - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -245,7 +348,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/integration.yaml index 05f9dfe3959..35ad7da4ace 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/integration.yaml @@ -4,12 +4,14 @@ # APTC-eligible Connecticut tax units at or below 175% FPL: # slcsp_annual = annualized second-lowest-cost silver plan (sum of 12 monthly slcsp) # ct_covered_connecticut = where(eligible, max(0, slcsp_annual - aca_ptc), 0) -# eligible = in_effect AND any(is_aca_ptc_eligible) AND aca_magi_fraction <= 1.75 +# eligible = in_effect AND any(is_aca_ptc_eligible) AND aca_magi / FPG <= 1.75 +# (the exact ratio to the prior-year guideline, not the truncated fraction) # Scenario 1 DERIVES the whole federal ACA chain (aca_magi, aca_magi_fraction, # slcsp, aca_ptc) from raw employment_income for a 40-year-old single Connecticut # enrollee and then asserts the residual -- the highest-value test, since the CT -# benefit rides on the derived federal APTC. Scenarios 2-7 inject -# aca_magi_fraction / slcsp / aca_ptc / is_aca_ptc_eligible directly to isolate +# benefit rides on the derived federal APTC. Scenarios 2-7 inject aca_magi +# against a $10,000 prior-year tax_unit_fpg, slcsp, aca_ptc and +# is_aca_ptc_eligible directly to isolate # the residual, boundary, zero-residual and ineligible branches. Derived values # in Scenario 1 are read from the model at 2026 parameters. @@ -60,7 +62,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 7_500 households: @@ -89,7 +93,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.75 + aca_magi: 17_500 + tax_unit_fpg: + 2025: 10_000 slcsp: 10_000 aca_ptc: 8_800 households: @@ -115,7 +121,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 8_000 households: @@ -140,7 +148,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.76 + aca_magi: 17_600 + tax_unit_fpg: + 2025: 10_000 slcsp: 10_000 aca_ptc: 9_000 households: @@ -166,7 +176,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 7_000 households: @@ -198,7 +210,9 @@ tax_units: tax_unit: members: [person1, person2, person3] - aca_magi_fraction: 1.60 + aca_magi: 16_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 20_000 aca_ptc: 18_500 households: @@ -225,7 +239,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.38 + aca_magi: 13_800 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_500 aca_ptc: 8_300 households: @@ -252,7 +268,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 8_000 aca_ptc: 9_000 households: @@ -290,7 +308,9 @@ tax_units: tax_unit: members: [person1, person2, person3, person4, person5] - aca_magi_fraction: 1.50 + aca_magi: 15_000 + tax_unit_fpg: + 2025: 10_000 slcsp: 32_000 aca_ptc: 27_000 households: diff --git a/policyengine_us/variables/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.py b/policyengine_us/variables/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.py index 2fd8e3c3877..57b2946373b 100644 --- a/policyengine_us/variables/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.py +++ b/policyengine_us/variables/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.py @@ -9,6 +9,8 @@ class ct_covered_connecticut_eligible(Variable): defined_for = StateCode.CT reference = ( "https://www.cga.ct.gov/2021/act/pa/pdf/2021PA-00002-R00SB-01202SS1-PA.pdf#page=22", + # C.G.S. 19a-754c(b)(1)(C)(iv), the codified income limit. + "https://www.cga.ct.gov/current/pub/chap_368dd.htm#sec_19a-754c", "https://portal.ct.gov/dss/health-and-home-care/covered-connecticut-program", ) documentation = ( @@ -17,7 +19,9 @@ class ct_covered_connecticut_eligible(Variable): "ACA premium tax credit (which embeds on-Marketplace enrollment, the " "married-filing-separately exclusion, and the federal required-" "contribution income test), and household ACA MAGI is at or below 175% " - "of the federal poverty line. Connecticut imposes no minimum-income " + "of the prior-year federal poverty guideline, measured as the exact " + "ratio rather than the truncated aca_magi_fraction. Connecticut imposes " + "no minimum-income " "floor: the statutory requirement that enrollees be ineligible for " "Medicaid because their income exceeds the Medicaid limits is inherited " "from the federal ACA PTC gate, which already excludes Medicaid-eligible " @@ -36,5 +40,12 @@ def formula(tax_unit, period, parameters): in_effect = p.in_effect # At least one member must be eligible for the federal ACA PTC. aptc_eligible = tax_unit.any(tax_unit.members("is_aca_ptc_eligible", period)) - income_eligible = tax_unit("aca_magi_fraction", period) <= p.fpl_limit + # C.G.S. 19a-754c(b)(1)(C)(iv) covers "household income up to one + # hundred seventy-five per cent of the federal poverty level", which + # DSS applies "up to an[d] including 175%". Test the exact ratio, not + # the truncated aca_magi_fraction: truncation maps 175.00%-175.99% to + # 1.75, which would admit incomes above 175%. + 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_limit return in_effect & aptc_eligible & income_eligible