diff --git a/changelog.d/ct-premium-assistance.added.md b/changelog.d/ct-premium-assistance.added.md new file mode 100644 index 00000000000..e1c546f3b3e --- /dev/null +++ b/changelog.d/ct-premium-assistance.added.md @@ -0,0 +1 @@ +Add Covered Connecticut Program premium assistance. diff --git a/policyengine_us/parameters/gov/states/ct/dss/covered_connecticut/fpl_limit.yaml b/policyengine_us/parameters/gov/states/ct/dss/covered_connecticut/fpl_limit.yaml new file mode 100644 index 00000000000..b37851b973f --- /dev/null +++ b/policyengine_us/parameters/gov/states/ct/dss/covered_connecticut/fpl_limit.yaml @@ -0,0 +1,15 @@ +description: Connecticut limits income eligibility to this share of the federal poverty line under the Covered Connecticut Program. +values: + 2026-01-01: 1.75 + +metadata: + unit: /1 + period: year + label: Covered Connecticut Program income limit as a fraction of the federal poverty line + reference: + - title: June Sp. Sess., Public Act No. 21-2 (2021), Sec. 16(b)(1)(B)(iv) + href: https://www.cga.ct.gov/2021/act/pa/pdf/2021PA-00002-R00SB-01202SS1-PA.pdf#page=23 + - title: June Sp. Sess., Public Act No. 21-2 (2021), Sec. 16(b)(1)(A)(iii) + href: https://www.cga.ct.gov/2021/act/pa/pdf/2021PA-00002-R00SB-01202SS1-PA.pdf#page=22 + - title: Covered Connecticut Program (CT DSS) + href: https://portal.ct.gov/dss/health-and-home-care/covered-connecticut-program diff --git a/policyengine_us/parameters/gov/states/ct/dss/covered_connecticut/in_effect.yaml b/policyengine_us/parameters/gov/states/ct/dss/covered_connecticut/in_effect.yaml new file mode 100644 index 00000000000..973817dbfc6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ct/dss/covered_connecticut/in_effect.yaml @@ -0,0 +1,20 @@ +description: Connecticut provides premium assistance when this is true under the Covered Connecticut Program. +values: + # Covered Connecticut is a standing statutory obligation under June Sp. Sess. + # Public Act No. 21-2 (2021), Sec. 16, which is "Effective from passage." The + # program launched under the statute on July 1, 2021 (parents and caretaker + # relatives) and July 1, 2022 (all adults 18-64), and is funded on an ongoing + # basis with no sunset date. The 2026-01-01 toggle is the modeled + # verified-coverage-start floor, not the year the program became standing. + 0000-01-01: false + 2026-01-01: true + +metadata: + unit: bool + period: year + label: Covered Connecticut Program in effect + reference: + - title: June Sp. Sess., Public Act No. 21-2 (2021), Sec. 16(b)(1) + href: https://www.cga.ct.gov/2021/act/pa/pdf/2021PA-00002-R00SB-01202SS1-PA.pdf#page=22 + - title: Covered Connecticut Program (CT DSS) + href: https://portal.ct.gov/dss/health-and-home-care/covered-connecticut-program diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index eabd0804539..e49b69a61a8 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1145,6 +1145,18 @@ programs: verified_years: "2026-2027" notes: PY2026 and PY2027 verified against Amended Regulation 4-2-78 (3 CCR 702-4) and enrolled HB25B-1006; re-verify after the DOI fall-2026 Regulation 4-2-78 amendment cycle. The per-member premium wrap is approximated as a pooled tax-unit amount, member counts use federal ACA PTC eligibility rather than the regulation's subject-to-a-premium test, and full-year enrollment is assumed. Not modeled - a tax unit that mixes OmniSalud and Colorado Premium Assistance members, which the two programs value independently. + - id: ct_covered_connecticut + name: Covered Connecticut + full_name: Covered Connecticut Program + category: Healthcare + agency: State + status: partial + coverage: CT + variable: ct_covered_connecticut + parameter_prefix: gov.states.ct.dss.covered_connecticut + verified_start_year: 2026 + notes: Premium axis only; cost-sharing, dental, and NEMT not modeled + - id: nm_premium_assistance name: New Mexico Premium Assistance full_name: Health Insurance Marketplace Affordability Program 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 new file mode 100644 index 00000000000..8284416c1e3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut.yaml @@ -0,0 +1,373 @@ +# Unit tests for the Covered Connecticut Program amount (REQ-5, REQ-6). For an +# eligible tax unit the benefit is the residual benchmark silver premium after +# the federal APTC, floored at zero: +# slcsp_annual = add(tax_unit, period, ["slcsp"]) # slcsp is MONTH-period, summed over 12 +# 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. + +# ---- Eligible, small residual near the Medicaid floor (~150% FPL) ---- +- name: Case 1, 150% FPL eligible residual of 500. + # slcsp 8_000 (annual) - aca_ptc 7_500 = 500 > 0. At low income the federal APTC + # nearly covers the benchmark premium, so the CT residual is small. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 7_500 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 500 + +# ---- Eligible, mid-band residual (~160% FPL) ---- +- name: Case 2, 160% FPL eligible residual of 300. + # slcsp 9_000 - aca_ptc 8_700 = 300 > 0. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.60 + slcsp: 9_000 + aca_ptc: 8_700 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 300 + +# ---- Eligible, larger residual near the 175% ceiling ---- +- name: Case 3, 175% FPL eligible larger residual of 1200. + # At higher income the applicable percentage is larger, so the federal APTC + # covers less of the benchmark premium and the CT residual grows: + # slcsp 10_000 - aca_ptc 8_800 = 1_200 > 0. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.75 + slcsp: 10_000 + aca_ptc: 8_800 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 1_200 + +# ---- Eligible but APTC exactly equals SLCSP: zero residual ---- +- name: Case 4, aca_ptc equal to slcsp gives a zero residual. + # slcsp 8_000 - aca_ptc 8_000 = 0. The unit is eligible but the federal APTC + # already covers the full benchmark premium, so CT pays nothing. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 8_000 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 0 + +# ---- Ineligible: income above the 175% FPL ceiling ---- +- name: Case 5, 176% FPL above the ceiling yields no benefit. + # aca_magi_fraction 1.76 > 1.75 -> not eligible -> 0 (defined_for gate), + # even though slcsp - aca_ptc would otherwise be 1_000. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.76 + slcsp: 10_000 + aca_ptc: 9_000 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 0 + +# ---- Ineligible: no APTC-eligible member ---- +- name: Case 6, no APTC-eligible member yields no benefit. + # any(is_aca_ptc_eligible) is false -> not eligible -> 0. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: false + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 7_000 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 0 + +# ---- Ineligible: non-Connecticut household ---- +- name: Case 7, non-Connecticut household yields no benefit via defined_for. + # defined_for = StateCode.CT (via the eligible variable); a New York household + # is gated off -> 0. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 7_000 + households: + household: + members: [person1] + state_code: NY + output: + ct_covered_connecticut: 0 + +# ---- Ineligible: not in effect (pre-2026) ---- +- name: Case 8, 2025 pre-effective year yields no benefit. + # in_effect is false before 2026 -> not eligible -> 0. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 7_000 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 0 + +# ---- Eligible multi-member family, positive residual ---- +- name: Case 9, three-person CT family gets a positive residual. + # A larger family has a larger benchmark premium; slcsp 20_000 - aca_ptc 18_500 + # = 1_500 > 0. Only one member need be APTC-eligible for the unit to qualify at + # aca_magi_fraction 1.60 <= 1.75. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + person2: + is_aca_ptc_eligible: true + person3: + is_aca_ptc_eligible: false + tax_units: + tax_unit: + members: [person1, person2, person3] + aca_magi_fraction: 1.60 + slcsp: 20_000 + aca_ptc: 18_500 + households: + household: + members: [person1, person2, person3] + state_code: CT + output: + ct_covered_connecticut: 1_500 + +# ---- Edge: zero SLCSP with zero APTC yields a zero residual ---- +- name: Case 10, zero benchmark premium yields a zero residual. + # slcsp 0 - aca_ptc 0 = 0. A data edge with no benchmark premium on file: + # max(0, 0 - 0) = 0, no negative or spurious payment. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + slcsp: 0 + aca_ptc: 0 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 0 + +# ---- Edge: APTC exceeds SLCSP (over-subsidized data edge) floors at zero ---- +- name: Case 11, APTC exceeding the benchmark premium floors the residual at zero. + # slcsp 8_000 - aca_ptc 9_000 = -1_000 -> max(0, -1_000) = 0. Guards the floor + # against an over-subsidized data edge where the federal APTC exceeds the + # modeled benchmark premium; CT never pays a negative amount. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 9_000 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 0 + +# ---- Edge: very low end (~138% FPL, just above Medicaid) positive residual ---- +- name: Case 12, 138% FPL just above the Medicaid floor gets a positive residual. + # aca_magi_fraction 1.38 (~138% FPL, the effective lower bound inherited from + # the federal APTC/Medicaid split) <= 1.75 -> eligible. At the low end the + # federal APTC nearly covers the benchmark premium: + # slcsp 8_500 - aca_ptc 8_300 = 200 > 0. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.38 + slcsp: 8_500 + aca_ptc: 8_300 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 200 + +# ---- Edge: large family, large benchmark premium, large residual ---- +- name: Case 13, five-person CT family gets a large residual scaling with the premium. + # A larger household has a larger benchmark premium; only one member need be + # APTC-eligible for the unit to qualify at aca_magi_fraction 1.50 <= 1.75: + # slcsp 32_000 - aca_ptc 27_000 = 5_000 > 0. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + person2: + is_aca_ptc_eligible: false + person3: + is_aca_ptc_eligible: false + person4: + is_aca_ptc_eligible: false + person5: + is_aca_ptc_eligible: false + tax_units: + tax_unit: + members: [person1, person2, person3, person4, person5] + aca_magi_fraction: 1.50 + slcsp: 32_000 + aca_ptc: 27_000 + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: CT + output: + ct_covered_connecticut: 5_000 + +# ---- T6 [G2]: 2027 amount assertion confirms in_effect carry-forward ---- +- name: Case 14, 2027 later year retains the same residual (standing statute, no sunset). + # in_effect stays true from 2026 onward, so a 2027 eligible unit with the same + # forced inputs as Case 1 keeps the same residual: slcsp 8_000 - aca_ptc 7_500 = + # 500. Confirms the carry-forward of the in_effect toggle past the launch year. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 7_500 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 500 + +# ---- T7 [G3]: negative-income low end -> residual behavior holds ---- +- name: Case 15, negative employment income at the low end retains the eligible residual. + # A self-employment loss drives income negative; aca_magi_fraction is forced to + # the low-end floor and the member is APTC-eligible, so the unit stays eligible + # via the federal gate and the residual is max(0, slcsp - aca_ptc) = 8_000 - + # 7_500 = 500. Guards against negative income inflating or zeroing the residual. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + is_aca_ptc_eligible: true + self_employment_income: -20_000 + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.38 + slcsp: 8_000 + aca_ptc: 7_500 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut: 500 # verified: negative income does not disturb the injected residual 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 new file mode 100644 index 00000000000..f46d4188fa1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.yaml @@ -0,0 +1,254 @@ +# Unit tests for Covered Connecticut Program eligibility (REQ-1, REQ-2, REQ-3, +# REQ-4). A Connecticut tax unit is eligible when all three gates hold: +# 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) +# 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. + +# ---- 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. + period: 2026 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.74 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: true + +# ---- 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. + period: 2026 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.75 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: true + +# ---- 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. + period: 2026 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.76 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: false + +# ---- No APTC-eligible member ---- +- name: Case 4, no APTC-eligible member is ineligible. + # income-eligible (1.50 <= 1.75) but any(is_aca_ptc_eligible) is false -> not eligible. + period: 2026 + input: + people: + person1: + is_aca_ptc_eligible: false + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: false + +# ---- Multi-member: one APTC-eligible member suffices ---- +- name: Case 5, multi-member family with one APTC-eligible member is eligible. + # any(is_aca_ptc_eligible) is true from person1; aca_magi_fraction 1.60 <= 1.75 -> eligible. + period: 2026 + input: + people: + person1: + is_aca_ptc_eligible: true + person2: + is_aca_ptc_eligible: false + person3: + is_aca_ptc_eligible: false + tax_units: + tax_unit: + members: [person1, person2, person3] + aca_magi_fraction: 1.60 + households: + household: + members: [person1, person2, person3] + state_code: CT + output: + ct_covered_connecticut_eligible: true + +# ---- Non-Connecticut household (defined_for gate) ---- +- name: Case 6, non-Connecticut household is ineligible via defined_for. + # defined_for = StateCode.CT; a New York household is gated off -> not eligible. + period: 2026 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + households: + household: + members: [person1] + state_code: NY + output: + ct_covered_connecticut_eligible: false + +# ---- Not in effect: pre-2026 year ---- +- name: Case 7, 2025 pre-effective year is ineligible. + # in_effect is false before 2026 -> not eligible even though income/APTC gates pass. + period: 2025 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: false + +# ---- In effect: a later year (2027) remains eligible (open-ended) ---- +- name: Case 8, 2027 later year remains eligible (standing statute, no sunset). + # in_effect stays true from 2026 onward -> eligible in 2027. + period: 2027 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: true + +# ---- 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 + # ~138% effective lower bound is inherited from the federal APTC/Medicaid + # split, so an APTC-eligible unit at 138% FPL is eligible. + period: 2026 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.38 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: true + +# ---- 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 + # tightest epsilon above the inclusive ceiling -> not eligible. Complements the + # existing 1.75 (eligible) and 1.76 (ineligible) boundary cases. + period: 2026 + input: + people: + person1: + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.7501 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: false + +# ---- T2 [C2]: MFS-driven ineligibility via a real filing_status input ---- +- 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, + # so SEPARATE forces the person-level gate to False regardless of income. Income + # is inside the 175% band (1.50 <= 1.75), so only the MFS gate drives the result: + # any(is_aca_ptc_eligible) is False -> not eligible. + period: 2026 + input: + people: + person1: + age: 40 + tax_units: + tax_unit: + members: [person1] + filing_status: SEPARATE + aca_magi_fraction: 1.50 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: false + +# ---- T5 [G1]: pays_aca_premium-false arm via a real immigration-status input ---- +- name: Case 12, an immigration-ineligible enrollee fails pays_aca_premium and is ineligible. + # is_aca_ptc_immigration_status_eligible = false is a REAL input; is_aca_ptc_eligible + # is NOT injected. pays_aca_premium requires is_status_eligible, so the immigration + # gate drives pays_aca_premium -> False -> is_aca_ptc_eligible -> False, even though + # income is inside the 175% band (1.50 <= 1.75) -> not eligible. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_immigration_status_eligible: false + tax_units: + tax_unit: + members: [person1] + aca_magi_fraction: 1.50 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: false 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 new file mode 100644 index 00000000000..05f9dfe3959 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ct/dss/covered_connecticut/integration.yaml @@ -0,0 +1,371 @@ +# Integration tests for the Covered Connecticut Program (PY2026, premium axis). +# Covered Connecticut pays the residual benchmark silver-plan premium remaining +# after the federal APTC, driving the enrollee's net benchmark premium to $0 for +# 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 +# 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 +# the residual, boundary, zero-residual and ineligible branches. Derived values +# in Scenario 1 are read from the model at 2026 parameters. + +# ---- Scenario 1: full federal derivation, ~160% FPL single CT enrollee ---- +- name: Scenario 1, single CT enrollee at 25k derives the federal chain and gets the residual premium. + # Raw input: employment_income 25_000 for a 40-year-old single Connecticut filer. + # Derived (read from the model): aca_magi = 25_000; aca_magi_fraction = 1.59 + # (~160% FPL, below the 175% ceiling -> eligible); the federal applicable % + # lands at aca_required_contribution_percentage = 0.046238; slcsp annualizes + # to 11_186.34. + # Federal APTC = max(0, slcsp - income*fed_pct) = 11_186.34 - 25_000*0.046238 + # = 11_186.34 - 1_155.95 = 10_030.39 (matches the model). + # CT residual = max(0, slcsp - aca_ptc) = 11_186.34 - 10_030.39 = 1_155.95. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 25_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CT + output: + aca_magi: 25_000 + aca_magi_fraction: 1.59 + is_aca_ptc_eligible: true + slcsp: 11_186.34 + aca_ptc: 10_030.39 + ct_covered_connecticut_eligible: true + ct_covered_connecticut: 1_155.95 + +# ---- Scenario 2: injected, ~150% FPL small residual ---- +- name: Scenario 2, 150% FPL injected inputs give a small residual of 500. + # At low income the federal APTC nearly covers the benchmark premium: + # residual = slcsp - aca_ptc = 8_000 - 7_500 = 500. + 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_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 7_500 + households: + household: + members: [person1] + state_code: CT + output: + aca_magi_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 7_500 + ct_covered_connecticut_eligible: true + ct_covered_connecticut: 500 + +# ---- Scenario 3: injected, exactly 175% FPL boundary, larger residual ---- +- name: Scenario 3, exactly 175% FPL stays eligible with a larger residual. + # aca_magi_fraction 1.75 == fpl_limit (inclusive ceiling) -> eligible. Near the + # ceiling the applicable % is larger, so the federal APTC covers less and the + # residual grows: slcsp - aca_ptc = 10_000 - 8_800 = 1_200. + 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_fraction: 1.75 + slcsp: 10_000 + aca_ptc: 8_800 + households: + household: + members: [person1] + state_code: CT + output: + aca_magi_fraction: 1.75 + ct_covered_connecticut_eligible: true + ct_covered_connecticut: 1_200 + +# ---- Scenario 4: injected, aca_ptc equals slcsp -> zero residual ---- +- name: Scenario 4, federal APTC covering the full premium gives a zero residual. + # slcsp - aca_ptc = 8_000 - 8_000 = 0. The unit is eligible, but the federal + # APTC already drives the net premium to $0, so CT pays nothing on top. + 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_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 8_000 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: true + ct_covered_connecticut: 0 + +# ---- Scenario 5: ineligible, above the 175% FPL ceiling ---- +- name: Scenario 5, CT enrollee above 175% FPL yields no benefit. + # aca_magi_fraction 1.76 > 1.75 -> above the ceiling -> not eligible -> 0, even + # though the member is APTC-eligible and slcsp - aca_ptc would be 1_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_fraction: 1.76 + slcsp: 10_000 + aca_ptc: 9_000 + households: + household: + members: [person1] + state_code: CT + output: + aca_magi_fraction: 1.76 + ct_covered_connecticut_eligible: false + ct_covered_connecticut: 0 + +# ---- Scenario 6: ineligible, non-Connecticut household ---- +- name: Scenario 6, non-Connecticut enrollee at 150% FPL yields no benefit via defined_for. + # APTC-eligible at 150% FPL, but defined_for = StateCode.CT gates the whole + # variable off for a New York household -> 0. + 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_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 7_000 + households: + household: + members: [person1] + state_code: NY + output: + ct_covered_connecticut_eligible: false + ct_covered_connecticut: 0 + +# ---- Scenario 7: multi-member CT family, gap-binding residual ---- +- name: Scenario 7, three-person CT family at 160% FPL gets a positive residual scaling with the premium. + # A larger family has a larger benchmark premium; only one member need be + # APTC-eligible for the unit to qualify at aca_magi_fraction 1.60 <= 1.75: + # residual = slcsp - aca_ptc = 20_000 - 18_500 = 1_500. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + person2: + age: 38 + is_aca_ptc_eligible: true + person3: + age: 8 + is_aca_ptc_eligible: false + tax_units: + tax_unit: + members: [person1, person2, person3] + aca_magi_fraction: 1.60 + slcsp: 20_000 + aca_ptc: 18_500 + households: + household: + members: [person1, person2, person3] + state_code: CT + output: + ct_covered_connecticut_eligible: true + ct_covered_connecticut: 1_500 + +# ---- Scenario 8: injected, ~138% FPL low end just above the Medicaid floor ---- +- name: Scenario 8, CT enrollee at 138% FPL just above the Medicaid floor gets a small residual. + # aca_magi_fraction 1.38 is the effective lower bound inherited from the + # federal APTC/Medicaid split (CT imposes no explicit floor), well below the + # 1.75 ceiling -> eligible. At the low end the federal APTC nearly covers the + # benchmark premium: residual = slcsp - aca_ptc = 8_500 - 8_300 = 200. + 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_fraction: 1.38 + slcsp: 8_500 + aca_ptc: 8_300 + households: + household: + members: [person1] + state_code: CT + output: + aca_magi_fraction: 1.38 + ct_covered_connecticut_eligible: true + ct_covered_connecticut: 200 + +# ---- Scenario 9: injected, APTC exceeds SLCSP -> floored at zero ---- +- name: Scenario 9, federal APTC exceeding the benchmark premium floors the residual at zero. + # Over-subsidized data edge: slcsp - aca_ptc = 8_000 - 9_000 = -1_000 -> + # max(0, -1_000) = 0. The unit is eligible, but CT never pays a negative + # amount when the federal APTC exceeds the modeled benchmark premium. + 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_fraction: 1.50 + slcsp: 8_000 + aca_ptc: 9_000 + households: + household: + members: [person1] + state_code: CT + output: + ct_covered_connecticut_eligible: true + ct_covered_connecticut: 0 + +# ---- Scenario 10: injected, large CT family, large benchmark premium ---- +- name: Scenario 10, five-person CT family at 150% FPL gets a large residual scaling with the premium. + # A larger household carries a larger benchmark premium; only one member need + # be APTC-eligible for the unit to qualify at aca_magi_fraction 1.50 <= 1.75: + # residual = slcsp - aca_ptc = 32_000 - 27_000 = 5_000. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 42 + is_aca_ptc_eligible: true + person2: + age: 40 + is_aca_ptc_eligible: false + person3: + age: 12 + is_aca_ptc_eligible: false + person4: + age: 9 + is_aca_ptc_eligible: false + person5: + age: 5 + is_aca_ptc_eligible: false + tax_units: + tax_unit: + members: [person1, person2, person3, person4, person5] + aca_magi_fraction: 1.50 + slcsp: 32_000 + aca_ptc: 27_000 + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: CT + output: + ct_covered_connecticut_eligible: true + ct_covered_connecticut: 5_000 + +# ---- Scenario 11: full federal derivation, exactly 150% FPL single CT enrollee ---- +- name: Scenario 11, single CT enrollee at 23.6k derives the federal chain and lands exactly at 150% FPL. + # Raw input: employment_income 23_600 for a 40-year-old single Connecticut + # filer, NO injected ACA inputs -- the full federal chain derives everything. + # Derived (read from the model at 2026 parameters): + # aca_magi = 23_600; aca_magi_fraction = 1.50 (exactly 150% FPL, below the + # 1.75 ceiling -> eligible); slcsp annualizes to 11_186.34 (age-40 CT + # benchmark, same as Scenario 1); federal APTC = 10_197.50, i.e. the + # benchmark less the required contribution (11_186.34 - 23_600*0.041900). + # CT residual = max(0, slcsp - aca_ptc) = 11_186.34 - 10_197.50 = 988.84. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 23_600 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CT + output: + aca_magi: 23_600 + aca_magi_fraction: 1.50 + is_aca_ptc_eligible: true + slcsp: 11_186.34 + aca_ptc: 10_197.50 + ct_covered_connecticut_eligible: true + ct_covered_connecticut: 988.84 + +# ---- Scenario 12 [T3]: full federal derivation, ~140% FPL single CT enrollee is Medicaid, not marketplace ---- +- name: Scenario 12, single CT enrollee at 22k derives into Medicaid and gets no CT residual. + # Raw input: employment_income 22_000 for a 40-year-old single Connecticut filer, + # NO injected ACA inputs -- the full federal chain derives everything and the + # federal PTC gate itself governs eligibility. + # aca_magi = 22_000; aca_magi_fraction = 1.40 (~140% FPL). In Connecticut (a + # Medicaid-expansion state, HUSKY up to 138% FPL for adults, with an adult + # coverage group extending above it) a single adult at this income is + # Medicaid-eligible, so is_aca_ptc_eligible derives to False (Medicaid coverage + # precludes marketplace APTC). With no APTC-eligible member, slcsp returns 0 for + # the ineligible and the CT residual is 0. This scenario documents that CT's + # ~138% effective lower bound is inherited from the federal Medicaid/APTC split: + # below the split, enrollees are on Medicaid rather than the Covered CT program. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 22_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CT + output: + aca_magi: 22_000 + aca_magi_fraction: 1.40 + is_aca_ptc_eligible: false + slcsp: 0 + aca_ptc: 0 + ct_covered_connecticut_eligible: false + ct_covered_connecticut: 0 diff --git a/policyengine_us/tests/policy/baseline/household/healthcare_benefit_value.yaml b/policyengine_us/tests/policy/baseline/household/healthcare_benefit_value.yaml index 51cddcdf52a..38e782fe106 100644 --- a/policyengine_us/tests/policy/baseline/household/healthcare_benefit_value.yaml +++ b/policyengine_us/tests/policy/baseline/household/healthcare_benefit_value.yaml @@ -229,3 +229,50 @@ output: assigned_nm_premium_assistance: 0 healthcare_benefit_value: 0 + +- name: Case 14, Covered Connecticut premium assistance is a healthcare benefit value. + # ct_covered_connecticut flows into healthcare_benefit_value via the adds list. + period: 2026 + absolute_error_margin: 0.01 + input: + medicaid_cost: 0 + msp_cost: 0 + chip: 0 + basic_health_program: 0 + assigned_aca_ptc: 0 + co_omnisalud: 0 + assigned_ca_premium_subsidy: 0 + assigned_co_premium_assistance: 0 + ct_covered_connecticut: 500 + md_premium_assistance: 0 + or_healthier_oregon_cost: 0 + output: + healthcare_benefit_value: 500 + +- name: Case 15, derived Covered Connecticut residual flows into healthcare benefit value. + # T4 [S2]: a REAL CT household -- ct_covered_connecticut is DERIVED from raw + # employment_income (not injected), then flows into healthcare_benefit_value via + # the adds list. A 40-year-old single CT enrollee at 23_600 (exactly 150% FPL, + # above the Medicaid split so is_aca_ptc_eligible derives to True) gets a genuine + # non-round CT residual of 988.84 on top of the federal assigned_aca_ptc of + # 10_197.50. healthcare_benefit_value sums the two health benefits actually on + # file (assigned_aca_ptc + ct_covered_connecticut = 10_197.50 + 988.84 = + # 11_186.34), confirming the CT residual reaches net income through the wiring. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 23_600 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CT + output: + assigned_aca_ptc: 10_197.50 + ct_covered_connecticut: 988.84 + healthcare_benefit_value: 11_186.34 diff --git a/policyengine_us/variables/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut.py b/policyengine_us/variables/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut.py new file mode 100644 index 00000000000..1ac3523df95 --- /dev/null +++ b/policyengine_us/variables/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut.py @@ -0,0 +1,40 @@ +from policyengine_us.model_api import * + + +class ct_covered_connecticut(Variable): + value_type = float + entity = TaxUnit + label = "Covered Connecticut Program premium assistance" + unit = USD + definition_period = YEAR + defined_for = "ct_covered_connecticut_eligible" + reference = ( + "https://www.cga.ct.gov/2021/act/pa/pdf/2021PA-00002-R00SB-01202SS1-PA.pdf#page=22", + "https://www.cga.ct.gov/2021/act/pa/pdf/2021PA-00002-R00SB-01202SS1-PA.pdf#page=24", + "https://portal.ct.gov/dss/health-and-home-care/covered-connecticut-program", + ) + documentation = ( + "Covered Connecticut pays the enrollee's residual benchmark silver-plan " + "premium after the federal advance premium tax credit, driving the net " + "premium to $0 for eligible enrollees at or below 175% of the federal " + "poverty line. The benchmark second-lowest-cost silver plan premium " + "(slcsp) proxies the benchmark plan and is annualized from its monthly " + "definition. The subsidy is additive on top of the federal APTC and is " + "capped implicitly at the benchmark premium because the federal APTC " + "never exceeds it, so the residual is never negative. The program also " + "zeroes cost-sharing and adds dental and non-emergency medical " + "transportation benefits, which form a separate benefits axis and are " + "not modeled here. The subsidy is not counted as income for Connecticut " + "personal income tax purposes under Public Act 21-2, Section 16(d), so " + "it is not added to Connecticut adjusted gross income." + ) + + def formula(tax_unit, period, parameters): + # slcsp is a MONTH-period variable; add() annualizes it to match the + # YEAR-period aca_ptc so both sides of the residual are annual. + slcsp_annual = add(tax_unit, period, ["slcsp"]) + aca_ptc = tax_unit("aca_ptc", period) + # Residual benchmark premium after the federal APTC. The max_(0, ...) + # floor is defensive and redundant in the baseline, where aca_ptc never + # exceeds the benchmark slcsp, but is retained for robustness. + return max_(0, slcsp_annual - aca_ptc) 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 new file mode 100644 index 00000000000..2fd8e3c3877 --- /dev/null +++ b/policyengine_us/variables/gov/states/ct/dss/covered_connecticut/ct_covered_connecticut_eligible.py @@ -0,0 +1,40 @@ +from policyengine_us.model_api import * + + +class ct_covered_connecticut_eligible(Variable): + value_type = bool + entity = TaxUnit + label = "Eligible for the Covered Connecticut Program" + definition_period = YEAR + defined_for = StateCode.CT + reference = ( + "https://www.cga.ct.gov/2021/act/pa/pdf/2021PA-00002-R00SB-01202SS1-PA.pdf#page=22", + "https://portal.ct.gov/dss/health-and-home-care/covered-connecticut-program", + ) + documentation = ( + "A tax unit is eligible for the Covered Connecticut Program when the " + "program is in effect, at least one member is eligible for the federal " + "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 " + "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 " + "people. The benchmark-silver-enrollment requirement is represented by " + "using the second-lowest-cost silver plan premium as the modeled " + "premium in the amount variable, and is not separately gated here. The " + "statutory eligibility cohorts (adults aged 18 to 64, dependents aged 26 " + "and under, and parents and caretaker relatives) are deliberately NOT " + "separately gated, matching sibling state premium-assistance programs, " + "because the ACA MAGI test and the federal PTC structure already capture " + "the eligible population." + ) + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.ct.dss.covered_connecticut + 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 + return in_effect & aptc_eligible & income_eligible diff --git a/policyengine_us/variables/household/healthcare_benefit_value.py b/policyengine_us/variables/household/healthcare_benefit_value.py index 138e2ce2cc2..65ee5489019 100644 --- a/policyengine_us/variables/household/healthcare_benefit_value.py +++ b/policyengine_us/variables/household/healthcare_benefit_value.py @@ -22,6 +22,7 @@ class healthcare_benefit_value(Variable): "basic_health_program", "co_omnisalud", "assigned_co_premium_assistance", + "ct_covered_connecticut", "md_premium_assistance", "assigned_nm_premium_assistance", "or_healthier_oregon_cost",