diff --git a/changelog.d/ltc-premiums-itemized-medical.fixed.md b/changelog.d/ltc-premiums-itemized-medical.fixed.md new file mode 100644 index 00000000000..574acbadd4d --- /dev/null +++ b/changelog.d/ltc-premiums-itemized-medical.fixed.md @@ -0,0 +1 @@ +Count eligible long-term care insurance premiums, up to the 26 U.S.C. 213(d)(10) limit for the insured person's age, in Schedule A medical expenses and the self-employed health insurance deduction. diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/medical/long_term_care_premium_limit.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/medical/long_term_care_premium_limit.yaml new file mode 100644 index 00000000000..a27131ff084 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/medical/long_term_care_premium_limit.yaml @@ -0,0 +1,177 @@ +description: The IRS counts premiums for a qualified long-term care insurance contract as medical care up to this amount for each insured person, based on that person's age at the end of the year. +# Each threshold is the top of an age band: "40 or less", "more than 40 but +# not more than 50", and so on. Look ages up with right=True so an age equal +# to a threshold stays in the lower band. +brackets: + - threshold: + values: + 2013-01-01: -.inf + amount: + values: + 2013-01-01: 360 + 2014-01-01: 370 + 2015-01-01: 380 + 2016-01-01: 390 + 2017-01-01: 410 + 2018-01-01: 420 + 2019-01-01: 420 + 2020-01-01: 430 + 2021-01-01: 450 + 2022-01-01: 450 + 2023-01-01: 480 + 2024-01-01: 470 + 2025-01-01: 480 + 2026-01-01: 500 + metadata: + uprating: + parameter: gov.bls.cpi.cpi_u_medical_care + rounding: + type: nearest + interval: 10 + - threshold: + values: + 2013-01-01: 40 + amount: + values: + 2013-01-01: 680 + 2014-01-01: 700 + 2015-01-01: 710 + 2016-01-01: 730 + 2017-01-01: 770 + 2018-01-01: 780 + 2019-01-01: 790 + 2020-01-01: 810 + 2021-01-01: 850 + 2022-01-01: 850 + 2023-01-01: 890 + 2024-01-01: 880 + 2025-01-01: 900 + 2026-01-01: 930 + metadata: + uprating: + parameter: gov.bls.cpi.cpi_u_medical_care + rounding: + type: nearest + interval: 10 + - threshold: + values: + 2013-01-01: 50 + amount: + values: + 2013-01-01: 1_360 + 2014-01-01: 1_400 + 2015-01-01: 1_430 + 2016-01-01: 1_460 + 2017-01-01: 1_530 + 2018-01-01: 1_560 + 2019-01-01: 1_580 + 2020-01-01: 1_630 + 2021-01-01: 1_690 + 2022-01-01: 1_690 + 2023-01-01: 1_790 + 2024-01-01: 1_760 + 2025-01-01: 1_800 + 2026-01-01: 1_860 + metadata: + uprating: + parameter: gov.bls.cpi.cpi_u_medical_care + rounding: + type: nearest + interval: 10 + - threshold: + values: + 2013-01-01: 60 + amount: + values: + 2013-01-01: 3_640 + 2014-01-01: 3_720 + 2015-01-01: 3_800 + 2016-01-01: 3_900 + 2017-01-01: 4_090 + 2018-01-01: 4_160 + 2019-01-01: 4_220 + 2020-01-01: 4_350 + 2021-01-01: 4_520 + 2022-01-01: 4_510 + 2023-01-01: 4_770 + 2024-01-01: 4_710 + 2025-01-01: 4_810 + 2026-01-01: 4_960 + metadata: + uprating: + parameter: gov.bls.cpi.cpi_u_medical_care + rounding: + type: nearest + interval: 10 + - threshold: + values: + 2013-01-01: 70 + amount: + values: + 2013-01-01: 4_550 + 2014-01-01: 4_660 + 2015-01-01: 4_750 + 2016-01-01: 4_870 + 2017-01-01: 5_110 + 2018-01-01: 5_200 + 2019-01-01: 5_270 + 2020-01-01: 5_430 + 2021-01-01: 5_640 + 2022-01-01: 5_640 + 2023-01-01: 5_960 + 2024-01-01: 5_880 + 2025-01-01: 6_020 + 2026-01-01: 6_200 + metadata: + uprating: + parameter: gov.bls.cpi.cpi_u_medical_care + rounding: + type: nearest + interval: 10 +metadata: + type: single_amount + threshold_unit: year + amount_unit: currency-USD + period: year + label: Eligible long-term care premium limit + # 26 U.S.C. 213(d)(10)(B) indexes these amounts by the medical care + # component of the chained CPI-U for August of the preceding year, + # rounding each increase to the nearest $10. Neither CBO nor CMS projects + # that index, so years after the last published values use CBO's + # projection of the CPI-U medical care component, by calendar year, as a + # proxy (as long_term_health_insurance_premiums does). + reference: + - title: 26 U.S. Code ยง 213(d)(10) - Eligible long-term care premiums + href: https://www.law.cornell.edu/uscode/text/26/213#d_10 + - title: Rev. Proc. 2012-41, section 3.11 (2013) + href: https://www.irs.gov/irb/2012-45_IRB#RP-2012-41 + - title: Rev. Proc. 2013-35, section 3.24 (2014) + href: https://www.irs.gov/pub/irs-drop/rp-13-35.pdf#page=16 + - title: Rev. Proc. 2014-61, section 3.25 (2015) + href: https://www.irs.gov/pub/irs-drop/rp-14-61.pdf#page=16 + - title: Rev. Proc. 2015-53, section 3.25 (2016) + href: https://www.irs.gov/pub/irs-drop/rp-15-53.pdf#page=17 + - title: Rev. Proc. 2016-55, section 3.26 (2017) + href: https://www.irs.gov/pub/irs-drop/rp-16-55.pdf#page=19 + - title: Rev. Proc. 2017-58, section 3.26 (2018) + href: https://www.irs.gov/pub/irs-drop/rp-17-58.pdf#page=17 + - title: Publication 502 (2018), Long-Term Care (2018 amounts after Rev. Proc. 2018-18) + href: https://www.irs.gov/pub/irs-prior/p502--2018.pdf#page=11 + - title: Rev. Proc. 2018-57, section 3.28 (2019) + href: https://www.irs.gov/pub/irs-drop/rp-18-57.pdf#page=19 + - title: Rev. Proc. 2019-44, section 3.28 (2020) + href: https://www.irs.gov/pub/irs-drop/rp-19-44.pdf#page=16 + - title: Rev. Proc. 2020-45, section 3.28 (2021) + href: https://www.irs.gov/pub/irs-drop/rp-20-45.pdf#page=17 + - title: Rev. Proc. 2021-45, section 3.28 (2022) + href: https://www.irs.gov/pub/irs-drop/rp-21-45.pdf#page=17 + - title: Rev. Proc. 2022-38, section 3.28 (2023) + href: https://www.irs.gov/pub/irs-drop/rp-22-38.pdf#page=17 + - title: Rev. Proc. 2023-34, section 3.28 (2024) + href: https://www.irs.gov/pub/irs-drop/rp-23-34.pdf#page=16 + - title: Rev. Proc. 2024-40, section 3.28 (2025) + href: https://www.irs.gov/pub/irs-drop/rp-24-40.pdf#page=15 + - title: Rev. Proc. 2025-32, section 3.27 (2026) + href: https://www.irs.gov/pub/irs-drop/rp-25-32.pdf#page=22 + - title: 2022 Instructions for Schedule A (Form 1040), line 1 (age at the end of the year) + href: https://www.irs.gov/pub/irs-prior/i1040sca--2022.pdf#page=2 diff --git a/policyengine_us/tests/core/test_long_term_care_premium_properties.py b/policyengine_us/tests/core/test_long_term_care_premium_properties.py new file mode 100644 index 00000000000..78bfeafd85c --- /dev/null +++ b/policyengine_us/tests/core/test_long_term_care_premium_properties.py @@ -0,0 +1,418 @@ +"""Properties of eligible long-term care premiums on Schedule A. + +26 U.S.C. 213(d)(1)(D) counts premiums for a qualified long-term care +insurance contract as medical care up to the 213(d)(10) limit for the insured +person's age at the end of the year. 162(l)(2)(C) lets the same eligible +premiums into the self-employed health insurance deduction, and 162(l)(3) +keeps the part deducted there off Schedule A. + +The limits below are typed from the Rev. Proc. tables, independently of the +parameter file, so the first test is a differential check of the parameter +against the published amounts. The other properties run generated tax units +(single, joint, with dependents; self-employed or not; the deduction derived +from self-employment income or supplied for the return) through one +vectorized simulation per example. Every unit appears twice: as drawn, and +with each person's long-term care premiums raised. Medicare enrollment is off, +so a person's medical insurance premiums are exactly their direct input. For +every tax unit: + +1. Eligible premiums equal min(premiums, published limit for the age), with + ages at each band edge; they are never negative or above the premiums. +2. The self-employed exclusion takes medical insurance premiums first: + medical part = min(filers' medical premiums, deduction); long-term care + part = min(filers' eligible premiums, deduction - medical part); together + they equal min(filers' medical + eligible premiums, deduction), and the + applied exclusion of a computed subtotal is their sum. +3. Accounting: the subtotal is members' medical premiums - medical part + + members' eligible premiums - long-term care part + other expenses, and the + long-term care premiums inside it lie between the dependents' eligible + premiums and all members' eligible premiums. +4. Conservation (derived deduction): every eligible premium is deducted once, + on Schedule A or as the self-employed deduction, never both. +5. The derived person deduction is min(max(0, earnings), health premiums + + eligible long-term care premiums) for the self-employed, else 0. +6. Monotonicity: raising long-term care premiums never lowers the subtotal + and raises it by at most the rise in eligible premiums; for units with no + deduction it raises it by exactly that amount. +7. A supplied subtotal holds no computed long-term care premiums and no + applied exclusion. +8. Years after the last published table: every amount is a multiple of $10, + amounts rise with age, and no amount falls from one year to the next. +""" + +import numpy as np +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation +from policyengine_us.system import system + +TOLERANCE = 0.01 + +# Rev. Proc. tables: 40 or less, 41-50, 51-60, 61-70, over 70. +PUBLISHED_LIMITS = { + 2013: (360, 680, 1_360, 3_640, 4_550), # Rev. Proc. 2012-41 + 2014: (370, 700, 1_400, 3_720, 4_660), # Rev. Proc. 2013-35 + 2015: (380, 710, 1_430, 3_800, 4_750), # Rev. Proc. 2014-61 + 2016: (390, 730, 1_460, 3_900, 4_870), # Rev. Proc. 2015-53 + 2017: (410, 770, 1_530, 4_090, 5_110), # Rev. Proc. 2016-55 + 2018: (420, 780, 1_560, 4_160, 5_200), # Rev. Proc. 2017-58 + 2019: (420, 790, 1_580, 4_220, 5_270), # Rev. Proc. 2018-57 + 2020: (430, 810, 1_630, 4_350, 5_430), # Rev. Proc. 2019-44 + 2021: (450, 850, 1_690, 4_520, 5_640), # Rev. Proc. 2020-45 + 2022: (450, 850, 1_690, 4_510, 5_640), # Rev. Proc. 2021-45 + 2023: (480, 890, 1_790, 4_770, 5_960), # Rev. Proc. 2022-38 + 2024: (470, 880, 1_760, 4_710, 5_880), # Rev. Proc. 2023-34 + 2025: (480, 900, 1_800, 4_810, 6_020), # Rev. Proc. 2024-40 + 2026: (500, 930, 1_860, 4_960, 6_200), # Rev. Proc. 2025-32 +} +BAND_TOPS = (40, 50, 60, 70) +LAST_PUBLISHED = max(PUBLISHED_LIMITS) +EDGE_AGES = [0, 18, 39, 40, 41, 50, 51, 60, 61, 70, 71, 95] + + +def _band(age): + return sum(age > top for top in BAND_TOPS) + + +def _published_limit(year, age): + return PUBLISHED_LIMITS[year][_band(age)] + + +def _limit_parameter(year): + return system.parameters( + f"{year}-01-01" + ).gov.irs.deductions.itemized.medical.long_term_care_premium_limit + + +def test_parameter_matches_published_limits(): + for year in PUBLISHED_LIMITS: + scale = _limit_parameter(year) + ages = np.array(EDGE_AGES, dtype=float) + expected = [_published_limit(year, age) for age in EDGE_AGES] + np.testing.assert_array_equal(scale.calc(ages, right=True), expected) + + +def test_limits_after_the_last_published_year(): + previous = np.array(PUBLISHED_LIMITS[LAST_PUBLISHED], dtype=float) + ages = np.array([30, 45, 55, 65, 75], dtype=float) + for year in range(LAST_PUBLISHED + 1, LAST_PUBLISHED + 10): + amounts = _limit_parameter(year).calc(ages, right=True) + assert (amounts % 10 == 0).all(), (year, amounts) + assert (np.diff(amounts) > 0).all(), (year, amounts) + assert (amounts >= previous).all(), (year, amounts, previous) + previous = amounts + + +premium = st.one_of(st.just(0), st.integers(1, 12_000)) +expense = st.one_of(st.just(0), st.integers(1, 15_000)) + + +@st.composite +def people(draw, adult=True): + self_employed = draw(st.booleans()) + return { + "age": draw(st.sampled_from(EDGE_AGES[2:] if adult else EDGE_AGES)), + "self_employed": self_employed, + "earnings": draw(st.integers(-10_000, 60_000)) if self_employed else 0, + "health_premiums": draw(expense), + "long_term_care": draw(premium), + "long_term_care_rise": draw(st.integers(0, 8_000)), + "other": draw(expense), + } + + +@st.composite +def tax_units(draw): + kind = draw(st.sampled_from(["single", "joint", "joint_dependents", "hoh"])) + n_dependents = draw(st.integers(1, 2)) if kind in ("joint_dependents", "hoh") else 0 + return { + "head": draw(people()), + "spouse": draw(people()) if kind.startswith("joint") else None, + "dependents": [draw(people(adult=False)) for _ in range(n_dependents)], + "supplied_deduction": draw(st.integers(0, 30_000)), + } + + +def _entries(unit): + entries = [("head", unit["head"])] + if unit["spouse"] is not None: + entries.append(("spouse", unit["spouse"])) + return entries + [("dependent", d) for d in unit["dependents"]] + + +VARIANTS = ["base", "raised"] + + +def _situation(units, year, supplied_deduction, supplied_subtotal=False): + people_, tax_units_, households, marital_units = {}, {}, {}, {} + for variant in VARIANTS: + for i, unit in enumerate(units): + names, couple = [], [] + for j, (role, values) in enumerate(_entries(unit)): + name = f"{variant}_{i}_{j}" + names.append(name) + long_term_care = values["long_term_care"] + ( + values["long_term_care_rise"] if variant == "raised" else 0 + ) + people_[name] = { + key: {year: value} + for key, value in { + "age": values["age"], + "is_tax_unit_head": role == "head", + "is_tax_unit_spouse": role == "spouse", + "is_tax_unit_dependent": role == "dependent", + "is_self_employed": values["self_employed"], + "self_employment_income": values["earnings"], + "medicare_enrolled": False, + "health_insurance_premiums": values["health_premiums"], + "long_term_health_insurance_premiums": long_term_care, + "other_medical_expenses": values["other"], + }.items() + } + if role == "dependent": + marital_units[f"{name}_single"] = {"members": [name]} + else: + couple.append(name) + marital_units[f"{variant}_{i}_couple"] = {"members": couple} + tax_unit = {"members": names} + if supplied_deduction: + tax_unit["self_employed_health_insurance_ald"] = { + year: unit["supplied_deduction"] + } + if supplied_subtotal: + tax_unit["itemized_medical_expenses"] = {year: 1_000} + tax_units_[f"{variant}_{i}_tax_unit"] = tax_unit + households[f"{variant}_{i}_household"] = { + "members": names, + "state_code": {year: "TX"}, + } + return { + "people": people_, + "tax_units": tax_units_, + "households": households, + "marital_units": marital_units, + } + + +PERSON_VARIABLES = [ + "eligible_long_term_care_premiums", + "self_employed_health_insurance_ald_person", + "is_tax_unit_dependent", +] +UNIT_VARIABLES = [ + "self_employed_health_insurance_ald", + "itemized_medical_expenses_excluded_premiums", + "itemized_medical_expenses_excluded_long_term_care_premiums", + "itemized_medical_expenses_applied_exclusion", + "itemized_medical_expenses_long_term_care_premiums", + "itemized_medical_expenses", +] + + +def _run(units, year, supplied_deduction): + sim = Simulation( + tax_benefit_system=system, + situation=_situation(units, year, supplied_deduction), + ) + out = { + name: np.asarray(sim.calculate(name, year), dtype=float) + for name in PERSON_VARIABLES + UNIT_VARIABLES + } + out["unit"] = sim.populations["tax_unit"].members_entity_id + return out + + +def _person_inputs(units): + rows = [] + for variant in VARIANTS: + for unit in units: + for role, values in _entries(unit): + long_term_care = values["long_term_care"] + ( + values["long_term_care_rise"] if variant == "raised" else 0 + ) + rows.append( + ( + values["age"], + role != "dependent", + values["self_employed"], + values["earnings"], + values["health_premiums"], + long_term_care, + values["other"], + ) + ) + return np.array(rows, dtype=float).T + + +def _check(units, year, supplied_deduction): + run = _run(units, year, supplied_deduction) + age, filer, self_employed, earnings, health, long_term_care, other = _person_inputs( + units + ) + unit = run["unit"] + n_units = unit.max() + 1 + + def unit_sum(values): + return np.bincount(unit, weights=values, minlength=n_units) + + # 1. Eligible premiums: min(premiums, published limit), within bounds. + eligible = run["eligible_long_term_care_premiums"] + if year in PUBLISHED_LIMITS: + limit = np.array([_published_limit(year, a) for a in age]) + np.testing.assert_allclose( + eligible, np.minimum(long_term_care, limit), atol=TOLERANCE + ) + assert (eligible >= 0).all() + assert (eligible <= long_term_care + TOLERANCE).all() + + # 2. The exclusion takes medical premiums first, then eligible premiums. + deduction = np.maximum(run["self_employed_health_insurance_ald"], 0) + filer_health = unit_sum(health * filer) + filer_eligible = unit_sum(eligible * filer) + medical_part = np.minimum(filer_health, deduction) + long_term_care_part = np.minimum(filer_eligible, deduction - medical_part) + np.testing.assert_allclose( + run["itemized_medical_expenses_excluded_premiums"], + medical_part, + atol=TOLERANCE, + ) + np.testing.assert_allclose( + run["itemized_medical_expenses_excluded_long_term_care_premiums"], + long_term_care_part, + atol=TOLERANCE, + ) + np.testing.assert_allclose( + medical_part + long_term_care_part, + np.minimum(filer_health + filer_eligible, deduction), + atol=TOLERANCE, + ) + np.testing.assert_allclose( + run["itemized_medical_expenses_applied_exclusion"], + medical_part + long_term_care_part, + atol=TOLERANCE, + ) + + # 3. Accounting identity for the subtotal, from the raw inputs. + inside = run["itemized_medical_expenses_long_term_care_premiums"] + all_eligible = unit_sum(eligible) + np.testing.assert_allclose( + inside, all_eligible - long_term_care_part, atol=TOLERANCE + ) + assert (inside >= unit_sum(eligible * ~filer.astype(bool)) - TOLERANCE).all() + assert (inside <= all_eligible + TOLERANCE).all() + subtotal = run["itemized_medical_expenses"] + np.testing.assert_allclose( + subtotal, + unit_sum(health) - medical_part + inside + unit_sum(other), + atol=TOLERANCE, + ) + + if not supplied_deduction: + # 4. Each eligible premium is deducted once. + np.testing.assert_allclose( + subtotal - unit_sum(other) + run["self_employed_health_insurance_ald"], + unit_sum(health) + all_eligible, + atol=TOLERANCE, + ) + # 5. The derived person deduction (Form 7206 lines 1-3). + np.testing.assert_allclose( + run["self_employed_health_insurance_ald_person"], + self_employed * np.minimum(np.maximum(earnings, 0), health + eligible), + atol=TOLERANCE, + ) + + # 6. Monotonicity in long-term care premiums. + half = n_units // 2 + base, raised = slice(0, half), slice(half, n_units) + rise_in_eligible = all_eligible[raised] - all_eligible[base] + rise_in_subtotal = subtotal[raised] - subtotal[base] + assert (rise_in_subtotal >= -TOLERANCE).all() + assert (rise_in_subtotal <= rise_in_eligible + TOLERANCE).all() + no_deduction = (deduction[base] == 0) & (deduction[raised] == 0) + np.testing.assert_allclose( + rise_in_subtotal[no_deduction], + rise_in_eligible[no_deduction], + atol=TOLERANCE, + ) + + +SETTINGS = dict(max_examples=10, deadline=None, derandomize=True) +YEARS = list(range(2015, LAST_PUBLISHED + 4)) + + +@settings(**SETTINGS) +@given( + st.sampled_from(YEARS), + st.booleans(), + st.lists(tax_units(), min_size=3, max_size=12), +) +def test_long_term_care_premium_properties(year, supplied_deduction, units): + _check(units, year, supplied_deduction) + + +@pytest.mark.parametrize("year", [2018, 2022, 2025]) +@pytest.mark.parametrize("supplied_deduction", [False, True]) +def test_seeded_population(year, supplied_deduction): + rng = np.random.default_rng(20261018 + year) + + def person(adult=True): + self_employed = bool(rng.random() < 0.4) + return { + "age": int(rng.choice(EDGE_AGES[2:] if adult else EDGE_AGES)), + "self_employed": self_employed, + "earnings": int(rng.integers(-10_000, 60_001)) if self_employed else 0, + "health_premiums": int(rng.integers(0, 15_001)), + "long_term_care": int(rng.integers(0, 12_001)) * (rng.random() < 0.7), + "long_term_care_rise": int(rng.integers(0, 8_001)), + "other": int(rng.integers(0, 15_001)), + } + + units = [] + for _ in range(60): + kind = rng.choice(["single", "joint", "joint_dependents", "hoh"]) + n_dependents = ( + int(rng.integers(1, 3)) if kind in ("joint_dependents", "hoh") else 0 + ) + units.append( + { + "head": person(), + "spouse": person() if kind.startswith("joint") else None, + "dependents": [person(adult=False) for _ in range(n_dependents)], + "supplied_deduction": int(rng.integers(0, 30_001)), + } + ) + _check(units, year, supplied_deduction) + + +def test_supplied_subtotal_holds_no_computed_long_term_care_premiums(): + units = [ + { + "head": { + "age": 75, + "self_employed": True, + "earnings": 40_000, + "health_premiums": 2_000, + "long_term_care": 9_000, + "long_term_care_rise": 0, + "other": 500, + }, + "spouse": None, + "dependents": [], + "supplied_deduction": 0, + } + ] + sim = Simulation( + tax_benefit_system=system, + situation=_situation(units, 2025, False, supplied_subtotal=True), + ) + np.testing.assert_array_equal( + sim.calculate("itemized_medical_expenses_long_term_care_premiums", 2025), 0 + ) + np.testing.assert_array_equal( + sim.calculate("itemized_medical_expenses_applied_exclusion", 2025), 0 + ) + np.testing.assert_array_equal( + sim.calculate("itemized_medical_expenses", 2025), 1_000 + ) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/eligible_long_term_care_premiums.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/eligible_long_term_care_premiums.yaml new file mode 100644 index 00000000000..c45de374373 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/eligible_long_term_care_premiums.yaml @@ -0,0 +1,234 @@ +# 26 U.S.C. 213(d)(10): premiums for a qualified long-term care insurance +# contract count as medical care up to a limit set by the insured person's +# attained age before the close of the taxable year. The limits below are the +# Rev. Proc. tables ("40 or less", "more than 40 but not more than 50", ...); +# the 2022 Schedule A instructions (PDF p. 2) restate them by "age at the end +# of 2022": 40 or under $450, 41-50 $850, 51-60 $1,690, 61-70 $4,510, 71 or +# older $5,640. + +- name: 2022, age 40 is in the lowest band. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 40 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: 450 + +- name: 2022, age 41 starts the second band. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 41 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: 850 + +- name: 2022, age 50 is still in the second band. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 50 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: 850 + +- name: 2022, age 51 starts the third band. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 51 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: 1_690 + +- name: 2022, age 60 is still in the third band. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 60 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: 1_690 + +- name: 2022, age 61 starts the fourth band, which fell from 2021's 4,520. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 61 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: 4_510 + +- name: 2022, age 70 is still in the fourth band. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 70 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: 4_510 + +- name: 2022, age 71 is over 70. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 71 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: 5_640 + +- name: Premiums under the limit count in full. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 75 + long_term_health_insurance_premiums: 3_000 + output: + eligible_long_term_care_premiums: 3_000 + +- name: No premiums, nothing eligible. + period: 2025 + absolute_error_margin: 0.01 + input: + age: 75 + output: + eligible_long_term_care_premiums: 0 + +- name: 2021 limits (Rev. Proc. 2020-45, section 3.28). + period: 2021 + absolute_error_margin: 0.01 + input: + people: + age_35: + age: 35 + long_term_health_insurance_premiums: 10_000 + age_45: + age: 45 + long_term_health_insurance_premiums: 10_000 + age_55: + age: 55 + long_term_health_insurance_premiums: 10_000 + age_65: + age: 65 + long_term_health_insurance_premiums: 10_000 + age_75: + age: 75 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: [450, 850, 1_690, 4_520, 5_640] + +- name: 2023 limits (Rev. Proc. 2022-38, section 3.28). + period: 2023 + absolute_error_margin: 0.01 + input: + people: + age_35: + age: 35 + long_term_health_insurance_premiums: 10_000 + age_45: + age: 45 + long_term_health_insurance_premiums: 10_000 + age_55: + age: 55 + long_term_health_insurance_premiums: 10_000 + age_65: + age: 65 + long_term_health_insurance_premiums: 10_000 + age_75: + age: 75 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: [480, 890, 1_790, 4_770, 5_960] + +- name: 2024 limits fell from 2023 (Rev. Proc. 2023-34, section 3.28). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + age_35: + age: 35 + long_term_health_insurance_premiums: 10_000 + age_45: + age: 45 + long_term_health_insurance_premiums: 10_000 + age_55: + age: 55 + long_term_health_insurance_premiums: 10_000 + age_65: + age: 65 + long_term_health_insurance_premiums: 10_000 + age_75: + age: 75 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: [470, 880, 1_760, 4_710, 5_880] + +- name: 2025 limits (Rev. Proc. 2024-40, section 3.28). + period: 2025 + absolute_error_margin: 0.01 + input: + people: + age_35: + age: 35 + long_term_health_insurance_premiums: 10_000 + age_45: + age: 45 + long_term_health_insurance_premiums: 10_000 + age_55: + age: 55 + long_term_health_insurance_premiums: 10_000 + age_65: + age: 65 + long_term_health_insurance_premiums: 10_000 + age_75: + age: 75 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: [480, 900, 1_800, 4_810, 6_020] + +- name: 2026 limits (Rev. Proc. 2025-32, section 3.27). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + age_35: + age: 35 + long_term_health_insurance_premiums: 10_000 + age_45: + age: 45 + long_term_health_insurance_premiums: 10_000 + age_55: + age: 55 + long_term_health_insurance_premiums: 10_000 + age_65: + age: 65 + long_term_health_insurance_premiums: 10_000 + age_75: + age: 75 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: [500, 930, 1_860, 4_960, 6_200] + +- name: 2018 limits (Rev. Proc. 2017-58, section 3.26; Publication 502 (2018)). + period: 2018 + absolute_error_margin: 0.01 + input: + people: + age_35: + age: 35 + long_term_health_insurance_premiums: 10_000 + age_45: + age: 45 + long_term_health_insurance_premiums: 10_000 + age_55: + age: 55 + long_term_health_insurance_premiums: 10_000 + age_65: + age: 65 + long_term_health_insurance_premiums: 10_000 + age_75: + age: 75 + long_term_health_insurance_premiums: 10_000 + output: + eligible_long_term_care_premiums: [420, 780, 1_560, 4_160, 5_200] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml index 580d1f7e672..96c82dcfdab 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml @@ -171,3 +171,167 @@ standard_deduction: 15_750 # 58,000 - 15,750. taxable_income: 42_250 + +# Long-term care insurance premiums. +# +# 26 U.S.C. 213(d)(1)(D) counts premiums for a qualified long-term care +# insurance contract as medical care, but 213(d)(10) limits them by the +# insured person's age at the end of the year. 2025 limits (Rev. Proc. 2024-40, +# section 3.28): 40 or less $480, 41-50 $900, 51-60 $1,800, 61-70 $4,810, +# over 70 $6,020. 26 U.S.C. 162(l)(2)(C) lets the same eligible premiums into +# the self-employed health insurance deduction, and 162(l)(3) keeps that part +# off Schedule A. medicare_enrolled is false so no modeled Part B premium +# enters these examples. + +- name: Long-term care premiums count up to the limit for the insured person's age. + period: 2025 + absolute_error_margin: 0.01 + input: + age: 72 + medicare_enrolled: false + employment_income: 50_000 + long_term_health_insurance_premiums: 8_000 + other_medical_expenses: 10_000 + output: + eligible_long_term_care_premiums: 6_020 + itemized_medical_expenses_long_term_care_premiums: 6_020 + # 10,000 + 6,020. + itemized_medical_expenses: 16_020 + # 16,020 - 7.5% of 50,000. + medical_expense_deduction: 12_270 + +- name: A dependent's contract is limited by the dependent's age, not the payer's. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + employment_income: 80_000 + is_tax_unit_head: true + parent: + age: 80 + medicare_enrolled: false + is_tax_unit_dependent: true + # Recorded on the insured person, whoever in the tax unit paid it. + long_term_health_insurance_premiums: 7_000 + tax_units: + tax_unit: + members: [head, parent] + output: + eligible_long_term_care_premiums: [0, 6_020] + itemized_medical_expenses: 6_020 + # 6,020 - 7.5% of 80,000. + medical_expense_deduction: 20 + +- name: Each spouse's contract has its own limit. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 66 + medicare_enrolled: false + is_tax_unit_head: true + long_term_health_insurance_premiums: 5_000 + spouse: + age: 58 + is_tax_unit_spouse: true + long_term_health_insurance_premiums: 2_500 + tax_units: + tax_unit: + members: [head, spouse] + output: + # 61-70: 4,810. 51-60: 1,800. + eligible_long_term_care_premiums: [4_810, 1_800] + itemized_medical_expenses: 6_610 + +- name: Premiums taken as the self-employed health insurance deduction are not on Schedule A. + period: 2025 + absolute_error_margin: 0.01 + input: + age: 55 + is_self_employed: true + self_employment_income: 50_000 + health_insurance_premiums: 3_000 + long_term_health_insurance_premiums: 2_000 + other_medical_expenses: 500 + output: + eligible_long_term_care_premiums: 1_800 + # Form 7206 line 2. + self_employed_long_term_care_premiums: 1_800 + # Line 3: 3,000 + 1,800, under the 50,000 limit. + self_employed_health_insurance_ald: 4_800 + itemized_medical_expenses_excluded_premiums: 3_000 + itemized_medical_expenses_excluded_long_term_care_premiums: 1_800 + itemized_medical_expenses_applied_exclusion: 4_800 + itemized_medical_expenses_long_term_care_premiums: 0 + itemized_medical_expenses: 500 + +- name: Eligible premiums the earnings limit keeps out of the deduction stay on Schedule A. + period: 2025 + absolute_error_margin: 0.01 + input: + age: 55 + is_self_employed: true + self_employment_income: 4_000 + health_insurance_premiums: 3_000 + long_term_health_insurance_premiums: 2_000 + output: + # min(4,000, 3,000 + 1,800). + self_employed_health_insurance_ald: 4_000 + # The deduction is taken from medical premiums first: 3,000, then 1,000 + # of the 1,800 eligible long-term care premiums. + itemized_medical_expenses_excluded_premiums: 3_000 + itemized_medical_expenses_excluded_long_term_care_premiums: 1_000 + itemized_medical_expenses_applied_exclusion: 4_000 + itemized_medical_expenses_long_term_care_premiums: 800 + itemized_medical_expenses: 800 + +- name: A supplied deduction cannot exclude more long-term care premiums than are eligible. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + is_tax_unit_head: true + health_insurance_premiums: 1_000 + long_term_health_insurance_premiums: 3_000 + other_medical_expenses: 2_000 + tax_units: + tax_unit: + members: [head] + self_employed_health_insurance_ald: 2_500 + output: + eligible_long_term_care_premiums: 900 + itemized_medical_expenses_excluded_premiums: 1_000 + # min(900, 2,500 - 1,000). + itemized_medical_expenses_excluded_long_term_care_premiums: 900 + itemized_medical_expenses_long_term_care_premiums: 0 + # The other expenses are untouched. + itemized_medical_expenses: 2_000 + +- name: A supplied subtotal holds no computed long-term care premiums. + period: 2025 + absolute_error_margin: 0.01 + input: + age: 75 + medicare_enrolled: false + long_term_health_insurance_premiums: 3_000 + itemized_medical_expenses: 5_000 + output: + itemized_medical_expenses: 5_000 + itemized_medical_expenses_long_term_care_premiums: 0 + itemized_medical_expenses_applied_exclusion: 0 + +- name: Supplying the long-term care premiums sets the amount the computed subtotal counts. + period: 2025 + absolute_error_margin: 0.01 + input: + age: 75 + medicare_enrolled: false + other_medical_expenses: 2_000 + itemized_medical_expenses_long_term_care_premiums: 1_000 + output: + itemized_medical_expenses: 3_000 diff --git a/policyengine_us/tests/policy/baseline/household/expense/health/self_employed_long_term_care_premiums.yaml b/policyengine_us/tests/policy/baseline/household/expense/health/self_employed_long_term_care_premiums.yaml new file mode 100644 index 00000000000..06b150bcd48 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/expense/health/self_employed_long_term_care_premiums.yaml @@ -0,0 +1,42 @@ +# Form 7206 line 2: for each person covered by a qualified long-term care +# insurance contract, the premiums paid or the 26 U.S.C. 213(d)(10) limit for +# that person's age, whichever is less (26 U.S.C. 162(l)(2)(C)). 2024 limits +# (Rev. Proc. 2023-34, section 3.28): 61-70 $4,710, over 70 $5,880. + +- name: A self-employed person's eligible long-term care premiums count toward the deduction. + period: 2024 + absolute_error_margin: 0.01 + input: + age: 64 + is_self_employed: true + self_employment_income: 40_000 + long_term_health_insurance_premiums: 6_000 + output: + self_employed_long_term_care_premiums: 4_710 + self_employed_health_insurance_ald_person: 4_710 + +- name: Premiums under the limit count in full, alongside health insurance premiums. + period: 2024 + absolute_error_margin: 0.01 + input: + age: 72 + medicare_enrolled: false + is_self_employed: true + self_employment_income: 40_000 + health_insurance_premiums: 7_000 + long_term_health_insurance_premiums: 2_000 + output: + self_employed_long_term_care_premiums: 2_000 + # Line 3: 7,000 + 2,000. + self_employed_health_insurance_ald_person: 9_000 + +- name: A person who is not self-employed has none. + period: 2024 + absolute_error_margin: 0.01 + input: + age: 64 + employment_income: 40_000 + long_term_health_insurance_premiums: 6_000 + output: + self_employed_long_term_care_premiums: 0 + self_employed_health_insurance_ald_person: 0 diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald_person.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald_person.py index 8ecda97461a..c7232f0d9c0 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald_person.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald_person.py @@ -6,11 +6,27 @@ class self_employed_health_insurance_ald_person(Variable): entity = Person label = "Self-employed health insurance ALD for each person" unit = USD - documentation = "Personal above-the-line deduction for self-employed health insurance contributions." + documentation = ( + "Personal above-the-line deduction for self-employed health insurance " + "premiums, including eligible long-term care premiums (Form 7206 " + "lines 1 to 3)." + ) definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/162#l" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/162#l", + "https://www.irs.gov/pub/irs-prior/f7206--2024.pdf#page=1", # Lines 1-3 + ) def formula(person, period, parameters): earnings = max_(0, person("total_self_employment_income", period)) - premiums = person("self_employed_health_insurance_premiums", period) + # Line 3: health insurance premiums (line 1) plus eligible long-term + # care premiums (line 2). + premiums = add( + person, + period, + [ + "self_employed_health_insurance_premiums", + "self_employed_long_term_care_premiums", + ], + ) return min_(earnings, premiums) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/eligible_long_term_care_premiums.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/eligible_long_term_care_premiums.py new file mode 100644 index 00000000000..0fbaa5a2a63 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/eligible_long_term_care_premiums.py @@ -0,0 +1,33 @@ +from policyengine_us.model_api import * + + +class eligible_long_term_care_premiums(Variable): + value_type = float + entity = Person + label = "Eligible long-term care premiums" + unit = USD + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/213#d_1_D", + "https://www.law.cornell.edu/uscode/text/26/213#d_10", + "https://www.irs.gov/pub/irs-prior/i1040sca--2022.pdf#page=2", + "https://www.irs.gov/pub/irs-prior/p502--2018.pdf#page=11", + ) + documentation = ( + "Premiums for a person's qualified long-term care insurance contract " + "that count as medical care under 26 U.S.C. 213(d)(1)(D): the " + "premiums up to the 213(d)(10) limit for the person's age at the end " + "of the year. The limit applies to each insured person, so " + "long_term_health_insurance_premiums is recorded on the person the " + "contract covers. These premiums enter the Schedule A medical " + "subtotal and, for a self-employed person, the self-employed health " + "insurance deduction (26 U.S.C. 162(l)(2)(C))." + ) + + def formula(person, period, parameters): + premiums = person("long_term_health_insurance_premiums", period) + p = parameters(period).gov.irs.deductions.itemized.medical + # The limit depends on the age, at the end of the year, of the person + # for whom the premiums were paid (Schedule A instructions, line 1). + limit = p.long_term_care_premium_limit.calc(person("age", period), right=True) + return min_(premiums, limit) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py index 91538c9b38a..f7be87931b4 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py @@ -11,6 +11,7 @@ class itemized_medical_expenses(Variable): "https://www.law.cornell.edu/uscode/text/26/213", "https://www.law.cornell.edu/uscode/text/26/162#l_3", "https://www.irs.gov/instructions/i1040sca", + "https://www.irs.gov/pub/irs-prior/i1040sca--2022.pdf#page=2", ] documentation = ( "Medical expenses counted before applying the itemized medical " @@ -18,9 +19,12 @@ class itemized_medical_expenses(Variable): "to include amounts paid for diagnosis, cure, mitigation, treatment, " "or prevention of disease; transportation primarily for essential " "medical care; qualified long-term care services; and insurance " - "premiums covering medical care. Current modeling uses health " - "insurance premiums and other medical expenses, excluding general " - "over-the-counter health expenses. Premiums deducted through the " + "premiums covering medical care, with long-term care insurance " + "premiums counted up to the 213(d)(10) limit for the insured person's " + "age. Current modeling uses health insurance premiums, those eligible " + "long-term care premiums (recorded on the insured person) and other " + "medical expenses, excluding general over-the-counter health " + "expenses. Premiums deducted through the " "filer's self-employed health insurance deduction are excluded under " "IRC Section 162(l)(3) before the medical expense floor is applied. " "Premium inputs use payer attribution: each person reports premiums " @@ -39,5 +43,10 @@ def formula(tax_unit, period, parameters): excluded_premiums = tax_unit( "itemized_medical_expenses_excluded_premiums", period ) + # Eligible long-term care premiums less the part the same deduction + # took. + long_term_care_premiums = tax_unit( + "itemized_medical_expenses_long_term_care_premiums", period + ) other_expenses = add(tax_unit, period, ["other_medical_expenses"]) - return premiums - excluded_premiums + other_expenses + return premiums - excluded_premiums + long_term_care_premiums + other_expenses diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_applied_exclusion.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_applied_exclusion.py index f034c37903a..7334895915a 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_applied_exclusion.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_applied_exclusion.py @@ -26,4 +26,13 @@ def formula(tax_unit, period, parameters): tax_unit.simulation, "itemized_medical_expenses", period ): return 0 - return tax_unit("itemized_medical_expenses_excluded_premiums", period) + # Medical insurance premiums and eligible long-term care premiums the + # self-employed health insurance deduction took. + return add( + tax_unit, + period, + [ + "itemized_medical_expenses_excluded_premiums", + "itemized_medical_expenses_excluded_long_term_care_premiums", + ], + ) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_excluded_long_term_care_premiums.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_excluded_long_term_care_premiums.py new file mode 100644 index 00000000000..63c1601a4af --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_excluded_long_term_care_premiums.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * + + +class itemized_medical_expenses_excluded_long_term_care_premiums(Variable): + value_type = float + entity = TaxUnit + label = "Self-employed long-term care premiums excluded from Schedule A" + unit = USD + definition_period = YEAR + reference = [ + "https://www.law.cornell.edu/uscode/text/26/162#l_2_C", + "https://www.law.cornell.edu/uscode/text/26/162#l_3", + "https://www.irs.gov/pub/irs-prior/i1040sca--2022.pdf#page=2", + ] + documentation = ( + "The part of the federal self-employed health insurance deduction " + "left after the head's and spouse's medical insurance premiums " + "(itemized_medical_expenses_excluded_premiums), limited to their " + "eligible long-term care premiums. 26 U.S.C. 162(l)(3) keeps premiums " + "taken as that deduction off Schedule A. Taking the deduction from " + "medical insurance premiums first leaves the Schedule A subtotal " + "unchanged; it fixes which premiums a state that treats long-term " + "care premiums separately sees as already deducted." + ) + + def formula(tax_unit, period, parameters): + filer_premiums = tax_unit_non_dep_sum( + "eligible_long_term_care_premiums", tax_unit, period + ) + deduction = max_(0, tax_unit("self_employed_health_insurance_ald", period)) + medical_premiums = tax_unit( + "itemized_medical_expenses_excluded_premiums", period + ) + return min_(filer_premiums, deduction - medical_premiums) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_excluded_premiums.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_excluded_premiums.py index c07cab1c17e..6a14fb5e408 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_excluded_premiums.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_excluded_premiums.py @@ -4,7 +4,7 @@ class itemized_medical_expenses_excluded_premiums(Variable): value_type = float entity = TaxUnit - label = "Self-employed health insurance premiums excluded from Schedule A" + label = "Self-employed medical insurance premiums excluded from Schedule A" unit = USD definition_period = YEAR reference = [ @@ -26,4 +26,7 @@ def formula(tax_unit, period, parameters): "medical_expense_health_insurance_premiums", tax_unit, period ) se_health_insurance_ald = tax_unit("self_employed_health_insurance_ald", period) + # The deduction is taken from medical insurance premiums first; any + # rest covers eligible long-term care premiums + # (itemized_medical_expenses_excluded_long_term_care_premiums). return min_(filer_premiums, max_(0, se_health_insurance_ald)) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_long_term_care_premiums.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_long_term_care_premiums.py new file mode 100644 index 00000000000..6f969e3a944 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses_long_term_care_premiums.py @@ -0,0 +1,37 @@ +from policyengine_us.model_api import * + + +class itemized_medical_expenses_long_term_care_premiums(Variable): + value_type = float + entity = TaxUnit + label = "Long-term care premiums in itemized medical expenses" + unit = USD + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/213#d_10", + "https://www.law.cornell.edu/uscode/text/26/162#l_3", + "https://www.irs.gov/pub/irs-prior/i1040sca--2022.pdf#page=2", + ) + documentation = ( + "The eligible long-term care insurance premiums inside the Schedule A " + "medical subtotal (itemized_medical_expenses): every member's " + "premiums up to the 26 U.S.C. 213(d)(10) limit for that member's age, " + "less the part the self-employed health insurance deduction took. " + "The computed subtotal adds this amount, so supplying it sets the " + "long-term care premiums the subtotal counts. A supplied subtotal " + "bypasses this formula, and the amount is 0 unless it is supplied " + "too: the long-term care premiums inside the supplied subtotal. " + "States that deduct long-term care premiums separately take this " + "amount out of the subtotal first." + ) + + def formula(tax_unit, period, parameters): + if has_input_for_period( + tax_unit.simulation, "itemized_medical_expenses", period + ): + return 0 + premiums = add(tax_unit, period, ["eligible_long_term_care_premiums"]) + excluded = tax_unit( + "itemized_medical_expenses_excluded_long_term_care_premiums", period + ) + return premiums - excluded diff --git a/policyengine_us/variables/household/expense/health/long_term_health_insurance_premiums.py b/policyengine_us/variables/household/expense/health/long_term_health_insurance_premiums.py index c238be6ff58..442a9bd5ab8 100644 --- a/policyengine_us/variables/household/expense/health/long_term_health_insurance_premiums.py +++ b/policyengine_us/variables/household/expense/health/long_term_health_insurance_premiums.py @@ -8,12 +8,19 @@ class long_term_health_insurance_premiums(Variable): unit = USD definition_period = YEAR documentation = ( - "Annual person-paid long-term care insurance premiums excluding pretax " - "payroll deductions and employer-paid premiums. Pretax payroll payments " - "belong only in pre_tax_health_insurance_premiums. The pretax and non- " - "pretax sets are disjoint; total person-paid premiums equal pretax " - "payments plus the applicable non-pretax aggregate. This component must " - "be counted only once when included in a broader premium input." + "Annual long-term care insurance premiums paid for this person's own " + "contract, excluding pretax payroll deductions and employer-paid " + "premiums. Unlike the other premium inputs, which sit on the person " + "who paid them, record these on the insured person, whoever in the " + "tax unit paid them: 26 U.S.C. 213(d)(10) limits the premiums that " + "count as medical care by the insured person's age. The model treats " + "the insured person as the payer, so these premiums enter only that " + "person's own self-employed health insurance deduction. Pretax payroll " + "payments belong only in pre_tax_health_insurance_premiums. The " + "pretax and non-pretax sets are disjoint; total person-paid premiums " + "equal pretax payments plus the applicable non-pretax aggregate. This " + "component must be counted only once when included in a broader " + "premium input." ) # No dataset fills this input, and CMS's health expenditure projections # have no long-term care insurance premium series. 26 U.S.C. diff --git a/policyengine_us/variables/household/expense/health/self_employed_long_term_care_premiums.py b/policyengine_us/variables/household/expense/health/self_employed_long_term_care_premiums.py new file mode 100644 index 00000000000..0eb73a9f3ba --- /dev/null +++ b/policyengine_us/variables/household/expense/health/self_employed_long_term_care_premiums.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class self_employed_long_term_care_premiums(Variable): + value_type = float + entity = Person + label = "Self-employed long-term care insurance premiums" + unit = USD + documentation = ( + "Eligible long-term care premiums that count toward the self-employed " + "health insurance deduction (Form 7206 line 2). 26 U.S.C. " + "162(l)(2)(C) takes in a qualified long-term care insurance contract " + "only up to the 26 U.S.C. 213(d)(10) limit for each person covered. " + "long_term_health_insurance_premiums is recorded on the insured " + "person, and the model treats that person as the payer, so a " + "self-employed person's deduction takes in that person's own contract." + ) + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/162#l_2_C", + "https://www.irs.gov/pub/irs-prior/f7206--2024.pdf#page=1", # Line 2 + ) + defined_for = "is_self_employed" + adds = ["eligible_long_term_care_premiums"]