diff --git a/changelog.d/claimable-filer-ptc.fixed.md b/changelog.d/claimable-filer-ptc.fixed.md new file mode 100644 index 00000000000..00c137474dd --- /dev/null +++ b/changelog.d/claimable-filer-ptc.fixed.md @@ -0,0 +1 @@ +Apply IRC 36B(c)(1)(D) to the premium tax credit: a filer who can be claimed as a dependent on another return is outside the tax family (26 CFR 1.36B-1(d)(2)), so the credit uses the tax family size for the poverty line and the coverage family's benchmark premium, on a joint return the other spouse keeps the credit, and the Washington, Massachusetts, Connecticut, Colorado, Maryland, California, New Mexico, Vermont and New Jersey premium programs follow their own rules for that person. diff --git a/policyengine_us/reforms/aca/aca_ptc_700_fpl_cliff.py b/policyengine_us/reforms/aca/aca_ptc_700_fpl_cliff.py index c9d449f6981..38002e777ac 100644 --- a/policyengine_us/reforms/aca/aca_ptc_700_fpl_cliff.py +++ b/policyengine_us/reforms/aca/aca_ptc_700_fpl_cliff.py @@ -66,7 +66,16 @@ def formula(person, period, parameters): ) is_income_eligible = standard_income_eligible | below_fpl_exception - return person("pays_aca_premium", period) & ~separate & is_income_eligible + # The reform leaves 26 U.S.C. 36B(c)(1)(D) in place: someone + # another taxpayer can claim is outside the tax family. + tax_family_member = person("is_aca_tax_family_member", period) + + return ( + person("pays_aca_premium", period) + & tax_family_member + & ~separate + & is_income_eligible + ) class reform(Reform): def apply(self): diff --git a/policyengine_us/reforms/aca/aca_ptc_additional_bracket.py b/policyengine_us/reforms/aca/aca_ptc_additional_bracket.py index a7ce0497a2d..b98cda69863 100644 --- a/policyengine_us/reforms/aca/aca_ptc_additional_bracket.py +++ b/policyengine_us/reforms/aca/aca_ptc_additional_bracket.py @@ -73,11 +73,16 @@ def formula(person, period, parameters): child_pays = person("aca_child_index", period) <= p_aca.max_child_count pays_aca_premium = is_aca_adult | child_pays + # The reform leaves 26 U.S.C. 36B(c)(1)(D) in place: someone + # another taxpayer can claim is outside the tax family. + tax_family_member = person("is_aca_tax_family_member", period) + return ( is_status_eligible & is_coverage_eligible & is_income_eligible & pays_aca_premium + & tax_family_member ) class reform(Reform): diff --git a/policyengine_us/reforms/aca/aca_ptc_simplified_bracket.py b/policyengine_us/reforms/aca/aca_ptc_simplified_bracket.py index 38603128235..9968a6a8728 100644 --- a/policyengine_us/reforms/aca/aca_ptc_simplified_bracket.py +++ b/policyengine_us/reforms/aca/aca_ptc_simplified_bracket.py @@ -74,11 +74,16 @@ def formula(person, period, parameters): child_pays = person("aca_child_index", period) <= p_aca.max_child_count pays_aca_premium = is_aca_adult | child_pays + # The reform leaves 26 U.S.C. 36B(c)(1)(D) in place: someone + # another taxpayer can claim is outside the tax family. + tax_family_member = person("is_aca_tax_family_member", period) + return ( is_status_eligible & is_coverage_eligible & is_income_eligible & pays_aca_premium + & tax_family_member ) class reform(Reform): diff --git a/policyengine_us/tests/core/test_claimable_filer_ptc_invariants.py b/policyengine_us/tests/core/test_claimable_filer_ptc_invariants.py new file mode 100644 index 00000000000..134745ca608 --- /dev/null +++ b/policyengine_us/tests/core/test_claimable_filer_ptc_invariants.py @@ -0,0 +1,420 @@ +"""The premium tax credit for a filer who can be claimed as a dependent. + +No credit is allowed to someone another taxpayer can claim (IRC 36B(c)(1)(D)); +such a spouse is outside the tax family (26 CFR 1.36B-1(d)(2)), and a return +with such a filer claims no dependents (IRC 152(b)(1)) unless the would-be +claimant meets the refund-only filing exception. The credit therefore uses +`aca_tax_family_size` for the poverty line, household income without +out-of-family dependents (`aca_magi`), the coverage family's benchmark +(`aca_ptc_slcsp`) and its share of the premium +(`aca_ptc_coverage_family_premium`), and the state premium programs that wrap +it follow. + +For couples drawn by Hypothesis in every year 2021-2026 (spouses claimable, +actually claimed or neither, with or without the filing exception, and +dependents with tax-exempt interest and Social Security) and a seeded +population in 2025 and 2026 that includes review r2's family and premium +cases: + +1. Swap invariance: each output is the same under either head/spouse + labelling. +2. Identities: `aca_tax_family_size` equals the filers who cannot be claimed + plus the dependents when one of them is an applicable taxpayer and no + filer is claimable outside the exception; nobody outside the tax family is + eligible; 0 <= `aca_ptc_slcsp` <= `slcsp`; `aca_magi` is AGI plus the + MAGI additions, less the tax-exempt interest and Social Security of + dependents outside the family, floored at zero once (computed here from + the inputs, not from the model's family variables); used credit <= + coverage-family premium <= selected premium; no family member means no + credit and no coverage-family premium; cost-sharing reductions only when + every enrollee is eligible for the credit; and on a return with a head or + spouse where neither can be claimed, `aca_ptc_slcsp` = `slcsp`, + `aca_magi` = `medicaid_magi`, `aca_tax_family_size` = `tax_unit_size` and + `aca_fpg` = `tax_unit_fpg` for the prior year (these simulations supply no + prior-year size, so the two sizes agree). A unit with neither a head nor a + spouse is filed by its oldest member, so a claimable minor changes it. +3. Monotonicity, for the drawn couples in every year and the seeded + population: marking another filer as claimable never raises the tax + family size, never raises the benchmark while the same people are + enrolled, and never raises the credit, except in two intended cases: + income crosses the 100% floor (below it for two, above it for one, so IRC + 36B allows a credit that was not allowed), or dependents leave the family + and their tax-exempt interest and Social Security leave household income + (identity 2 checks the amount removed). +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation +from policyengine_us.tests.core.test_dependent_elsewhere_head_spouse_swap_invariance import ( + CLAIM_PATTERNS, + TOLERANCE, + _calc, + _crafted_cases, + _monotone_variants, + _seeded_couples, + _situation, + _swap_variants, + adults, + dependents, + money, +) + +# Texas (age curve, no expansion), New York and Vermont (family tiers), and +# the states whose premium programs wrap the credit. +STATES = ["TX", "NY", "VT", "MA", "CT", "CO", "MD", "CA", "NM", "NJ", "WA"] +YEARS = [2021, 2022, 2023, 2024, 2025, 2026] +FEDERAL = [ + "aca_tax_family_size", + "aca_fpg", + "aca_magi", + "aca_magi_fraction", + "slcsp", + "aca_ptc_slcsp", + "aca_ptc", + "aca_ptc_coverage_family_premium", + "used_aca_ptc", + "marketplace_csr_eligible", +] +STATE_WRAPS = [ + "ma_connector_care", + "ct_covered_connecticut", + "co_premium_assistance", + "md_premium_assistance", + "ca_premium_subsidy", + "nm_premium_assistance", + "vt_premium_assistance", + "nj_njhps", + "wa_cascade_care_savings", +] +OUTPUTS = FEDERAL + STATE_WRAPS +INCOME_SEED = 20261012 + + +@st.composite +def ptc_dependents(draw): + # Tax-exempt interest and Social Security count in household income only + # while the dependent is in the family. + return { + **draw(dependents()), + "tax_exempt_interest_income": draw(money(20_000)), + "social_security_survivors": draw(money(12_000)), + } + + +@st.composite +def couples(draw): + claimed = draw(st.sampled_from(CLAIM_PATTERNS)) + # Claimable whenever claimed, and sometimes claimable but not claimed. + extra = draw(st.sampled_from(CLAIM_PATTERNS)) + return { + "state": draw(st.sampled_from(STATES)), + "adults": [draw(adults()), draw(adults())], + "dependents": draw(st.lists(ptc_dependents(), max_size=2)), + "claimed": claimed, + "claimable": (claimed[0] or extra[0], claimed[1] or extra[1]), + "exception": draw(st.sampled_from(CLAIM_PATTERNS)), + } + + +def _ptc_cases(states): + # Two 30-year-olds whose income is 150% of the one-person line in 2025, + # with and without a child: the credit for the unclaimable spouse. + adult = {"age": 30, "employment_income": 11_295.0} + cases = [] + for state in states: + for claimed in [(True, False), (True, True)]: + cases += [ + { + "state": state, + "adults": [adult, adult], + "dependents": [], + "claimed": claimed, + }, + { + "state": state, + "adults": [adult, adult], + "dependents": [{"age": 4}], + "claimed": claimed, + }, + ] + return cases + + +def _review_r2_cases(): + # A New York family-tier policy and a Texas coverage family of one adult + # and three children, each beside a claimable spouse whose claimant meets + # the refund-only exception, and dependents whose tax-exempt interest or + # negative Social Security leaves household income. + adult = {"age": 30, "employment_income": 11_000.0} + exception = {"claimable": (True, False), "exception": (True, False)} + return [ + { + "state": "NY", + "adults": [adult, adult], + "dependents": [{"age": 10}], + "claimed": (False, False), + **exception, + }, + { + "state": "TX", + "adults": [ + {"age": 19, "employment_income": 11_000.0}, + {"age": 30, "employment_income": 29_000.0}, + ], + "dependents": [{"age": 10}, {"age": 8}, {"age": 6}], + "claimed": (False, False), + **exception, + }, + { + "state": "TX", + "adults": [adult, adult], + "dependents": [{"age": 10, "tax_exempt_interest_income": 20_000.0}], + "claimed": (True, False), + }, + { + "state": "TX", + "adults": [adult, adult], + "dependents": [{"age": 10, "social_security_survivors": -30_000.0}], + "claimed": (True, False), + }, + ] + + +def _with_dependent_income(units): + # Some seeded dependents get tax-exempt interest or Social Security. + rng = np.random.default_rng(INCOME_SEED) + out = [] + for unit in units: + dependents_with_income = [] + for amounts in unit["dependents"]: + amounts = dict(amounts) + if rng.random() < 0.5: + amounts["tax_exempt_interest_income"] = float( + round(rng.uniform(1, 20_000)) + ) + if rng.random() < 0.3: + amounts["social_security_survivors"] = float( + round(rng.uniform(1, 12_000)) + ) + dependents_with_income.append(amounts) + out.append({**unit, "dependents": dependents_with_income}) + return out + + +def _tax_unit_sum(sim, name, year): + return np.asarray(sim.calculate(name, year, map_to="tax_unit"), dtype=float) + + +def _tax_unit_sum_array(sim, values): + tax_units = sim.populations["tax_unit"] + return np.bincount( + np.asarray(tax_units.members_entity_id), + weights=np.asarray(values, dtype=float), + minlength=tax_units.count, + ) + + +def _enrollment_signature(sim, year): + # Which members are enrolled, as one number per tax unit, so variants + # can be compared person by person rather than by count. + position = np.asarray(sim.populations["tax_unit"].members_position) + enrolled = np.asarray(sim.calculate("pays_aca_premium", year), dtype=float) + return _tax_unit_sum_array(sim, enrolled * 2.0**position) + + +def _expected_aca_magi(sim, year): + # 26 CFR 1.36B-1(e)(1): household income counts a dependent's income only + # if the return claims them. A return claims none when no filer is an + # applicable taxpayer or when a filer can be claimed and the claimant + # does not meet the refund-only exception. Built from the inputs. + filer = np.asarray(sim.calculate("is_tax_unit_head_or_spouse", year)) + claimable = np.asarray( + sim.calculate("claimable_as_dependent_on_another_return", year) + ) + exception = np.asarray(sim.calculate("dependent_claimant_filing_exception", year)) + independent = _tax_unit_sum_array(sim, filer & ~claimable) + blocked = _tax_unit_sum_array(sim, filer & claimable & ~exception) > 0 + dependent_income = _tax_unit_sum_array( + sim, + ~filer + * ( + _calc(sim, "tax_exempt_interest_income", year) + + _calc(sim, "social_security", year) + ), + ) + removed = np.where((independent == 0) | blocked, dependent_income, 0) + unfloored = _calc(sim, "adjusted_gross_income", year) + additions = sim.tax_benefit_system.parameters( + f"{year}-01-01" + ).gov.hhs.medicaid.income.modification + for name in additions: + if sim.tax_benefit_system.variables[name].entity.key == "person": + unfloored = unfloored + _tax_unit_sum(sim, name, year) + else: + unfloored = unfloored + _calc(sim, name, year) + roleful = _tax_unit_sum_array(sim, filer) > 0 + return np.maximum(0, unfloored - removed), removed, roleful + + +def _identities(sim, year): + claimable_filer = _calc(sim, "head_or_spouse_is_dependent_elsewhere", year) > 0 + blocked = ( + _calc( + sim, "head_or_spouse_is_dependent_elsewhere_without_filing_exception", year + ) + > 0 + ) + independent = _calc(sim, "head_spouse_count_not_dependent_elsewhere", year) + dependents_count = _calc(sim, "tax_unit_count_dependents", year) + size = _calc(sim, "aca_tax_family_size", year) + claims_dependents = (independent > 0) & ~blocked + np.testing.assert_array_equal( + size, independent + np.where(claims_dependents, dependents_count, 0) + ) + eligible = np.asarray(sim.calculate("is_aca_ptc_eligible", year)) + member = np.asarray(sim.calculate("is_aca_tax_family_member", year)) + assert not (eligible & ~member).any(), "an eligible person is outside the family" + slcsp = _calc(sim, "slcsp", year) + benchmark = _calc(sim, "aca_ptc_slcsp", year) + assert (benchmark >= -TOLERANCE).all() + assert (benchmark <= slcsp + TOLERANCE).all(), "benchmark above slcsp" + aca_magi = _calc(sim, "aca_magi", year) + medicaid_magi = _calc(sim, "medicaid_magi", year) + expected_magi, _, roleful = _expected_aca_magi(sim, year) + np.testing.assert_allclose( + aca_magi[roleful], expected_magi[roleful], atol=TOLERANCE + ) + premium = _calc(sim, "selected_marketplace_plan_premium_proxy", year) + family_premium = _calc(sim, "aca_ptc_coverage_family_premium", year) + used = _calc(sim, "used_aca_ptc", year) + assert (used <= family_premium + TOLERANCE).all() + assert (family_premium <= premium + TOLERANCE).all() + csr = _calc(sim, "marketplace_csr_eligible", year) > 0 + enrolled = np.asarray(sim.calculate("pays_aca_premium", year)) + ineligible_enrollees = _tax_unit_sum_array(sim, enrolled & ~eligible) + assert not (csr & (ineligible_enrollees > 0)).any() + assert not _calc(sim, "aca_ptc", year)[size == 0].any() + assert not family_premium[size == 0].any() + unclaimed = roleful & ~claimable_filer + np.testing.assert_allclose(benchmark[unclaimed], slcsp[unclaimed], atol=TOLERANCE) + np.testing.assert_allclose( + aca_magi[unclaimed], medicaid_magi[unclaimed], atol=TOLERANCE + ) + np.testing.assert_array_equal( + size[unclaimed], _calc(sim, "tax_unit_size", year)[unclaimed] + ) + np.testing.assert_allclose( + _calc(sim, "aca_fpg", year)[unclaimed], + _calc(sim, "tax_unit_fpg", year - 1)[unclaimed], + ) + + +def check_swap(units, year, outputs, require_coverage=False): + sim = Simulation(situation=_situation(units, year, _swap_variants)) + if require_coverage: + # The batch must reach the rule: returns with exactly one claimable + # filer that still get a credit and, from 2026 when the modeled state + # programs start, a state wrap paid on one. + one_claimable = ( + _calc(sim, "head_or_spouse_is_dependent_elsewhere", year) > 0 + ) & (_calc(sim, "head_spouse_count_not_dependent_elsewhere", year) == 1) + assert (_calc(sim, "aca_ptc", year)[one_claimable] > 0).any() + if year >= 2026: + wraps = sum(_calc(sim, name, year) for name in STATE_WRAPS) + assert (wraps[one_claimable] > 0).any() + values = {name: _calc(sim, name, year) for name in outputs} + values["eligible members"] = _tax_unit_sum(sim, "is_aca_ptc_eligible", year) + for name, value in values.items(): + assert np.isfinite(value).all(), f"{name} in {year} is not finite" + drawn, swapped = value[0::2], value[1::2] + differs = ~(np.abs(swapped - drawn) <= TOLERANCE) + assert not differs.any(), ( + f"{name} in {year} changes when the head and spouse labels are " + "exchanged: " + + ", ".join( + f"{units[i]['state']} claimed={units[i]['claimed']} " + f"{drawn[i]:.2f} -> {swapped[i]:.2f}" + for i in np.flatnonzero(differs)[:10] + ) + ) + _identities(sim, year) + + +def check_monotone(units, year, require_removal=False): + # Each filer is claimable exactly when claimed in the variant; drawn + # filing exceptions stay. + sim = Simulation( + situation=_situation( + units, year, _monotone_variants, explicit_claimability=True + ) + ) + fraction = _calc(sim, "aca_magi_fraction", year).reshape(-1, 4) + removed = _expected_aca_magi(sim, year)[1].reshape(-1, 4) + if require_removal: + assert (removed > 0).any(), "no dependent income leaves household income" + enrollees = _enrollment_signature(sim, year).reshape(-1, 4) + for name in ("aca_tax_family_size", "aca_ptc_slcsp", "aca_ptc"): + values = _calc(sim, name, year).reshape(-1, 4) + for more, fewer, label in ( + (1, 0, "first adult"), + (2, 0, "second adult"), + (3, 1, "both after first"), + (3, 2, "both after second"), + ): + rises = values[:, more] > values[:, fewer] + TOLERANCE + if name == "aca_ptc_slcsp": + rises &= enrollees[:, more] == enrollees[:, fewer] + if name == "aca_ptc": + crosses_floor = (fraction[:, fewer] < 1) & (fraction[:, more] >= 1) + income_left = removed[:, more] > removed[:, fewer] + TOLERANCE + rises &= ~crosses_floor & ~income_left + assert not rises.any(), ( + f"{name} in {year} rises when the {label} is claimed " + "elsewhere: " + + ", ".join( + f"{units[i]['state']} {values[i, fewer]:.2f} -> " + f"{values[i, more]:.2f}" + for i in np.flatnonzero(rises)[:10] + ) + ) + _identities(sim, year) + + +@pytest.mark.parametrize("year", YEARS) +@settings( + max_examples=2, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.lists(couples(), min_size=6, max_size=15)) +def test_claimable_filer_ptc_is_label_free(year, units): + check_swap(units, year, OUTPUTS) + + +@pytest.mark.parametrize("year", YEARS) +@settings( + max_examples=2, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.lists(couples(), min_size=6, max_size=15)) +def test_claimable_filer_ptc_is_monotone(year, units): + check_monotone(units, year) + + +@pytest.mark.parametrize("year", [2025, 2026]) +def test_claimable_filer_ptc_seeded_population(year): + units = ( + _with_dependent_income(_seeded_couples(STATES, per_state=2)) + + _crafted_cases(STATES, review_case_only=True) + + _ptc_cases(STATES) + + _review_r2_cases() + ) + check_swap(units, year, OUTPUTS, require_coverage=True) + check_monotone(units, year, require_removal=True) diff --git a/policyengine_us/tests/policy/baseline/gov/aca/csr/marketplace_csr_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/aca/csr/marketplace_csr_eligible.yaml index b3780f6fe83..de6bf23e9d8 100644 --- a/policyengine_us/tests/policy/baseline/gov/aca/csr/marketplace_csr_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/aca/csr/marketplace_csr_eligible.yaml @@ -28,3 +28,114 @@ selected_marketplace_plan_actuarial_value: 0.7 marketplace_csr_actuarial_value: 0 marketplace_effective_actuarial_value: 0.7 + +- name: 2025 one policy covering a claimable spouse gets no cost-sharing reductions (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 45 CFR 155.305(g)(3): a policy gets only the category all its + # enrollees qualify for; the model prices one policy per tax unit. + person_receives_aca: [false, true] + marketplace_csr_eligible: false + +- name: 2025 one policy covering a claimable spouse gets no cost-sharing reductions (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: TX + output: + person_receives_aca: [true, false] + marketplace_csr_eligible: false + +- name: 2025 one policy whose enrollees are all in the family keeps cost-sharing reductions + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + marketplace_csr_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_fpg.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_fpg.yaml new file mode 100644 index 00000000000..ea84ca29d74 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_fpg.yaml @@ -0,0 +1,280 @@ +- name: 2025 joint return with one claimable spouse and a child uses the one-person poverty line (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # 2024 contiguous guideline for one person (parameters/gov/hhs/fpg.yaml). + aca_tax_family_size: 1 + aca_fpg: 15_060 + +- name: 2025 joint return with one claimable spouse and a child uses the one-person poverty line (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + aca_tax_family_size: 1 + aca_fpg: 15_060 + +- name: 2025 joint return with neither spouse claimable and a child uses the three-person line + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # 15,060 + 2 * 5,380 + aca_fpg: 25_820 + +- name: 2025 single filer who can be claimed keeps the tax unit's one-person line + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a] + filing_status: SINGLE + households: + household: + members: [person_a] + state_code: TX + output: + # No tax family, so the line for the whole tax unit (1 person). + aca_tax_family_size: 0 + aca_fpg: 15_060 + +- name: 2025 joint return with both spouses claimable keeps the tax unit's two-person line + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_tax_family_size: 0 + aca_fpg: 20_440 + +- name: 2026 joint return with one claimable spouse uses the 2025 one-person line (the claimable spouse is the head) + period: 2026 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_fpg: 15_650 + +- name: 2026 joint return with one claimable spouse uses the 2025 one-person line (the claimable spouse is the spouse) + period: 2026 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_fpg: 15_650 + +- name: 2025 the poverty line uses the tax year's family size, not a supplied prior-year size + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + tax_unit_size: {2024: 2, 2025: 3} + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # Form 8962 line 1 is the tax year's family; line 4 takes the 2024 + # guideline for it: 15,060 + 2 * 5,380. tax_unit_fpg for 2024 would + # have used the supplied 2024 size of 2 (20,440). + aca_tax_family_size: 3 + aca_fpg: 25_820 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi.yaml index 1a5d780bf1d..d2bb20d5090 100644 --- a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi.yaml +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi.yaml @@ -41,3 +41,300 @@ # Form 8962 Worksheet 1-1 line 3 takes Form 2555 lines 45 and 50. medicaid_magi: 65_000 aca_magi: 65_000 + +- name: 2025 joint return with a claimable spouse leaves the child's tax-exempt interest out of household income (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + employment_income: 10_000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_exempt_interest_income: 20_000 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # 26 CFR 1.36B-1(e)(1): household income counts other individuals only + # if they are in the family; this return's child is not (IRC 152(b)(1)). + adjusted_gross_income: 20_000 + medicaid_magi: 40_000 + aca_magi: 20_000 + +- name: 2025 joint return with a claimable spouse leaves the child's tax-exempt interest out of household income (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + employment_income: 10_000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_exempt_interest_income: 20_000 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + adjusted_gross_income: 20_000 + medicaid_magi: 40_000 + aca_magi: 20_000 + +- name: 2025 joint return with neither spouse claimable keeps household income as medicaid_magi + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_exempt_interest_income: 20_000 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + aca_magi: 40_000 + +- name: 2025 refund-only exception keeps the child in the family and their income in household income (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + employment_income: 10_000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_exempt_interest_income: 20_000 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + aca_magi: 40_000 + +- name: 2025 refund-only exception keeps the child in the family and their income in household income (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + employment_income: 10_000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_exempt_interest_income: 20_000 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + aca_magi: 40_000 + +- name: 2025 an excluded dependent's negative Social Security is removed before the zero floor (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + social_security: -30_000 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # max(0, 20,000 - 30,000 - (-30,000)) = 20,000. + medicaid_magi: 0 + aca_magi: 20_000 + +- name: 2025 an excluded dependent's negative Social Security is removed before the zero floor (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + social_security: -30_000 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + medicaid_magi: 0 + aca_magi: 20_000 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi_fraction.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi_fraction.yaml new file mode 100644 index 00000000000..5f8e0b0d996 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi_fraction.yaml @@ -0,0 +1,123 @@ +- name: 2025 joint return with one claimable spouse measures income against the one-person line (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 22,590 / 15,060 = 1.50 + aca_magi_fraction: 1.5 + +- name: 2025 joint return with one claimable spouse measures income against the one-person line (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 22_590 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_magi_fraction: 1.5 + +- name: 2025 joint return with neither spouse claimable measures income against the two-person line + period: 2025 + absolute_error_margin: 0.001 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 22,590 / 20,440 = 1.105, truncated to 1.10 (Form 8962 Worksheet 2) + aca_magi_fraction: 1.1 + +- name: 2025 single filer who can be claimed keeps the one-person line + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a] + filing_status: SINGLE + aca_magi: 22_590 + households: + household: + members: [person_a] + state_code: TX + output: + aca_magi_fraction: 1.5 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_ptc_below_fpl_immigration_exception.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_ptc_below_fpl_immigration_exception.yaml new file mode 100644 index 00000000000..8e6b856b402 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_ptc_below_fpl_immigration_exception.yaml @@ -0,0 +1,105 @@ +- name: 2025 below-FPL exception does not apply when the lawfully present spouse can be claimed elsewhere (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + immigration_status: TPS + is_basic_health_program_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi_fraction: 0.75 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 26 CFR 1.36B-2(b)(5): the taxpayer or a member of the taxpayer's + # family must be the lawfully present person. + aca_ptc_below_fpl_immigration_exception: false + +- name: 2025 below-FPL exception does not apply when the lawfully present spouse can be claimed elsewhere (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + immigration_status: TPS + is_basic_health_program_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi_fraction: 0.75 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_ptc_below_fpl_immigration_exception: false + +- name: 2025 below-FPL exception applies when the lawfully present spouse is in the tax family + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + immigration_status: TPS + is_basic_health_program_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi_fraction: 0.75 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_ptc_below_fpl_immigration_exception: true diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_tax_family_size.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_tax_family_size.yaml new file mode 100644 index 00000000000..07a0ecbaf5e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_tax_family_size.yaml @@ -0,0 +1,325 @@ +- name: 2025 single filer who can be claimed elsewhere has tax family size 0 + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, child] + filing_status: SINGLE + households: + household: + members: [person_a, child] + state_code: TX + output: + # Form 8962 line 1 (2025 instructions p.8): if someone can claim you, or + # your spouse on a joint return, you or your spouse is not included in + # the tax family size. + aca_tax_family_size: 0 + tax_unit_size: 2 + +- name: 2025 joint return with one claimable spouse and two children has tax family size 1 (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + child2: + age: 7 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child, child2] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child, child2] + state_code: TX + output: + # Form 8962 line 1 (2025 instructions p.8): if someone can claim you, or + # your spouse on a joint return, you or your spouse is not included in + # the tax family size. + aca_tax_family_size: 1 + tax_unit_size: 4 + +- name: 2025 joint return with one claimable spouse and two children has tax family size 1 (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + child2: + age: 7 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_b, person_a, child, child2] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child, child2] + state_code: TX + output: + aca_tax_family_size: 1 + tax_unit_size: 4 + +- name: 2025 joint return with both spouses claimable has tax family size 0 + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + aca_tax_family_size: 0 + +- name: 2025 joint return with neither spouse claimable counts the whole tax unit + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + child2: + age: 7 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child, child2] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child, child2] + state_code: TX + output: + aca_tax_family_size: 4 + +- name: 2025 single parent who cannot be claimed counts their child + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, child] + filing_status: SINGLE + households: + household: + members: [person_a, child] + state_code: TX + output: + aca_tax_family_size: 2 + +- name: "2025 joint return, claimable spouse with the refund-only exception, two children: family of three (the claimable spouse is the head)" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + child2: + age: 7 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child, child2] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child, child2] + state_code: TX + output: + # Publication 501 (2025) p.11, Dependent Taxpayer Test: the return can claim + # dependents when the would-be claimant files only for a refund. + aca_tax_family_size: 3 + +- name: "2025 joint return, claimable spouse with the refund-only exception, two children: family of three (the claimable spouse is the spouse)" + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + child2: + age: 7 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_b, person_a, child, child2] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child, child2] + state_code: TX + output: + aca_tax_family_size: 3 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_coverage_family_member.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_coverage_family_member.yaml new file mode 100644 index 00000000000..cd17edc030b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_coverage_family_member.yaml @@ -0,0 +1,115 @@ +- name: "2025 joint return with one claimable spouse: the coverage family is the other spouse (the claimable spouse is the head)" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + aca_magi: 30_000 + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # 26 CFR 1.36B-3(b)(2): members of the taxpayer's family who enroll. + pays_aca_premium: [true, true, true] + is_aca_coverage_family_member: [false, true, false] + +- name: "2025 joint return with one claimable spouse: the coverage family is the other spouse (the claimable spouse is the spouse)" + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + aca_magi: 30_000 + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + pays_aca_premium: [true, true, true] + is_aca_coverage_family_member: [true, false, false] + +- name: "2025 joint return with neither spouse claimable: a Medicaid-eligible child is not in the coverage family" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + aca_magi: 30_000 + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + is_aca_coverage_family_member: [true, true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_marketplace_enrollee.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_marketplace_enrollee.yaml new file mode 100644 index 00000000000..b8dee264d14 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_marketplace_enrollee.yaml @@ -0,0 +1,64 @@ +- name: 2025 a fourth child is enrolled but pays no age-rated premium + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + child2: + age: 8 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + child3: + age: 6 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + child4: + age: 4 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child, child2, child3, child4] + filing_status: JOINT + aca_magi: 40_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b, child, child2, child3, child4] + state_code: TX + output: + # 45 CFR 147.102(c)(1): premiums for the three oldest children only. + is_aca_marketplace_enrollee: [true, true, true, true, true, true] + pays_aca_premium: [true, true, true, true, true, false] 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..519ed8c4040 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,347 @@ has_qualifying_non_marketplace_health_coverage_at_interview: [true] pays_aca_premium: [false] is_aca_ptc_eligible: [false] + +- name: "2025 joint return with one claimable spouse: only the other spouse is eligible (the claimable spouse is the head)" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # IRC 36B(c)(1)(D); 26 CFR 1.36B-2(b)(3): someone another taxpayer may + # claim is not an applicable taxpayer. + aca_magi_fraction: 1.5 + is_aca_ptc_eligible: [false, true] + +- name: "2025 joint return with one claimable spouse: only the other spouse is eligible (the claimable spouse is the spouse)" + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 22_590 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_magi_fraction: 1.5 + is_aca_ptc_eligible: [true, false] + +- name: 2025 single filer who can be claimed elsewhere is not eligible + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a] + filing_status: SINGLE + aca_magi: 22_590 + households: + household: + members: [person_a] + state_code: TX + output: + is_aca_ptc_eligible: [false] + +- name: "2025 joint return with both spouses claimable: neither is eligible" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + is_aca_ptc_eligible: [false, false] + +- name: "2025 joint return with neither spouse claimable: both are eligible" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + is_aca_ptc_eligible: [true, true] + +- name: "2025 joint return with one claimable spouse: their child is not eligible either (the claimable spouse is the head)" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + aca_magi: 30_000 + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # IRC 152(b)(1): the return claims no dependents. + is_aca_ptc_eligible: [false, true, false] + +- name: "2025 joint return with one claimable spouse: their child is not eligible either (the claimable spouse is the spouse)" + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + aca_magi: 30_000 + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + is_aca_ptc_eligible: [true, false, false] + +- name: "2025 couple below the two-person line but above the one-person line: the unclaimable spouse is eligible (the claimable spouse is the head)" + period: 2025 + absolute_error_margin: 0.001 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 18_000 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 18,000 / 15,060 = 1.195 for a tax family of one, so 100-400% FPL; + # the same couple with nobody claimable is at 0.88 and not eligible. + aca_magi_fraction: 1.19 + is_aca_ptc_eligible: [false, true] + +- name: "2025 couple below the two-person line but above the one-person line: the unclaimable spouse is eligible (the claimable spouse is the spouse)" + period: 2025 + absolute_error_margin: 0.001 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 18_000 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_magi_fraction: 1.19 + is_aca_ptc_eligible: [true, false] + +- name: 2025 couple below the two-person line with neither spouse claimable is not eligible + period: 2025 + absolute_error_margin: 0.001 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 18_000 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_magi_fraction: 0.88 + is_aca_ptc_eligible: [false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_tax_family_member.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_tax_family_member.yaml new file mode 100644 index 00000000000..4e7fbfc067e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/is_aca_tax_family_member.yaml @@ -0,0 +1,499 @@ +- name: 2025 single filer who can be claimed elsewhere, and their child, are outside the tax family + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, child] + filing_status: SINGLE + households: + household: + members: [person_a, child] + state_code: TX + output: + # 26 CFR 1.36B-1(d)(2): the family is the taxpayer, both spouses on a + # joint return, except for individuals who qualify as a dependent of + # another taxpayer; IRC 152(b)(1): such a return claims no dependents. + is_aca_tax_family_member: [false, false] + +- name: 2025 joint return with one claimable spouse keeps only the other spouse in the tax family (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # 26 CFR 1.36B-1(d)(2): the family is the taxpayer, both spouses on a + # joint return, except for individuals who qualify as a dependent of + # another taxpayer; IRC 152(b)(1): such a return claims no dependents. + is_aca_tax_family_member: [false, true, false] + +- name: 2025 joint return with one claimable spouse keeps only the other spouse in the tax family (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + is_aca_tax_family_member: [true, false, false] + +- name: 2025 joint return with both spouses claimable has no tax family + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + is_aca_tax_family_member: [false, false, false] + +- name: 2025 joint return with neither spouse claimable keeps everyone in the tax family + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + is_aca_tax_family_member: [true, true, true] + +- name: 2025 dependent another taxpayer could also claim stays in the family of the return that claims them + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + child: + age: 10 + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a, child] + filing_status: SINGLE + households: + household: + members: [person_a, child] + state_code: TX + output: + is_aca_tax_family_member: [true, true] + +- name: "2025 joint return, claimable spouse whose claimant meets the refund-only exception: spouse out, child in (the claimable spouse is the head)" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # Publication 501 (2025) p.11, Dependent Taxpayer Test: the return can claim + # dependents when the would-be claimant files only for a refund. + # Form 1040 line 12a still marks the spouse as claimable (2025 instructions p.33). + is_aca_tax_family_member: [false, true, true] + +- name: "2025 joint return, claimable spouse whose claimant meets the refund-only exception: spouse out, child in (the claimable spouse is the spouse)" + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + is_aca_tax_family_member: [true, false, true] + +- name: 2025 single claimable filer with the refund-only exception has no applicable taxpayer, so no family + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [person_a, child] + filing_status: SINGLE + households: + household: + members: [person_a, child] + state_code: TX + output: + # IRC 36B(c)(1)(D): no credit to someone another taxpayer can claim. + is_aca_tax_family_member: [false, false] + +- name: 2025 joint return, spouse claimable but not actually claimed is still out of the family (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 26 CFR 1.36B-2(b)(3): 'may claim', whether or not claimed. + is_aca_tax_family_member: [false, true] + +- name: 2025 joint return, spouse claimable but not actually claimed is still out of the family (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: TX + output: + is_aca_tax_family_member: [true, false] + +- name: "2025 tax unit with neither head nor spouse: a claimable 17-year-old filer is out of the family" + period: 2025 + input: + people: + minor: + age: 17 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [minor] + filing_status: SINGLE + households: + household: + members: [minor] + state_code: TX + output: + # Role inference gives a lone minor no head; own claimability decides. + is_tax_unit_head: [false] + is_aca_tax_family_member: [false] + +- name: "2025 tax unit with neither head nor spouse: an unclaimed 17-year-old filer stays in the family" + period: 2025 + input: + people: + minor: + age: 17 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_units: + tax_unit: + members: [minor] + filing_status: SINGLE + households: + household: + members: [minor] + state_code: TX + output: + is_tax_unit_head: [false] + is_aca_tax_family_member: [true] + +- name: "2025 claimable 17-year-old filing with an infant: neither is in a family" + period: 2025 + input: + people: + parent: + age: 17 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + infant: + age: 1 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + tax_units: + tax_unit: + members: [parent, infant] + filing_status: SINGLE + households: + household: + members: [parent, infant] + state_code: TX + output: + # Role inference makes the oldest adult the head, so this unit has none; the + # oldest member is taken as the filer (IRC 36B(c)(1)(D)). + is_tax_unit_head: [false, false] + is_aca_tax_family_member: [false, false] + +- name: "2025 unclaimed 17-year-old filing with an infant: both are in the family" + period: 2025 + input: + people: + parent: + age: 17 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + infant: + age: 1 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + tax_units: + tax_unit: + members: [parent, infant] + filing_status: SINGLE + households: + household: + members: [parent, infant] + state_code: TX + output: + is_aca_tax_family_member: [true, true] diff --git a/policyengine_us/tests/policy/baseline/gov/aca/ptc/aca_ptc.yaml b/policyengine_us/tests/policy/baseline/gov/aca/ptc/aca_ptc.yaml index e16d13d56e5..01b99f555c2 100644 --- a/policyengine_us/tests/policy/baseline/gov/aca/ptc/aca_ptc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/aca/ptc/aca_ptc.yaml @@ -256,3 +256,790 @@ output: is_aca_ptc_eligible: [false] aca_ptc: 0 + +- name: "2025 joint return with one claimable spouse: credit for the other spouse's benchmark (the claimable spouse is the head)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 150% FPL for one, 0% contribution: 6,000 - 0 + aca_tax_family_size: 1 + aca_ptc_slcsp: 6000 + aca_ptc: 6000 + +- name: "2025 joint return with one claimable spouse: credit for the other spouse's benchmark (the claimable spouse is the spouse)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_tax_family_size: 1 + aca_ptc_slcsp: 6000 + aca_ptc: 6000 + +- name: 2025 single filer who can be claimed elsewhere gets no credit + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + tax_units: + tax_unit: + members: [person_a] + filing_status: SINGLE + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [person_a] + state_code: TX + output: + # Form 8962 instructions: no one can claim you as a dependent. + aca_ptc: 0 + +- name: 2025 joint return with both spouses claimable gets no credit + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_ptc: 0 + +- name: "2025 joint return with neither spouse claimable: credit for both benchmarks" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 2025: 110% FPL for two, 0% contribution. + aca_tax_family_size: 2 + aca_ptc: 12_000 + +- name: "2026 joint return with one claimable spouse: credit for the other spouse's benchmark (the claimable spouse is the head)" + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 150% FPL for one, 4.19% contribution: 6,000 - 0.0419 * 23,475 + aca_tax_family_size: 1 + aca_ptc_slcsp: 6000 + aca_ptc: 5016.4 + +- name: "2026 joint return with one claimable spouse: credit for the other spouse's benchmark (the claimable spouse is the spouse)" + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_tax_family_size: 1 + aca_ptc_slcsp: 6000 + aca_ptc: 5016.4 + +- name: 2026 single filer who can be claimed elsewhere gets no credit + period: 2026 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + tax_units: + tax_unit: + members: [person_a] + filing_status: SINGLE + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a] + state_code: TX + output: + # Form 8962 instructions: no one can claim you as a dependent. + aca_ptc: 0 + +- name: 2026 joint return with both spouses claimable gets no credit + period: 2026 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_ptc: 0 + +- name: "2026 joint return with neither spouse claimable: credit for both benchmarks" + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 2026: 23,475 / 21,150 = 110%, 2.1%: 12,000 - 0.021 * 23,475 + aca_tax_family_size: 2 + aca_ptc: 11507.03 + +- name: "2025 couple below the two-person line: the unclaimable spouse gets a credit (the claimable spouse is the head)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 18_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 18,000 / 15,060 = 119% FPL for a family of one: 0% contribution. + aca_ptc: 6000 + +- name: "2025 couple below the two-person line: the unclaimable spouse gets a credit (the claimable spouse is the spouse)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 18_000 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_ptc: 6000 + +- name: 2025 couple below the two-person line with neither spouse claimable gets no credit + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 18_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_ptc: 0 + +- name: 2025 child's tax-exempt interest no longer cuts the credit of a return with a claimable spouse (the claimable spouse is the head) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + employment_income: 10_000 + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_exempt_interest_income: 20_000 + slcsp_age_curve_amount_person: 3000 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # 20,000 / 15,060 = 132% FPL: 0% contribution; benchmark 6,000 (child out). + aca_magi: 20_000 + aca_magi_fraction: 1.32 + aca_ptc: 6000 + +- name: 2025 child's tax-exempt interest no longer cuts the credit of a return with a claimable spouse (the claimable spouse is the spouse) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + employment_income: 10_000 + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + employment_income: 10_000 + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + tax_exempt_interest_income: 20_000 + slcsp_age_curve_amount_person: 3000 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + aca_magi: 20_000 + aca_magi_fraction: 1.32 + aca_ptc: 6000 + +- name: "2025 refund-only exception: credit for the other spouse and the child (the claimable spouse is the head)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 3000 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + aca_magi: 30_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # 30,000 / 20,440 = 146% FPL: 0% contribution; benchmark 6,000 + 3,000. + aca_tax_family_size: 2 + aca_ptc_slcsp: 9000 + aca_ptc: 9000 + +- name: "2025 refund-only exception: credit for the other spouse and the child (the claimable spouse is the spouse)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 3000 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + aca_magi: 30_000 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + aca_tax_family_size: 2 + aca_ptc_slcsp: 9000 + aca_ptc: 9000 + +- name: 2025 claimable 17-year-old filing alone gets no credit + period: 2025 + input: + people: + minor: + age: 17 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [minor] + filing_status: SINGLE + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [minor] + state_code: TX + output: + is_aca_ptc_eligible: [false] + aca_ptc: 0 + +- name: 2025 unclaimed 17-year-old filing alone keeps the credit + period: 2025 + absolute_error_margin: 0.01 + input: + people: + minor: + age: 17 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + tax_units: + tax_unit: + members: [minor] + filing_status: SINGLE + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [minor] + state_code: TX + output: + is_aca_ptc_eligible: [true] + aca_ptc: 6000 + +- name: 2025 claimable 17-year-old filing with an infant gets no credit + period: 2025 + input: + people: + parent: + age: 17 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + infant: + age: 1 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + tax_units: + tax_unit: + members: [parent, infant] + filing_status: SINGLE + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [parent, infant] + state_code: TX + output: + # Role inference makes the oldest adult the head, so this unit has none; the + # oldest member is taken as the filer (IRC 36B(c)(1)(D)). + aca_ptc: 0 + +- name: 2025 unclaimed 17-year-old filing with an infant gets the credit for both + period: 2025 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 17 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + infant: + age: 1 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + pays_aca_premium: true + slcsp_age_curve_amount_person: 6000 + tax_units: + tax_unit: + members: [parent, infant] + filing_status: SINGLE + aca_magi: 22_590 + tax_unit_is_filer: true + households: + household: + members: [parent, infant] + state_code: TX + output: + # 22,590 / 20,440 = 110% FPL: 0% contribution; 2 * 6,000. + aca_ptc: 12_000 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/ptc/aca_ptc_coverage_family_premium.yaml b/policyengine_us/tests/policy/baseline/gov/aca/ptc/aca_ptc_coverage_family_premium.yaml new file mode 100644 index 00000000000..7d80d1157d5 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/ptc/aca_ptc_coverage_family_premium.yaml @@ -0,0 +1,339 @@ +- name: "2025 joint return with a claimable spouse on the policy: half the premium is the coverage family's (the claimable spouse is the head)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_benchmark_ratio: 0.75 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # Silver plan at 0.75 of the 12,000 benchmark: 9,000. 26 CFR 1.36B-3(h)(1): + # the coverage family's share is 6,000 / 12,000, so 4,500. + selected_marketplace_plan_premium_proxy: 9000 + aca_ptc_coverage_family_premium: 4500 + +- name: "2025 joint return with a claimable spouse on the policy: half the premium is the coverage family's (the claimable spouse is the spouse)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_benchmark_ratio: 0.75 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + selected_marketplace_plan_premium_proxy: 9000 + aca_ptc_coverage_family_premium: 4500 + +- name: "2025 joint return with neither spouse claimable: the whole premium is the coverage family's" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_benchmark_ratio: 0.75 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_ptc_coverage_family_premium: 9000 + +- name: 2025 bronze plan is allocated by the same benchmark share (the claimable spouse is the head) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_category: BRONZE + lcbp: 7000 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 7,000 * 6,000 / 12,000. + aca_ptc_coverage_family_premium: 3500 + +- name: 2025 bronze plan is allocated by the same benchmark share (the claimable spouse is the spouse) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_category: BRONZE + lcbp: 7000 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_ptc_coverage_family_premium: 3500 + +- name: "2025 NY policy with a claimable spouse (refund-only exception) and a child: allocate by each family's tier (the claimable spouse is the head)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + aca_magi: 22_000 + tax_unit_is_filer: true + selected_marketplace_plan_benchmark_ratio: 0.5 + households: + household: + members: [person_a, person_b, child] + state_code: NY + slcsp_age_0: 6000 + slcsp_rating_area: 3 + output: + # NY tiers on 6,000: whole policy 2.85 = 17,100; coverage family (one + # adult and a child) 1.70 = 10,200; the claimable spouse alone 1.00 = + # 6,000. 26 CFR 1.36B-3(h)(1): 8,550 * 10,200 / (10,200 + 6,000). + slcsp: 17_100 + aca_ptc_slcsp: 10_200 + selected_marketplace_plan_premium_proxy: 8550 + aca_ptc_coverage_family_premium: 5383.33 + +- name: "2025 NY policy with a claimable spouse (refund-only exception) and a child: allocate by each family's tier (the claimable spouse is the spouse)" + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + aca_magi: 22_000 + tax_unit_is_filer: true + selected_marketplace_plan_benchmark_ratio: 0.5 + households: + household: + members: [person_b, person_a, child] + state_code: NY + slcsp_age_0: 6000 + slcsp_rating_area: 3 + output: + slcsp: 17_100 + aca_ptc_slcsp: 10_200 + selected_marketplace_plan_premium_proxy: 8550 + aca_ptc_coverage_family_premium: 5383.33 + +- name: "2025 both spouses claimable: no coverage family, so none of a bronze premium" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_category: BRONZE + lcbp: 7000 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_ptc_coverage_family_premium: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/ptc/marketplace_net_premium.yaml b/policyengine_us/tests/policy/baseline/gov/aca/ptc/marketplace_net_premium.yaml index ddf141f5162..c5a2aa7a740 100644 --- a/policyengine_us/tests/policy/baseline/gov/aca/ptc/marketplace_net_premium.yaml +++ b/policyengine_us/tests/policy/baseline/gov/aca/ptc/marketplace_net_premium.yaml @@ -55,3 +55,80 @@ selected_marketplace_plan_premium_proxy: 5_000 used_aca_ptc: 3_000 marketplace_net_premium: 2_000 + +- name: 2025 the claimable spouse's share of the premium stays unsubsidized (the claimable spouse is the head) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_benchmark_ratio: 0.75 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 9,000 - 4,500. + marketplace_net_premium: 4500 + +- name: 2025 the claimable spouse's share of the premium stays unsubsidized (the claimable spouse is the spouse) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_benchmark_ratio: 0.75 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + marketplace_net_premium: 4500 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/ptc/used_aca_ptc.yaml b/policyengine_us/tests/policy/baseline/gov/aca/ptc/used_aca_ptc.yaml index c1f36cf129c..776d01b85dd 100644 --- a/policyengine_us/tests/policy/baseline/gov/aca/ptc/used_aca_ptc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/aca/ptc/used_aca_ptc.yaml @@ -74,3 +74,82 @@ selected_marketplace_plan_premium_proxy: 7_000 used_aca_ptc: 7_000 unused_aca_ptc: 2_200 + +- name: 2025 credit is capped at the coverage family's share of the premium (the claimable spouse is the head) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_benchmark_ratio: 0.75 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # min(6,000, 4,500); before, the cap was the whole 9,000 premium. + aca_ptc: 6000 + used_aca_ptc: 4500 + +- name: 2025 credit is capped at the coverage family's share of the premium (the claimable spouse is the spouse) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 22_590 + tax_unit_is_filer: true + selected_marketplace_plan_benchmark_ratio: 0.75 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_ptc: 6000 + used_aca_ptc: 4500 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/slcsp/aca_ptc_slcsp.yaml b/policyengine_us/tests/policy/baseline/gov/aca/slcsp/aca_ptc_slcsp.yaml new file mode 100644 index 00000000000..5a70e1a382f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/slcsp/aca_ptc_slcsp.yaml @@ -0,0 +1,504 @@ +- name: "2025 joint return with one claimable spouse: the benchmark covers only the other spouse (the claimable spouse is the head)" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 3000 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + aca_magi: 30_000 + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # 26 CFR 1.36B-3(f): the benchmark offered to the coverage family; + # slcsp still counts every enrollee. + slcsp: 15_000 + aca_ptc_slcsp: 6000 + +- name: "2025 joint return with one claimable spouse: the benchmark covers only the other spouse (the claimable spouse is the spouse)" + period: 2025 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 3000 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + aca_magi: 30_000 + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + slcsp: 15_000 + aca_ptc_slcsp: 6000 + +- name: "2025 joint return with neither spouse claimable: the benchmark is slcsp" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + slcsp_age_curve_amount_person: 3000 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + aca_magi: 30_000 + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + slcsp: 15_000 + aca_ptc_slcsp: 15_000 + +- name: "2025 single filer who can be claimed: no coverage family, no benchmark" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + tax_units: + tax_unit: + members: [person_a] + filing_status: SINGLE + aca_magi: 22_590 + households: + household: + members: [person_a] + state_code: TX + output: + slcsp: 6000 + aca_ptc_slcsp: 0 + +- name: "2025 joint return with both spouses claimable: no benchmark" + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + slcsp: 12_000 + aca_ptc_slcsp: 0 + +- name: 2025 a supplied slcsp carries through when every enrollee is in the coverage family + period: 2025 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_590 + slcsp: 1200 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + aca_ptc_slcsp: 1200 + +- name: "2025 NY joint return with one claimable spouse and a child: one-adult benchmark (the claimable spouse is the head)" + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NY + state_code_str: NY + slcsp_rating_area: 3 + slcsp_age_0: 760 + output: + slcsp: 2166.0 + aca_ptc_slcsp: 760 + +- name: "2025 NY joint return with one claimable spouse and a child: one-adult benchmark (the claimable spouse is the spouse)" + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: NY + state_code_str: NY + slcsp_rating_area: 3 + slcsp_age_0: 760 + output: + slcsp: 2166.0 + aca_ptc_slcsp: 760 + +- name: "2025 VT joint return with one claimable spouse and a child: one-adult benchmark (the claimable spouse is the head)" + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: VT + state_code_str: VT + slcsp_rating_area: 1 + slcsp_age_0: 760 + output: + slcsp: 2135.6 + aca_ptc_slcsp: 760 + +- name: "2025 VT joint return with one claimable spouse and a child: one-adult benchmark (the claimable spouse is the spouse)" + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: VT + state_code_str: VT + slcsp_rating_area: 1 + slcsp_age_0: 760 + output: + slcsp: 2135.6 + aca_ptc_slcsp: 760 + +- name: 2025 coverage family's three oldest children are charged once a claimable 19-year-old spouse is out (the claimable spouse is the head) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 19 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + child2: + age: 8 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + child3: + age: 6 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + tax_units: + tax_unit: + members: [person_a, person_b, child, child2, child3] + filing_status: JOINT + aca_magi: 40_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b, child, child2, child3] + state_code: TX + slcsp_age_0: 1000 + slcsp_rating_area: 1 + output: + # 45 CFR 147.102(c)(1): the three oldest covered children. The whole + # tax unit ranks the 19-year-old first, so the youngest child pays no + # premium in slcsp; the coverage family (the other spouse and all + # three children) charges all three. Default age curve on 1,000. + pays_aca_premium: [true, true, true, true, false] + is_aca_marketplace_enrollee: [true, true, true, true, true] + slcsp: 4713.8 + aca_ptc_slcsp: 4483.7 + +- name: 2025 coverage family's three oldest children are charged once a claimable 19-year-old spouse is out (the claimable spouse is the spouse) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 19 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + dependent_claimant_filing_exception: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 10 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + child2: + age: 8 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + child3: + age: 6 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + tax_units: + tax_unit: + members: [person_b, person_a, child, child2, child3] + filing_status: JOINT + aca_magi: 40_000 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a, child, child2, child3] + state_code: TX + slcsp_age_0: 1000 + slcsp_rating_area: 1 + output: + pays_aca_premium: [true, true, true, true, false] + is_aca_marketplace_enrollee: [true, true, true, true, true] + slcsp: 4713.8 + aca_ptc_slcsp: 4483.7 diff --git a/policyengine_us/tests/policy/baseline/gov/aca/slcsp/aca_ptc_slcsp_family_tier_category.yaml b/policyengine_us/tests/policy/baseline/gov/aca/slcsp/aca_ptc_slcsp_family_tier_category.yaml new file mode 100644 index 00000000000..a2269f7968d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/slcsp/aca_ptc_slcsp_family_tier_category.yaml @@ -0,0 +1,285 @@ +- name: "2025 NY joint return with one claimable spouse and a child: the coverage family is one adult (the claimable spouse is the head)" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NY + state_code_str: NY + slcsp_rating_area: 3 + slcsp_age_0: 760 + output: + slcsp_family_tier_category: TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN + aca_ptc_slcsp_family_tier_category: ONE_ADULT + +- name: "2025 NY joint return with one claimable spouse and a child: the coverage family is one adult (the claimable spouse is the spouse)" + period: 2025-01 + input: + people: + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: NY + state_code_str: NY + slcsp_rating_area: 3 + slcsp_age_0: 760 + output: + slcsp_family_tier_category: TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN + aca_ptc_slcsp_family_tier_category: ONE_ADULT + +- name: "2025 NY joint return with both spouses claimable: no coverage family tier" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NY + state_code_str: NY + slcsp_rating_area: 3 + slcsp_age_0: 760 + output: + aca_ptc_slcsp_family_tier_category: INDIVIDUAL_AGE_RATED + +- name: "2025 NY joint return with neither spouse claimable: same tier as slcsp" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NY + state_code_str: NY + slcsp_rating_area: 3 + slcsp_age_0: 760 + output: + slcsp_family_tier_category: TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN + aca_ptc_slcsp_family_tier_category: TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN + +- name: "2025 VT joint return with one claimable spouse and a child: the coverage family is one adult (the claimable spouse is the head)" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: VT + state_code_str: VT + slcsp_rating_area: 1 + slcsp_age_0: 760 + output: + slcsp_family_tier_category: TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN + aca_ptc_slcsp_family_tier_category: ONE_ADULT + +- name: "2025 VT joint return with one claimable spouse and a child: the coverage family is one adult (the claimable spouse is the spouse)" + period: 2025-01 + input: + people: + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: VT + state_code_str: VT + slcsp_rating_area: 1 + slcsp_age_0: 760 + output: + slcsp_family_tier_category: TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN + aca_ptc_slcsp_family_tier_category: ONE_ADULT + +- name: "2025 VT joint return with both spouses claimable: no coverage family tier" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: VT + state_code_str: VT + slcsp_rating_area: 1 + slcsp_age_0: 760 + output: + aca_ptc_slcsp_family_tier_category: INDIVIDUAL_AGE_RATED + +- name: "2025 VT joint return with neither spouse claimable: same tier as slcsp" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: VT + state_code_str: VT + slcsp_rating_area: 1 + slcsp_age_0: 760 + output: + slcsp_family_tier_category: TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN + aca_ptc_slcsp_family_tier_category: TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN diff --git a/policyengine_us/tests/policy/baseline/gov/aca/slcsp/aca_ptc_slcsp_family_tier_multiplier.yaml b/policyengine_us/tests/policy/baseline/gov/aca/slcsp/aca_ptc_slcsp_family_tier_multiplier.yaml new file mode 100644 index 00000000000..427bf1091c7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/slcsp/aca_ptc_slcsp_family_tier_multiplier.yaml @@ -0,0 +1,215 @@ +- name: "2025 NY joint return with one claimable spouse and a child: one-adult multiplier (the claimable spouse is the head)" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NY + state_code_str: NY + slcsp_rating_area: 3 + slcsp_age_0: 760 + output: + slcsp_family_tier_multiplier: 2.85 + aca_ptc_slcsp_family_tier_multiplier: 1.0 + +- name: "2025 NY joint return with one claimable spouse and a child: one-adult multiplier (the claimable spouse is the spouse)" + period: 2025-01 + input: + people: + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: NY + state_code_str: NY + slcsp_rating_area: 3 + slcsp_age_0: 760 + output: + slcsp_family_tier_multiplier: 2.85 + aca_ptc_slcsp_family_tier_multiplier: 1.0 + +- name: "2025 NY joint return with both spouses claimable: zero multiplier" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NY + state_code_str: NY + slcsp_rating_area: 3 + slcsp_age_0: 760 + output: + aca_ptc_slcsp_family_tier_multiplier: 0 + +- name: "2025 VT joint return with one claimable spouse and a child: one-adult multiplier (the claimable spouse is the head)" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: VT + state_code_str: VT + slcsp_rating_area: 1 + slcsp_age_0: 760 + output: + slcsp_family_tier_multiplier: 2.81 + aca_ptc_slcsp_family_tier_multiplier: 1.0 + +- name: "2025 VT joint return with one claimable spouse and a child: one-adult multiplier (the claimable spouse is the spouse)" + period: 2025-01 + input: + people: + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: VT + state_code_str: VT + slcsp_rating_area: 1 + slcsp_age_0: 760 + output: + slcsp_family_tier_multiplier: 2.81 + aca_ptc_slcsp_family_tier_multiplier: 1.0 + +- name: "2025 VT joint return with both spouses claimable: zero multiplier" + period: 2025-01 + input: + people: + person_a: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + monthly_age: 30 + pays_aca_premium: true + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + monthly_age: 5 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: VT + state_code_str: VT + slcsp_rating_area: 1 + slcsp_age_0: 760 + output: + aca_ptc_slcsp_family_tier_multiplier: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.yaml index 889da75d6d7..78d17264213 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.yaml @@ -504,3 +504,123 @@ output: ca_premium_subsidy_eligible: true ca_premium_subsidy: 3_935 + +- name: 2026 CA joint return with one claimable spouse (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: CA + output: + # Program Design (c)(2), (d)(7)(B). 22,000 / 15,650 = 140% FPL; + # federal 3.5724%: aca_ptc = 6,000 - 785.92 = 5,214.08; CA 0%: + # 6,000 - 5,214.08 = 785.92. + ca_premium_subsidy: 785.92 + +- name: 2026 CA joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 22_000 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: CA + output: + ca_premium_subsidy: 785.92 + +- name: 2026 CA joint return with neither spouse claimable + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 22_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: CA + output: + # 104% FPL, 2.1%: aca_ptc = 12,000 - 462 = 11,538; CA 0%: 462. + ca_premium_subsidy: 462 diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/doi/premium_assistance/co_premium_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/doi/premium_assistance/co_premium_assistance.yaml index ec7b5568aeb..0b311e29096 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/doi/premium_assistance/co_premium_assistance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/doi/premium_assistance/co_premium_assistance.yaml @@ -681,3 +681,248 @@ co_premium_assistance_eligible: true co_premium_assistance: 960 assigned_co_premium_assistance: 0 + +- name: 2026 CO joint return with one claimable spouse (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: CO + output: + # Tax family of one: 23,475 / 15,650 = 150% FPL; aca_ptc_slcsp 6,000; + # aca_ptc = 6,000 - 0.0419 * 23,475 = 5,016.40. + # C.R.S. 10-16-1205(1)(b)(II): payments for Coloradans who receive the + # premium tax credit. One member: min(12 * 80, 6,000 - 5,016.40) = 960. + co_premium_assistance: 960 + +- name: 2026 CO joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: CO + output: + co_premium_assistance: 960 + +- name: 2026 CO joint return with neither spouse claimable + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: CO + output: + # Tax family of two: 110% FPL; aca_ptc = 12,000 - 0.021 * 23,475 = 11,507.03. + co_premium_assistance: 492.97 + +- name: 2026 CO joint return with both spouses claimable gets nothing from the wrap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: CO + output: + # Neither spouse is in a federal tax family. + co_premium_assistance: 0 + +- name: 2026 CO joint return with one claimable spouse where the residual premium binds (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 20_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: CO + output: + # 20,000 / 15,650 = 127% FPL, 2.1%: contribution 420, aca_ptc 5,580. + # min(12 * 80, 6,000 - 5,580) = 420; the whole-enrollment benchmark + # would give min(960, 12,000 - 5,580) = 960. + aca_ptc: 5580 + co_premium_assistance: 420 + +- name: 2026 CO joint return with one claimable spouse where the residual premium binds (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 20_000 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: CO + output: + aca_ptc: 5580 + co_premium_assistance: 420 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..3fd48f2227c 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 @@ -371,3 +371,165 @@ state_code: CT output: ct_covered_connecticut: 500 # verified: negative income does not disturb the injected residual + +- name: 2026 CT joint return with one claimable spouse (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: CT + output: + # Tax family of one: 23,475 / 15,650 = 150% FPL; aca_ptc_slcsp 6,000; + # aca_ptc = 6,000 - 0.0419 * 23,475 = 5,016.40. + # Public Act 21-2, Section 16(b)(1): enrollees must be eligible for premium + # and cost-sharing subsidies. 6,000 - 5,016.40 = 983.60. + ct_covered_connecticut: 983.6 + +- name: 2026 CT joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: CT + output: + ct_covered_connecticut: 983.6 + +- name: 2026 CT joint return with neither spouse claimable + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: CT + output: + # Tax family of two: 110% FPL; aca_ptc = 12,000 - 0.021 * 23,475 = 11,507.03. + ct_covered_connecticut: 492.97 + +- name: 2026 CT joint return with both spouses claimable gets nothing from the wrap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: CT + output: + # Neither spouse is in a federal tax family. + ct_covered_connecticut: 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..b254072224a 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 @@ -566,3 +566,165 @@ state_code: MA output: ma_connector_care: 500 + +- name: 2026 MA joint return with one claimable spouse (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: MA + output: + # Tax family of one: 23,475 / 15,650 = 150% FPL; aca_ptc_slcsp 6,000; + # aca_ptc = 6,000 - 0.0419 * 23,475 = 5,016.40. + # 956 CMR 12.04(3)(a)2: ConnectorCare needs APTC eligibility, so only the + # other spouse counts. Band 2A ($0): 6,000 - 5,016.40 - 0 = 983.60. + ma_connector_care: 983.6 + +- name: 2026 MA joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: MA + output: + ma_connector_care: 983.6 + +- name: 2026 MA joint return with neither spouse claimable + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: MA + output: + # Tax family of two: 110% FPL; aca_ptc = 12,000 - 0.021 * 23,475 = 11,507.03. + ma_connector_care: 492.97 + +- name: 2026 MA joint return with both spouses claimable gets nothing from the wrap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: MA + output: + # Neither spouse is in a federal tax family. + ma_connector_care: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care_member_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care_member_eligible.yaml index 211a5ca2dc7..80802d19164 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care_member_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/hic/connector_care/ma_connector_care_member_eligible.yaml @@ -57,3 +57,79 @@ ma_connector_care_member_eligible: true person2: ma_connector_care_member_eligible: false + +- name: 2026 MA a spouse who can be claimed elsewhere is not a ConnectorCare member (the claimable spouse is the head) + period: 2026 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: MA + output: + ma_connector_care_member_eligible: [false, true] + +- name: 2026 MA a spouse who can be claimed elsewhere is not a ConnectorCare member (the claimable spouse is the spouse) + period: 2026 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: MA + output: + ma_connector_care_member_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/mhbe/premium_assistance/md_premium_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/mhbe/premium_assistance/md_premium_assistance.yaml index 785e626bc21..6ab68a2a44b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/md/mhbe/premium_assistance/md_premium_assistance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/md/mhbe/premium_assistance/md_premium_assistance.yaml @@ -723,3 +723,165 @@ state_code: VA output: md_premium_assistance: [868.57, 0] # MD unit derived > 0, VA unit masked to 0 by defined_for + +- name: 2026 MD joint return with one claimable spouse (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: MD + output: + # Tax family of one: 23,475 / 15,650 = 150% FPL; aca_ptc_slcsp 6,000; + # aca_ptc = 6,000 - 0.0419 * 23,475 = 5,016.40. + # COMAR 14.35.21.03A(1): members of an APTC-eligible tax household. + # 0% target at 150% FPL: 6,000 - 5,016.40 = 983.60. + md_premium_assistance: 983.6 + +- name: 2026 MD joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: MD + output: + md_premium_assistance: 983.6 + +- name: 2026 MD joint return with neither spouse claimable + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: MD + output: + # Tax family of two: 110% FPL; aca_ptc = 12,000 - 0.021 * 23,475 = 11,507.03. + md_premium_assistance: 492.97 + +- name: 2026 MD joint return with both spouses claimable gets nothing from the wrap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: MD + output: + # Neither spouse is in a federal tax family. + md_premium_assistance: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nj/dobi/njhps/nj_njhps.yaml b/policyengine_us/tests/policy/baseline/gov/states/nj/dobi/njhps/nj_njhps.yaml index 5ce199518f4..dda7665f27a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nj/dobi/njhps/nj_njhps.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nj/dobi/njhps/nj_njhps.yaml @@ -792,3 +792,165 @@ state_code: NJ output: nj_njhps: 0 + +- name: 2026 NJ joint return with one claimable spouse (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: NJ + output: + # Tax family of one: 23,475 / 15,650 = 150% FPL; aca_ptc_slcsp 6,000; + # aca_ptc = 6,000 - 0.0419 * 23,475 = 5,016.40. + # NJHPS goes to enrollees otherwise eligible for APTC: one member at + # $20 PMPM (138-150% FPL) = 240; nobody claimable is 110% FPL, below 138%. + nj_njhps: 240 + +- name: 2026 NJ joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: NJ + output: + nj_njhps: 240 + +- name: 2026 NJ joint return with neither spouse claimable + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: NJ + output: + # Tax family of two: 110% FPL; aca_ptc = 12,000 - 0.021 * 23,475 = 11,507.03. + nj_njhps: 0 + +- name: 2026 NJ joint return with both spouses claimable gets nothing from the wrap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: NJ + output: + # Neither spouse is in a federal tax family. + nj_njhps: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nj/dobi/njhps/nj_njhps_member_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nj/dobi/njhps/nj_njhps_member_eligible.yaml index 0aadc7215a3..095bbeaa877 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nj/dobi/njhps/nj_njhps_member_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nj/dobi/njhps/nj_njhps_member_eligible.yaml @@ -267,3 +267,79 @@ state_code: NJ output: nj_njhps_member_eligible: false + +- name: 2026 NJ a spouse who can be claimed elsewhere is not an NJHPS member (the claimable spouse is the head) + period: 2026 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: NJ + output: + nj_njhps_member_eligible: [false, true] + +- name: 2026 NJ a spouse who can be claimed elsewhere is not an NJHPS member (the claimable spouse is the spouse) + period: 2026 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: NJ + output: + nj_njhps_member_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance.yaml index 7852514f67b..a0238c6b2ba 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance.yaml @@ -885,3 +885,165 @@ # Derived SLCSP from the Bernalillo County rating area yields a positive base # top-up. Value is data-driven from the premium table. nm_premium_assistance: 2_645.60 + +- name: 2026 NM joint return with one claimable spouse (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # Tax family of one: 23,475 / 15,650 = 150% FPL; aca_ptc_slcsp 6,000; + # aca_ptc = 6,000 - 0.0419 * 23,475 = 5,016.40. + # MAP manual: consumers must qualify for the federal premium tax credit. + # Gross-up to 200% FPL, 0% target: 6,000 * 1.1 - 5,016.40 = 1,583.60. + nm_premium_assistance: 1583.6 + +- name: 2026 NM joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: NM + output: + nm_premium_assistance: 1583.6 + +- name: 2026 NM joint return with neither spouse claimable + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # Tax family of two: 110% FPL; aca_ptc = 12,000 - 0.021 * 23,475 = 11,507.03. + nm_premium_assistance: 1692.97 + +- name: 2026 NM joint return with both spouses claimable gets nothing from the wrap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # Neither spouse is in a federal tax family. + nm_premium_assistance: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.yaml index 9dc6fda92fb..60f13561961 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.yaml @@ -237,3 +237,126 @@ state_code: NM output: nm_premium_assistance_mih_eligible: false + +- name: "2026 NM above 400% FPL for a tax family of one: the other spouse qualifies for the MIH component (the claimable spouse is the head)" + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 70_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # 70,000 / 15,650 = 447% FPL; 6,000 - 0.085 * 70,000 = 50. + aca_magi_fraction: 4.47 + nm_premium_assistance_mih_eligible: true + nm_premium_assistance: 50 + +- name: "2026 NM above 400% FPL for a tax family of one: the other spouse qualifies for the MIH component (the claimable spouse is the spouse)" + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 70_000 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: NM + output: + aca_magi_fraction: 4.47 + nm_premium_assistance_mih_eligible: true + nm_premium_assistance: 50 + +- name: "2026 NM above 400% FPL with both spouses claimable: no MIH component" + period: 2026 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 70_000 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # The Addendum requires every federal PTC criterion except income. + nm_premium_assistance_mih_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/dvha/premium_assistance/vt_premium_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/dvha/premium_assistance/vt_premium_assistance.yaml index 52b98db3dc1..1ae052648d3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/dvha/premium_assistance/vt_premium_assistance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/dvha/premium_assistance/vt_premium_assistance.yaml @@ -406,3 +406,161 @@ state_code: VT output: vt_premium_assistance: 0 + +- name: 2026 VT joint return with one claimable spouse (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: VT + slcsp_age_0: 6000 + output: + # Tax family of one: 23,475 / 15,650 = 150% FPL; aca_ptc_slcsp 6,000; + # aca_ptc = 6,000 - 0.0419 * 23,475 = 5,016.40. + # 33 V.S.A. 1812(a)(1). Family tier ONE_ADULT: benchmark 6,000. + # min(23,475 * 0.015, 6,000 - 5,016.40) = 352.13. + vt_premium_assistance: 352.13 + +- name: 2026 VT joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: VT + slcsp_age_0: 6000 + output: + vt_premium_assistance: 352.13 + +- name: 2026 VT joint return with neither spouse claimable + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: VT + slcsp_age_0: 6000 + output: + # Tax family of two: 110% FPL; aca_ptc = 12,000 - 0.021 * 23,475 = 11,507.03. + vt_premium_assistance: 352.13 + +- name: 2026 VT joint return with both spouses claimable gets nothing from the wrap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: VT + slcsp_age_0: 6000 + output: + # Neither spouse is in a federal tax family. + vt_premium_assistance: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings.yaml index 986ed843771..062ef7cd7cd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings.yaml @@ -749,3 +749,166 @@ state_code: WA output: wa_cascade_care_savings: 3_000 + +- name: 2026 WA joint return with one claimable spouse (the claimable spouse is the head) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: WA + output: + # Tax family of one: 23,475 / 15,650 = 150% FPL; aca_ptc_slcsp 6,000; + # aca_ptc = 6,000 - 0.0419 * 23,475 = 5,016.40. + # Group 2 ($250 PMPM) for the claimable spouse, who is QHP-eligible but + # has no federal subsidy, and Group 1 ($55) for the other: 3,000 + 660; + # cap = 12,000 - 5,016.40 = 6,983.60 (both enrollees' net premium). + wa_cascade_care_savings: 3660 + +- name: 2026 WA joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: WA + output: + wa_cascade_care_savings: 3660 + +- name: 2026 WA joint return with neither spouse claimable + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: WA + output: + # Tax family of two: 110% FPL; aca_ptc = 12,000 - 0.021 * 23,475 = 11,507.03. + wa_cascade_care_savings: 492.97 + +- name: 2026 WA joint return with both spouses claimable gets nothing from the wrap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: WA + output: + # Both spouses are Group 2: 2 * 12 * 250 = 6,000, below the cap of 12,000. + wa_cascade_care_savings: 6000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_amount.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_amount.yaml index 0352869ffe8..73694ff8b65 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_amount.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_amount.yaml @@ -154,3 +154,80 @@ state_code: WA output: wa_cascade_care_savings_member_amount: [660, 3_000, 0, 0] + +- name: 2026 WA the claimable spouse gets the Group 2 amount and the other the Group 1 amount (the claimable spouse is the head) + period: 2026 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: WA + output: + # 12 * 250 and 12 * 55. + wa_cascade_care_savings_member_amount: [3000, 660] + +- name: 2026 WA the claimable spouse gets the Group 2 amount and the other the Group 1 amount (the claimable spouse is the spouse) + period: 2026 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: WA + output: + wa_cascade_care_savings_member_amount: [660, 3000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_eligible.yaml index 15b9fdf1d30..c18970e2535 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_eligible.yaml @@ -173,3 +173,79 @@ state_code: WA output: wa_cascade_care_savings_member_eligible: [true, true, false, false] + +- name: 2026 WA a spouse who can be claimed elsewhere is a Group 2 member (the claimable spouse is the head) + period: 2026 + input: + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_a, person_b] + state_code: WA + output: + wa_cascade_care_savings_member_eligible: [true, true] + +- name: 2026 WA a spouse who can be claimed elsewhere is a Group 2 member (the claimable spouse is the spouse) + period: 2026 + input: + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_basic_health_program_eligible: false + slcsp_age_curve_amount_person: 6000 + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 23_475 + tax_unit_is_filer: true + households: + household: + members: [person_b, person_a] + state_code: WA + output: + wa_cascade_care_savings_member_eligible: [true, true] diff --git a/policyengine_us/tests/policy/contrib/aca/ptc_700_fpl_cliff.yaml b/policyengine_us/tests/policy/contrib/aca/ptc_700_fpl_cliff.yaml index ba79805f0ff..11dd41d0214 100644 --- a/policyengine_us/tests/policy/contrib/aca/ptc_700_fpl_cliff.yaml +++ b/policyengine_us/tests/policy/contrib/aca/ptc_700_fpl_cliff.yaml @@ -298,3 +298,76 @@ output: aca_ptc_below_fpl_immigration_exception: true is_aca_ptc_eligible: [true] + +- name: 2026 reform keeps a claimable spouse out of the tax family (the claimable spouse is the head) + period: 2026 + reforms: policyengine_us.reforms.aca.aca_ptc_700_fpl_cliff.aca_ptc_700_fpl_cliff + input: + gov.contrib.aca.ptc_700_fpl_cliff.in_effect: true + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 46_950 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 46,950 / 15,650 = 300% FPL for a family of one. + aca_tax_family_size: 1 + is_aca_ptc_eligible: [false, true] + +- name: 2026 reform keeps a claimable spouse out of the tax family (the claimable spouse is the spouse) + period: 2026 + reforms: policyengine_us.reforms.aca.aca_ptc_700_fpl_cliff.aca_ptc_700_fpl_cliff + input: + gov.contrib.aca.ptc_700_fpl_cliff.in_effect: true + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 46_950 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_tax_family_size: 1 + is_aca_ptc_eligible: [true, false] diff --git a/policyengine_us/tests/policy/contrib/aca/ptc_additional_bracket.yaml b/policyengine_us/tests/policy/contrib/aca/ptc_additional_bracket.yaml index 3f5846288f5..8c568ee903e 100644 --- a/policyengine_us/tests/policy/contrib/aca/ptc_additional_bracket.yaml +++ b/policyengine_us/tests/policy/contrib/aca/ptc_additional_bracket.yaml @@ -115,3 +115,76 @@ aca_magi_fraction: 1.75 output: aca_required_contribution_percentage: 0.01 + +- name: 2026 reform keeps a claimable spouse out of the tax family (the claimable spouse is the head) + period: 2026 + reforms: policyengine_us.reforms.aca.aca_ptc_additional_bracket.aca_ptc_additional_bracket + input: + gov.contrib.aca.ptc_additional_bracket.in_effect: true + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 46_950 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 46,950 / 15,650 = 300% FPL for a family of one. + aca_tax_family_size: 1 + is_aca_ptc_eligible: [false, true] + +- name: 2026 reform keeps a claimable spouse out of the tax family (the claimable spouse is the spouse) + period: 2026 + reforms: policyengine_us.reforms.aca.aca_ptc_additional_bracket.aca_ptc_additional_bracket + input: + gov.contrib.aca.ptc_additional_bracket.in_effect: true + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 46_950 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_tax_family_size: 1 + is_aca_ptc_eligible: [true, false] diff --git a/policyengine_us/tests/policy/contrib/aca/ptc_simplified_bracket.yaml b/policyengine_us/tests/policy/contrib/aca/ptc_simplified_bracket.yaml index 93bb7d35bdd..cd3d6932461 100644 --- a/policyengine_us/tests/policy/contrib/aca/ptc_simplified_bracket.yaml +++ b/policyengine_us/tests/policy/contrib/aca/ptc_simplified_bracket.yaml @@ -115,3 +115,76 @@ aca_magi_fraction: 6.0 output: aca_required_contribution_percentage: 0.20 + +- name: 2026 reform keeps a claimable spouse out of the tax family (the claimable spouse is the head) + period: 2026 + reforms: policyengine_us.reforms.aca.aca_ptc_simplified_bracket.aca_ptc_simplified_bracket + input: + gov.contrib.aca.ptc_simplified_bracket.in_effect: true + people: + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + aca_magi: 46_950 + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # 46,950 / 15,650 = 300% FPL for a family of one. + aca_tax_family_size: 1 + is_aca_ptc_eligible: [false, true] + +- name: 2026 reform keeps a claimable spouse out of the tax family (the claimable spouse is the spouse) + period: 2026 + reforms: policyengine_us.reforms.aca.aca_ptc_simplified_bracket.aca_ptc_simplified_bracket + input: + gov.contrib.aca.ptc_simplified_bracket.in_effect: true + people: + person_b: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + is_medicaid_eligible: false + is_chip_eligible: false + is_aca_eshi_eligible: false + is_medicare_eligible: false + claimable_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + aca_magi: 46_950 + households: + household: + members: [person_b, person_a] + state_code: TX + output: + aca_tax_family_size: 1 + is_aca_ptc_eligible: [true, false] diff --git a/policyengine_us/variables/gov/aca/csr/marketplace_csr_eligible.py b/policyengine_us/variables/gov/aca/csr/marketplace_csr_eligible.py index 837491802ee..5d6718c052a 100644 --- a/policyengine_us/variables/gov/aca/csr/marketplace_csr_eligible.py +++ b/policyengine_us/variables/gov/aca/csr/marketplace_csr_eligible.py @@ -6,15 +6,28 @@ class marketplace_csr_eligible(Variable): entity = TaxUnit label = "Tax unit is eligible for ordinary Marketplace cost-sharing reductions" definition_period = YEAR - reference = "https://www.law.cornell.edu/cfr/text/45/155.305#g_1" + reference = "https://www.law.cornell.edu/cfr/text/45/155.305#g" + documentation = ( + "The model prices one Marketplace policy per tax unit. Under 45 CFR " + "155.305(g)(3), a policy covering people who would be eligible for " + "different cost-sharing reductions gets only the category all of " + "them qualify for, so a policy that also covers an enrollee without " + "premium tax credit eligibility (one outside the tax family, such as " + "a spouse who can be claimed on another return) gets none." + ) def formula(tax_unit, period, parameters): person = tax_unit.members receives_aca = tax_unit.sum(person("person_receives_aca", period)) > 0 + enrollee = person("pays_aca_premium", period) + ptc_eligible = person("is_aca_ptc_eligible", period) + every_enrollee_eligible = ~tax_unit.any(enrollee & ~ptc_eligible) category = tax_unit("selected_marketplace_plan_category", period) silver_selected = category == category.possible_values.SILVER magi_fraction = tax_unit("aca_magi_fraction", period) income_eligible = ( magi_fraction <= parameters(period).gov.aca.csr.income_threshold.maximum ) - return receives_aca & silver_selected & income_eligible + return ( + receives_aca & every_enrollee_eligible & silver_selected & income_eligible + ) diff --git a/policyengine_us/variables/gov/aca/eligibility/aca_fpg.py b/policyengine_us/variables/gov/aca/eligibility/aca_fpg.py new file mode 100644 index 00000000000..0ba7f9a9adc --- /dev/null +++ b/policyengine_us/variables/gov/aca/eligibility/aca_fpg.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class aca_fpg(Variable): + value_type = float + entity = TaxUnit + label = "Federal poverty line for the premium tax credit tax family" + unit = USD + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/36B#d_3", + # Line 1 (tax family size) and line 4 (poverty line). + "https://www.irs.gov/pub/irs-prior/i8962--2025.pdf#page=8", + ) + documentation = ( + "Form 8962 line 4: the prior year's poverty guideline for the tax " + "year's tax family size on line 1 (aca_tax_family_size). The former " + "lookup, tax_unit_fpg for the prior year, also took the household " + "size from the prior year; the size now comes from the tax year, as " + "line 1 requires, which differs only when a prior-year size is " + "supplied. A return on which every head and spouse can be claimed " + "elsewhere has no tax family and no applicable taxpayer; as a " + "numerical convention, not a legal rule, it keeps the guideline for " + "the whole tax unit, so readers that are not about the credit see " + "the same value as before." + ) + + def formula(tax_unit, period, parameters): + family_size = tax_unit("aca_tax_family_size", period) + size = where(family_size > 0, family_size, tax_unit("tax_unit_size", period)) + # The guideline year lags the tax year; the state group is read for + # the prior year, as the guideline lookup did before. + state_group = tax_unit.household("state_group_str", period.last_year) + return fpg(size, state_group, period, parameters, year_lag=1) diff --git a/policyengine_us/variables/gov/aca/eligibility/aca_magi.py b/policyengine_us/variables/gov/aca/eligibility/aca_magi.py index 2b9764bf6af..118e4f16533 100644 --- a/policyengine_us/variables/gov/aca/eligibility/aca_magi.py +++ b/policyengine_us/variables/gov/aca/eligibility/aca_magi.py @@ -6,4 +6,43 @@ class aca_magi(Variable): entity = TaxUnit label = "ACA-related modified AGI for this tax unit" definition_period = YEAR - adds = ["medicaid_magi"] + unit = USD + reference = ( + "https://www.law.cornell.edu/uscode/text/26/36B#d_2", + "https://www.law.cornell.edu/cfr/text/26/1.36B-1#e_1", + ) + documentation = ( + "Household income for the premium tax credit (IRC 36B(d)(2), 26 CFR " + "1.36B-1(e)(1)): the taxpayer's modified AGI, both spouses' on a " + "joint return, plus that of other members of the tax family. It is " + "medicaid_magi's AGI plus MAGI additions, without the tax-exempt " + "interest and Social Security of the return's dependents who are " + "outside the tax family (is_aca_tax_family_member), floored at zero " + "once. Those amounts are left out only on a return whose head or " + "spouse can be claimed elsewhere (or, in a tax unit with neither, " + "whose oldest member can be); otherwise this equals medicaid_magi. " + "Tax unit AGI already leaves dependents' income out." + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + filer = person("is_tax_unit_head_or_spouse", period) + has_filer = person.tax_unit.any(filer) + age = person("age", period) + oldest = person.get_rank(tax_unit, -age) == 0 + files_return = where(has_filer, filer, oldest) + outside_family = ~files_return & ~person("is_aca_tax_family_member", period) + excluded = tax_unit.sum( + outside_family + * ( + person("tax_exempt_interest_income", period) + + person("social_security", period) + ) + ) + # medicaid_magi's AGI plus additions, before its zero floor, so a + # negative excluded amount is removed before the floor too. + additions = parameters(period).gov.hhs.medicaid.income.modification + unfloored = tax_unit("adjusted_gross_income", period) + add( + tax_unit, period, additions + ) + return max_(0, unfloored - excluded) diff --git a/policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py b/policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py index 9464de7bdd2..d3780926dfe 100644 --- a/policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py +++ b/policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py @@ -6,23 +6,18 @@ class aca_magi_fraction(Variable): entity = TaxUnit label = "ACA-related modified AGI as fraction of prior-year FPL" documentation = ( - "ACA-related MAGI as fraction of federal poverty line. The first " - "reference documents the use of the prior-year FPL; the second " - "documents the truncation of the fraction." + "Household income as a fraction of the prior-year federal poverty " + "line for the premium tax credit tax family (Form 8962 line 5), " + "truncated to whole percentage points as Worksheet 2 instructs." ) reference = ( - dict( - title="2022 IRS Form 8962 (ACA PTC) instructions, Line 4", - href="https://www.irs.gov/pub/irs-pdf/i8962.pdf#page=7", - ), - dict( - title="2022 IRS Form 8962 instructions, Line 5 Worksheet 2", - href="https://www.irs.gov/pub/irs-pdf/i8962.pdf#page=8", - ), + # PDF pages 8-9: line 4 (prior-year poverty line for the line 1 + # family size) and line 5 Worksheet 2 (truncation). + "https://www.irs.gov/pub/irs-prior/i8962--2025.pdf#page=8", ) definition_period = YEAR def formula(tax_unit, period, parameters): magi = max_(0, tax_unit("aca_magi", period)) - fpg = tax_unit("tax_unit_fpg", period.last_year) + fpg = tax_unit("aca_fpg", period) return np.floor(100 * magi / fpg) / 100 diff --git a/policyengine_us/variables/gov/aca/eligibility/aca_ptc_below_fpl_immigration_exception.py b/policyengine_us/variables/gov/aca/eligibility/aca_ptc_below_fpl_immigration_exception.py index d12ea8c17cf..207731b8c21 100644 --- a/policyengine_us/variables/gov/aca/eligibility/aca_ptc_below_fpl_immigration_exception.py +++ b/policyengine_us/variables/gov/aca/eligibility/aca_ptc_below_fpl_immigration_exception.py @@ -23,7 +23,13 @@ def formula(tax_unit, period, parameters): "is_medicaid_immigration_status_eligible", period ) + # 26 CFR 1.36B-2(b)(5): "the taxpayer or a member of the taxpayer's + # family" must be the lawfully present, Medicaid-ineligible person. + tax_family_member = person("is_aca_tax_family_member", period) qualifying_family_member = tax_unit.any( - non_citizen & aca_lawfully_present & medicaid_ineligible_due_to_status + tax_family_member + & non_citizen + & aca_lawfully_present + & medicaid_ineligible_due_to_status ) return in_effect & below_fpl & qualifying_family_member diff --git a/policyengine_us/variables/gov/aca/eligibility/aca_tax_family_size.py b/policyengine_us/variables/gov/aca/eligibility/aca_tax_family_size.py new file mode 100644 index 00000000000..96889d8b9ec --- /dev/null +++ b/policyengine_us/variables/gov/aca/eligibility/aca_tax_family_size.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class aca_tax_family_size(Variable): + value_type = int + entity = TaxUnit + label = "Premium tax credit tax family size" + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/36B#d_1", + "https://www.law.cornell.edu/cfr/text/26/1.36B-1#d", + "https://www.irs.gov/pub/irs-prior/i8962--2025.pdf#page=8", + ) + documentation = ( + "Form 8962 line 1: the number of people in the premium tax credit " + "tax family. It equals the tax unit size unless a head or spouse can " + "be claimed as a dependent on another return, and is 0 when every " + "head and spouse can be (no applicable taxpayer). The shared " + "tax_unit_size is unchanged." + ) + + adds = ["is_aca_tax_family_member"] diff --git a/policyengine_us/variables/gov/aca/eligibility/is_aca_coverage_family_member.py b/policyengine_us/variables/gov/aca/eligibility/is_aca_coverage_family_member.py new file mode 100644 index 00000000000..68907d3f4e4 --- /dev/null +++ b/policyengine_us/variables/gov/aca/eligibility/is_aca_coverage_family_member.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class is_aca_coverage_family_member(Variable): + value_type = bool + entity = Person + label = "Member of the premium tax credit coverage family" + definition_period = YEAR + reference = "https://www.law.cornell.edu/cfr/text/26/1.36B-3#b_2" + documentation = ( + "A Marketplace enrollee (is_aca_marketplace_enrollee) who is in the " + "premium tax credit tax family. 26 CFR 1.36B-3(b)(2) defines the " + "coverage family as the members of the taxpayer's family for whom the " + "month is a coverage month, and the benchmark plan in 1.36B-3(f) is " + "the one offered to the coverage family. An enrollee who can be " + "claimed on another return buys a plan but is not in it." + ) + + def formula(person, period, parameters): + return person("is_aca_marketplace_enrollee", period) & person( + "is_aca_tax_family_member", period + ) diff --git a/policyengine_us/variables/gov/aca/eligibility/is_aca_marketplace_enrollee.py b/policyengine_us/variables/gov/aca/eligibility/is_aca_marketplace_enrollee.py new file mode 100644 index 00000000000..022a6059d4d --- /dev/null +++ b/policyengine_us/variables/gov/aca/eligibility/is_aca_marketplace_enrollee.py @@ -0,0 +1,56 @@ +from policyengine_us.model_api import * + + +class is_aca_marketplace_enrollee(Variable): + value_type = bool + entity = Person + label = "Person is enrolled in Marketplace coverage" + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/cfr/text/45/155.305#a", + "https://www.law.cornell.edu/uscode/text/26/36B#c_1", + ) + documentation = ( + "Whether a person is enrolled in a Marketplace plan: the TIN, " + "immigration-status, other-minimum-essential-coverage and Medicaid " + "coverage-gap conditions of pays_aca_premium, without its premium " + "condition. A child beyond the three oldest is enrolled but pays no " + "age-rated premium (45 CFR 147.102(c)(1)); pays_aca_premium is this " + "variable restricted to those who pay one." + ) + + def formula(person, period, parameters): + immigration_eligible = person("is_aca_ptc_immigration_status_eligible", period) + taxpayer_has_tin = person.tax_unit("taxpayer_has_tin", period) + is_status_eligible = taxpayer_has_tin & immigration_eligible + + p = parameters(period).gov.aca + is_coverage_eligible = add(person, period, p.ineligible_coverage) == 0 + + # The Medicaid coverage gap: under the premium tax credit's income + # floor, income-ineligible under the eligibility scale itself, with no + # Medicaid pathway (already required by ineligible_coverage above: a + # non-expansion state, or a status bar), and without the below-poverty + # exception for lawfully present immigrants, which 26 U.S.C. + # 36B(c)(1)(B) carried until P.L. 119-21 repealed it from 2026. The + # scale's own result is read, not just its threshold, so a reform that + # makes the first bracket income-eligible reopens the credit here + # instead of being blocked by a threshold that did not move. The model + # assumes nobody in that gap buys the full-price benchmark plan; + # charging it made a Wyoming adult at $0 of income pay $11,769 a year + # (policyengine-us #9472). Only the floor is gated: above the + # eligibility ceiling, where the 2026 subsidy cliff returns, full + # price is what an enrollee pays. + income_eligibility = p.ptc_income_eligibility + magi_frac = person.tax_unit("aca_magi_fraction", period) + floor = income_eligibility.thresholds[1] + below_fpl_exception = person.tax_unit( + "aca_ptc_below_fpl_immigration_exception", period + ) + in_coverage_gap = ( + (magi_frac < floor) + & ~income_eligibility.calc(magi_frac) + & ~below_fpl_exception + ) + + return is_status_eligible & is_coverage_eligible & ~in_coverage_gap diff --git a/policyengine_us/variables/gov/aca/eligibility/is_aca_ptc_eligible.py b/policyengine_us/variables/gov/aca/eligibility/is_aca_ptc_eligible.py index b21baa83d3c..88cdd68742d 100644 --- a/policyengine_us/variables/gov/aca/eligibility/is_aca_ptc_eligible.py +++ b/policyengine_us/variables/gov/aca/eligibility/is_aca_ptc_eligible.py @@ -6,6 +6,10 @@ class is_aca_ptc_eligible(Variable): entity = Person label = "Person is eligible for ACA premium tax credit and pays ACA premium" definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/36B#c_1", + "https://www.law.cornell.edu/cfr/text/26/1.36B-2#b", + ) def formula(person, period, parameters): fstatus = person.tax_unit("filing_status", period) @@ -20,4 +24,15 @@ def formula(person, period, parameters): ) is_income_eligible = standard_income_eligible | below_fpl_exception - return person("pays_aca_premium", period) & ~separate & is_income_eligible + # No credit is allowed to someone another taxpayer can claim (26 + # U.S.C. 36B(c)(1)(D)); they are not an applicable taxpayer (26 CFR + # 1.36B-2(b)(3)) and are outside the tax family, along with the + # dependents of a return that has such a filer (IRC 152(b)(1)). + tax_family_member = person("is_aca_tax_family_member", period) + + return ( + person("pays_aca_premium", period) + & tax_family_member + & ~separate + & is_income_eligible + ) diff --git a/policyengine_us/variables/gov/aca/eligibility/is_aca_tax_family_member.py b/policyengine_us/variables/gov/aca/eligibility/is_aca_tax_family_member.py new file mode 100644 index 00000000000..48294317543 --- /dev/null +++ b/policyengine_us/variables/gov/aca/eligibility/is_aca_tax_family_member.py @@ -0,0 +1,59 @@ +from policyengine_us.model_api import * + + +class is_aca_tax_family_member(Variable): + value_type = bool + entity = Person + label = "Member of the premium tax credit tax family" + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/36B#c_1_D", + "https://www.law.cornell.edu/uscode/text/26/152#b_1", + "https://www.law.cornell.edu/cfr/text/26/1.36B-1#d_2", + "https://www.law.cornell.edu/cfr/text/26/1.36B-2#b_3", + "https://www.irs.gov/pub/irs-prior/i8962--2025.pdf#page=8", + "https://www.irs.gov/pub/irs-prior/p501--2025.pdf#page=11", + ) + documentation = ( + "Whether the person is in an applicable taxpayer's family for the " + "premium tax credit (Form 8962 line 1, tax family size). 26 CFR " + "1.36B-1(d)(2) defines the family as the taxpayer, both spouses on a " + "joint return, 'except for individuals who qualify as a dependent of " + "another taxpayer', plus the dependents the taxpayer claims. A head or " + "spouse who can be claimed on another return " + "(claimable_as_dependent_on_another_return, whether or not claimed) " + "is not a member; on a joint return the other spouse still is. Such a " + "return claims no dependents (IRC 152(b)(1)) unless the would-be " + "claimant meets the refund-only filing exception (Publication 501, " + "Dependent Taxpayer Test). No credit is allowed to someone another " + "taxpayer can claim (IRC 36B(c)(1)(D)), so a return whose every head " + "and spouse can be claimed has no applicable taxpayer and no family " + "for the credit. A tax unit with neither a head nor a spouse (role " + "inference makes the oldest adult the head, so a unit of minors has " + "none) is treated as filed by its oldest member: everyone is in the " + "family if that member cannot be claimed elsewhere, and nobody is if " + "they can. A " + "dependent on a return with an applicable taxpayer is a member even if " + "another taxpayer could also claim them: this return claims them." + ) + + def formula(person, period, parameters): + filer = person("is_tax_unit_head_or_spouse", period) + claimable = person("claimable_as_dependent_on_another_return", period) + has_filer = person.tax_unit.any(filer) + applicable_taxpayer = ( + person.tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 + ) + claims_dependents = ~person.tax_unit( + "head_or_spouse_is_dependent_elsewhere_without_filing_exception", period + ) + dependent_member = applicable_taxpayer & claims_dependents + # No head or spouse: the oldest member files. + age = person("age", period) + oldest = person.get_rank(person.tax_unit, -age) == 0 + oldest_claimable = person.tax_unit.any(oldest & claimable) + return where( + has_filer, + where(filer, ~claimable, dependent_member), + ~oldest_claimable, + ) diff --git a/policyengine_us/variables/gov/aca/eligibility/pays_aca_premium.py b/policyengine_us/variables/gov/aca/eligibility/pays_aca_premium.py index 75c148f4766..044d4f01591 100644 --- a/policyengine_us/variables/gov/aca/eligibility/pays_aca_premium.py +++ b/policyengine_us/variables/gov/aca/eligibility/pays_aca_premium.py @@ -24,49 +24,16 @@ class pays_aca_premium(Variable): "does, because the unsubsidized benchmark premium exceeds their " "income (policyengine-us #9472). A reader that wants the status and " "coverage conditions without that assumption should not reuse this " - "variable." + "variable. It is is_aca_marketplace_enrollee restricted to people " + "charged an age-rated premium: adults, and the three oldest children " + "in the tax unit." ) def formula(person, period, parameters): - immigration_eligible = person("is_aca_ptc_immigration_status_eligible", period) - taxpayer_has_tin = person.tax_unit("taxpayer_has_tin", period) - is_status_eligible = taxpayer_has_tin & immigration_eligible - + # An enrollee pays an age-rated premium unless they are a child + # beyond the three oldest (45 CFR 147.102(c)(1)). + enrollee = person("is_aca_marketplace_enrollee", period) p = parameters(period).gov.aca - is_coverage_eligible = add(person, period, p.ineligible_coverage) == 0 is_aca_adult = person("age", period) > p.slcsp.max_child_age child_pays = person("aca_child_index", period) <= p.max_child_count - pays_age_based_premium = is_aca_adult | child_pays - - # The Medicaid coverage gap: under the premium tax credit's income - # floor, income-ineligible under the eligibility scale itself, with no - # Medicaid pathway (already required by ineligible_coverage above: a - # non-expansion state, or a status bar), and without the below-poverty - # exception for lawfully present immigrants, which 26 U.S.C. - # 36B(c)(1)(B) carried until P.L. 119-21 repealed it from 2026. The - # scale's own result is read, not just its threshold, so a reform that - # makes the first bracket income-eligible reopens the credit here - # instead of being blocked by a threshold that did not move. The model - # assumes nobody in that gap buys the full-price benchmark plan; - # charging it made a Wyoming adult at $0 of income pay $11,769 a year - # (policyengine-us #9472). Only the floor is gated: above the - # eligibility ceiling, where the 2026 subsidy cliff returns, full - # price is what an enrollee pays. - income_eligibility = p.ptc_income_eligibility - magi_frac = person.tax_unit("aca_magi_fraction", period) - floor = income_eligibility.thresholds[1] - below_fpl_exception = person.tax_unit( - "aca_ptc_below_fpl_immigration_exception", period - ) - in_coverage_gap = ( - (magi_frac < floor) - & ~income_eligibility.calc(magi_frac) - & ~below_fpl_exception - ) - - return ( - is_status_eligible - & is_coverage_eligible - & pays_age_based_premium - & ~in_coverage_gap - ) + return enrollee & (is_aca_adult | child_pays) diff --git a/policyengine_us/variables/gov/aca/ptc/aca_ptc.py b/policyengine_us/variables/gov/aca/ptc/aca_ptc.py index 76c9fbe2464..79a7ff5a45a 100644 --- a/policyengine_us/variables/gov/aca/ptc/aca_ptc.py +++ b/policyengine_us/variables/gov/aca/ptc/aca_ptc.py @@ -19,7 +19,9 @@ def formula(tax_unit, period, parameters): return 0 def formula_2018(tax_unit, period, parameters): - plan_cost = tax_unit("slcsp", period) + # The benchmark for the coverage family (26 CFR 1.36B-3(f)), which + # leaves out an enrollee who can be claimed on another return. + plan_cost = tax_unit("aca_ptc_slcsp", period) income = tax_unit("aca_magi", period) applicable_figure = tax_unit("aca_required_contribution_percentage", period) # IRC ยง 36B(a) conditions the PTC on filing a return (Form 8962). diff --git a/policyengine_us/variables/gov/aca/ptc/aca_ptc_coverage_family_premium.py b/policyengine_us/variables/gov/aca/ptc/aca_ptc_coverage_family_premium.py new file mode 100644 index 00000000000..d9c00f4f054 --- /dev/null +++ b/policyengine_us/variables/gov/aca/ptc/aca_ptc_coverage_family_premium.py @@ -0,0 +1,44 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.aca.slspc.aca_ptc_slcsp import ( + benchmark_premium, +) + + +class aca_ptc_coverage_family_premium(Variable): + value_type = float + entity = TaxUnit + label = "Selected Marketplace plan premium allocated to the coverage family" + unit = USD + definition_period = YEAR + reference = "https://www.law.cornell.edu/cfr/text/26/1.36B-3#d_1" + documentation = ( + "The enrollment premiums for the premium tax credit's lesser-of test " + "(26 CFR 1.36B-3(d)(1)(i)). The model prices one policy per tax unit. " + "When it also covers enrollees outside the tax family, 26 CFR " + "1.36B-3(h)(1) allocates the premium 'in proportion to the premiums " + "for each taxpayer's applicable benchmark plan': the coverage family's " + "benchmark (aca_ptc_slcsp) over that plus the benchmark of the other " + "enrollees, priced as one family of their own. The whole premium is " + "the coverage family's when every enrollee is in it, and none is when " + "no enrollee is. It applies to bronze plans too." + ) + + def formula(tax_unit, period, parameters): + premium = tax_unit("selected_marketplace_plan_premium_proxy", period) + person = tax_unit.members + enrolled = person("is_aca_marketplace_enrollee", period) + member = person("is_aca_tax_family_member", period) + every_enrollee_covered = ~tax_unit.any(enrolled & ~member) + coverage_family_benchmark = add(tax_unit, period, ["aca_ptc_slcsp"]) + other_benchmark = sum( + benchmark_premium(tax_unit, month, parameters, ~member, enrolled) + for month in period.get_subperiods(MONTH) + ) + total = coverage_family_benchmark + other_benchmark + share = np.divide( + coverage_family_benchmark, + total, + out=np.zeros_like(total), + where=total > 0, + ) + return premium * where(every_enrollee_covered, 1, share) diff --git a/policyengine_us/variables/gov/aca/ptc/used_aca_ptc.py b/policyengine_us/variables/gov/aca/ptc/used_aca_ptc.py index 6de35a53b29..da994b651b4 100644 --- a/policyengine_us/variables/gov/aca/ptc/used_aca_ptc.py +++ b/policyengine_us/variables/gov/aca/ptc/used_aca_ptc.py @@ -7,9 +7,13 @@ class used_aca_ptc(Variable): label = "Used ACA premium tax credit" unit = USD definition_period = YEAR + reference = "https://www.law.cornell.edu/cfr/text/26/1.36B-3#d_1" def formula(tax_unit, period, parameters): + # The credit cannot exceed the enrollment premiums of the coverage + # family (26 CFR 1.36B-3(d)(1)(i)); an enrollee outside the tax family + # pays their own share of the policy. return min_( tax_unit("aca_ptc", period), - tax_unit("selected_marketplace_plan_premium_proxy", period), + tax_unit("aca_ptc_coverage_family_premium", period), ) diff --git a/policyengine_us/variables/gov/aca/slspc/aca_ptc_slcsp.py b/policyengine_us/variables/gov/aca/slspc/aca_ptc_slcsp.py new file mode 100644 index 00000000000..a49a4df22a1 --- /dev/null +++ b/policyengine_us/variables/gov/aca/slspc/aca_ptc_slcsp.py @@ -0,0 +1,77 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.aca.slspc.slcsp_family_tier_category import ( + family_tier_category, +) +from policyengine_us.variables.gov.aca.slspc.slcsp_family_tier_multiplier import ( + family_tier_multiplier, +) + + +def benchmark_premium(tax_unit, period, parameters, group, enrolled): + """Monthly second-lowest-cost silver premium for the enrollees of one + family on the return. + + `group` marks the family's members and `enrolled` the Marketplace + enrollees (person-level boolean arrays). Children are ranked within the + family, so only its three oldest children add an age-rated premium (45 CFR + 147.102(c)(1)); in family-tier states the tier is the family's own. + """ + person = tax_unit.members + p = parameters(period).gov.aca + age = person("age", period.this_year) + is_child = age <= p.slcsp.max_child_age + child_rank = person.get_rank(tax_unit, -age, condition=group & is_child) + charged = group & enrolled & (~is_child | (child_rank < p.max_child_count)) + base = person.household("slcsp_age_0", period) + multiplier = person("slcsp_age_curve_multiplier", period) + age_curve_applies = tax_unit("slcsp_age_curve_applies", period) + age_curve = tax_unit.sum(charged * base * multiplier) * age_curve_applies + tier_enrolled = group & enrolled + category = family_tier_category(tax_unit, period, parameters, tier_enrolled) + tier_multiplier = family_tier_multiplier( + tax_unit, period, parameters, category, tier_enrolled + ) + family_tier = where( + tax_unit("slcsp_family_tier_applies", period), + tax_unit.household("slcsp_age_0", period) * tier_multiplier, + 0, + ) + return age_curve + family_tier + + +class aca_ptc_slcsp(Variable): + value_type = float + entity = TaxUnit + label = "Benchmark silver-plan premium for the premium tax credit" + unit = USD + definition_period = MONTH + reference = ( + "https://www.law.cornell.edu/uscode/text/26/36B#b_3_B", + "https://www.law.cornell.edu/cfr/text/26/1.36B-3#f", + ) + documentation = ( + "The second-lowest-cost silver plan premium offered to the coverage " + "family (26 CFR 1.36B-3(f)): the Marketplace enrollees who are in the " + "premium tax credit tax family. When every enrollee on the return is " + "in it, this is slcsp itself, read directly so a supplied slcsp " + "carries through. When an enrollee is not (one who can be claimed on " + "another return, or a dependent of a return with such a filer), the " + "coverage family is priced on its own: age-curve premiums with its " + "children ranked among themselves, so its three oldest children are " + "charged, or, where family tiers apply, its own tier. slcsp keeps " + "every enrollee, since everyone still buys a plan." + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + enrolled = person("is_aca_marketplace_enrollee", period.this_year) + member = person("is_aca_tax_family_member", period.this_year) + every_enrollee_covered = ~tax_unit.any(enrolled & ~member) + coverage_family_benchmark = benchmark_premium( + tax_unit, period, parameters, member, enrolled + ) + return where( + every_enrollee_covered, + tax_unit("slcsp", period), + coverage_family_benchmark, + ) diff --git a/policyengine_us/variables/gov/aca/slspc/aca_ptc_slcsp_family_tier_category.py b/policyengine_us/variables/gov/aca/slspc/aca_ptc_slcsp_family_tier_category.py new file mode 100644 index 00000000000..cd6475506cb --- /dev/null +++ b/policyengine_us/variables/gov/aca/slspc/aca_ptc_slcsp_family_tier_category.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.aca.slspc.slcsp_family_tier_category import ( + FamilyTierCategory, + family_tier_category, +) + + +class aca_ptc_slcsp_family_tier_category(Variable): + value_type = Enum + entity = TaxUnit + possible_values = FamilyTierCategory + default_value = FamilyTierCategory.INDIVIDUAL_AGE_RATED + definition_period = MONTH + label = "ACA family tier category for the premium tax credit coverage family" + defined_for = "slcsp_family_tier_applies" + reference = "https://www.law.cornell.edu/cfr/text/26/1.36B-3#f" + documentation = ( + "The family tier (New York and Vermont) of the coverage family's " + "benchmark plan: slcsp_family_tier_category counted over coverage " + "family members only, so an enrollee who can be claimed on another " + "return does not move the tier." + ) + + def formula(tax_unit, period, parameters): + covered = tax_unit.members("is_aca_coverage_family_member", period.this_year) + return family_tier_category(tax_unit, period, parameters, covered) diff --git a/policyengine_us/variables/gov/aca/slspc/aca_ptc_slcsp_family_tier_multiplier.py b/policyengine_us/variables/gov/aca/slspc/aca_ptc_slcsp_family_tier_multiplier.py new file mode 100644 index 00000000000..4edacc5d1a6 --- /dev/null +++ b/policyengine_us/variables/gov/aca/slspc/aca_ptc_slcsp_family_tier_multiplier.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.aca.slspc.slcsp_family_tier_multiplier import ( + family_tier_multiplier, +) + + +class aca_ptc_slcsp_family_tier_multiplier(Variable): + value_type = float + entity = TaxUnit + label = "ACA family tier multiplier for the premium tax credit coverage family" + unit = "/1" + definition_period = MONTH + defined_for = "slcsp_family_tier_applies" + reference = "https://www.law.cornell.edu/cfr/text/26/1.36B-3#f" + + def formula(tax_unit, period, parameters): + family_category = tax_unit("aca_ptc_slcsp_family_tier_category", period) + covered = tax_unit.members("is_aca_coverage_family_member", period.this_year) + return family_tier_multiplier( + tax_unit, period, parameters, family_category, covered + ) diff --git a/policyengine_us/variables/gov/aca/slspc/is_aca_family_tier_dependent_child.py b/policyengine_us/variables/gov/aca/slspc/is_aca_family_tier_dependent_child.py index 89231d0249b..82b44cf6cee 100644 --- a/policyengine_us/variables/gov/aca/slspc/is_aca_family_tier_dependent_child.py +++ b/policyengine_us/variables/gov/aca/slspc/is_aca_family_tier_dependent_child.py @@ -1,4 +1,21 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.aca.slspc.is_aca_ny_age_29_dependent_child import ( + ny_age_29_child_condition, +) + + +def family_tier_child_condition(person, period, parameters): + """The family-tier dependent-child test without the premium condition.""" + p = parameters(period).gov.aca + age = person("monthly_age", period) + is_tax_dependent = person("is_tax_unit_dependent", period) + under_child_only_age = age <= p.slcsp.max_child_age + under_age_threshold = age < p.family_tier_dependent_child_age_threshold + return ( + under_child_only_age + | (is_tax_dependent & under_age_threshold) + | ny_age_29_child_condition(person, period, parameters) + ) class is_aca_family_tier_dependent_child(Variable): @@ -8,14 +25,6 @@ class is_aca_family_tier_dependent_child(Variable): definition_period = MONTH def formula(person, period, parameters): - p = parameters(period).gov.aca - age = person("monthly_age", period) - is_tax_dependent = person("is_tax_unit_dependent", period) - under_child_only_age = age <= p.slcsp.max_child_age - under_age_threshold = age < p.family_tier_dependent_child_age_threshold - ny_age_29_child = person("is_aca_ny_age_29_dependent_child", period) - return person("pays_aca_premium", period) & ( - under_child_only_age - | (is_tax_dependent & under_age_threshold) - | ny_age_29_child + return person("pays_aca_premium", period) & family_tier_child_condition( + person, period, parameters ) diff --git a/policyengine_us/variables/gov/aca/slspc/is_aca_ny_age_29_dependent_child.py b/policyengine_us/variables/gov/aca/slspc/is_aca_ny_age_29_dependent_child.py index cbd16a30ef5..2f925c9b6b0 100644 --- a/policyengine_us/variables/gov/aca/slspc/is_aca_ny_age_29_dependent_child.py +++ b/policyengine_us/variables/gov/aca/slspc/is_aca_ny_age_29_dependent_child.py @@ -1,6 +1,19 @@ from policyengine_us.model_api import * +def ny_age_29_child_condition(person, period, parameters): + """New York's age-29 dependent-child test without the premium condition.""" + p = parameters(period).gov.aca + state_code = person.household("state_code", period) + in_ny = state_code == state_code.possible_values.NY + age = person("monthly_age", period) + lower_age = p.family_tier_dependent_child_age_threshold + upper_age = p.ny_age_29_dependent_child_age_threshold + in_age_range = (age >= lower_age) & (age < upper_age) + is_tax_dependent = person("is_tax_unit_dependent", period) + return in_ny & in_age_range & is_tax_dependent + + class is_aca_ny_age_29_dependent_child(Variable): value_type = bool entity = Person @@ -8,14 +21,6 @@ class is_aca_ny_age_29_dependent_child(Variable): definition_period = MONTH def formula(person, period, parameters): - p = parameters(period).gov.aca - state_code = person.household("state_code", period) - in_ny = state_code == state_code.possible_values.NY - age = person("monthly_age", period) - lower_age = p.family_tier_dependent_child_age_threshold - upper_age = p.ny_age_29_dependent_child_age_threshold - in_age_range = (age >= lower_age) & (age < upper_age) - is_tax_dependent = person("is_tax_unit_dependent", period) - return ( - person("pays_aca_premium", period) & in_ny & in_age_range & is_tax_dependent + return person("pays_aca_premium", period) & ny_age_29_child_condition( + person, period, parameters ) diff --git a/policyengine_us/variables/gov/aca/slspc/slcsp_family_tier_category.py b/policyengine_us/variables/gov/aca/slspc/slcsp_family_tier_category.py index 147aa042035..a61cc5b803e 100644 --- a/policyengine_us/variables/gov/aca/slspc/slcsp_family_tier_category.py +++ b/policyengine_us/variables/gov/aca/slspc/slcsp_family_tier_category.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.aca.slspc.is_aca_family_tier_dependent_child import ( + family_tier_child_condition, +) class FamilyTierCategory(Enum): @@ -10,6 +13,80 @@ class FamilyTierCategory(Enum): CHILD_ONLY = "Child_only" +def family_tier_category(tax_unit, period, parameters, enrolled): + """ + Determine the family tier category for premium calculation in states + that use family tiers instead of individual age rating, counting only + the members flagged in `enrolled` (a person-level boolean array). + + Categories for both NY and VT: + - One_adult + - Two_adults + - One_adult_and_one_or_more_children + - Two_adults_and_one_or_more_children + - Child_only (only for NY) + """ + # Get inputs + state_code = tax_unit.household("state_code_str", period) + + person = tax_unit.members + dependent_child = enrolled & family_tier_child_condition(person, period, parameters) + non_dependent_adult_count = tax_unit.sum(enrolled & ~dependent_child) + anchored_child_count = tax_unit.sum(dependent_child) + + under_child_only_age = ( + person("monthly_age", period) <= parameters(period).gov.aca.slcsp.max_child_age + ) + child_only_child = enrolled & under_child_only_age + unanchored_adult_count = tax_unit.sum(enrolled & ~child_only_child) + unanchored_child_count = tax_unit.sum(child_only_child) + has_adult_anchor = non_dependent_adult_count > 0 + eligible_adult_count = where( + has_adult_anchor, + non_dependent_adult_count, + unanchored_adult_count, + ) + eligible_child_count = where( + has_adult_anchor, + anchored_child_count, + unanchored_child_count, + ) + + # Common conditions for both states + one_adult_no_children = (eligible_adult_count == 1) & (eligible_child_count == 0) + two_plus_adults_no_children = (eligible_adult_count >= 2) & ( + eligible_child_count == 0 + ) + one_adult_with_children = (eligible_adult_count == 1) & (eligible_child_count > 0) + two_plus_adults_with_children = (eligible_adult_count >= 2) & ( + eligible_child_count > 0 + ) + + # NY-specific condition (child-only households) + ny_child_only = ( + (state_code == "NY") & (eligible_adult_count == 0) & (eligible_child_count > 0) + ) + + return select( + [ + # Apply conditions only to family tier states + ny_child_only, + one_adult_no_children, + two_plus_adults_no_children, + one_adult_with_children, + two_plus_adults_with_children, + ], + [ + FamilyTierCategory.CHILD_ONLY, + FamilyTierCategory.ONE_ADULT, + FamilyTierCategory.TWO_ADULTS, + FamilyTierCategory.ONE_ADULT_AND_ONE_OR_MORE_CHILDREN, + FamilyTierCategory.TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN, + ], + default=FamilyTierCategory.INDIVIDUAL_AGE_RATED, + ) + + class slcsp_family_tier_category(Variable): value_type = Enum entity = TaxUnit @@ -20,81 +97,5 @@ class slcsp_family_tier_category(Variable): defined_for = "slcsp_family_tier_applies" def formula(tax_unit, period, parameters): - """ - Determine the family tier category for premium calculation in states - that use family tiers instead of individual age rating. - - Categories for both NY and VT: - - One_adult - - Two_adults - - One_adult_and_one_or_more_children - - Two_adults_and_one_or_more_children - - Child_only (only for NY) - """ - # Get inputs - state_code = tax_unit.household("state_code_str", period) - - person = tax_unit.members - pays_premium = person("pays_aca_premium", period) - dependent_child = person("is_aca_family_tier_dependent_child", period) - non_dependent_adult_count = tax_unit.sum(pays_premium & ~dependent_child) - anchored_child_count = tax_unit.sum(dependent_child) - - under_child_only_age = ( - person("monthly_age", period) - <= parameters(period).gov.aca.slcsp.max_child_age - ) - child_only_child = pays_premium & under_child_only_age - unanchored_adult_count = tax_unit.sum(pays_premium & ~child_only_child) - unanchored_child_count = tax_unit.sum(child_only_child) - has_adult_anchor = non_dependent_adult_count > 0 - eligible_adult_count = where( - has_adult_anchor, - non_dependent_adult_count, - unanchored_adult_count, - ) - eligible_child_count = where( - has_adult_anchor, - anchored_child_count, - unanchored_child_count, - ) - - # Common conditions for both states - one_adult_no_children = (eligible_adult_count == 1) & ( - eligible_child_count == 0 - ) - two_plus_adults_no_children = (eligible_adult_count >= 2) & ( - eligible_child_count == 0 - ) - one_adult_with_children = (eligible_adult_count == 1) & ( - eligible_child_count > 0 - ) - two_plus_adults_with_children = (eligible_adult_count >= 2) & ( - eligible_child_count > 0 - ) - - # NY-specific condition (child-only households) - ny_child_only = ( - (state_code == "NY") - & (eligible_adult_count == 0) - & (eligible_child_count > 0) - ) - - return select( - [ - # Apply conditions only to family tier states - ny_child_only, - one_adult_no_children, - two_plus_adults_no_children, - one_adult_with_children, - two_plus_adults_with_children, - ], - [ - FamilyTierCategory.CHILD_ONLY, - FamilyTierCategory.ONE_ADULT, - FamilyTierCategory.TWO_ADULTS, - FamilyTierCategory.ONE_ADULT_AND_ONE_OR_MORE_CHILDREN, - FamilyTierCategory.TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN, - ], - default=FamilyTierCategory.INDIVIDUAL_AGE_RATED, - ) + enrolled = tax_unit.members("pays_aca_premium", period) + return family_tier_category(tax_unit, period, parameters, enrolled) diff --git a/policyengine_us/variables/gov/aca/slspc/slcsp_family_tier_multiplier.py b/policyengine_us/variables/gov/aca/slspc/slcsp_family_tier_multiplier.py index 64f85feb3d7..04227da99d9 100644 --- a/policyengine_us/variables/gov/aca/slspc/slcsp_family_tier_multiplier.py +++ b/policyengine_us/variables/gov/aca/slspc/slcsp_family_tier_multiplier.py @@ -1,9 +1,69 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.aca.slspc.is_aca_family_tier_dependent_child import ( + family_tier_child_condition, +) +from policyengine_us.variables.gov.aca.slspc.is_aca_ny_age_29_dependent_child import ( + ny_age_29_child_condition, +) from policyengine_us.variables.gov.aca.slspc.slcsp_family_tier_category import ( FamilyTierCategory, ) +def family_tier_multiplier(tax_unit, period, parameters, family_category, enrolled): + """ + Family tier premium multiplier for `family_category`, counting only the + members flagged in `enrolled` (a person-level boolean array). + """ + p = parameters(period).gov.aca.family_tier_ratings + state_code = tax_unit.household("state_code", period) + in_ny = state_code == state_code.possible_values.NY + + one_adult = where(in_ny, p.ny.ONE_ADULT, p.vt.ONE_ADULT) + two_adults = where(in_ny, p.ny.TWO_ADULTS, p.vt.TWO_ADULTS) + one_adult_with_children = where( + in_ny, + p.ny.ONE_ADULT_AND_ONE_OR_MORE_CHILDREN, + p.vt.ONE_ADULT_AND_ONE_OR_MORE_CHILDREN, + ) + two_adults_with_children = where( + in_ny, + p.ny.TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN, + p.vt.TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN, + ) + child_only = where(in_ny, p.ny.CHILD_ONLY, 0) + + base_multiplier = select( + [ + family_category == FamilyTierCategory.ONE_ADULT, + family_category == FamilyTierCategory.TWO_ADULTS, + family_category == FamilyTierCategory.ONE_ADULT_AND_ONE_OR_MORE_CHILDREN, + family_category == FamilyTierCategory.TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN, + family_category == FamilyTierCategory.CHILD_ONLY, + ], + [ + one_adult, + two_adults, + one_adult_with_children, + two_adults_with_children, + child_only, + ], + default=0, + ) + + person = tax_unit.members + dependent_child = enrolled & family_tier_child_condition(person, period, parameters) + adult_count = tax_unit.sum(enrolled & ~dependent_child) + extra_adults = max_(adult_count - 2, 0) + age_29_child = enrolled & ny_age_29_child_condition(person, period, parameters) + age_29_multiplier = where( + tax_unit.any(age_29_child), + parameters(period).gov.aca.ny_age_29_dependent_child_tier_multiplier, + 1, + ) + return base_multiplier * age_29_multiplier + extra_adults * one_adult + + class slcsp_family_tier_multiplier(Variable): value_type = float entity = TaxUnit @@ -14,53 +74,7 @@ class slcsp_family_tier_multiplier(Variable): def formula(tax_unit, period, parameters): family_category = tax_unit("slcsp_family_tier_category", period) - p = parameters(period).gov.aca.family_tier_ratings - state_code = tax_unit.household("state_code", period) - in_ny = state_code == state_code.possible_values.NY - - one_adult = where(in_ny, p.ny.ONE_ADULT, p.vt.ONE_ADULT) - two_adults = where(in_ny, p.ny.TWO_ADULTS, p.vt.TWO_ADULTS) - one_adult_with_children = where( - in_ny, - p.ny.ONE_ADULT_AND_ONE_OR_MORE_CHILDREN, - p.vt.ONE_ADULT_AND_ONE_OR_MORE_CHILDREN, - ) - two_adults_with_children = where( - in_ny, - p.ny.TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN, - p.vt.TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN, - ) - child_only = where(in_ny, p.ny.CHILD_ONLY, 0) - - base_multiplier = select( - [ - family_category == FamilyTierCategory.ONE_ADULT, - family_category == FamilyTierCategory.TWO_ADULTS, - family_category - == FamilyTierCategory.ONE_ADULT_AND_ONE_OR_MORE_CHILDREN, - family_category - == FamilyTierCategory.TWO_ADULTS_AND_ONE_OR_MORE_CHILDREN, - family_category == FamilyTierCategory.CHILD_ONLY, - ], - [ - one_adult, - two_adults, - one_adult_with_children, - two_adults_with_children, - child_only, - ], - default=0, - ) - - person = tax_unit.members - pays_premium = person("pays_aca_premium", period) - dependent_child = person("is_aca_family_tier_dependent_child", period) - adult_count = tax_unit.sum(pays_premium & ~dependent_child) - extra_adults = max_(adult_count - 2, 0) - age_29_child = person("is_aca_ny_age_29_dependent_child", period) - age_29_multiplier = where( - tax_unit.any(age_29_child), - parameters(period).gov.aca.ny_age_29_dependent_child_tier_multiplier, - 1, + enrolled = tax_unit.members("pays_aca_premium", period) + return family_tier_multiplier( + tax_unit, period, parameters, family_category, enrolled ) - return base_multiplier * age_29_multiplier + extra_adults * one_adult diff --git a/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.py b/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.py index 32ad4395a41..213e55ae882 100644 --- a/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.py +++ b/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.py @@ -31,9 +31,10 @@ def formula(tax_unit, period, parameters): # Clamp income to zero so a negative ACA MAGI cannot inflate the top-up # above the benchmark - APTC residual via the -pct*income term. income = max_(tax_unit("aca_magi", period), 0) - # slcsp is a MONTH-period variable; core sums the 12 months when - # called from this YEAR formula. - slcsp = add(tax_unit, period, ["slcsp"]) + # The benchmark for the coverage family (Program Design (d)(5) points + # to 26 CFR 1.36B-3(f)); a MONTH variable that core sums over the + # year here. + slcsp = add(tax_unit, period, ["aca_ptc_slcsp"]) aca_ptc = tax_unit("aca_ptc", period) ca_pct = tax_unit("ca_premium_subsidy_applicable_percentage", period) # Eligibility is enforced by defined_for = ca_premium_subsidy_eligible. diff --git a/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py b/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py index e9fd47b9eaf..9e873bcd408 100644 --- a/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py +++ b/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py @@ -42,7 +42,10 @@ def formula(tax_unit, period, parameters): # must be a filer. This gate replaces reliance on the federal 100% FPL # floor exception: the federal PTC gate admits a below-poverty # immigration exception that the applicable-return-filer definition - # excludes. Dependents claimed by another filer do not form separate - # claiming units in the model, so the dependent exclusion is structural. + # excludes. Program Design (d)(7)(B) allows no assistance to an + # individual claimed as a dependent of another return filer; the + # federal gate already leaves such a person out of the tax family + # (26 U.S.C. 36B(c)(1)(D)), and Program Design (c)(2) requires every + # federal APTC eligibility requirement. is_filer = tax_unit("tax_unit_is_filer", period) return in_effect & aptc_eligible & income_eligible & is_filer diff --git a/policyengine_us/variables/gov/states/co/doi/premium_assistance/co_premium_assistance.py b/policyengine_us/variables/gov/states/co/doi/premium_assistance/co_premium_assistance.py index 53bf7dc4fd2..3905017f842 100644 --- a/policyengine_us/variables/gov/states/co/doi/premium_assistance/co_premium_assistance.py +++ b/policyengine_us/variables/gov/states/co/doi/premium_assistance/co_premium_assistance.py @@ -60,9 +60,12 @@ def formula(tax_unit, period, parameters): p.amount.first_member + p.amount.additional_member * (n - 1), 0, ) - # slcsp is a MONTH-period variable; core sums the 12 months when called - # from this YEAR formula. Approximates the enrolled-plan premium. - slcsp_annual = add(tax_unit, period, ["slcsp"]) + # The benchmark for the federal coverage family, a MONTH variable that + # core sums over the year here; it approximates the eligible members' + # enrolled-plan premiums. C.R.S. 10-16-1205(1)(b)(II) pays carriers + # for Coloradans who receive the premium tax credit, so an enrollee + # outside the federal tax family is not counted. + slcsp_annual = add(tax_unit, period, ["aca_ptc_slcsp"]) aca_ptc = tax_unit("aca_ptc", period) residual = max_(0, slcsp_annual - aca_ptc) annual = min_(MONTHS_IN_YEAR * flat_monthly, residual) 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 index 1ac3523df95..6f9ea164e88 100644 --- 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 @@ -17,9 +17,11 @@ class ct_covered_connecticut(Variable): "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 " + "poverty line. The premium tax credit's benchmark second-lowest-cost " + "silver plan premium for the coverage family (aca_ptc_slcsp) proxies " + "the benchmark plan and is annualized from its monthly definition; it " + "leaves out an enrollee who is not eligible for the federal subsidies " + "because they can be claimed on another return. 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 " @@ -30,9 +32,12 @@ class ct_covered_connecticut(Variable): ) 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"]) + # The benchmark for the federal coverage family, a MONTH variable that + # add() annualizes to match the YEAR-period aca_ptc. Public Act 21-2, + # Section 16(b)(1), covers only people eligible for premium and + # cost-sharing subsidies, so an enrollee outside the federal tax + # family (one who can be claimed on another return) is left out. + slcsp_annual = add(tax_unit, period, ["aca_ptc_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 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..45f1f0deb3d 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 @@ -48,9 +48,11 @@ def formula(tax_unit, period, parameters): magi_fraction = tax_unit("aca_magi_fraction", period) per_person_monthly = p.enrollee_premium.calc(magi_fraction) 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). - slcsp_annual = add(tax_unit, period, ["slcsp"]) + # The benchmark for the federal coverage family (a MONTH variable + # summed to the year) proxies the benchmark ConnectorCare plan of the + # APTC-eligible enrollees (956 CMR 12.04(3)(a)2); the full APTC is + # netted per 956 CMR 12.04(3)(c). + slcsp_annual = add(tax_unit, period, ["aca_ptc_slcsp"]) aca_ptc = tax_unit("aca_ptc", period) subsidy = max_(0, slcsp_annual - aca_ptc - enrollee_contribution) eligible = tax_unit("ma_connector_care_eligible", period) diff --git a/policyengine_us/variables/gov/states/md/mhbe/premium_assistance/md_premium_assistance.py b/policyengine_us/variables/gov/states/md/mhbe/premium_assistance/md_premium_assistance.py index 28b2835f2c9..e67ff51f223 100644 --- a/policyengine_us/variables/gov/states/md/mhbe/premium_assistance/md_premium_assistance.py +++ b/policyengine_us/variables/gov/states/md/mhbe/premium_assistance/md_premium_assistance.py @@ -46,9 +46,10 @@ def formula(tax_unit, period, parameters): md_percentage = tax_unit( "md_premium_assistance_target_contribution_percentage", period ) - # slcsp is a MONTH-period variable; core sums the 12 months when - # called from this YEAR formula. - slcsp = add(tax_unit, period, ["slcsp"]) + # The benchmark for the federal coverage family, a MONTH variable that + # core sums over the year here: COMAR 14.35.21.03A(1) limits the + # subsidy to members of an APTC-eligible tax filer's tax household. + slcsp = add(tax_unit, period, ["aca_ptc_slcsp"]) aca_ptc = tax_unit("aca_ptc", period) # Premium balance remaining after the federal APTC (COMAR .04B). premium_after_aptc = max_(0, slcsp - aca_ptc) diff --git a/policyengine_us/variables/gov/states/nj/dobi/njhps/nj_njhps.py b/policyengine_us/variables/gov/states/nj/dobi/njhps/nj_njhps.py index 09adfb9943c..14c442961c6 100644 --- a/policyengine_us/variables/gov/states/nj/dobi/njhps/nj_njhps.py +++ b/policyengine_us/variables/gov/states/nj/dobi/njhps/nj_njhps.py @@ -49,7 +49,10 @@ def formula(tax_unit, period, parameters): # plan premium. NJHPS is added atop APTC and cannot lower the net # premium below zero, so it is capped at the post-APTC residual. In the # 400-600% band aca_ptc is 0, so the residual equals slcsp_annual. - slcsp_annual = add(tax_unit, period, ["slcsp"]) + # The benchmark is the federal coverage family's: NJHPS members must + # be in a tax household otherwise eligible for APTC (an + # interpretation; see nj_njhps_member_eligible). + slcsp_annual = add(tax_unit, period, ["aca_ptc_slcsp"]) aca_ptc = tax_unit("aca_ptc", period) residual = max_(0, slcsp_annual - aca_ptc) return min_(base_annual, residual) diff --git a/policyengine_us/variables/gov/states/nj/dobi/njhps/nj_njhps_member_eligible.py b/policyengine_us/variables/gov/states/nj/dobi/njhps/nj_njhps_member_eligible.py index 6be048c756f..2578759eebe 100644 --- a/policyengine_us/variables/gov/states/nj/dobi/njhps/nj_njhps_member_eligible.py +++ b/policyengine_us/variables/gov/states/nj/dobi/njhps/nj_njhps_member_eligible.py @@ -13,7 +13,12 @@ class nj_njhps_member_eligible(Variable): ) documentation = ( "A person is a New Jersey Health Plan Savings enrollee when they pay an " - "ACA marketplace premium, do not file a separate return, and household " + "ACA marketplace premium, are in the federal premium tax credit tax " + "family (an interpretation: Treasury's 1332 addendum describes NJHPS " + "as for enrollees 'otherwise eligible for the Advanced Premium Tax " + "Credit', and no current New Jersey rule found addresses someone " + "another taxpayer can claim), do not file a separate return, " + "and household " "income (ACA MAGI per 26 U.S.C. 36B(d)(2)) falls within the 138-600% " "FPL NJHPS band. This deliberately does NOT reuse is_aca_ptc_eligible: " "that variable embeds the federal 400% FPL income cliff (the PY2026 " @@ -45,7 +50,13 @@ def formula(person, period, parameters): # income under the federal PTC floor of 100% FPL, and within_band # already starts at the 138% FPL NJHPS floor. pays_premium = person("pays_aca_premium", period) + # Interpretation: NJHPS goes to enrollees otherwise eligible for APTC + # (Treasury 1332 addendum), so a person outside the federal tax family + # (one who can be claimed on another return, or a dependent of a + # return with such a filer) is not a member; 26 U.S.C. 36B(c)(1)(D) + # is not one of the income tests NJHPS replaces. + tax_family_member = person("is_aca_tax_family_member", period) fstatus = person.tax_unit("filing_status", period) not_separate = fstatus != fstatus.possible_values.SEPARATE within_band = (magi_fraction >= p.fpl_floor) & (magi_fraction <= p.fpl_limit) - return pays_premium & not_separate & within_band + return pays_premium & tax_family_member & not_separate & within_band diff --git a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance.py b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance.py index df33b423c65..877bbee18e6 100644 --- a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance.py +++ b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance.py @@ -36,9 +36,11 @@ def formula(tax_unit, period, parameters): # Clamp income to zero so a negative ACA MAGI cannot inflate the top-up # above the benchmark - APTC residual via the -pct*income term. income = max_(tax_unit("aca_magi", period), 0) - # slcsp is a MONTH-period variable; core sums the 12 months when - # called from this YEAR formula. - slcsp = add(tax_unit, period, ["slcsp"]) + # The benchmark for the federal coverage family, a MONTH variable that + # core sums over the year here: both components cover only enrollees + # who meet the federal PTC criteria (the base program in full, the + # Middle Income Household component except for income). + slcsp = add(tax_unit, period, ["aca_ptc_slcsp"]) aca_ptc = tax_unit("aca_ptc", period) magi_frac = tax_unit("aca_magi_fraction", period) diff --git a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.py b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.py index 78a6a44ff95..bbb6133f84d 100644 --- a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.py +++ b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.py @@ -29,7 +29,11 @@ def formula(tax_unit, period, parameters): # the Medicaid coverage gap, income under the federal PTC floor of 100% # FPL; that cannot bind here, because income_eligible already requires # income above 400% FPL. - pays_premium = add(tax_unit, period, ["pays_aca_premium"]) > 0 + # The Addendum requires every federal PTC criterion except income, so + # the enrollee must also be in the federal tax family (26 U.S.C. + # 36B(c)(1)(D)): is_aca_coverage_family_member is pays_aca_premium + # restricted to that family. + pays_premium = add(tax_unit, period, ["is_aca_coverage_family_member"]) > 0 filing_status = tax_unit("filing_status", period) not_separate = filing_status != filing_status.possible_values.SEPARATE # is_aca_ptc_eligible does not embed the federal PTC filing requirement diff --git a/policyengine_us/variables/gov/states/vt/dvha/premium_assistance/vt_premium_assistance.py b/policyengine_us/variables/gov/states/vt/dvha/premium_assistance/vt_premium_assistance.py index b19a3450ccd..ff3d842334d 100644 --- a/policyengine_us/variables/gov/states/vt/dvha/premium_assistance/vt_premium_assistance.py +++ b/policyengine_us/variables/gov/states/vt/dvha/premium_assistance/vt_premium_assistance.py @@ -19,9 +19,9 @@ class vt_premium_assistance(Variable): "percentage-point reduction to the federal section 36B applicable " "percentage, so the subsidy equals the enrollee's income times that " "reduction, capped at the benchmark premium remaining after the federal " - "APTC so it never duplicates the federal credit. The benchmark second-" - "lowest-cost silver plan premium (slcsp) is annualized from its monthly " - "definition. At very low income the applicable percentage can fall below " + "APTC so it never duplicates the federal credit. The premium tax " + "credit's benchmark for the coverage family (aca_ptc_slcsp) is " + "annualized from its monthly definition. At very low income the applicable percentage can fall below " "1.5%, in which case the reduction floors at zero and the cap lets " "Vermont Premium Assistance cover the full residual premium after the " "federal APTC. The amount is not taxable. Advance-payment, " @@ -54,8 +54,10 @@ def formula(tax_unit, period, parameters): # Incremental contribution gap between the federal applicable # percentage and the lower Vermont percentage. contribution_gap = max_(0, income * (federal_percentage - vt_percentage)) - # slcsp is a MONTH-period variable; annualize it to match aca_ptc. - slcsp = add(tax_unit, period, ["slcsp"]) + # The benchmark for the federal coverage family, annualized to match + # aca_ptc: 33 V.S.A. 1812(a)(1) covers only those eligible for the + # federal premium tax credit. + slcsp = add(tax_unit, period, ["aca_ptc_slcsp"]) aca_ptc = tax_unit("aca_ptc", period) # Cap at the premium balance remaining after the federal APTC. premium_after_aptc = max_(0, slcsp - aca_ptc) diff --git a/policyengine_us/variables/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_amount.py b/policyengine_us/variables/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_amount.py index 307e9cc6f90..de701b06234 100644 --- a/policyengine_us/variables/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_amount.py +++ b/policyengine_us/variables/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_amount.py @@ -10,6 +10,7 @@ class wa_cascade_care_savings_member_amount(Variable): defined_for = StateCode.WA reference = ( "https://app.leg.wa.gov/rcw/default.aspx?cite=43.71.110", + # PDF pages 1, 5 (the PMPM amounts and the three groups). "https://www.wahbexchange.org/content/dam/materials/communications/legislative/2025/WAHBE_Final_PY_2026_Cascade_Care_Savings_Maximum_Per_Member_Per_Month_Methodology.pdf#page=1", "https://www.wahbexchange.org/content/dam/wahbe-assets/materials/collateral/cc/FinalPY2026CascadeCareSavingsPolicy_Combined.pdf#page=14", ) @@ -22,15 +23,18 @@ class wa_cascade_care_savings_member_amount(Variable): "with-federal-subsidy amount ($55 PMPM for PY2026), and Group 3 " "members without a federally recognized immigration status who lack " "minimum essential coverage through a state medical assistance program " - "receive the without-federal-subsidy amount ($250 PMPM for PY2026). " + "receive the without-federal-subsidy amount ($250 PMPM for PY2026), " + "as do Group 2 members modeled as QHP-eligible enrollees outside the " + "federal tax family (someone another taxpayer can claim). " "Group 3 excludes members eligible for Washington Apple Health " "Expansion (undocumented adults) or Apple Health for Kids (children " "under 19), per Policy Section 4(1)(f), which also avoids double " "counting with the Medicaid-cost proxy in healthcare_benefit_value; it " "further requires the member not be premium-tax-credit-eligible, so " - "Groups 1 and 3 are mutually exclusive. Group 2 (lawfully present but " - "premium-tax-credit-ineligible) is not separately identifiable in the " - "model and is documented away. The monthly amount is annualized by " + "Groups 1 and 3 are mutually exclusive. Other Group 2 members " + "(lawfully present but premium-tax-credit-ineligible for other " + "reasons) are not separately identifiable in the model and are " + "documented away. The monthly amount is annualized by " "MONTHS_IN_YEAR, assuming full-year enrollment." ) @@ -54,6 +58,11 @@ def formula(person, period, parameters): apple_health_expansion = person("wa_apple_health_expansion_eligible", period) apple_health_kids = person("wa_apple_health_kids_eligible", period) group_3 = undocumented & ~apple_health_expansion & ~apple_health_kids & ~group_1 + # Group 2: QHP-eligible enrollees outside the federal tax family, who + # have no federal subsidy (see wa_cascade_care_savings_member_eligible). + group_2 = person("pays_aca_premium", period) & ~person( + "is_aca_tax_family_member", period + ) with_subsidy = where(group_1, p.amount.with_federal_subsidy, 0) - without_subsidy = where(group_3, p.amount.without_federal_subsidy, 0) + without_subsidy = where(group_2 | group_3, p.amount.without_federal_subsidy, 0) return MONTHS_IN_YEAR * (with_subsidy + without_subsidy) diff --git a/policyengine_us/variables/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_eligible.py b/policyengine_us/variables/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_eligible.py index 1eb0c88c9db..d931c332c49 100644 --- a/policyengine_us/variables/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_eligible.py +++ b/policyengine_us/variables/gov/states/wa/wahbe/cascade_care_savings/wa_cascade_care_savings_member_eligible.py @@ -13,10 +13,14 @@ class wa_cascade_care_savings_member_eligible(Variable): "https://www.wahbexchange.org/content/dam/materials/communications/legislative/2025/WAHBE_Final_PY_2026_Cascade_Care_Savings_Maximum_Per_Member_Per_Month_Methodology.pdf#page=5", ) documentation = ( - "A person is a Cascade Care Savings enrollee if they fall in Group 1 " - "or Group 3. Group 1 members are eligible for the federal ACA premium " + "A person is a Cascade Care Savings enrollee if they fall in Group 1, " + "2 or 3. Group 1 members are eligible for the federal ACA premium " "tax credit (embedding on-Marketplace enrollment, the MFS exclusion, " "immigration/TIN status, and the required-contribution income test). " + "Group 2 members are QHP-eligible but not eligible for the federal " + "credit; the model identifies those outside the federal tax family " + "(someone another taxpayer can claim, or a dependent of a return with " + "such a filer), since Policy Section 4(2) does not exclude them. " "Group 3 members are undocumented residents who lack minimum essential " "coverage through a state medical assistance program: they are excluded " "if eligible for Washington Apple Health Expansion (undocumented adults) " @@ -24,8 +28,9 @@ class wa_cascade_care_savings_member_eligible(Variable): "4(1)(f), which also avoids double counting with the Medicaid-cost " "proxy in healthcare_benefit_value. Group 3 further requires the member " "not be premium-tax-credit-eligible, making Groups 1 and 3 mutually " - "exclusive. Group 2 (lawfully present but premium-tax-credit-" - "ineligible) is not separately identifiable and is documented away." + "exclusive. Other Group 2 members (lawfully present but premium-tax-" + "credit-ineligible for other reasons) are not separately identifiable " + "and are documented away." ) def formula(person, period, parameters): @@ -43,4 +48,13 @@ def formula(person, period, parameters): apple_health_expansion = person("wa_apple_health_expansion_eligible", period) apple_health_kids = person("wa_apple_health_kids_eligible", period) group_3 = undocumented & ~apple_health_expansion & ~apple_health_kids & ~group_1 - return group_1 | group_3 + # Group 2: QHP-eligible enrollees outside the federal tax family. The + # PMPM methodology defines Group 2 as QHP-eligible residents "not + # eligible for APTC Federal subsidies but eligible for CCS", and + # Policy Section 4(2) does not list being claimable among its + # exclusions. pays_aca_premium already excludes undocumented people, + # so Groups 2 and 3 do not overlap. + group_2 = person("pays_aca_premium", period) & ~person( + "is_aca_tax_family_member", period + ) + return group_1 | group_2 | group_3