diff --git a/changelog.d/delaware-mortgage-premium-allocation.fixed.md b/changelog.d/delaware-mortgage-premium-allocation.fixed.md new file mode 100644 index 00000000000..5576841be17 --- /dev/null +++ b/changelog.d/delaware-mortgage-premium-allocation.fixed.md @@ -0,0 +1 @@ +Allocate Delaware mortgage insurance premiums after the federal phase-out to the spouse who paid them, using payments on both qualified residences. diff --git a/changelog.d/ky-principal-residence-mortgage-interest.added.md b/changelog.d/ky-principal-residence-mortgage-interest.added.md new file mode 100644 index 00000000000..2b8e94d7211 --- /dev/null +++ b/changelog.d/ky-principal-residence-mortgage-interest.added.md @@ -0,0 +1 @@ +Add `second_residence_mortgage_interest`, `second_residence_mortgage_points` and `second_residence_mortgage_insurance_premiums` inputs, which join the federal mortgage deduction, and from 2026 cap Kentucky's qualified residence interest deduction at the interest paid on the principal residence (KRS 141.019(2)(j); 2026 Ky. Acts ch. 161 sec. 7, as amended by ch. 198 sec. 48). diff --git a/changelog.d/points-mortgage-insurance-premium-inputs.added.md b/changelog.d/points-mortgage-insurance-premium-inputs.added.md new file mode 100644 index 00000000000..601bba53334 --- /dev/null +++ b/changelog.d/points-mortgage-insurance-premium-inputs.added.md @@ -0,0 +1 @@ +Add `home_mortgage_points` and `mortgage_insurance_premiums` inputs. Points are deducted as home mortgage interest under the acquisition-debt caps. Qualified mortgage insurance premiums are deducted under 26 U.S.C. 163(h)(3)(E) with its AGI phase-out, in 2007–2021 and again from 2026 (P.L. 119-21, sec. 70108). diff --git a/changelog.d/state-mortgage-insurance-exclusions.fixed.md b/changelog.d/state-mortgage-insurance-exclusions.fixed.md new file mode 100644 index 00000000000..1029a6906a6 --- /dev/null +++ b/changelog.d/state-mortgage-insurance-exclusions.fixed.md @@ -0,0 +1 @@ +Exclude federally deductible mortgage insurance premiums from California, New York, Minnesota, Wisconsin, Arkansas, and Iowa itemized deductions or credits in years those states disallow them. diff --git a/changelog.d/state-mortgage-insurance-inclusions.fixed.md b/changelog.d/state-mortgage-insurance-inclusions.fixed.md new file mode 100644 index 00000000000..39f875a54c6 --- /dev/null +++ b/changelog.d/state-mortgage-insurance-inclusions.fixed.md @@ -0,0 +1 @@ +Include qualified mortgage insurance premiums in Montana and Alabama interest deductions, in North Carolina from 2026, and in Hawaii through 2021, using Hawaii adjusted gross income for its phase-out. diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/in_effect.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/in_effect.yaml new file mode 100644 index 00000000000..4a49e0a836b --- /dev/null +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/in_effect.yaml @@ -0,0 +1,27 @@ +description: The IRS treats qualified mortgage insurance premiums as deductible qualified residence interest if this is true. +values: + 1900-01-01: false + # Premiums paid or accrued after December 31, 2006, under contracts issued + # after that date (P.L. 109-432, sec. 419). + 2007-01-01: true + # 163(h)(3)(E)(iv): no deduction for amounts paid or accrued after + # December 31, 2021. + 2022-01-01: false + # P.L. 119-21, sec. 70108: 163(h)(3)(F)(i)(III) turns off clause (iv) for + # taxable years beginning after December 31, 2025. + 2026-01-01: true +metadata: + unit: bool + period: year + label: IRS mortgage insurance premiums deduction in effect + reference: + - title: 26 U.S. Code § 163(h)(3)(E) - Mortgage insurance premiums treated as interest + href: https://www.law.cornell.edu/uscode/text/26/163#h_3_E + - title: 26 U.S. Code § 163(h)(3)(F)(i)(III) + href: https://www.law.cornell.edu/uscode/text/26/163#h_3_F_i_III + - title: Public Law 119-21, sec. 70108 + href: https://www.congress.gov/119/plaws/publ21/PLAW-119publ21.pdf#page=93 + - title: 2021 Instructions for Schedule A (Form 1040), line 8d + href: https://www.irs.gov/pub/irs-prior/i1040sca--2021.pdf#page=9 + - title: 2025 Publication 936, Reminders + href: https://www.irs.gov/pub/irs-pdf/p936.pdf#page=1 diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/phase_out/increment.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/phase_out/increment.yaml new file mode 100644 index 00000000000..d3ac8435652 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/phase_out/increment.yaml @@ -0,0 +1,22 @@ +description: The IRS reduces the qualified mortgage insurance premiums deduction for each of these amounts, or fraction thereof, by which adjusted gross income exceeds the phase-out start, based on filing status. +SINGLE: + 2007-01-01: 1_000 +JOINT: + 2007-01-01: 1_000 +SEPARATE: + 2007-01-01: 500 +HEAD_OF_HOUSEHOLD: + 2007-01-01: 1_000 +SURVIVING_SPOUSE: + 2007-01-01: 1_000 +metadata: + propagate_metadata_to_children: true + breakdown: filing_status + unit: currency-USD + period: year + label: IRS mortgage insurance premiums deduction phase-out increment + reference: + - title: 26 U.S. Code § 163(h)(3)(E)(ii) - Phaseout + href: https://www.law.cornell.edu/uscode/text/26/163#h_3_E_ii + - title: 2021 Instructions for Schedule A (Form 1040), Mortgage Insurance Premiums Deduction Worksheet line 4 + href: https://www.irs.gov/pub/irs-prior/i1040sca--2021.pdf#page=10 diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/phase_out/rate.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/phase_out/rate.yaml new file mode 100644 index 00000000000..a3e7a96271a --- /dev/null +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/phase_out/rate.yaml @@ -0,0 +1,12 @@ +description: The IRS reduces the qualified mortgage insurance premiums deduction by this share of the premiums for each phase-out increment by which adjusted gross income exceeds the phase-out start. +values: + 2007-01-01: 0.1 +metadata: + unit: /1 + period: year + label: IRS mortgage insurance premiums deduction phase-out rate + reference: + - title: 26 U.S. Code § 163(h)(3)(E)(ii) - Phaseout + href: https://www.law.cornell.edu/uscode/text/26/163#h_3_E_ii + - title: 2021 Instructions for Schedule A (Form 1040), Mortgage Insurance Premiums Deduction Worksheet line 5 + href: https://www.irs.gov/pub/irs-prior/i1040sca--2021.pdf#page=10 diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/phase_out/start.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/phase_out/start.yaml new file mode 100644 index 00000000000..357feceef89 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/interest/mortgage_insurance_premiums/phase_out/start.yaml @@ -0,0 +1,22 @@ +description: The IRS reduces the qualified mortgage insurance premiums deduction when adjusted gross income exceeds this amount, based on filing status. +SINGLE: + 2007-01-01: 100_000 +JOINT: + 2007-01-01: 100_000 +SEPARATE: + 2007-01-01: 50_000 +HEAD_OF_HOUSEHOLD: + 2007-01-01: 100_000 +SURVIVING_SPOUSE: + 2007-01-01: 100_000 +metadata: + propagate_metadata_to_children: true + breakdown: filing_status + unit: currency-USD + period: year + label: IRS mortgage insurance premiums deduction phase-out start + reference: + - title: 26 U.S. Code § 163(h)(3)(E)(ii) - Phaseout + href: https://www.law.cornell.edu/uscode/text/26/163#h_3_E_ii + - title: 2021 Instructions for Schedule A (Form 1040), Mortgage Insurance Premiums Deduction Worksheet line 3 + href: https://www.irs.gov/pub/irs-prior/i1040sca--2021.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/disallowed_interest_components.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/disallowed_interest_components.yaml new file mode 100644 index 00000000000..2c58af8b11f --- /dev/null +++ b/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/disallowed_interest_components.yaml @@ -0,0 +1,24 @@ +description: >- + Arkansas excludes these federal interest components from itemized deductions + under its fixed January 1, 2017, Internal Revenue Code conformity. Act 155 of + 2017 section 19 replaced the earlier January 1, 2015, conformity date, but + section 25 applies the change retroactively from tax year 2015. Premiums + therefore remain deductible for 2015 and 2016 under the federal extension + enacted in P.L. 114-113, division Q, section 152. +values: + 1900-01-01: [] + 2017-01-01: + - deductible_mortgage_insurance_premiums +metadata: + unit: list + period: year + label: Arkansas disallowed federal interest components + reference: + - title: "Arkansas Act 155 of 2017, sections 19 and 25, interest conformity and retroactive effective date #page 9-10" + href: https://arkleg.state.ar.us/Home/FTPDocument?path=%2FACTS%2F2017R%2FPublic%2FACT155.pdf#page=9 + - title: Arkansas Act 580 of 2015, section 17, earlier January 1, 2015, interest conformity date + href: https://www.arkleg.state.ar.us/Home/FTPDocument?path=%2FACTS%2F2015%2FPublic%2FACT580.pdf#page=7 + - title: P.L. 114-113, division Q, section 152, mortgage insurance premium extension through 2016 + href: https://www.govinfo.gov/content/pkg/PLAW-114publ113/html/PLAW-114publ113.htm + - title: 2021 Arkansas Individual Income Tax Instructions, AR3 interest expense + href: https://www.dfa.arkansas.gov/wp-content/uploads/2021_AR1000F_and_AR1000NR_Instructions.pdf#page=20 diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/mortgage_insurance_premiums/in_effect.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/mortgage_insurance_premiums/in_effect.yaml new file mode 100644 index 00000000000..22ac47d889e --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/mortgage_insurance_premiums/in_effect.yaml @@ -0,0 +1,16 @@ +description: Hawaii allows this qualified mortgage insurance premium deduction. +values: + 1900-01-01: false + 2007-01-01: true + 2022-01-01: false +metadata: + unit: bool + period: year + label: Hawaii mortgage insurance premiums deduction in effect + reference: + - title: Hawaii Department of Taxation Announcement 2021-01 + href: https://files.hawaii.gov/tax/news/announce/ann21-01.pdf + - title: Hawaii 2021 Digest of Tax Measures, IRC 163(h)(3)(E)(iv)(I) + href: https://files.hawaii.gov/tax/legal/legis/2021/21digest.pdf#page=9 + - title: HRS 235-2.4(j), IRC 163(h)(3)(F) is inoperative + href: https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/disallowed_interest_components.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/disallowed_interest_components.yaml new file mode 100644 index 00000000000..5da9f26263f --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/disallowed_interest_components.yaml @@ -0,0 +1,13 @@ +description: Iowa excludes these federal interest components from its itemized deductions. +values: + 1900-01-01: [] + 2018-01-01: + - deductible_mortgage_insurance_premiums + 2020-01-01: [] +metadata: + unit: list + period: year + label: Iowa disallowed federal interest components + reference: + - title: Iowa DOR Nonconformity, Taxpayer Certainty and Disaster Tax Relief Act of 2019, section 102 + href: https://revenue.iowa.gov/taxes/tax-guidance/individual-income-tax/iowa-nonconformity-taxpayer-certainty-and-disaster-tax-relief-act-2019 diff --git a/policyengine_us/parameters/gov/states/ky/tax/income/deductions/itemized/principal_residence_interest_only.yaml b/policyengine_us/parameters/gov/states/ky/tax/income/deductions/itemized/principal_residence_interest_only.yaml new file mode 100644 index 00000000000..c4bd5c9d061 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/tax/income/deductions/itemized/principal_residence_interest_only.yaml @@ -0,0 +1,17 @@ +description: Kentucky limits the qualified residence interest deduction to the principal residence if this is true. +values: + # Before HB 757, KRS 141.019(2) ended at paragraph (i) and had no qualified residence interest limit. + 2018-01-01: false + # HB 757 (2026 Ky. Acts ch. 161) sec. 7, as amended by HB 869 (ch. 198) sec. 48, adds KRS 141.019(2)(j) for taxable years beginning on or after January 1, 2026. + 2026-01-01: true +metadata: + unit: bool + period: year + label: Kentucky qualified residence interest limited to the principal residence + reference: + - title: KRS 141.019(2)(j), net income of taxpayers other than corporations + href: https://apps.legislature.ky.gov/law/statutes/statute.aspx?id=57914#page=4 + - title: 2026 Ky. Acts ch. 161 (HB 757), sec. 7, KRS 141.019(2)(j) + href: https://apps.legislature.ky.gov/law/acts/26RS/documents/0161.pdf#page=13 + - title: 2026 Ky. Acts ch. 198 (HB 869), sec. 48, amending HB 757 sec. 7 + href: https://apps.legislature.ky.gov/law/acts/26RS/documents/0198.pdf#page=74 diff --git a/policyengine_us/parameters/gov/states/mn/tax/income/deductions/itemized/disallowed_interest_components.yaml b/policyengine_us/parameters/gov/states/mn/tax/income/deductions/itemized/disallowed_interest_components.yaml new file mode 100644 index 00000000000..c491b8f732d --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/tax/income/deductions/itemized/disallowed_interest_components.yaml @@ -0,0 +1,15 @@ +description: Minnesota excludes these federal interest components from its itemized deductions. +values: + 1900-01-01: [] + # Section 290.0122 expressly excludes premiums from 2019. + 2019-01-01: + - deductible_mortgage_insurance_premiums +metadata: + unit: list + period: year + label: Minnesota disallowed federal interest components + reference: + - title: Minnesota Statutes 290.0122, subdivision 5(3) + href: https://www.revisor.mn.gov/statutes/cite/290.0122#stat.290.0122.5 + - title: Minnesota 2019 First Special Session Laws, chapter 6, article 1, section 16 + href: https://www.revisor.mn.gov/laws/2019/1/Session+Law/Chapter/6/ diff --git a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/mortgage_insurance_premiums_allowed.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/mortgage_insurance_premiums_allowed.yaml new file mode 100644 index 00000000000..a509b226da4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/mortgage_insurance_premiums_allowed.yaml @@ -0,0 +1,15 @@ +description: North Carolina includes deductible qualified mortgage insurance premiums in its mortgage interest and real estate tax deduction if this is true. +values: + 1900-01-01: false + 2026-01-01: true +metadata: + unit: bool + period: year + label: North Carolina qualified mortgage insurance premiums allowed + reference: + - title: North Carolina General Statutes 105-153.5(a)(2)b + href: https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html + - title: North Carolina Session Law 2026-31, section 12(a) + href: https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-31.pdf#page=42 + - title: 2021 Form D-401, Form D-400 Schedule A line 1 + href: https://www.ncdor.gov/2021-d-401-individual-income-tax-instructions/open#page=20 diff --git a/policyengine_us/parameters/gov/states/ny/tax/income/deductions/itemized/disallowed_interest_components.yaml b/policyengine_us/parameters/gov/states/ny/tax/income/deductions/itemized/disallowed_interest_components.yaml new file mode 100644 index 00000000000..d2a6e29959c --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/tax/income/deductions/itemized/disallowed_interest_components.yaml @@ -0,0 +1,22 @@ +description: >- + New York excludes these federal interest components from its itemized deduction. + The 2026 premium exclusion is an inference from New York's pre-TCJA computation + and P.L. 119-21 section 70108 placing the restoration in IRC 163(h)(3)(F). + New York has not yet published 2026 forms. +values: + 1900-01-01: [] + 2021-01-01: + - deductible_mortgage_insurance_premiums +metadata: + unit: list + period: year + label: New York disallowed federal interest components + reference: + - title: 2021 Instructions for Form IT-196, Interest you paid + href: https://www.tax.ny.gov/pdf/2021/inc/it196i_2021.pdf#page=2 + - title: New York 2020 forms corrections, 2018 and 2019 IT-196 mortgage insurance deductions + href: https://www.tax.ny.gov/forms/updates/forms_updates_2020.htm + - title: New York Tax Law 615(a), pre-TCJA itemized deduction computation + href: https://www.nysenate.gov/legislation/laws/TAX/615 + - title: P.L. 119-21, section 70108, mortgage insurance premium restoration + href: https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=93 diff --git a/policyengine_us/tests/core/test_mortgage_points_and_premiums_properties.py b/policyengine_us/tests/core/test_mortgage_points_and_premiums_properties.py new file mode 100644 index 00000000000..6d81dc8a61a --- /dev/null +++ b/policyengine_us/tests/core/test_mortgage_points_and_premiums_properties.py @@ -0,0 +1,376 @@ +"""Vectorized mortgage invariants that household YAML examples cannot express. + +One module fixture builds one Simulation for 300 tax units in 2021, 2025 and +2026. Seeded draws and paired tax units exercise conservation, bounds, +interest/points transfers, an independent integer-cents Schedule A worksheet, +AGI monotonicity and compatibility with zero or omitted new inputs. Paired +units avoid changing inputs or sharing mutable simulations between tests; +the fixture exposes only read-only arrays after calculation. + +Reference: 2021 Instructions for Schedule A, Mortgage Insurance Premiums +Deduction Worksheet, lines 1-7 (page A-10), and Publication 936, Table 1, +line 13 instructions. YAML covers the individual thresholds and 2022-2024. +""" + +import numpy as np +import pytest + +from policyengine_us import Simulation +from policyengine_us.variables.household.expense.tax_unit.mortgage_interest_structure import ( + _debt_by_vintage, + _qualified_loan_limit, +) + +N = 60 +YEARS = (2021, 2025, 2026) +STATUSES = ("SINGLE", "JOINT", "SEPARATE", "HEAD_OF_HOUSEHOLD", "SURVIVING_SPOUSE") +SCENARIOS = ("base", "moved", "richer", "zero", "omitted") +PERSON_INPUTS = ( + "home_mortgage_interest", + "home_mortgage_points", + "mortgage_insurance_premiums", + "investment_interest_expense", +) +PERSON_OUTPUTS = ( + "home_mortgage_interest_share", + "deductible_mortgage_interest", + "non_deductible_mortgage_interest", + "mortgage_interest", + "deductible_interest_expense", +) +UNIT_OUTPUTS = ( + "home_mortgage_interest_tax_unit", + "deductible_mortgage_interest_tax_unit", + "non_deductible_mortgage_interest_tax_unit", + "deductible_mortgage_insurance_premiums", + "interest_deduction", +) +# Mortgage outputs are float32. Allow accumulated rounding in person sums +# and transfers, scaling with the operands, not with a small difference. +RTOL = 1e-6 +ATOL = 0.01 + + +def _close(actual, expected): + np.testing.assert_allclose(actual, expected, rtol=RTOL, atol=ATOL) + + +def _draw(): + rng = np.random.default_rng(936_2021) + statuses = rng.choice(STATUSES, N) + statuses[: 3 * len(STATUSES)] = np.tile(STATUSES, 3) + spouse = statuses == "JOINT" + start_cents = np.where(statuses == "SEPARATE", 5_000_000, 10_000_000) + increment_cents = np.where(statuses == "SEPARATE", 50_000, 100_000) + agi_cents = rng.integers(0, 15_000_001, N) + # Include exact multiples and one cent on either side of increments. + boundary = np.arange(N) % 3 != 2 + steps = rng.integers(0, 12, N) + offsets = rng.choice((-1, 0, 1), N) + agi_cents[boundary] = (start_cents + steps * increment_cents + offsets)[boundary] + # Guarantee an exact increment and nearby cents in every filing status. + for block, (step, offset) in enumerate(((1, 0), (1, 1), (9, -1))): + rows = slice(block * len(STATUSES), (block + 1) * len(STATUSES)) + agi_cents[rows] = start_cents[rows] + step * increment_cents[rows] + offset + inputs = {} + for name, upper in zip(PERSON_INPUTS, (40_000, 10_000, 12_000, 4_000)): + # Integer premiums make the independent worksheet entirely integer + # arithmetic through line 7; other expenses include cents. + if name == "mortgage_insurance_premiums": + amount = rng.integers(0, upper + 1, (N, 2)).astype(float) + else: + amount = rng.integers(0, upper * 100 + 1, (N, 2)) / 100 + amount[rng.random((N, 2)) < 0.2] = 0 + amount[~spouse, 1] = 0 + inputs[name] = amount + # Exercise zero-interest, points-only and all-zero mortgage pools. + inputs["home_mortgage_interest"][::5] = 0 + inputs["home_mortgage_points"][::10] = 0 + first = rng.integers(1, 1_500_001, N).astype(float) + second = rng.integers(1, 750_001, N).astype(float) + first[::6] = 0 + second[::3] = 0 + transfer_fraction = rng.uniform(0, 1, (N, 2)) + # Include complete transfers, so interest can vanish and invoke fallback. + transfer_fraction[::3] = 0 + transfer_fraction[1::3] = 1 + return { + **inputs, + "filing_status": statuses, + "adjusted_gross_income": agi_cents / 100, + "agi_cents": agi_cents, + "raised_agi_cents": agi_cents + rng.integers(1, 2_000_001, N), + "first_home_mortgage_balance": first, + "second_home_mortgage_balance": second, + "first_home_mortgage_origination_year": rng.choice( + (0, 1986, 1987, 1988, 2010, 2016, 2017, 2018, 2020), N + ), + "second_home_mortgage_origination_year": rng.choice( + (0, 1986, 1987, 1988, 2010, 2016, 2017, 2018, 2020), N + ), + "transfer_fraction": transfer_fraction, + } + + +@pytest.fixture(scope="module") +def mortgage_results(): + draws = _draw() + people, tax_units, households = {}, {}, {} + unit_indices, person_indices, person_to_unit = {}, {}, [] + periods = tuple(map(str, YEARS)) + + def annual(value): + return dict.fromkeys(periods, value) + + for scenario in SCENARIOS: + unit_indices[scenario], person_indices[scenario] = [], [] + for i in range(N): + unit_index = len(tax_units) + unit_indices[scenario].append(unit_index) + members = [] + count = 2 if draws["filing_status"][i] == "JOINT" else 1 + for j in range(count): + person_index = len(people) + person_indices[scenario].append(person_index) + person_to_unit.append(unit_index) + person_id = f"p{person_index}" + members.append(person_id) + person = {"age": annual(50 - j)} + total = sum(draws[name][i, j] for name in PERSON_INPUTS[:2]) + for name in PERSON_INPUTS: + value = float(draws[name][i, j]) + if scenario == "moved": + fraction = draws["transfer_fraction"][i, j] + if name == "home_mortgage_interest": + value = total * fraction + elif name == "home_mortgage_points": + value = total * (1 - fraction) + if scenario in ("zero", "omitted") and name in ( + "home_mortgage_points", + "mortgage_insurance_premiums", + ): + if scenario == "omitted": + continue + value = 0.0 + person[name] = annual(value) + people[person_id] = person + unit = { + "members": members, + "filing_status": annual(str(draws["filing_status"][i])), + "adjusted_gross_income": annual( + float( + draws[ + "raised_agi_cents" if scenario == "richer" else "agi_cents" + ][i] + / 100 + ) + ), + **{ + name: annual(float(draws[name][i])) + for name in ( + "first_home_mortgage_balance", + "second_home_mortgage_balance", + ) + }, + **{ + name: annual(int(draws[name][i])) + for name in ( + "first_home_mortgage_origination_year", + "second_home_mortgage_origination_year", + ) + }, + } + tax_units[f"t{unit_index}"] = unit + households[f"h{unit_index}"] = {"members": members} + + simulation = Simulation( + situation={"people": people, "tax_units": tax_units, "households": households} + ) + results = {} + for year in YEARS: + results[year] = {} + for name in UNIT_OUTPUTS + PERSON_OUTPUTS + PERSON_INPUTS: + values = simulation.calculate(name, str(year)).copy() + values.flags.writeable = False + results[year][name] = values + # Use current main's vintage-based debt limits to reconstruct the + # mortgage computation without points or premiums. + p = simulation.tax_benefit_system.parameters( + f"{year}-01-01" + ).gov.irs.deductions.itemized.interest.mortgage + pre_cap = p.pre_tcja_cap[draws["filing_status"]] + post_cap = p.cap[draws["filing_status"]] + first = draws["first_home_mortgage_balance"] + second = draws["second_home_mortgage_balance"] + debt = _debt_by_vintage( + [first, second], + [ + draws["first_home_mortgage_origination_year"], + draws["second_home_mortgage_origination_year"], + ], + p.grandfathered_origination_year, + p.pre_tcja_origination_year, + ) + limited = _qualified_loan_limit(*debt, pre_cap, post_cap) + share = np.minimum( + 1, + np.divide( + limited, first + second, out=np.ones(N), where=first + second > 0 + ), + ) + share.flags.writeable = False + results[year]["baseline_deductible_share"] = share + # Do not retain the Simulation, its mutable caches or its policy tree. + return draws, results, unit_indices, person_indices, np.array(person_to_unit) + + +def _sum_people(values, person_to_unit): + return np.bincount(person_to_unit, weights=values, minlength=N * len(SCENARIOS)) + + +def test_mortgage_pool_and_person_allocations_are_conserved(mortgage_results): + _, results, _, _, membership = mortgage_results + for values in results.values(): + pool = values["home_mortgage_interest_tax_unit"] + _sum_people( + values["home_mortgage_points"], membership + ) + deductible = values["deductible_mortgage_interest_tax_unit"] + nondeductible = values["non_deductible_mortgage_interest_tax_unit"] + _close(deductible + nondeductible, pool) + _close( + _sum_people(values["deductible_mortgage_interest"], membership), deductible + ) + _close( + _sum_people(values["non_deductible_mortgage_interest"], membership), + nondeductible, + ) + _close(_sum_people(values["mortgage_interest"], membership), pool) + + +def test_outputs_are_nonnegative_and_deductions_are_bounded(mortgage_results): + _, results, _, _, membership = mortgage_results + for values in results.values(): + for name in UNIT_OUTPUTS + PERSON_OUTPUTS: + assert np.all(values[name] >= 0), name + pool = values["home_mortgage_interest_tax_unit"] + _sum_people( + values["home_mortgage_points"], membership + ) + assert np.all(values["deductible_mortgage_interest_tax_unit"] <= pool + ATOL) + premiums = _sum_people(values["mortgage_insurance_premiums"], membership) + assert np.all(values["deductible_mortgage_insurance_premiums"] <= premiums) + + +def test_points_and_interest_are_interchangeable(mortgage_results): + _, results, units, persons, _ = mortgage_results + for values in results.values(): + for name in ( + "deductible_mortgage_interest_tax_unit", + "non_deductible_mortgage_interest_tax_unit", + ): + _close(values[name][units["moved"]], values[name][units["base"]]) + # Moving amounts within a person also preserves their allocation. + for name in ("deductible_mortgage_interest", "mortgage_interest"): + _close(values[name][persons["moved"]], values[name][persons["base"]]) + + +def _premium_worksheet_cents(premiums_cents, agi_cents, statuses): + """2021 Schedule A lines 1-7, independently using integer cents. + + Line 4 rounds excess AGI UP to a $1,000/$500 multiple; line 5 divides + that rounded dollar excess by $10,000/$5,000, capped at 1. Lines 6-7 + multiply premiums by the fraction and subtract the reduction. + """ + deductions = [] + for line_1, line_2, status in zip(premiums_cents, agi_cents, statuses): + separate = status == "SEPARATE" + line_3 = 5_000_000 if separate else 10_000_000 + if line_2 <= line_3: + deductions.append(int(line_1)) + continue + multiple = 50_000 if separate else 100_000 + line_4 = ((int(line_2) - line_3 + multiple - 1) // multiple) * multiple + denominator = 500_000 if separate else 1_000_000 + numerator = min(line_4, denominator) + line_6 = int(line_1) * numerator // denominator + deductions.append(int(line_1) - line_6) + return np.array(deductions) + + +def test_premiums_match_the_independent_integer_worksheet(mortgage_results): + draws, results, units, _, membership = mortgage_results + for year, values in results.items(): + premiums_cents = np.rint( + _sum_people(values["mortgage_insurance_premiums"], membership) * 100 + ).astype(np.int64) + for scenario in SCENARIOS: + indices = units[scenario] + agi = draws["raised_agi_cents" if scenario == "richer" else "agi_cents"] + expected = ( + _premium_worksheet_cents( + premiums_cents[indices], agi, draws["filing_status"] + ) + / 100 + ) + if year == 2025: + expected = np.zeros(N) + _close(values["deductible_mortgage_insurance_premiums"][indices], expected) + + +def test_premium_deduction_never_increases_with_agi(mortgage_results): + _, results, units, _, _ = mortgage_results + for year, values in results.items(): + premiums = values["deductible_mortgage_insurance_premiums"] + assert np.all(premiums[units["richer"]] <= premiums[units["base"]]) + if year != 2025: + assert np.any(premiums[units["richer"]] < premiums[units["base"]]) + + +def test_zero_new_inputs_preserve_the_previous_computation(mortgage_results): + _, results, units, persons, membership = mortgage_results + for values in results.values(): + for name in UNIT_OUTPUTS: + np.testing.assert_array_equal( + values[name][units["zero"]], values[name][units["omitted"]] + ) + for name in PERSON_OUTPUTS: + np.testing.assert_array_equal( + values[name][persons["zero"]], values[name][persons["omitted"]] + ) + for scenario in ("zero", "omitted"): + index = units[scenario] + person_index = persons[scenario] + interest = values["home_mortgage_interest_tax_unit"][index] + person_interest = values["home_mortgage_interest"][person_index] + totals = _sum_people(values["home_mortgage_interest"], membership)[index] + local_membership = membership[person_index] - index[0] + filer_count = np.bincount(local_membership, minlength=N) + allocation = np.divide( + person_interest, + totals[local_membership], + out=1.0 / filer_count[local_membership], + where=totals[local_membership] > 0, + ) + _close(values["home_mortgage_interest_share"][person_index], allocation) + deductible = values["deductible_mortgage_interest_tax_unit"][index] + nondeductible = values["non_deductible_mortgage_interest_tax_unit"][index] + _close(deductible, interest * values["baseline_deductible_share"]) + _close(deductible + nondeductible, interest) + _close( + values["deductible_mortgage_interest"][person_index], + deductible[local_membership] * allocation, + ) + _close( + values["non_deductible_mortgage_interest"][person_index], + nondeductible[local_membership] * allocation, + ) + _close( + values["mortgage_interest"][person_index], + interest[local_membership] * allocation, + ) + previous_interest = _sum_people( + values["deductible_interest_expense"], membership + )[index] + _close(values["interest_deduction"][index], previous_interest) + np.testing.assert_array_equal( + values["deductible_mortgage_insurance_premiums"][index], np.zeros(N) + ) diff --git a/policyengine_us/tests/core/test_second_residence_mortgage_properties.py b/policyengine_us/tests/core/test_second_residence_mortgage_properties.py new file mode 100644 index 00000000000..96e1adbfd72 --- /dev/null +++ b/policyengine_us/tests/core/test_second_residence_mortgage_properties.py @@ -0,0 +1,330 @@ +"""Vectorized invariants for second-residence mortgage inputs and Kentucky. + +One module fixture builds one Simulation of Kentucky tax units in 2025 and +2026. Four paired scenarios share identical draws: + +- base: separate principal- and second-residence interest, points and premiums; +- pooled: every second-residence amount moved onto the principal residence; +- principal_only: second-residence inputs set to zero; +- omitted: second-residence inputs left out entirely. + +A fifth scenario, equal_rate, gives each tax unit a principal and a second +mortgage at one common rate, so the result can be checked against an +independent recomputation on the principal residence alone. + +Federal law pools both qualified residences (26 U.S.C. 163(h)(5)(A)), so the +federal deduction cannot depend on the residence split. From 2026, KRS +141.019(2)(j) caps Kentucky's deduction at the interest paid on the +principal residence. +""" + +import numpy as np +import pytest + +from policyengine_us import Simulation + +N = 48 +YEARS = (2025, 2026) +STATUSES = ("SINGLE", "JOINT", "SEPARATE", "HEAD_OF_HOUSEHOLD") +SCENARIOS = ("base", "pooled", "principal_only", "omitted", "equal_rate") +# Principal-residence input, its second-residence counterpart, upper draw. +PAIRS = ( + ("home_mortgage_interest", "second_residence_mortgage_interest", 40_000), + ("home_mortgage_points", "second_residence_mortgage_points", 6_000), + ( + "mortgage_insurance_premiums", + "second_residence_mortgage_insurance_premiums", + 5_000, + ), +) +UNIT_OUTPUTS = ( + "home_mortgage_interest_tax_unit", + "deductible_mortgage_interest_tax_unit", + "non_deductible_mortgage_interest_tax_unit", + "deductible_mortgage_insurance_premiums", + "interest_deduction", + "itemized_deductions_less_salt", + "ky_disallowed_second_residence_interest", + "ky_itemized_deductions_unit", +) +PERSON_OUTPUTS = ( + "home_mortgage_interest_share", + "deductible_mortgage_interest", + "mortgage_interest", +) +FEDERAL_UNIT_OUTPUTS = ( + "deductible_mortgage_interest_tax_unit", + "non_deductible_mortgage_interest_tax_unit", + "deductible_mortgage_insurance_premiums", + "interest_deduction", + "itemized_deductions_less_salt", +) +# 26 U.S.C. 163(h)(3)(F)(i)(II): $750,000 ($375,000 married filing +# separately) for debt incurred after December 15, 2017. +POST_2017_LIMIT = {"SEPARATE": 375_000} +DEFAULT_POST_2017_LIMIT = 750_000 +RTOL = 1e-6 +ATOL = 0.01 + + +def _close(actual, expected): + np.testing.assert_allclose(actual, expected, rtol=RTOL, atol=ATOL) + + +def _draw(): + rng = np.random.default_rng(141_019) + statuses = rng.choice(STATUSES, N) + statuses[: 2 * len(STATUSES)] = np.tile(STATUSES, 2) + spouse = statuses == "JOINT" + amounts = {} + for principal, second, upper in PAIRS: + for name in (principal, second): + amount = rng.integers(0, upper * 100 + 1, (N, 2)) / 100 + amount[rng.random((N, 2)) < 0.25] = 0 + amount[~spouse, 1] = 0 + amounts[name] = amount + # Units with only second-residence interest, and with no mortgage at all. + amounts["home_mortgage_interest"][::7] = 0 + for principal, second, _ in PAIRS: + amounts[principal][::11] = 0 + amounts[second][::11] = 0 + first = rng.integers(1, 1_500_001, N).astype(float) + second_balance = rng.integers(1, 750_001, N).astype(float) + # Every fourth unit reports no balances, so the pool is fully deductible. + first[::4] = 0 + second_balance[::4] = 0 + # Equal-rate units: principal debt on the first loan, second-residence + # debt on the second, both incurred after 2017 at one rate. + equal_principal = rng.integers(1, 1_200_001, N).astype(float) + equal_second = rng.integers(0, 800_001, N).astype(float) + equal_rate = rng.uniform(0.02, 0.09, N) + # AGI on both sides of the premium phase-out range. + agi = rng.integers(0, 15_000_001, N) / 100 + return { + **amounts, + "filing_status": statuses, + "adjusted_gross_income": agi, + "first_home_mortgage_balance": first, + "second_home_mortgage_balance": second_balance, + "first_home_mortgage_origination_year": rng.choice((2010, 2016, 2020), N), + "second_home_mortgage_origination_year": rng.choice((2010, 2016, 2020), N), + "equal_principal": equal_principal, + "equal_second": equal_second, + "equal_rate": equal_rate, + } + + +def _person_inputs(draws, scenario, i, j): + if scenario == "equal_rate": + if j > 0: + return {} + return { + "home_mortgage_interest": float( + draws["equal_rate"][i] * draws["equal_principal"][i] + ), + "second_residence_mortgage_interest": float( + draws["equal_rate"][i] * draws["equal_second"][i] + ), + } + inputs = {} + for principal, second, _ in PAIRS: + principal_value = float(draws[principal][i, j]) + second_value = float(draws[second][i, j]) + if scenario == "pooled": + principal_value += second_value + second_value = 0.0 + elif scenario in ("principal_only", "omitted"): + second_value = 0.0 + inputs[principal] = principal_value + if scenario != "omitted": + inputs[second] = second_value + return inputs + + +def _balances(draws, scenario, i): + if scenario == "equal_rate": + return { + "first_home_mortgage_balance": float(draws["equal_principal"][i]), + "second_home_mortgage_balance": float(draws["equal_second"][i]), + "first_home_mortgage_origination_year": 2020, + "second_home_mortgage_origination_year": 2020, + } + return { + name: (float if "balance" in name else int)(draws[name][i]) + for name in ( + "first_home_mortgage_balance", + "second_home_mortgage_balance", + "first_home_mortgage_origination_year", + "second_home_mortgage_origination_year", + ) + } + + +@pytest.fixture(scope="module") +def results(): + draws = _draw() + people, tax_units, households = {}, {}, {} + units, persons = {}, {} + periods = tuple(map(str, YEARS)) + + def annual(value): + return dict.fromkeys(periods, value) + + for scenario in SCENARIOS: + units[scenario], persons[scenario] = [], [] + for i in range(N): + unit_index = len(tax_units) + units[scenario].append(unit_index) + members = [] + count = 2 if draws["filing_status"][i] == "JOINT" else 1 + for j in range(count): + person_index = len(people) + persons[scenario].append(person_index) + person_id = f"p{person_index}" + members.append(person_id) + person = {"age": annual(50 - j)} + for name, value in _person_inputs(draws, scenario, i, j).items(): + person[name] = annual(value) + people[person_id] = person + tax_units[f"t{unit_index}"] = { + "members": members, + "filing_status": annual(str(draws["filing_status"][i])), + "adjusted_gross_income": annual( + float(draws["adjusted_gross_income"][i]) + ), + **{ + name: annual(value) + for name, value in _balances(draws, scenario, i).items() + }, + } + households[f"h{unit_index}"] = { + "members": members, + "state_code": annual("KY"), + } + + simulation = Simulation( + situation={"people": people, "tax_units": tax_units, "households": households} + ) + values = {} + for year in YEARS: + values[year] = {} + for name in UNIT_OUTPUTS + PERSON_OUTPUTS: + array = simulation.calculate(name, str(year)).copy() + array.flags.writeable = False + values[year][name] = array + # Do not retain the Simulation, its mutable caches or its policy tree. + return ( + draws, + values, + {k: np.array(v) for k, v in units.items()}, + {k: np.array(v) for k, v in persons.items()}, + ) + + +def test_residence_split_does_not_change_federal_amounts(results): + _, values, units, persons = results + for year_values in values.values(): + for name in FEDERAL_UNIT_OUTPUTS: + _close(year_values[name][units["base"]], year_values[name][units["pooled"]]) + # Moving amounts within a person keeps that person's allocation. + for name in PERSON_OUTPUTS: + _close( + year_values[name][persons["base"]], + year_values[name][persons["pooled"]], + ) + + +def test_kentucky_disallowance_is_bounded(results): + _, values, _, _ = results + np.testing.assert_array_equal( + values[2025]["ky_disallowed_second_residence_interest"], 0 + ) + disallowed = values[2026]["ky_disallowed_second_residence_interest"] + assert np.all(disallowed >= 0) + assert np.all(disallowed <= values[2026]["interest_deduction"] + ATOL) + assert np.any(disallowed > 0) + + +def test_kentucky_lies_between_principal_only_and_pooled(results): + # Adding second-residence amounts cannot lower Kentucky's deduction, and + # moving them onto the principal residence cannot lower it either. + _, values, units, _ = results + kentucky = values[2026]["ky_itemized_deductions_unit"] + assert np.all(kentucky[units["principal_only"]] <= kentucky[units["base"]] + ATOL) + assert np.all(kentucky[units["base"]] <= kentucky[units["pooled"]] + ATOL) + + +def test_kentucky_ignores_second_residence_when_the_pool_is_fully_deductible( + results, +): + draws, values, units, _ = results + no_balance = (draws["first_home_mortgage_balance"] == 0) & ( + draws["second_home_mortgage_balance"] == 0 + ) + assert no_balance.sum() >= N // 4 + kentucky = values[2026]["ky_itemized_deductions_unit"] + _close( + kentucky[units["base"]][no_balance], + kentucky[units["principal_only"]][no_balance], + ) + + +def test_kentucky_matches_a_principal_residence_recomputation_at_equal_rates( + results, +): + # Independent statutory computation: with one rate r, the federal + # deduction is r * min(limit, P + S), and the deduction on the principal + # residence alone is r * min(limit, P). + draws, values, units, _ = results + limit = np.array( + [ + POST_2017_LIMIT.get(status, DEFAULT_POST_2017_LIMIT) + for status in draws["filing_status"] + ] + ) + rate = draws["equal_rate"] + principal, second = draws["equal_principal"], draws["equal_second"] + index = units["equal_rate"] + year_values = values[2026] + _close( + year_values["interest_deduction"][index], + rate * np.minimum(limit, principal + second), + ) + _close( + year_values["interest_deduction"][index] + - year_values["ky_disallowed_second_residence_interest"][index], + rate * np.minimum(limit, principal), + ) + # The draws exercise both sides of the limit. + assert np.any(principal > limit) and np.any(principal + second > limit) + assert np.any((principal < limit) & (principal + second > limit)) + + +def test_kentucky_follows_the_federal_deduction_before_2026(results): + _, values, units, _ = results + before = values[2025] + _close( + before["ky_itemized_deductions_unit"][units["base"]] + - before["ky_itemized_deductions_unit"][units["principal_only"]], + before["interest_deduction"][units["base"]] + - before["interest_deduction"][units["principal_only"]], + ) + + +def test_omitted_and_zero_second_residence_inputs_match(results): + _, values, units, persons = results + for year_values in values.values(): + for name in UNIT_OUTPUTS: + np.testing.assert_array_equal( + year_values[name][units["principal_only"]], + year_values[name][units["omitted"]], + ) + for name in PERSON_OUTPUTS: + np.testing.assert_array_equal( + year_values[name][persons["principal_only"]], + year_values[name][persons["omitted"]], + ) + np.testing.assert_array_equal( + year_values["ky_disallowed_second_residence_interest"][units["omitted"]], + 0, + ) diff --git a/policyengine_us/tests/core/test_uprating_series.py b/policyengine_us/tests/core/test_uprating_series.py index 4ca3e841381..8fc14e84fb2 100644 --- a/policyengine_us/tests/core/test_uprating_series.py +++ b/policyengine_us/tests/core/test_uprating_series.py @@ -122,6 +122,11 @@ "homeowners_association_fees": CPI_U, "pre_subsidy_transportation_expense": CPI_U, "home_mortgage_interest": CPI_U, + "home_mortgage_points": CPI_U, + "mortgage_insurance_premiums": CPI_U, + "second_residence_mortgage_interest": CPI_U, + "second_residence_mortgage_points": CPI_U, + "second_residence_mortgage_insurance_premiums": CPI_U, "student_loan_interest": CPI_U, "investment_interest_expense": CPI_U, # Still on the default. diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/deductible_mortgage_interest.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/deductible_mortgage_interest.yaml index 4e844efa46b..3b89ebfed28 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/deductible_mortgage_interest.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/deductible_mortgage_interest.yaml @@ -108,6 +108,7 @@ home_mortgage_interest_tax_unit: 45_000 # 45,000 x 750,000 / 900,000 = 37,500, split 2:1 by reported interest deductible_mortgage_interest_tax_unit: 37_500 + non_deductible_mortgage_interest_tax_unit: 7_500 deductible_mortgage_interest: [25_000, 12_500] non_deductible_mortgage_interest: [5_000, 2_500] @@ -179,3 +180,140 @@ # 60,000 x 1,000,000 / 1,200,000 deductible_mortgage_interest_tax_unit: 50_000 non_deductible_mortgage_interest_tax_unit: 10_000 + + +- name: Points take the same deductible share as post-TCJA mortgage interest + period: 2026 + input: + filing_status: SINGLE + home_mortgage_interest: 40_000 + home_mortgage_points: 4_000 + first_home_mortgage_balance: 1_000_000 + first_home_mortgage_origination_year: 2020 + output: + # Pub. 936 (2025), Table 1: lines 11/12 give 750,000/1,000,000 + # = 0.750 on line 14. Line 15 interest = 40,000 x 0.750 = 30,000; + # line 13 instructions give points = 4,000 x 0.750 = 3,000. + # Combined deduction = 33,000; remainder = 44,000 - 33,000 = 11,000. + # Mortgage interest before the cap = 40,000 + 4,000 = 44,000. + deductible_mortgage_interest_tax_unit: 33_000 + non_deductible_mortgage_interest_tax_unit: 11_000 + deductible_mortgage_interest: 33_000 + non_deductible_mortgage_interest: 11_000 + mortgage_interest: 44_000 + +- name: Points are fully deductible when no mortgage balance is supplied + period: 2026 + input: + filing_status: SINGLE + home_mortgage_interest: 12_000 + home_mortgage_points: 2_000 + output: + # No balance is reported, so assume debt within the Pub. 936 limits: + # Table 1's line 12 stop rule makes the deductible fraction 1. + # Interest = 12,000 x 1; points = 2,000 x 1; total = 14,000; + # non-deductible amount = 14,000 - 14,000 = 0. + deductible_mortgage_interest_tax_unit: 14_000 + non_deductible_mortgage_interest_tax_unit: 0 + deductible_mortgage_interest: 14_000 + mortgage_interest: 14_000 + +- name: Points on acquisition debt grandfathered under the pre-TCJA cap are fully deductible + period: 2026 + input: + filing_status: SINGLE + home_mortgage_interest: 36_000 + home_mortgage_points: 4_000 + first_home_mortgage_balance: 900_000 + first_home_mortgage_origination_year: 2016 + output: + # Pub. 936 Table 1: line 2 = 900,000; line 3 = 1,000,000; + # line 6 = min(1,000,000, 900,000) = 900,000. With no post-2017 + # debt, line 11 = 900,000 = line 12, so the deductible fraction is 1. + # Interest = 36,000; points = 4,000 x 1 = 4,000; total = 40,000. + # Non-deductible amount = 40,000 - 40,000 = 0. + deductible_mortgage_interest_tax_unit: 40_000 + non_deductible_mortgage_interest_tax_unit: 0 + deductible_mortgage_interest: 40_000 + mortgage_interest: 40_000 + +- name: Joint return allocates spouse-reported points with head-reported interest + period: 2026 + input: + people: + head: + age: 45 + home_mortgage_interest: 30_000 + spouse: + age: 44 + home_mortgage_points: 10_000 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + first_home_mortgage_balance: 1_000_000 + first_home_mortgage_origination_year: 2020 + output: + # Pub. 936 Table 1 line 14 = 750,000/1,000,000 = 0.750. + # Interest = 30,000 x 0.750 = 22,500; deductible points = + # 10,000 x 0.750 = 7,500. Deduction = 30,000; remainder = 10,000. + # Reported interest + points = 40,000, allocated 30,000/40,000 = + # 0.75 to head and 10,000/40,000 = 0.25 to spouse. Deductible + # amounts 22,500 + 7,500 = 30,000; remainders 7,500 + 2,500 = 10,000. + # Gross mortgage amounts = 22,500 + 7,500 = 30,000 for head and + # 7,500 + 2,500 = 10,000 for spouse, summing to 40,000. + people: + head: + home_mortgage_interest_share: 0.75 + deductible_mortgage_interest: 22_500 + non_deductible_mortgage_interest: 7_500 + mortgage_interest: 30_000 + spouse: + home_mortgage_interest_share: 0.25 + deductible_mortgage_interest: 7_500 + non_deductible_mortgage_interest: 2_500 + mortgage_interest: 10_000 + tax_units: + tax_unit: + deductible_mortgage_interest_tax_unit: 30_000 + non_deductible_mortgage_interest_tax_unit: 10_000 + +- name: Points-only joint return allocates to the reporting spouse when interest is zero + period: 2026 + input: + people: + head: + age: 45 + spouse: + age: 44 + home_mortgage_points: 8_000 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + first_home_mortgage_balance: 1_000_000 + first_home_mortgage_origination_year: 2020 + output: + # Pub. 936 Table 1 line 14 = 750,000/1,000,000 = 0.750. + # Interest = 0; points = 8,000 x 0.750 = 6,000 deductible; + # non-deductible amount = 8,000 - 6,000 = 2,000. + # The spouse reported all 8,000, so shares are 0/8,000 = 0 for + # head and 8,000/8,000 = 1 for spouse, even with no reported interest. + # Head receives 0; spouse receives deduction 6,000, remainder 2,000, + # and gross mortgage amount 6,000 + 2,000 = 8,000. + people: + head: + home_mortgage_interest_share: 0 + deductible_mortgage_interest: 0 + non_deductible_mortgage_interest: 0 + mortgage_interest: 0 + spouse: + home_mortgage_interest_share: 1 + deductible_mortgage_interest: 6_000 + non_deductible_mortgage_interest: 2_000 + mortgage_interest: 8_000 + tax_units: + tax_unit: + home_mortgage_interest_tax_unit: 0 + deductible_mortgage_interest_tax_unit: 6_000 + non_deductible_mortgage_interest_tax_unit: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/deductible_mortgage_insurance_premiums.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/deductible_mortgage_insurance_premiums.yaml new file mode 100644 index 00000000000..f394efb9e15 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/deductible_mortgage_insurance_premiums.yaml @@ -0,0 +1,310 @@ +# 2021 Schedule A instructions, Mortgage Insurance Premiums Deduction +# Worksheet, lines 1-7: round positive excess AGI up to the next $1,000 +# ($500 if married filing separately), divide by $10,000 ($5,000), cap the +# reduction at 100%, and subtract that share of qualified premiums. +# https://www.irs.gov/pub/irs-prior/i1040sca--2021.pdf#page=10 +# Pub. 936 (2021), Table 1, line 13 instructions apply this AGI worksheet +# to premiums, without multiplying them by the qualified-loan-limit share. +# https://www.irs.gov/pub/irs-prior/p936--2021.pdf#page=14 + +- name: Qualified premiums are deductible in 2021 + period: 2021 + input: + filing_status: SINGLE + adjusted_gross_income: 90_000 + mortgage_insurance_premiums: 1_000 + output: + # Worksheet line 2 ($90,000) <= line 3 ($100,000): deduct line 1 in full. + deductible_mortgage_insurance_premiums: 1_000 + +- name: Qualified premiums are not deductible in 2022 + period: 2022 + input: + filing_status: SINGLE + adjusted_gross_income: 90_000 + mortgage_insurance_premiums: 1_000 + output: + # 26 USC 163(h)(3)(E)(iv) disallows premiums paid after 2021: $0. + deductible_mortgage_insurance_premiums: 0 + +- name: Qualified premiums are not deductible in 2023 + period: 2023 + input: + filing_status: SINGLE + adjusted_gross_income: 90_000 + mortgage_insurance_premiums: 1_000 + output: + # 26 USC 163(h)(3)(E)(iv) disallows premiums paid after 2021: $0. + deductible_mortgage_insurance_premiums: 0 + +- name: Qualified premiums are not deductible in 2024 + period: 2024 + input: + filing_status: SINGLE + adjusted_gross_income: 90_000 + mortgage_insurance_premiums: 1_000 + output: + # 26 USC 163(h)(3)(E)(iv) disallows premiums paid after 2021: $0. + deductible_mortgage_insurance_premiums: 0 + +- name: Qualified premiums are excluded from the interest deduction in 2025 + period: 2025 + input: + filing_status: SINGLE + adjusted_gross_income: 90_000 + home_mortgage_interest: 12_000 + mortgage_insurance_premiums: 1_000 + output: + # Premium deduction is suspended; $12,000 uncapped interest + $0 premiums. + deductible_mortgage_insurance_premiums: 0 + interest_deduction: 12_000 + +- name: Qualified premiums return and enter the interest deduction in 2026 + period: 2026 + input: + filing_status: SINGLE + adjusted_gross_income: 90_000 + home_mortgage_interest: 12_000 + mortgage_insurance_premiums: 1_000 + output: + # P.L. 119-21 sec. 70108 restores the deduction. AGI is below $100,000, + # so $1,000 premiums are deductible; $12,000 interest + $1,000 = $13,000. + deductible_mortgage_insurance_premiums: 1_000 + interest_deduction: 13_000 + +- name: Single filer at the premium phase-out threshold + period: 2026 + input: + filing_status: SINGLE + adjusted_gross_income: 100_000 + mortgage_insurance_premiums: 1_000 + output: + # Worksheet line 2 equals line 3: line 1 ($1,000) is fully deductible. + deductible_mortgage_insurance_premiums: 1_000 + +- name: Single filer one cent above the premium phase-out threshold + period: 2026 + input: + filing_status: SINGLE + adjusted_gross_income: 100_000.01 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 rounds $0.01 up to $1,000; line 5 = $1,000 / $10,000 = 0.1. + # Lines 6-7: $1,000 - $1,000 * 0.1 = $900. + deductible_mortgage_insurance_premiums: 900 + +- name: Single filer one dollar above the premium phase-out threshold + period: 2026 + input: + filing_status: SINGLE + adjusted_gross_income: 100_001 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 rounds $1 up to $1,000; $1,000 - $1,000 * 0.1 = $900. + deductible_mortgage_insurance_premiums: 900 + +- name: Single filer at the first complete premium phase-out increment + period: 2026 + input: + filing_status: SINGLE + adjusted_gross_income: 101_000 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 is exactly $1,000; $1,000 - $1,000 * ($1,000 / $10,000) = $900. + deductible_mortgage_insurance_premiums: 900 + +- name: Single filer one dollar into the second premium phase-out increment + period: 2026 + input: + filing_status: SINGLE + adjusted_gross_income: 101_001 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 rounds $1,001 to $2,000; $1,000 - $1,000 * 0.2 = $800. + deductible_mortgage_insurance_premiums: 800 + +- name: Single filer at the last partly deductible premium boundary + period: 2026 + # Float32 subtraction of a 90% reduction leaves a small rounding residual. + absolute_error_margin: 0.001 + input: + filing_status: SINGLE + adjusted_gross_income: 109_000 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 = $9,000; line 5 = 0.9; $1,000 - $1,000 * 0.9 = $100. + deductible_mortgage_insurance_premiums: 100 + +- name: Single filer one cent beyond the last deductible premium boundary + period: 2026 + input: + filing_status: SINGLE + adjusted_gross_income: 109_000.01 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 rounds $9,000.01 to $10,000; line 5 = 1; $1,000 - $1,000 = $0. + deductible_mortgage_insurance_premiums: 0 + +- name: Single filer well beyond the premium phase-out range + period: 2026 + input: + filing_status: SINGLE + adjusted_gross_income: 150_000 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 = $50,000; line 5 is capped at 1; $1,000 - $1,000 = $0. + deductible_mortgage_insurance_premiums: 0 + +- name: Separate filer at the premium phase-out threshold + period: 2026 + input: + filing_status: SEPARATE + adjusted_gross_income: 50_000 + mortgage_insurance_premiums: 1_000 + output: + # Worksheet line 2 equals the MFS line 3 ($50,000): deduct $1,000 in full. + deductible_mortgage_insurance_premiums: 1_000 + +- name: Separate filer one dollar above the premium phase-out threshold + period: 2026 + input: + filing_status: SEPARATE + adjusted_gross_income: 50_001 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 rounds $1 to $500; line 5 = $500 / $5,000 = 0.1. + # Lines 6-7: $1,000 - $1,000 * 0.1 = $900. + deductible_mortgage_insurance_premiums: 900 + +- name: Separate filer at the last partly deductible premium boundary + period: 2026 + # Float32 subtraction of a 90% reduction leaves a small rounding residual. + absolute_error_margin: 0.001 + input: + filing_status: SEPARATE + adjusted_gross_income: 54_500 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 = $4,500; line 5 = $4,500 / $5,000 = 0.9; deduction = $100. + deductible_mortgage_insurance_premiums: 100 + +- name: Separate filer one dollar beyond the last deductible premium boundary + period: 2026 + input: + filing_status: SEPARATE + adjusted_gross_income: 54_501 + mortgage_insurance_premiums: 1_000 + output: + # Line 4 rounds $4,501 to $5,000; line 5 = 1; $1,000 - $1,000 = $0. + deductible_mortgage_insurance_premiums: 0 + +- name: Joint filers use the single premium phase-out schedule + period: 2026 + input: + filing_status: JOINT + adjusted_gross_income: 101_001 + mortgage_insurance_premiums: 1_000 + output: + # Start $100,000, increment $1,000: round $1,001 to $2,000. + # $1,000 - $1,000 * ($2,000 / $10,000) = $800. + deductible_mortgage_insurance_premiums: 800 + +- name: Head of household uses the single premium phase-out schedule + period: 2026 + input: + filing_status: HEAD_OF_HOUSEHOLD + adjusted_gross_income: 101_001 + mortgage_insurance_premiums: 1_000 + output: + # Start $100,000, increment $1,000: round $1,001 to $2,000. + # $1,000 - $1,000 * ($2,000 / $10,000) = $800. + deductible_mortgage_insurance_premiums: 800 + +- name: Surviving spouse uses the single premium phase-out schedule + period: 2026 + input: + filing_status: SURVIVING_SPOUSE + adjusted_gross_income: 101_001 + mortgage_insurance_premiums: 1_000 + output: + # Start $100,000, increment $1,000: round $1,001 to $2,000. + # $1,000 - $1,000 * ($2,000 / $10,000) = $800. + deductible_mortgage_insurance_premiums: 800 + +- name: Acquisition-debt cap does not prorate qualified premiums + period: 2021 + input: + filing_status: SINGLE + adjusted_gross_income: 90_000 + home_mortgage_interest: 40_000 + mortgage_insurance_premiums: 2_000 + first_home_mortgage_balance: 1_000_000 + first_home_mortgage_origination_year: 2020 + output: + # Pub. 936 Table 1: line 14 = $750,000 / $1,000,000 = 0.75; + # line 15 interest = $40,000 * 0.75 = $30,000. + # The separate premium instructions use only AGI: $2,000 in full. + # Total interest deduction = $30,000 + $2,000 = $32,000. + deductible_mortgage_interest_tax_unit: 30_000 + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 32_000 + +- name: Both spouses premiums enter the joint deduction before the phase-out + period: 2026 + input: + people: + head: + mortgage_insurance_premiums: 1_000 + is_tax_unit_head: true + spouse: + mortgage_insurance_premiums: 1_500 + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 101_001 + output: + # Worksheet line 1 = $1,000 + $1,500 = $2,500; line 4 = $2,000. + # Lines 5-7: $2,500 - $2,500 * 0.2 = $2,000. + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 2_000 + +- name: Mortgage points and premiums flow into itemized deductions and taxable income in 2026 + period: 2026 + input: + people: + head: + age: 40 + employment_income: 90_000 + home_mortgage_interest: 40_000 + home_mortgage_points: 4_000 + mortgage_insurance_premiums: 2_000 + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + tax_unit_itemizes: true + state_sales_tax: 0 + first_home_mortgage_balance: 1_000_000 + first_home_mortgage_origination_year: 2020 + households: + household: + members: [head] + state_code: TX + output: + # Pub. 936 Table 1 line 14 = $750,000 / $1,000,000 = 0.75. + # Interest is $40,000 * 0.75 = $30,000; deductible points are + # $4,000 * 0.75 = $3,000, for a combined mortgage deduction of $33,000. + # AGI $90,000 is below the $100,000 premium phase-out start, so add all + # $2,000 premiums: interest and total itemized deductions are $35,000. + # No other deductions, exemptions or itemized reduction apply; + # taxable income = $90,000 - $35,000 = $55,000. + adjusted_gross_income: 90_000 + mortgage_interest: 44_000 + deductible_mortgage_interest_tax_unit: 33_000 + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 35_000 + itemized_taxable_income_deductions: 35_000 + taxable_income_deductions: 35_000 + taxable_income: 55_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/interest_deduction.yaml new file mode 100644 index 00000000000..669cf649c4a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/interest_deduction.yaml @@ -0,0 +1,66 @@ +- name: Second-residence interest, points and premiums join the federal deduction in 2026 + period: 2026 + absolute_error_margin: 0.01 + input: + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 10_000 + second_residence_mortgage_interest: 4_000 + second_residence_mortgage_points: 500 + mortgage_insurance_premiums: 1_000 + second_residence_mortgage_insurance_premiums: 600 + output: + # No balances are given, so the pool is fully deductible: + # 10,000 + 4,000 + 500 points. + deductible_mortgage_interest_tax_unit: 14_500 + # AGI is below the $100,000 phase-out start: 1,000 + 600. + deductible_mortgage_insurance_premiums: 1_600 + interest_deduction: 16_100 + +- name: Second-residence interest takes the acquisition-debt share and its premiums the phase-out + period: 2026 + absolute_error_margin: 0.01 + input: + filing_status: SINGLE + adjusted_gross_income: 105_000 + first_home_mortgage_balance: 900_000 + first_home_mortgage_origination_year: 2020 + second_home_mortgage_balance: 300_000 + second_home_mortgage_origination_year: 2020 + home_mortgage_interest: 30_000 + home_mortgage_points: 2_000 + second_residence_mortgage_interest: 12_000 + second_residence_mortgage_insurance_premiums: 1_000 + output: + # Deductible share: $750,000 / ($900,000 + $300,000) = 0.625. + # (30,000 + 2,000 + 12,000) * 0.625 = 27,500. + deductible_mortgage_interest_tax_unit: 27_500 + # $5,000 above the start is 5 increments of 10%: 1,000 * 0.5. + deductible_mortgage_insurance_premiums: 500 + interest_deduction: 28_000 + +- name: Second-residence and principal-residence premiums share the phase-out + period: 2026 + absolute_error_margin: 0.01 + input: + filing_status: SINGLE + adjusted_gross_income: 100_500 + mortgage_insurance_premiums: 900 + second_residence_mortgage_insurance_premiums: 300 + output: + # $500 above the start is one increment or fraction: (900 + 300) * 0.9. + deductible_mortgage_insurance_premiums: 1_080 + interest_deduction: 1_080 + +- name: Second-residence premiums are not deductible in 2025 + period: 2025 + absolute_error_margin: 0.01 + input: + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 10_000 + second_residence_mortgage_interest: 4_000 + second_residence_mortgage_insurance_premiums: 600 + output: + deductible_mortgage_insurance_premiums: 0 + interest_deduction: 14_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_interest_deduction.yaml new file mode 100644 index 00000000000..cdc2ff16ce1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_interest_deduction.yaml @@ -0,0 +1,42 @@ +- name: Qualified premiums for both residences enter Alabama interest deduction + period: 2021 + input: + state_code: AL + adjusted_gross_income: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + al_interest_deduction: 2_000 + +- name: Alabama includes only the federally deductible premium amount + period: 2021 + absolute_error_margin: 0.01 + input: + state_code: AL + adjusted_gross_income: 100_001 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + # First $1,000 increment above $100,000 reduces premiums by 10%. + al_interest_deduction: 1_800 + +- name: Alabama premium deduction sunsets with the federal deduction in 2022 + period: 2022 + input: + state_code: AL + adjusted_gross_income: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + al_interest_deduction: 0 + +- name: Alabama follows the federal restoration of premiums from 2026 + period: 2026 + input: + state_code: AL + adjusted_gross_income: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + al_interest_deduction: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.yaml index 2143354aae2..590f359d705 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.yaml @@ -105,3 +105,45 @@ # min(6,000 + 4,000, 10,000) split 60% / 40% ar_gambling_loss_deduction: 10_000 ar_itemized_deductions_indiv: [6_000, 4_000] + +- name: Arkansas excludes mortgage insurance premiums for indiv itemization in 2021. + period: 2021 + input: + state_code: AR + adjusted_gross_income: 80_000 + ar_agi_indiv: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ar_itemized_deductions_indiv: 10_000 + +- name: Arkansas excludes mortgage insurance premiums for indiv itemization in 2022. + period: 2022 + input: + state_code: AR + adjusted_gross_income: 80_000 + ar_agi_indiv: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 0 + interest_deduction: 10_000 + ar_itemized_deductions_indiv: 10_000 + +- name: Arkansas excludes mortgage insurance premiums for indiv itemization in 2026. + period: 2026 + input: + state_code: AR + adjusted_gross_income: 80_000 + ar_agi_indiv: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ar_itemized_deductions_indiv: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.yaml index 758767e96ee..49b443878b1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.yaml @@ -122,3 +122,45 @@ output: ar_gambling_loss_deduction: 4_000 ar_itemized_deductions_joint: [5_000, 0] + +- name: Arkansas excludes mortgage insurance premiums for joint itemization in 2021. + period: 2021 + input: + state_code: AR + adjusted_gross_income: 80_000 + ar_agi_indiv: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ar_itemized_deductions_joint: 10_000 + +- name: Arkansas excludes mortgage insurance premiums for joint itemization in 2022. + period: 2022 + input: + state_code: AR + adjusted_gross_income: 80_000 + ar_agi_indiv: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 0 + interest_deduction: 10_000 + ar_itemized_deductions_joint: 10_000 + +- name: Arkansas excludes mortgage insurance premiums for joint itemization in 2026. + period: 2026 + input: + state_code: AR + adjusted_gross_income: 80_000 + ar_agi_indiv: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ar_itemized_deductions_joint: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml index 01b7b1b685f..ad5f25b1b29 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml @@ -413,3 +413,55 @@ # The federal deduction, now positive, is replaced by California's 4,900, # not added to it. ca_itemized_deductions_pre_limitation: 4_900 + +- name: CA excludes mortgage insurance premiums in 2021. + period: 2021 + input: + state_code: CA + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ca_itemized_deductions_pre_limitation: 10_000 + +- name: CA excludes mortgage insurance premiums in 2022. + period: 2022 + input: + state_code: CA + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 0 + interest_deduction: 10_000 + ca_itemized_deductions_pre_limitation: 10_000 + +- name: CA excludes mortgage insurance premiums in 2026. + period: 2026 + input: + state_code: CA + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ca_itemized_deductions_pre_limitation: 10_000 + +- name: California excludes only the federally deductible premium after its AGI phase-out. + period: 2021 + input: + state_code: CA + adjusted_gross_income: 101_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 1_800 + interest_deduction: 11_800 + ca_itemized_deductions_pre_limitation: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.yaml b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.yaml index 6c405d41d19..755bbb44249 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.yaml @@ -198,3 +198,140 @@ state_code: DE output: de_itemized_deductions_indv: [0, 5_000, 0] + +- name: Mortgage premiums belong to their payer separately from mortgage interest + period: 2021 + input: + people: + head: + age: 40 + adjusted_gross_income_person: 48_000 + deductible_mortgage_interest: 10_000 + spouse: + age: 40 + adjusted_gross_income_person: 32_000 + mortgage_insurance_premiums: 2_000 + tax_units: + couple: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 80_000 + households: + household: + members: [head, spouse] + state_code: DE + output: + # PIT-RES 2021 instructions PDF pp7,12: allocate identifiable expenses + # to their payer; PIT-RSA line 8d includes qualified mortgage premiums. + # https://revenuefiles.delaware.gov/2021/PIT-RES_TY21_2021-01_Instructions.pdf#page=7 + de_itemized_deductions_unit: 12_000 + de_itemized_deductions_indv: [10_000, 2_000] + +- name: Both residences' premiums retain payer shares after the federal phase-out + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + adjusted_gross_income_person: 61_800 + deductible_mortgage_interest: 10_000 + mortgage_insurance_premiums: 1_000 + spouse: + age: 40 + adjusted_gross_income_person: 41_200 + second_residence_mortgage_insurance_premiums: 2_000 + tax_units: + couple: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 103_000 + households: + household: + members: [head, spouse] + state_code: DE + output: + # Federal 30% reduction leaves 700 of head-paid premiums and 1,400 + # of spouse-paid second-residence premiums, allocated independently + # of the 10,000 interest that the head paid. + deductible_mortgage_insurance_premiums: 2_100 + de_itemized_deductions_unit: 12_100 + de_itemized_deductions_indv: [10_700, 1_400] + +- name: Premium-only deductions are specifically allocated rather than prorated + period: 2021 + input: + people: + head: + age: 40 + adjusted_gross_income_person: 48_000 + spouse: + age: 40 + adjusted_gross_income_person: 32_000 + mortgage_insurance_premiums: 2_000 + tax_units: + couple: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 80_000 + households: + household: + members: [head, spouse] + state_code: DE + output: + de_itemized_deductions_unit: 2_000 + de_itemized_deductions_indv: [0, 2_000] + +- name: Only the dependent-paid premium share uses Delaware income proration + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + adjusted_gross_income_person: 48_000 + spouse: + age: 40 + adjusted_gross_income_person: 32_000 + mortgage_insurance_premiums: 1_000 + dependent: + age: 10 + is_tax_unit_dependent: true + second_residence_mortgage_insurance_premiums: 2_000 + tax_units: + couple: + members: [head, spouse, dependent] + filing_status: JOINT + adjusted_gross_income: 80_000 + households: + household: + members: [head, spouse, dependent] + state_code: DE + output: + # The spouse's identifiable 1,000 stays with the spouse. Only the + # dependent-paid 2,000 is split at the filers' 60% / 40% income shares. + de_itemized_deductions_unit: 3_000 + de_itemized_deductions_indv: [1_200, 1_800, 0] + +- name: Premium allocation respects reformed sources that omit interest + period: 2021 + input: + gov.states.de.tax.income.deductions.itemized.sources: [] + people: + head: + age: 40 + spouse: + age: 40 + mortgage_insurance_premiums: 2_000 + tax_units: + couple: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 0 + households: + household: + members: [head, spouse] + state_code: DE + output: + de_itemized_deductions_unit: 0 + de_itemized_deductions_indv: [0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_interest_deduction.yaml new file mode 100644 index 00000000000..c28059ce8c0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_interest_deduction.yaml @@ -0,0 +1,84 @@ +- name: Hawaii includes qualified premiums on both residences in 2020 + period: 2020 + input: + state_code: HI + adjusted_gross_income: 80_000 + hi_agi: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + hi_interest_deduction: 2_000 + +- name: Hawaii phases out premiums using Hawaii AGI rather than federal AGI + period: 2020 + absolute_error_margin: 0.01 + input: + state_code: HI + adjusted_gross_income: 80_000 + hi_agi: 100_001 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + # Hawaii AGI exceeds $100,000 by a fraction of a $1,000 increment. + deductible_mortgage_insurance_premiums: 2_000 + hi_interest_deduction: 1_800 + +- name: Hawaii separate filer has no phase-out at exactly 50000 of Hawaii AGI + period: 2020 + input: + state_code: HI + filing_status: SEPARATE + adjusted_gross_income: 60_000 + hi_agi: 50_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 0 + hi_interest_deduction: 2_000 + +- name: Hawaii separate filer phase-out begins with the first dollar above 50000 + period: 2020 + absolute_error_margin: 0.01 + input: + state_code: HI + filing_status: SEPARATE + adjusted_gross_income: 40_000 + hi_agi: 50_001 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + hi_interest_deduction: 1_800 + +- name: Hawaii adopted the federal extension through 2021 + period: 2021 + input: + state_code: HI + hi_agi: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + hi_interest_deduction: 2_000 + +- name: Hawaii premium deduction expired after 2021 + period: 2022 + input: + state_code: HI + hi_agi: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + hi_interest_deduction: 0 + +- name: Hawaii does not adopt the federal subsection that restores premiums in 2026 + period: 2026 + input: + state_code: HI + adjusted_gross_income: 80_000 + hi_agi: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + hi_interest_deduction: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml index ad62b3d64e5..9494a5b10c4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml @@ -39,3 +39,59 @@ filing_status: JOINT output: ia_itemized_deductions_unit: 11_000 + +- name: Iowa mortgage insurance premium conformity in 2018. + period: 2018 + input: + state_code: IA + adjusted_gross_income: 80_000 + salt_deduction: 0 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ia_itemized_deductions_unit: 10_000 + +- name: Iowa mortgage insurance premium conformity in 2019. + period: 2019 + input: + state_code: IA + adjusted_gross_income: 80_000 + salt_deduction: 0 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ia_itemized_deductions_unit: 10_000 + +- name: Iowa mortgage insurance premium conformity in 2020. + period: 2020 + input: + state_code: IA + adjusted_gross_income: 80_000 + salt_deduction: 0 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ia_itemized_deductions_unit: 12_000 + +- name: Iowa mortgage insurance premium conformity in 2026. + period: 2026 + input: + state_code: IA + adjusted_gross_income: 80_000 + salt_deduction: 0 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ia_itemized_deductions_unit: 12_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_disallowed_second_residence_interest.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_disallowed_second_residence_interest.yaml new file mode 100644 index 00000000000..73e9725f936 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_disallowed_second_residence_interest.yaml @@ -0,0 +1,162 @@ +- name: Kentucky allows second-residence interest before 2026 + period: 2025 + absolute_error_margin: 0 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 10_000 + second_residence_mortgage_interest: 4_000 + output: + ky_disallowed_second_residence_interest: 0 + +- name: Kentucky disallows second-residence interest from 2026 + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 10_000 + second_residence_mortgage_interest: 4_000 + output: + # Pool 14,000 fully deductible; principal residence 10,000. + ky_disallowed_second_residence_interest: 4_000 + +- name: The Kentucky disallowance applies only to Kentucky + period: 2026 + absolute_error_margin: 0 + input: + state_code: OH + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 10_000 + second_residence_mortgage_interest: 4_000 + output: + ky_disallowed_second_residence_interest: 0 + +- name: Kentucky keeps the federal deduction when principal-residence debt alone exceeds the limit + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + first_home_mortgage_balance: 1_000_000 + first_home_mortgage_origination_year: 2020 + second_home_mortgage_balance: 500_000 + second_home_mortgage_origination_year: 2020 + home_mortgage_interest: 50_000 + second_residence_mortgage_interest: 25_000 + output: + # Both loans at 5%. Federal: 75,000 * 750,000 / 1,500,000 = 37,500, + # below the principal residence's 50,000, so nothing is disallowed. + # A recomputation on the principal residence alone gives the same: + # 50,000 * 750,000 / 1,000,000 = 37,500. + ky_disallowed_second_residence_interest: 0 + +- name: Kentucky distinguishes principal-residence interest from second-residence interest + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 8_000 + second_residence_mortgage_interest: 4_000 + output: + # The fully deductible pool includes both residences: 8,000 + 4,000. + home_mortgage_interest_tax_unit: 12_000 + ky_disallowed_second_residence_interest: 4_000 + +- name: Kentucky disallows second-residence premiums after the federal phase-out + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 100_500 + mortgage_insurance_premiums: 900 + second_residence_mortgage_insurance_premiums: 300 + output: + # One $1,000 increment or fraction above $100,000: 10% reduction. + # (900 + 300) * 0.9 = 1,080, of which the second residence's 300 * 0.9. + deductible_mortgage_insurance_premiums: 1_080 + ky_disallowed_second_residence_interest: 270 + # d1070: principal-residence premiums remain deductible after phase-out. + ky_itemized_deductions_unit: 810 + +- name: Kentucky disallows second-residence points beside principal-residence interest + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 8_000 + second_residence_mortgage_points: 400 + output: + # The second residence's 400 of points join the fully deductible pool + # but exceed the principal residence's 8,000 of interest. + home_mortgage_interest_tax_unit: 8_000 + deductible_mortgage_interest_tax_unit: 8_400 + ky_disallowed_second_residence_interest: 400 + +- name: Kentucky counts principal-residence interest toward the principal-residence cap + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + first_home_mortgage_balance: 1_000_000 + first_home_mortgage_origination_year: 2020 + second_home_mortgage_balance: 500_000 + second_home_mortgage_origination_year: 2020 + home_mortgage_interest: 8_000 + second_residence_mortgage_interest: 4_000 + output: + # Federal: (8,000 + 4,000) * 750,000 / 1,500,000 = 6,000, below the + # principal residence's 8,000, so nothing is disallowed. + home_mortgage_interest_tax_unit: 12_000 + deductible_mortgage_interest_tax_unit: 6_000 + ky_disallowed_second_residence_interest: 0 + +- name: Kentucky retains uncapped grandfathered principal-residence interest and points + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + first_home_mortgage_balance: 1_200_000 + first_home_mortgage_origination_year: 1986 + home_mortgage_interest: 60_000 + home_mortgage_points: 4_000 + output: + # Pre-October 1987 debt has no dollar limit. All 64,000 belongs to the + # principal residence, so Kentucky's residence ceiling removes nothing. + deductible_mortgage_interest_tax_unit: 64_000 + ky_disallowed_second_residence_interest: 0 + ky_itemized_deductions_unit: 64_000 + +- name: Kentucky applies the principal-residence ceiling after the mixed-vintage federal limit + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + first_home_mortgage_balance: 800_000 + first_home_mortgage_origination_year: 2016 + second_home_mortgage_balance: 900_000 + second_home_mortgage_origination_year: 2020 + home_mortgage_interest: 40_000 + second_residence_mortgage_interest: 45_000 + output: + # The 800,000 pre-2018 debt exhausts the 750,000 limit for newer debt. + # Federal: 85,000 * 800,000 / 1,700,000 = 40,000, equal to the + # principal residence's interest, so the Kentucky ceiling removes zero. + deductible_mortgage_interest_tax_unit: 40_000 + ky_disallowed_second_residence_interest: 0 + ky_itemized_deductions_unit: 40_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_indiv.yaml index a8865e77a6a..719e329c905 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_indiv.yaml @@ -87,3 +87,31 @@ state_code: KY output: ky_itemized_deductions_indiv: [0, 0] + +- name: From 2026 spouses filing separately share Kentucky itemized deductions without second-residence interest + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + ky_agi: 60_000 + home_mortgage_interest: 10_000 + person2: + age: 50 + ky_agi: 40_000 + second_residence_mortgage_interest: 5_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + adjusted_gross_income: 100_000 + households: + household: + members: [person1, person2] + state_code: KY + output: + # Federal 15,000 less the 5,000 second-residence excess leaves 10,000, + # split by Kentucky AGI: 60% and 40%. + ky_itemized_deductions_unit: 10_000 + ky_itemized_deductions_indiv: [6_000, 4_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.yaml index c43befc6998..2c95b9fe2fc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.yaml @@ -17,3 +17,127 @@ medical_expense_deduction: 2 output: ky_itemized_deductions_unit: 8 + +- name: In 2025 Kentucky keeps federal second-residence mortgage interest + period: 2025 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 10_000 + second_residence_mortgage_interest: 4_000 + second_residence_mortgage_points: 500 + mortgage_insurance_premiums: 1_000 + second_residence_mortgage_insurance_premiums: 600 + output: + # Federal premiums are not deductible in 2025: 10,000 + 4,000 + 500. + itemized_deductions_less_salt: 14_500 + ky_disallowed_second_residence_interest: 0 + ky_itemized_deductions_unit: 14_500 + +- name: From 2026 Kentucky keeps only principal-residence interest, points and premiums + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 10_000 + second_residence_mortgage_interest: 4_000 + second_residence_mortgage_points: 500 + mortgage_insurance_premiums: 1_000 + second_residence_mortgage_insurance_premiums: 600 + output: + # Federal: 10,000 + 4,000 + 500 + 1,000 + 600. + itemized_deductions_less_salt: 16_100 + # No balances are given, so the federal pool of 14,500 is fully + # deductible and exceeds the principal residence's 10,000 by 4,500; + # the 600 of second-residence premiums is disallowed too. + ky_disallowed_second_residence_interest: 5_100 + # Principal residence: 10,000 + 1,000. + ky_itemized_deductions_unit: 11_000 + +- name: From 2026 Kentucky allows principal-residence interest that combined debt pushed over the federal limit + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + first_home_mortgage_balance: 600_000 + first_home_mortgage_origination_year: 2020 + second_home_mortgage_balance: 300_000 + second_home_mortgage_origination_year: 2020 + home_mortgage_interest: 36_000 + second_residence_mortgage_interest: 18_000 + output: + # Federal: (36,000 + 18,000) * 750,000 / 900,000 = 45,000. + itemized_deductions_less_salt: 45_000 + # Kentucky caps the federal deduction at the principal residence's + # 36,000, which its $600,000 debt alone would keep in full. + ky_disallowed_second_residence_interest: 9_000 + ky_itemized_deductions_unit: 36_000 + +- name: From 2026 Kentucky allows the federal deduction up to principal-residence interest and points + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 105_000 + first_home_mortgage_balance: 900_000 + first_home_mortgage_origination_year: 2020 + second_home_mortgage_balance: 300_000 + second_home_mortgage_origination_year: 2020 + home_mortgage_interest: 30_000 + home_mortgage_points: 2_000 + second_residence_mortgage_interest: 12_000 + second_residence_mortgage_insurance_premiums: 1_000 + output: + # Federal: (30,000 + 2,000 + 12,000) * 0.625 + 1,000 * 0.5 = 28,000. + itemized_deductions_less_salt: 28_000 + # The 27,500 of deductible interest and points is below the principal + # residence's 32,000, so only the 500 of second-residence premiums goes. + ky_disallowed_second_residence_interest: 500 + ky_itemized_deductions_unit: 27_500 + +- name: From 2026 Kentucky itemized deductions are unchanged without second-residence amounts + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: KY + filing_status: SINGLE + adjusted_gross_income: 50_000 + home_mortgage_interest: 10_000 + home_mortgage_points: 500 + mortgage_insurance_premiums: 1_000 + output: + itemized_deductions_less_salt: 11_500 + ky_disallowed_second_residence_interest: 0 + ky_itemized_deductions_unit: 11_500 + +- name: From 2026 Kentucky disallows second-residence interest on a joint return + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + home_mortgage_interest: 9_000 + spouse: + age: 50 + second_residence_mortgage_interest: 3_000 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 90_000 + households: + household: + members: [head, spouse] + state_code: KY + output: + itemized_deductions_less_salt: 12_000 + ky_disallowed_second_residence_interest: 3_000 + ky_itemized_deductions_unit: 9_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml index acb1fa03b73..5746bbfd9a0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml @@ -149,3 +149,55 @@ # The federal deduction, now positive inside itemized_deductions_less_salt, # is replaced by Minnesota's 4,900, not added to it. mn_itemized_deductions: 14_900 + +- name: MN excludes mortgage insurance premiums in 2021. + period: 2021 + input: + state_code: MN + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + mn_itemized_deductions: 10_000 + +- name: MN excludes mortgage insurance premiums in 2022. + period: 2022 + input: + state_code: MN + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 0 + interest_deduction: 10_000 + mn_itemized_deductions: 10_000 + +- name: MN excludes mortgage insurance premiums in 2026. + period: 2026 + input: + state_code: MN + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + mn_itemized_deductions: 10_000 + +- name: MN excludes only the federally deductible premium after its AGI phase-out. + period: 2021 + input: + state_code: MN + adjusted_gross_income: 101_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 1_800 + interest_deduction: 11_800 + mn_itemized_deductions: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml index 164292cde5f..64ff39f3bf7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml @@ -334,3 +334,73 @@ # Joint charity remains intact regardless of the separate allocation rate. mt_itemized_deductions_joint: [0, 0, 0, 400, 0, 0, 800, 0, 0] mt_itemized_deductions_for_federal_itemization_joint: [0, 0, 0, 400, 0, 0, 800, 0, 0] + +- name: Premiums from both residences use the full federal deduction + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + mortgage_insurance_premiums: 600 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_indiv: 0 + mt_federal_income_tax_deduction_for_federal_itemization_indiv: 0 + spouse: + is_tax_unit_spouse: true + second_residence_mortgage_insurance_premiums: 1_400 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_indiv: 0 + mt_federal_income_tax_deduction_for_federal_itemization_indiv: 0 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 80_000 + charitable_deduction: 0 + households: + household: + members: [head, spouse] + state_code: MT + output: + # Form 2 line 9 includes the federal premium deduction. AGI of + # $80,000 is below the phase-out, so all $2,000 is allowed. + deductible_mortgage_insurance_premiums: 2_000 + mt_itemized_deductions_for_federal_itemization_indiv: [600, 1_400] + +- name: Premiums from both residences use the federal phase-out before allocation + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + mortgage_insurance_premiums: 600 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_indiv: 0 + mt_federal_income_tax_deduction_for_federal_itemization_indiv: 0 + spouse: + is_tax_unit_spouse: true + second_residence_mortgage_insurance_premiums: 1_400 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_indiv: 0 + mt_federal_income_tax_deduction_for_federal_itemization_indiv: 0 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 100_001 + charitable_deduction: 0 + households: + household: + members: [head, spouse] + state_code: MT + output: + # Form 2 line 9 includes the federal premium deduction. At $100,001 + # AGI the first $1,000 phase-out increment reduces $2,000 by 10%. + deductible_mortgage_insurance_premiums: 1_800 + mt_itemized_deductions_for_federal_itemization_indiv: [540, 1_260] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml index 5c2836f99a1..23a5bc1ffd9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml @@ -170,3 +170,86 @@ # filing a separate return, so $15,000 of the $40,000 is allowed. deductible_mortgage_interest: 15_000 mt_itemized_deductions_for_federal_itemization_joint: 15_000 + +- name: Premiums from both residences use the full federal deduction + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + mortgage_insurance_premiums: 600 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_joint: 0 + spouse: + is_tax_unit_spouse: true + second_residence_mortgage_insurance_premiums: 1_400 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_joint: 0 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 80_000 + charitable_deduction: 0 + mt_federal_income_tax_deduction_for_federal_itemization: 0 + households: + household: + members: [head, spouse] + state_code: MT + output: + # Form 2 line 9 includes the federal premium deduction. AGI of + # $80,000 is below the phase-out, so all $2,000 is allowed. + deductible_mortgage_insurance_premiums: 2_000 + mt_itemized_deductions_for_federal_itemization_joint: [2_000, 0] + +- name: Premiums from both residences use the federal phase-out before allocation + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + mortgage_insurance_premiums: 600 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_joint: 0 + spouse: + is_tax_unit_spouse: true + second_residence_mortgage_insurance_premiums: 1_400 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_joint: 0 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 100_001 + charitable_deduction: 0 + mt_federal_income_tax_deduction_for_federal_itemization: 0 + households: + household: + members: [head, spouse] + state_code: MT + output: + # Form 2 line 9 includes the federal premium deduction. At $100,001 + # AGI the first $1,000 phase-out increment reduces $2,000 by 10%. + deductible_mortgage_insurance_premiums: 1_800 + mt_itemized_deductions_for_federal_itemization_joint: [1_800, 0] + +- name: Premium deduction expires with the federal deduction in 2022 + period: 2022 + input: + state_code: MT + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + adjusted_gross_income: 80_000 + charitable_deduction: 0 + mt_misc_deductions: 0 + mt_medical_expense_deduction_joint: 0 + mt_salt_deduction: 0 + mt_federal_income_tax_deduction_for_federal_itemization: 0 + output: + mt_itemized_deductions_for_federal_itemization_joint: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml index ac0685090a1..822905cf764 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml @@ -115,3 +115,73 @@ # (2023 Form 2 instructions, p. 13), so the joint federal amount is used. deductible_mortgage_interest: [22_500, 7_500] mt_itemized_deductions_indiv: [22_500, 7_500] + +- name: Premiums from both residences use the full federal deduction + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + mortgage_insurance_premiums: 600 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_indiv: 0 + mt_federal_income_tax_deduction_indiv: 0 + spouse: + is_tax_unit_spouse: true + second_residence_mortgage_insurance_premiums: 1_400 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_indiv: 0 + mt_federal_income_tax_deduction_indiv: 0 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 80_000 + charitable_deduction: 0 + households: + household: + members: [head, spouse] + state_code: MT + output: + # Form 2 line 9 includes the federal premium deduction. AGI of + # $80,000 is below the phase-out, so all $2,000 is allowed. + deductible_mortgage_insurance_premiums: 2_000 + mt_itemized_deductions_indiv: [600, 1_400] + +- name: Premiums from both residences use the federal phase-out before allocation + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + mortgage_insurance_premiums: 600 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_indiv: 0 + mt_federal_income_tax_deduction_indiv: 0 + spouse: + is_tax_unit_spouse: true + second_residence_mortgage_insurance_premiums: 1_400 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_indiv: 0 + mt_federal_income_tax_deduction_indiv: 0 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 100_001 + charitable_deduction: 0 + households: + household: + members: [head, spouse] + state_code: MT + output: + # Form 2 line 9 includes the federal premium deduction. At $100,001 + # AGI the first $1,000 phase-out increment reduces $2,000 by 10%. + deductible_mortgage_insurance_premiums: 1_800 + mt_itemized_deductions_indiv: [540, 1_260] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml index 7999af696a9..d63d546a8f9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml @@ -204,3 +204,86 @@ # law allows all $15,000, and so does Montana (2022 Form 2 line 9). deductible_mortgage_interest: [15_000, 0] mt_itemized_deductions_joint: [15_000, 0] + +- name: Premiums from both residences use the full federal deduction + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + mortgage_insurance_premiums: 600 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_joint: 0 + spouse: + is_tax_unit_spouse: true + second_residence_mortgage_insurance_premiums: 1_400 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_joint: 0 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 80_000 + charitable_deduction: 0 + mt_federal_income_tax_deduction_unit: 0 + households: + household: + members: [head, spouse] + state_code: MT + output: + # Form 2 line 9 includes the federal premium deduction. AGI of + # $80,000 is below the phase-out, so all $2,000 is allowed. + deductible_mortgage_insurance_premiums: 2_000 + mt_itemized_deductions_joint: [2_000, 0] + +- name: Premiums from both residences use the federal phase-out before allocation + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + mortgage_insurance_premiums: 600 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_joint: 0 + spouse: + is_tax_unit_spouse: true + second_residence_mortgage_insurance_premiums: 1_400 + mt_misc_deductions: 0 + mt_salt_deduction: 0 + mt_medical_expense_deduction_joint: 0 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + adjusted_gross_income: 100_001 + charitable_deduction: 0 + mt_federal_income_tax_deduction_unit: 0 + households: + household: + members: [head, spouse] + state_code: MT + output: + # Form 2 line 9 includes the federal premium deduction. At $100,001 + # AGI the first $1,000 phase-out increment reduces $2,000 by 10%. + deductible_mortgage_insurance_premiums: 1_800 + mt_itemized_deductions_joint: [1_800, 0] + +- name: Premium deduction expires with the federal deduction in 2022 + period: 2022 + input: + state_code: MT + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + adjusted_gross_income: 80_000 + charitable_deduction: 0 + mt_misc_deductions: 0 + mt_medical_expense_deduction_joint: 0 + mt_salt_deduction: 0 + mt_federal_income_tax_deduction_unit: 0 + output: + mt_itemized_deductions_joint: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/nc_mortgage_and_property_tax_before_limitation.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/nc_mortgage_and_property_tax_before_limitation.yaml index 482f949d18b..d4c2643bd29 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/nc_mortgage_and_property_tax_before_limitation.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/nc_mortgage_and_property_tax_before_limitation.yaml @@ -30,3 +30,64 @@ state_code: NC output: nc_mortgage_and_property_tax_before_limitation: 25_000 + +- name: North Carolina excludes mortgage insurance premiums through 2021 + period: 2021 + input: + state_code: NC + adjusted_gross_income: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + # 2021 D-401 p20, Schedule A line 1, excludes premiums explicitly. + deductible_mortgage_insurance_premiums: 2_000 + nc_mortgage_and_property_tax_before_limitation: 0 + +- name: No federal or North Carolina premium deduction in 2022 + period: 2022 + input: + state_code: NC + adjusted_gross_income: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 0 + nc_mortgage_and_property_tax_before_limitation: 0 + +- name: North Carolina conforms to the restored federal premiums in 2026 + period: 2026 + input: + state_code: NC + adjusted_gross_income: 80_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + # SL 2026-31 section 12(a) adopts the IRC as of July 5, 2025. + # GS 105-153.5(a)(2)b limits the express premium exclusion to 2014-2021. + deductible_mortgage_insurance_premiums: 2_000 + nc_mortgage_and_property_tax_before_limitation: 2_000 + +- name: North Carolina includes premiums after the federal AGI phase-out + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: NC + adjusted_gross_income: 100_001 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + # The first fraction of a $1,000 AGI increment reduces $2,000 by 10%. + deductible_mortgage_insurance_premiums: 1_800 + nc_mortgage_and_property_tax_before_limitation: 1_800 + +- name: Restored premiums share North Carolina's 20000 mortgage and tax cap + period: 2026 + input: + state_code: NC + adjusted_gross_income: 80_000 + mortgage_interest: 19_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + nc_mortgage_and_property_tax_before_limitation: 21_000 + nc_mortgage_and_property_tax_deduction: 20_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.yaml new file mode 100644 index 00000000000..0c48b5ed88b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.yaml @@ -0,0 +1,63 @@ + +- name: NY excludes mortgage insurance premiums in 2021. + period: 2021 + input: + state_code: NY + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ny_itemized_deductions_max: 10_000 + +- name: NY excludes mortgage insurance premiums in 2022. + period: 2022 + input: + state_code: NY + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 0 + interest_deduction: 10_000 + ny_itemized_deductions_max: 10_000 + +- name: NY excludes mortgage insurance premiums in 2026. + period: 2026 + input: + state_code: NY + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + ny_itemized_deductions_max: 10_000 + +- name: New York allows mortgage insurance premiums before the 2021 nonconformity. + period: 2020 + input: + state_code: NY + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 2_000 + output: + deductible_mortgage_insurance_premiums: 2_000 + ny_itemized_deductions_max: 12_000 + +- name: NY excludes only the federally deductible premium after its AGI phase-out. + period: 2021 + input: + state_code: NY + adjusted_gross_income: 101_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + output: + deductible_mortgage_insurance_premiums: 1_800 + interest_deduction: 11_800 + ny_itemized_deductions_max: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_itemized_deduction_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_itemized_deduction_credit.yaml index 38850b7f5cd..a0e07a30469 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_itemized_deduction_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_itemized_deduction_credit.yaml @@ -25,3 +25,59 @@ state_code: WI output: wi_itemized_deduction_credit_potential: 0 + +- name: WI excludes mortgage insurance premiums in 2021. + period: 2021 + input: + state_code: WI + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + wi_standard_deduction: 10_000 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + wi_itemized_deduction_credit_potential: 0 + +- name: WI excludes mortgage insurance premiums in 2022. + period: 2022 + input: + state_code: WI + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + wi_standard_deduction: 10_000 + output: + deductible_mortgage_insurance_premiums: 0 + interest_deduction: 10_000 + wi_itemized_deduction_credit_potential: 0 + +- name: WI excludes mortgage insurance premiums in 2026. + period: 2026 + input: + state_code: WI + adjusted_gross_income: 80_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + wi_standard_deduction: 10_000 + output: + deductible_mortgage_insurance_premiums: 2_000 + interest_deduction: 12_000 + wi_itemized_deduction_credit_potential: 0 + +- name: WI excludes only the federally deductible premium after its AGI phase-out. + period: 2021 + input: + state_code: WI + adjusted_gross_income: 101_000 + deductible_interest_expense: 10_000 + mortgage_insurance_premiums: 1_200 + second_residence_mortgage_insurance_premiums: 800 + wi_standard_deduction: 10_000 + output: + deductible_mortgage_insurance_premiums: 1_800 + interest_deduction: 11_800 + wi_itemized_deduction_credit_potential: 0 diff --git a/policyengine_us/tests/policy/baseline/household/expense/person/deductible_mortgage_insurance_premiums_indiv.yaml b/policyengine_us/tests/policy/baseline/household/expense/person/deductible_mortgage_insurance_premiums_indiv.yaml new file mode 100644 index 00000000000..70b669b0e67 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/expense/person/deductible_mortgage_insurance_premiums_indiv.yaml @@ -0,0 +1,83 @@ +- name: Both residences' premiums are allocated to filers after the federal phase-out + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + mortgage_insurance_premiums: 1_000 + spouse: + age: 40 + second_residence_mortgage_insurance_premiums: 2_000 + dependent: + age: 10 + is_tax_unit_dependent: true + tax_units: + couple: + members: [head, spouse, dependent] + filing_status: JOINT + adjusted_gross_income: 103_000 + output: + # Federal deduction: 3,000 * (1 - 30%) = 2,100, then split 1/3 and 2/3. + deductible_mortgage_insurance_premiums: 2_100 + deductible_mortgage_insurance_premiums_indiv: [700, 1_400, 0] + +- name: Dependent-only premiums are split equally between filers + period: 2021 + input: + people: + head: + age: 40 + spouse: + age: 40 + dependent: + age: 10 + is_tax_unit_dependent: true + mortgage_insurance_premiums: 2_000 + tax_units: + couple: + members: [head, spouse, dependent] + filing_status: JOINT + adjusted_gross_income: 80_000 + output: + # Matches the federal mortgage-interest convention: dependent expenses + # never give a dependent an individual deduction; the unit total is kept. + deductible_mortgage_insurance_premiums: 2_000 + deductible_mortgage_insurance_premiums_indiv: [1_000, 1_000, 0] + +- name: A tax-unit premium override with no reported expenses uses only filers + period: 2021 + input: + people: + head: + age: 40 + spouse: + age: 40 + dependent: + age: 10 + is_tax_unit_dependent: true + tax_units: + couple: + members: [head, spouse, dependent] + deductible_mortgage_insurance_premiums: 2_000 + output: + deductible_mortgage_insurance_premiums_indiv: [1_000, 1_000, 0] + +- name: Zero premiums give zero allocations for single and married filers + period: 2021 + input: + people: + single: + age: 40 + head: + age: 40 + spouse: + age: 40 + tax_units: + single_unit: + members: [single] + couple: + members: [head, spouse] + output: + deductible_mortgage_insurance_premiums: [0, 0] + deductible_mortgage_insurance_premiums_indiv: [0, 0, 0] diff --git a/policyengine_us/tests/policy/baseline/household/expense/person/home_mortgage_interest_share.yaml b/policyengine_us/tests/policy/baseline/household/expense/person/home_mortgage_interest_share.yaml new file mode 100644 index 00000000000..b03b21757e5 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/expense/person/home_mortgage_interest_share.yaml @@ -0,0 +1,24 @@ +- name: Spouses' shares of the mortgage deduction count second-residence amounts + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + home_mortgage_interest: 6_000 + spouse: + age: 50 + second_residence_mortgage_interest: 1_500 + second_residence_mortgage_points: 500 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + households: + household: + members: [head, spouse] + output: + # 6,000 / 8,000 and (1,500 + 500) / 8,000. + home_mortgage_interest_share: [0.75, 0.25] + deductible_mortgage_interest: [6_000, 2_000] + mortgage_interest: [6_000, 2_000] diff --git a/policyengine_us/tests/policy/baseline/household/expense/tax_unit/home_mortgage_interest_tax_unit.yaml b/policyengine_us/tests/policy/baseline/household/expense/tax_unit/home_mortgage_interest_tax_unit.yaml new file mode 100644 index 00000000000..e0888db8fcb --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/expense/tax_unit/home_mortgage_interest_tax_unit.yaml @@ -0,0 +1,39 @@ +- name: Interest on both residences is pooled + period: 2026 + absolute_error_margin: 0 + input: + home_mortgage_interest: 10_000 + second_residence_mortgage_interest: 4_000 + output: + home_mortgage_interest_tax_unit: 14_000 + +- name: Second-residence interest is pooled when principal-residence interest is omitted + period: 2026 + absolute_error_margin: 0 + input: + second_residence_mortgage_interest: 4_000 + output: + home_mortgage_interest_tax_unit: 4_000 + +- name: Interest on both residences sums across spouses + period: 2026 + absolute_error_margin: 0 + input: + people: + head: + age: 50 + home_mortgage_interest: 5_000 + second_residence_mortgage_interest: 1_000 + spouse: + age: 50 + home_mortgage_interest: 3_000 + second_residence_mortgage_interest: 2_000 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + households: + household: + members: [head, spouse] + output: + home_mortgage_interest_tax_unit: 11_000 diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/deductible_mortgage_insurance_premiums.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/deductible_mortgage_insurance_premiums.py new file mode 100644 index 00000000000..291008442da --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/deductible_mortgage_insurance_premiums.py @@ -0,0 +1,43 @@ +from policyengine_us.model_api import * + + +class deductible_mortgage_insurance_premiums(Variable): + value_type = float + entity = TaxUnit + label = "Deductible mortgage insurance premiums" + unit = USD + definition_period = YEAR + documentation = ( + "Qualified mortgage insurance premiums treated as qualified residence " + "interest under 26 U.S.C. 163(h)(3)(E), after the adjusted gross " + "income phase-out (Schedule A line 8d). Publication 936 applies only " + "the phase-out to premiums, not the qualified-loan-limit fraction that " + "applies to interest and points." + ) + reference = [ + "https://www.law.cornell.edu/uscode/text/26/163#h_3_E", + "https://www.law.cornell.edu/uscode/text/26/163#h_3_F_i_III", + "https://www.irs.gov/pub/irs-prior/i1040sca--2021.pdf#page=10", + "https://www.irs.gov/pub/irs-prior/p936--2021.pdf#page=14", + ] + + def formula(tax_unit, period, parameters): + p = parameters( + period + ).gov.irs.deductions.itemized.interest.mortgage_insurance_premiums + premiums = add( + tax_unit, + period, + [ + "mortgage_insurance_premiums", + "second_residence_mortgage_insurance_premiums", + ], + ) + agi = tax_unit("adjusted_gross_income", period) + filing_status = tax_unit("filing_status", period) + # Worksheet lines 3-5: the deduction falls by the rate for each + # increment, or fraction of one, of AGI above the start. + excess = max_(0, agi - p.phase_out.start[filing_status]) + increments = np.ceil(excess / p.phase_out.increment[filing_status]) + reduction = min_(1, increments * p.phase_out.rate) + return p.in_effect * premiums * (1 - reduction) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/interest_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/interest_deduction.py index effde0252ef..a0f48ec0de2 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/interest_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/interest_deduction.py @@ -6,8 +6,11 @@ class interest_deduction(Variable): entity = TaxUnit label = "Interest deduction" unit = USD - documentation = "Interest expenses deducted from taxable income." + documentation = ( + "Interest expenses deducted from taxable income, including deductible " + "mortgage insurance premiums (Schedule A line 8d)." + ) definition_period = YEAR reference = "https://www.law.cornell.edu/uscode/text/26/163" - adds = ["deductible_interest_expense"] + adds = ["deductible_interest_expense", "deductible_mortgage_insurance_premiums"] diff --git a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_investment_interest.py b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_investment_interest.py index 7d576955da5..9648bc2f892 100644 --- a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_investment_interest.py +++ b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_investment_interest.py @@ -4,10 +4,14 @@ class al_interest_deduction(Variable): value_type = float entity = TaxUnit - label = "Alabama investment interest" + label = "Alabama interest deduction" unit = USD definition_period = YEAR reference = ( + "https://www.revenue.alabama.gov/wp-content/uploads/2022/06/21f40.pdf#page=3", + # Schedule A line 11: qualified mortgage insurance premiums. + "https://www.revenue.alabama.gov/wp-content/uploads/2022/06/21f40bk.pdf#page=19", + # Lines 10a-14 use the amount currently allowed for federal purposes. "https://law.justia.com/codes/alabama/2022/title-40/chapter-18/article-1/section-40-18-15/", # Code of Alabama Section 40-18-15 "https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2023/01/22f40schabdc_blk.pdf#page=1", @@ -17,4 +21,8 @@ class al_interest_deduction(Variable): ) defined_for = StateCode.AL - adds = ["mortgage_interest", "investment_interest_expense"] + adds = [ + "mortgage_interest", + "investment_interest_expense", + "deductible_mortgage_insurance_premiums", + ] diff --git a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.py b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.py index 84c1a80dacf..c57d8fd8a8c 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.py @@ -8,7 +8,8 @@ class ar_itemized_deductions_indiv(Variable): unit = USD definition_period = YEAR reference = ( - "https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR3_ItemizedDeduction.pdf" + "https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR3_ItemizedDeduction.pdf", + "https://www.dfa.arkansas.gov/wp-content/uploads/2021_AR1000F_and_AR1000NR_Instructions.pdf#page=20", ) defined_for = StateCode.AR @@ -16,6 +17,13 @@ def formula(person, period, parameters): # Arkansas does not tie itemization choice to federal choice. p = parameters(period).gov.states.ar.tax.income.deductions.itemized.sources unit_deds = add(person.tax_unit, period, p.individual) + # Arkansas freezes IRC 163 as of January 1, 2017, when the premium + # deduction had expired after 2016 (AR3 interest lines 8-10). + disallowed_interest = parameters( + period + ).gov.states.ar.tax.income.deductions.itemized.disallowed_interest_components + if disallowed_interest: + unit_deds = unit_deds - add(person.tax_unit, period, disallowed_interest) person_agi = person("ar_agi_indiv", period) total_agi = person.tax_unit.sum(person_agi) diff --git a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.py index 265dca396a5..8864ed977b9 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.py @@ -8,7 +8,8 @@ class ar_itemized_deductions_joint(Variable): unit = USD definition_period = YEAR reference = ( - "https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR3_ItemizedDeduction.pdf" + "https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR3_ItemizedDeduction.pdf", + "https://www.dfa.arkansas.gov/wp-content/uploads/2021_AR1000F_and_AR1000NR_Instructions.pdf#page=20", ) defined_for = StateCode.AR @@ -18,5 +19,12 @@ def formula(person, period, parameters): period ).gov.states.ar.tax.income.deductions.itemized.sources.joint unit_deds = add(person.tax_unit, period, deductions) + # Arkansas freezes IRC 163 as of January 1, 2017, when the premium + # deduction had expired after 2016 (AR3 interest lines 8-10). + disallowed_interest = parameters( + period + ).gov.states.ar.tax.income.deductions.itemized.disallowed_interest_components + if disallowed_interest: + unit_deds = unit_deds - add(person.tax_unit, period, disallowed_interest) is_head = person("is_tax_unit_head", period) return unit_deds * is_head diff --git a/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py index 091b5150824..cad0de42a80 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py @@ -43,7 +43,14 @@ def formula(tax_unit, period, parameters): "ca_misc_deduction", "ca_casualty_loss_deduction", ] - # Federal investment interest is nested in interest_deduction. + # Federal investment interest and mortgage insurance premiums are + # nested in interest_deduction. California disallows the premiums + # (Schedule CA line 8d), including when federal law restores them. return add(tax_unit, period, deductions) - add( - tax_unit, period, ["investment_interest_expense"] + tax_unit, + period, + [ + "investment_interest_expense", + "deductible_mortgage_insurance_premiums", + ], ) diff --git a/policyengine_us/variables/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.py b/policyengine_us/variables/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.py index 6bc3b2570fb..44d1a83f168 100644 --- a/policyengine_us/variables/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.py +++ b/policyengine_us/variables/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.py @@ -33,6 +33,9 @@ class de_itemized_deductions_indv(Variable): # 2023 PIT-RES instructions, line 13: prorate only the deductions that # cannot be specifically allocated between spouses. "https://revenuefiles.delaware.gov/2023/PIT-RES_TY23_2023-01_Instructions.pdf#page=7", + # 2021 PIT-RES instructions, PDF pages 7, 12: specific allocation + # where possible; PIT-RSA line 8d includes qualified premiums. + "https://revenuefiles.delaware.gov/2021/PIT-RES_TY21_2021-01_Instructions.pdf#page=7", ) defined_for = StateCode.DE @@ -60,6 +63,30 @@ def formula(person, period, parameters): if expense_sources is None: continue amount = person.tax_unit(deduction, period) + if deduction == "interest_deduction": + premiums = person.tax_unit( + "deductible_mortgage_insurance_premiums", period + ) + premium_expense = add( + person, + period, + [ + "mortgage_insurance_premiums", + "second_residence_mortgage_insurance_premiums", + ], + ) + total_premium_expense = person.tax_unit.sum(premium_expense) + filer_premium_expense = person.tax_unit.sum( + head_or_spouse * premium_expense + ) + # Allocate the post-phase-out premium component separately. + # The federal person allocation conserves the entire amount + # across filers; retain only the known-payer share here so + # dependent/unknown expenses use Delaware's income fallback. + allocated += person( + "deductible_mortgage_insurance_premiums_indiv", period + ) * _share(filer_premium_expense, total_premium_expense) + amount = max_(amount - premiums, 0) expense = add(person, period, expense_sources) total_expense = person.tax_unit.sum(expense) allocated += amount * _share(head_or_spouse * expense, total_expense) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_deductible_mortgage_insurance_premiums.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_deductible_mortgage_insurance_premiums.py new file mode 100644 index 00000000000..643917b5298 --- /dev/null +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_deductible_mortgage_insurance_premiums.py @@ -0,0 +1,45 @@ +from policyengine_us.model_api import * + + +class hi_deductible_mortgage_insurance_premiums(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii deductible mortgage insurance premiums" + unit = USD + definition_period = YEAR + defined_for = StateCode.HI + reference = ( + "https://files.hawaii.gov/tax/news/announce/ann21-01.pdf", + "https://files.hawaii.gov/tax/legal/legis/2021/21digest.pdf#page=9", + "https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm", + ) + documentation = ( + "Qualified mortgage insurance premiums on the first and second home, " + "after the federal worksheet's phase-out using Hawaii adjusted gross " + "income. HRS 235-2.4(j) does not adopt IRC 163(h)(3)(F), including " + "the federal restoration of premiums from 2026." + ) + + def formula(tax_unit, period, parameters): + in_effect = parameters( + period + ).gov.states.hi.tax.income.deductions.itemized.mortgage_insurance_premiums.in_effect + if not in_effect: + return 0 + p = parameters( + period + ).gov.irs.deductions.itemized.interest.mortgage_insurance_premiums.phase_out + premiums = add( + tax_unit, + period, + [ + "mortgage_insurance_premiums", + "second_residence_mortgage_insurance_premiums", + ], + ) + agi = tax_unit("hi_agi", period) + filing_status = tax_unit("filing_status", period) + excess = max_(0, agi - p.start[filing_status]) + increments = np.ceil(excess / p.increment[filing_status]) + reduction = min_(1, increments * p.rate) + return in_effect * premiums * (1 - reduction) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_interest_deduction.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_interest_deduction.py index 5d90b997a73..4b8f9a3707e 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_interest_deduction.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_interest_deduction.py @@ -8,9 +8,13 @@ class hi_interest_deduction(Variable): unit = USD reference = ( "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=17", - "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=32", # total itemized deduction worksheet + "https://files.hawaii.gov/tax/news/announce/ann21-01.pdf", ) definition_period = YEAR defined_for = StateCode.HI - adds = ["investment_interest_expense", "mortgage_interest"] + adds = [ + "investment_interest_expense", + "mortgage_interest", + "hi_deductible_mortgage_insurance_premiums", + ] diff --git a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py index 83b5c86c38b..f0a8c46af27 100644 --- a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py +++ b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py @@ -12,6 +12,7 @@ class ia_itemized_deductions_unit(Variable): "https://revenue.iowa.gov/media/2650/download?inline", "https://revenue.iowa.gov/sites/default/files/2023-01/2022IA1040%2841001%29.pdf", "https://revenue.iowa.gov/media/2721/download?inline", + "https://revenue.iowa.gov/taxes/tax-guidance/individual-income-tax/iowa-nonconformity-taxpayer-certainty-and-disaster-tax-relief-act-2019", ) defined_for = StateCode.IA @@ -29,8 +30,15 @@ def formula(tax_unit, period, parameters): p_fed = parameters(period).gov.irs.deductions return add(tax_unit, period, p_fed.itemized_deductions) else: - return add( + deductions = add( tax_unit, period, ["itemized_deductions_less_salt", "real_estate_taxes"], ) + # Iowa did not adopt the retroactive extension of the mortgage + # insurance premium deduction for 2018 and 2019. + if p.disallowed_interest_components: + deductions = deductions - add( + tax_unit, period, p.disallowed_interest_components + ) + return deductions diff --git a/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_disallowed_second_residence_interest.py b/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_disallowed_second_residence_interest.py new file mode 100644 index 00000000000..d023779c5c8 --- /dev/null +++ b/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_disallowed_second_residence_interest.py @@ -0,0 +1,71 @@ +from policyengine_us.model_api import * + + +class ky_disallowed_second_residence_interest(Variable): + value_type = float + entity = TaxUnit + label = "Kentucky disallowed second-residence interest deduction" + unit = USD + definition_period = YEAR + documentation = ( + "Federal qualified residence interest that Kentucky removes from " + "itemized deductions once it limits the deduction to the principal " + "residence. The federal deduction for mortgage interest and points " + "is capped at the interest and points paid on the principal " + "residence. Mortgage insurance premiums on the " + "second residence are removed after the federal phase-out. The " + "capped deduction equals a recomputation on the principal residence " + "alone when the combined debt is within the federal limit, or when " + "both loans carry the same rate, fall under the same debt limit and " + "carry points in proportion to their debt (or none). Otherwise it " + "can differ in either direction, because the model does not record " + "which loan secures which residence." + ) + reference = ( + "https://apps.legislature.ky.gov/law/statutes/statute.aspx?id=57914#page=4", # (2)(j) + "https://apps.legislature.ky.gov/law/acts/26RS/documents/0161.pdf#page=13", + "https://apps.legislature.ky.gov/law/acts/26RS/documents/0198.pdf#page=74", + ) + defined_for = StateCode.KY + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.ky.tax.income.deductions.itemized + # Federal deductible interest and points on both qualified + # residences, after the acquisition-debt limit. + deductible_interest = tax_unit("deductible_mortgage_interest_tax_unit", period) + # Interest and points paid on the principal residence. + principal_residence_interest = ( + tax_unit("home_mortgage_interest_tax_unit", period) + - add(tax_unit, period, ["second_residence_mortgage_interest"]) + + add(tax_unit, period, ["home_mortgage_points"]) + ) + second_residence_interest = add( + tax_unit, + period, + ["second_residence_mortgage_interest", "second_residence_mortgage_points"], + ) + # The excess can never exceed the second residence's own interest and + # points, since the deductible pool is at most the sum of both. + excess_interest = min_( + second_residence_interest, + max_(0, deductible_interest - principal_residence_interest), + ) + # Premiums: the federal phase-out applies to all premiums alike, so + # the second residence's part of the deduction is its share of them. + second_residence_premiums = add( + tax_unit, period, ["second_residence_mortgage_insurance_premiums"] + ) + premiums = second_residence_premiums + add( + tax_unit, period, ["mortgage_insurance_premiums"] + ) + deductible_premiums = tax_unit("deductible_mortgage_insurance_premiums", period) + second_residence_premium_share = np.divide( + second_residence_premiums, + premiums, + out=np.zeros_like(premiums), + where=premiums > 0, + ) + disallowed = ( + excess_interest + deductible_premiums * second_residence_premium_share + ) + return disallowed * p.principal_residence_interest_only diff --git a/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.py b/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.py index 7adcc1154a8..94e39ee398d 100644 --- a/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.py +++ b/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.py @@ -10,8 +10,12 @@ class ky_itemized_deductions_unit(Variable): reference = ( "https://revenue.ky.gov/Forms/Form%20740%20Schedule%20A%202022.pdf", "https://law.justia.com/codes/kentucky/2022/chapter-141/section-141-019/", # (2)(i) + "https://apps.legislature.ky.gov/law/statutes/statute.aspx?id=57914#page=4", # (2)(j) ) defined_for = StateCode.KY adds = ["itemized_deductions_less_salt"] - subtracts = ["medical_expense_deduction"] + subtracts = [ + "medical_expense_deduction", + "ky_disallowed_second_residence_interest", + ] diff --git a/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py index 7fc89edb407..b7154dc4d8a 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py @@ -27,7 +27,17 @@ def formula(tax_unit, period, parameters): # pay less Minnesota income tax if you take the larger of your # itemized or standard deduction. # ... calculate pre-limitation itemized deductions + # Minn. Stat. 290.0122, subd. 5(3), excludes mortgage insurance + # premiums from interest even when deductible federally. Remove + # only the federal amount included, after its AGI phase-out. + disallowed_interest = parameters( + period + ).gov.states.mn.tax.income.deductions.itemized.disallowed_interest_components itm_deds_less_salt = tax_unit("itemized_deductions_less_salt", period) + if disallowed_interest: + itm_deds_less_salt = itm_deds_less_salt - add( + tax_unit, period, disallowed_interest + ) capped_property_taxes = tax_unit("capped_property_taxes", period) # Schedule M1SA line 19 takes Minnesota's own casualty and theft loss # (Schedule M1CAT) in place of the federal one, which is limited to diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py index 20150ae2602..d946cd9d98e 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py @@ -8,6 +8,9 @@ class mt_itemized_deductions_for_federal_itemization_indiv(Variable): unit = USD definition_period = YEAR reference = ( + "https://revenuefiles.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2021_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=33", + # 2021 Form 2 line 9 includes qualified mortgage insurance premiums + # allowed on the federal return, after the federal AGI phase-out. "https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2/2022_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=7", "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-21/section-15-30-2131/", # MT Code § 15-30-2131 (2022) (1) @@ -34,7 +37,12 @@ def formula(person, period, parameters): # on acquisition debt above the IRC 163(h)(3) caps is not deductible # (MCA 15-30-2131(1)(a); Form 2 instructions, itemized line 9). mortgage_interest = person("deductible_mortgage_interest", period) - interest_ded = investment_interest + mortgage_interest + # Apply the federal phase-out first, then allocate the allowed + # premiums according to each spouse's payments on both residences. + mortgage_insurance = person( + "deductible_mortgage_insurance_premiums_indiv", period + ) + interest_ded = investment_interest + mortgage_interest + mortgage_insurance other_deductions = add( person, period, diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py index ba977871a6f..86ab41904a5 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py @@ -8,6 +8,9 @@ class mt_itemized_deductions_for_federal_itemization_joint(Variable): unit = USD definition_period = YEAR reference = ( + "https://revenuefiles.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2021_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=33", + # 2021 Form 2 line 9 includes qualified mortgage insurance premiums + # allowed on the federal return, after the federal AGI phase-out. "https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2/2022_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=7", "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-21/section-15-30-2131/", # MT Code § 15-30-2131 (2022) (1) @@ -27,7 +30,12 @@ def formula(person, period, parameters): # on acquisition debt above the IRC 163(h)(3) caps is not deductible # (MCA 15-30-2131(1)(a); Form 2 instructions, itemized line 9). mortgage_interest = person("deductible_mortgage_interest", period) - interest_ded = investment_interest + mortgage_interest + # The joint return includes the federal premium deduction for both + # spouses, once, in the head's itemized deduction total. + mortgage_insurance = person.tax_unit( + "deductible_mortgage_insurance_premiums", period + ) + interest_ded = investment_interest + mortgage_interest + mortgage_insurance other_deductions = add( person.tax_unit, period, diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py index aa99346184a..b690c0195d8 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py @@ -8,6 +8,9 @@ class mt_itemized_deductions_indiv(Variable): unit = USD definition_period = YEAR reference = ( + "https://revenuefiles.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2021_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=33", + # 2021 Form 2 line 9 includes qualified mortgage insurance premiums + # allowed on the federal return, after the federal AGI phase-out. "https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2/2022_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=7", "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-21/section-15-30-2131/", # MT Code § 15-30-2131 (2022) (1) @@ -34,7 +37,12 @@ def formula(person, period, parameters): # on acquisition debt above the IRC 163(h)(3) caps is not deductible # (MCA 15-30-2131(1)(a); Form 2 instructions, itemized line 9). mortgage_interest = person("deductible_mortgage_interest", period) - interest_ded = investment_interest + mortgage_interest + # Apply the federal phase-out first, then allocate the allowed + # premiums according to each spouse's payments on both residences. + mortgage_insurance = person( + "deductible_mortgage_insurance_premiums_indiv", period + ) + interest_ded = investment_interest + mortgage_interest + mortgage_insurance other_deductions = add( person, period, diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py index 5f503e42a9b..58f0ceff504 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py @@ -8,6 +8,9 @@ class mt_itemized_deductions_joint(Variable): unit = USD definition_period = YEAR reference = ( + "https://revenuefiles.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2021_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=33", + # 2021 Form 2 line 9 includes qualified mortgage insurance premiums + # allowed on the federal return, after the federal AGI phase-out. "https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2/2022_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=7", "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-21/section-15-30-2131/", # MT Code § 15-30-2131 (2022) (1) @@ -27,7 +30,12 @@ def formula(person, period, parameters): # on acquisition debt above the IRC 163(h)(3) caps is not deductible # (MCA 15-30-2131(1)(a); Form 2 instructions, itemized line 9). mortgage_interest = person("deductible_mortgage_interest", period) - interest_ded = investment_interest + mortgage_interest + # The joint return includes the federal premium deduction for both + # spouses, once, in the head's itemized deduction total. + mortgage_insurance = person.tax_unit( + "deductible_mortgage_insurance_premiums", period + ) + interest_ded = investment_interest + mortgage_interest + mortgage_insurance other_deductions = add( person.tax_unit, period, diff --git a/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_mortgage_and_property_tax_before_limitation.py b/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_mortgage_and_property_tax_before_limitation.py index 24da4bdf9a9..578a7359c15 100644 --- a/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_mortgage_and_property_tax_before_limitation.py +++ b/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_mortgage_and_property_tax_before_limitation.py @@ -9,6 +9,9 @@ class nc_mortgage_and_property_tax_before_limitation(Variable): definition_period = YEAR documentation = "Form D-400 Schedule A line 3: home mortgage interest (line 1) plus real estate property taxes (line 2), before the $20,000 limitation. Calculated for all states so a separately filing spouse outside North Carolina contributes to the shared cap." reference = ( + # SL 2026-31 section 12(a) adopts the IRC as of July 5, 2025, + # including the federal restoration of qualified premiums from 2026. + "https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-31.pdf#page=42", # N.C. Gen. Stat. 105-153.5(a)(2)b "https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html", # 2025 Form D-401 instructions, Form D-400 Schedule A lines 1 to 3 @@ -21,6 +24,16 @@ def formula(tax_unit, period, parameters): filing_status = tax_unit("filing_status", period) mortgage_interest = add(tax_unit, period, ["mortgage_interest"]) + premiums_allowed = parameters( + period + ).gov.states.nc.tax.income.deductions.itemized.mortgage_insurance_premiums_allowed + if premiums_allowed: + # The express state premium exclusion applies through 2021. + # From 2026 the conformed federal deduction joins the mortgage + # and property-tax pool before North Carolina's shared cap. + mortgage_interest = mortgage_interest + tax_unit( + "deductible_mortgage_insurance_premiums", period + ) pirs = parameters(period).gov.irs.deductions.itemized.salt_and_real_estate property_taxes = min_( add(tax_unit, period, ["real_estate_taxes"]), diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.py index c6b5d9dc9d8..c217b06ddea 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.py @@ -35,3 +35,9 @@ class ny_itemized_deductions_max(Variable): # NY-specific addition per Tax Law § 615(d) "ny_college_tuition_deduction", ] + + # IT-196-I (2021), page 2: New York did not adopt the extension + # of the mortgage-insurance deduction beyond 2020. + subtracts = ( + "gov.states.ny.tax.income.deductions.itemized.disallowed_interest_components" + ) diff --git a/policyengine_us/variables/gov/states/wi/tax/income/credits/itemized_deduction/wi_itemized_deduction_credit_potential.py b/policyengine_us/variables/gov/states/wi/tax/income/credits/itemized_deduction/wi_itemized_deduction_credit_potential.py index da76fe63638..59932a185af 100644 --- a/policyengine_us/variables/gov/states/wi/tax/income/credits/itemized_deduction/wi_itemized_deduction_credit_potential.py +++ b/policyengine_us/variables/gov/states/wi/tax/income/credits/itemized_deduction/wi_itemized_deduction_credit_potential.py @@ -9,13 +9,18 @@ class wi_itemized_deduction_credit_potential(Variable): definition_period = YEAR reference = ( "https://www.revenue.wi.gov/TaxForms2021/2021-Form1f.pdf#page=4", + "https://www.revenue.wi.gov/TaxForms2021/2021-Form1NPR-Inst.pdf#page=34", "https://www.revenue.wi.gov/TaxForms2022/2022-Form1f.pdf#page=4", "https://docs.legis.wisconsin.gov/misc/lfb/informational_papers/january_2023/0002_individual_income_tax_informational_paper_2.pdf#page=19", ) defined_for = StateCode.WI def formula(tax_unit, period, parameters): - wi_itmded = tax_unit("itemized_deductions_less_salt", period) + # Form 1 instructions exclude mortgage insurance premiums treated + # as interest from the itemized deduction credit. + wi_itmded = tax_unit("itemized_deductions_less_salt", period) - tax_unit( + "deductible_mortgage_insurance_premiums", period + ) wi_stdded = tax_unit("wi_standard_deduction", period) excess_itmded = max_(0, wi_itmded - wi_stdded) p = parameters(period).gov.states.wi.tax.income diff --git a/policyengine_us/variables/household/expense/person/deductible_mortgage_insurance_premiums_indiv.py b/policyengine_us/variables/household/expense/person/deductible_mortgage_insurance_premiums_indiv.py new file mode 100644 index 00000000000..b63bdd4db4f --- /dev/null +++ b/policyengine_us/variables/household/expense/person/deductible_mortgage_insurance_premiums_indiv.py @@ -0,0 +1,40 @@ +from policyengine_us.model_api import * + + +class deductible_mortgage_insurance_premiums_indiv(Variable): + value_type = float + entity = Person + label = "Deductible mortgage insurance premiums allocated to each filer" + unit = USD + definition_period = YEAR + documentation = ( + "Allocates the tax unit's qualified mortgage insurance premium " + "deduction after its AGI phase-out to the head and spouse, in " + "proportion to their reported premiums on both residences. If " + "neither filer reports premiums, splits the deduction equally " + "between filers. Allocations sum to the tax-unit deduction." + ) + reference = "https://www.law.cornell.edu/uscode/text/26/163#h_3_E" + + def formula(person, period, parameters): + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + reported_premiums = head_or_spouse * add( + person, + period, + [ + "mortgage_insurance_premiums", + "second_residence_mortgage_insurance_premiums", + ], + ) + total_reported_premiums = person.tax_unit.sum(reported_premiums) + share = np.zeros_like(total_reported_premiums) + reported_mask = total_reported_premiums > 0 + share[reported_mask] = ( + reported_premiums[reported_mask] / total_reported_premiums[reported_mask] + ) + filer_count = person.tax_unit.sum(head_or_spouse) + equal_share = np.zeros_like(total_reported_premiums) + filer_mask = filer_count > 0 + equal_share[filer_mask] = head_or_spouse[filer_mask] / filer_count[filer_mask] + share = where(total_reported_premiums > 0, share, equal_share) + return person.tax_unit("deductible_mortgage_insurance_premiums", period) * share diff --git a/policyengine_us/variables/household/expense/person/deductible_mortgage_interest.py b/policyengine_us/variables/household/expense/person/deductible_mortgage_interest.py index 917801673d0..fe92a98e205 100644 --- a/policyengine_us/variables/household/expense/person/deductible_mortgage_interest.py +++ b/policyengine_us/variables/household/expense/person/deductible_mortgage_interest.py @@ -6,10 +6,10 @@ class deductible_mortgage_interest(Variable): entity = Person label = "Deductible mortgage interest" documentation = ( - "Federal deductible mortgage interest. When structural mortgage inputs " - "are provided at the tax-unit level, PolicyEngine applies the " - "acquisition-debt caps and allocates the resulting deduction across " - "filers." + "Federal deductible home mortgage interest and points. When " + "structural mortgage inputs are provided at the tax-unit level, " + "PolicyEngine applies the acquisition-debt caps and allocates the " + "resulting deduction across filers." ) unit = USD definition_period = YEAR diff --git a/policyengine_us/variables/household/expense/person/home_mortgage_interest.py b/policyengine_us/variables/household/expense/person/home_mortgage_interest.py index f25312cafea..0a5f5700a14 100644 --- a/policyengine_us/variables/household/expense/person/home_mortgage_interest.py +++ b/policyengine_us/variables/household/expense/person/home_mortgage_interest.py @@ -8,4 +8,10 @@ class home_mortgage_interest(Variable): unit = USD definition_period = YEAR uprating = "gov.bls.cpi.cpi_u" - documentation = "Home mortgage interest, including both reported and not reported on federal Form 1098." + documentation = ( + "Home mortgage interest on debt secured by the principal residence, " + "including interest reported and not reported on federal Form 1098. " + "Report interest on a second qualified residence, such as a vacation " + "home, in second_residence_mortgage_interest instead. Interest whose " + "residence is unknown is treated as principal-residence interest." + ) diff --git a/policyengine_us/variables/household/expense/person/home_mortgage_interest_share.py b/policyengine_us/variables/household/expense/person/home_mortgage_interest_share.py index 3f567c3e097..d779ba21283 100644 --- a/policyengine_us/variables/household/expense/person/home_mortgage_interest_share.py +++ b/policyengine_us/variables/household/expense/person/home_mortgage_interest_share.py @@ -7,17 +7,26 @@ class home_mortgage_interest_share(Variable): label = "Share of tax-unit home mortgage interest" definition_period = YEAR documentation = ( - "Allocates the tax unit's mortgage interest (and its deductible and " - "non-deductible parts) to the filers, who claim the deduction: in " - "proportion to the head's and spouse's own reported interest, or " - "evenly between them when only other members (such as dependents) " - "report interest. Shares sum to one across head and spouse, so the " - "person amounts always add back to the tax-unit totals." + "Allocates the tax unit's mortgage interest and points (and their " + "deductible and non-deductible parts) to the filers: in proportion " + "to the head's and spouse's own reported interest and points on both " + "residences, or evenly between them when only other members (such " + "as dependents) report them. Shares sum to one across head and " + "spouse, so the person amounts add back to the tax-unit totals." ) def formula(person, period, parameters): head_or_spouse = person("is_tax_unit_head_or_spouse", period) - reported_interest = head_or_spouse * person("home_mortgage_interest", period) + reported_interest = head_or_spouse * add( + person, + period, + [ + "home_mortgage_interest", + "home_mortgage_points", + "second_residence_mortgage_interest", + "second_residence_mortgage_points", + ], + ) total_reported_interest = person.tax_unit.sum(reported_interest) reported_share = np.zeros_like(total_reported_interest) diff --git a/policyengine_us/variables/household/expense/person/home_mortgage_points.py b/policyengine_us/variables/household/expense/person/home_mortgage_points.py new file mode 100644 index 00000000000..46398f3bc36 --- /dev/null +++ b/policyengine_us/variables/household/expense/person/home_mortgage_points.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class home_mortgage_points(Variable): + value_type = float + entity = Person + label = "Deductible points paid on a home mortgage" + unit = USD + definition_period = YEAR + uprating = "gov.bls.cpi.cpi_u" + documentation = ( + "Points on debt secured by the principal residence that are " + "deductible this year: points deductible in the year paid under 26 " + "U.S.C. 461(g)(2), plus this year's ratable share of points deducted " + "over the life of a loan. Exclude points already included in " + "home_mortgage_interest. Points reported on Form 1098 usually are, " + "because Schedule A line 8a combines them with interest, so this is " + "typically Schedule A line 8c, points not reported on Form 1098. " + "Report points on a second qualified residence in " + "second_residence_mortgage_points." + ) + reference = [ + "https://www.law.cornell.edu/uscode/text/26/461#g_2", + "https://www.irs.gov/pub/irs-pdf/p936.pdf#page=6", + ] diff --git a/policyengine_us/variables/household/expense/person/mortgage_insurance_premiums.py b/policyengine_us/variables/household/expense/person/mortgage_insurance_premiums.py new file mode 100644 index 00000000000..f38f418b345 --- /dev/null +++ b/policyengine_us/variables/household/expense/person/mortgage_insurance_premiums.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class mortgage_insurance_premiums(Variable): + value_type = float + entity = Person + label = "Qualified mortgage insurance premiums" + unit = USD + definition_period = YEAR + uprating = "gov.bls.cpi.cpi_u" + documentation = ( + "Premiums paid for qualified mortgage insurance (from the Department " + "of Veterans Affairs, the Federal Housing Administration, the Rural " + "Housing Service, or private mortgage insurance) in connection with " + "home acquisition debt on the principal residence, under a contract issued " + "after 2006, and allocable to this year (Form 1098 box 5). Enter the " + "premiums before any adjusted-gross-income phase-out. Report premiums " + "on a second qualified residence in " + "second_residence_mortgage_insurance_premiums." + ) + reference = [ + "https://www.law.cornell.edu/uscode/text/26/163#h_3_E", + "https://www.law.cornell.edu/uscode/text/26/163#h_5_E", + ] diff --git a/policyengine_us/variables/household/expense/person/mortgage_interest.py b/policyengine_us/variables/household/expense/person/mortgage_interest.py index f1591180b52..ff8d88ccd81 100644 --- a/policyengine_us/variables/household/expense/person/mortgage_interest.py +++ b/policyengine_us/variables/household/expense/person/mortgage_interest.py @@ -7,5 +7,9 @@ class mortgage_interest(Variable): label = "Mortgage interest" unit = USD definition_period = YEAR + documentation = ( + "Home mortgage interest and deductible points, before the federal " + "acquisition-debt caps." + ) adds = ["non_deductible_mortgage_interest", "deductible_mortgage_interest"] diff --git a/policyengine_us/variables/household/expense/person/non_deductible_mortgage_interest.py b/policyengine_us/variables/household/expense/person/non_deductible_mortgage_interest.py index 0f38d351d09..f72266c9c30 100644 --- a/policyengine_us/variables/household/expense/person/non_deductible_mortgage_interest.py +++ b/policyengine_us/variables/household/expense/person/non_deductible_mortgage_interest.py @@ -8,8 +8,8 @@ class non_deductible_mortgage_interest(Variable): unit = USD definition_period = YEAR documentation = ( - "Home mortgage interest that is not deductible federally after " - "applying the acquisition-debt caps." + "Home mortgage interest and points that are not deductible federally " + "after applying the acquisition-debt caps." ) def formula(person, period, parameters): diff --git a/policyengine_us/variables/household/expense/person/second_residence_mortgage_insurance_premiums.py b/policyengine_us/variables/household/expense/person/second_residence_mortgage_insurance_premiums.py new file mode 100644 index 00000000000..6ea02978d5b --- /dev/null +++ b/policyengine_us/variables/household/expense/person/second_residence_mortgage_insurance_premiums.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class second_residence_mortgage_insurance_premiums(Variable): + value_type = float + entity = Person + label = "Qualified mortgage insurance premiums on a second residence" + unit = USD + definition_period = YEAR + uprating = "gov.bls.cpi.cpi_u" + documentation = ( + "Qualified mortgage insurance premiums paid in connection with " + "acquisition debt on the taxpayer's second qualified residence (a " + "residence other than the principal residence), allocable to this " + "year, before any adjusted-gross-income phase-out. Do not include " + "them in mortgage_insurance_premiums." + ) + reference = [ + "https://www.law.cornell.edu/uscode/text/26/163#h_3_E", + "https://www.law.cornell.edu/uscode/text/26/163#h_5_A", + ] diff --git a/policyengine_us/variables/household/expense/person/second_residence_mortgage_interest.py b/policyengine_us/variables/household/expense/person/second_residence_mortgage_interest.py new file mode 100644 index 00000000000..1bc46d2db1a --- /dev/null +++ b/policyengine_us/variables/household/expense/person/second_residence_mortgage_interest.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class second_residence_mortgage_interest(Variable): + value_type = float + entity = Person + label = "Interest paid on a second-residence mortgage" + unit = USD + definition_period = YEAR + uprating = "gov.bls.cpi.cpi_u" + documentation = ( + "Home mortgage interest on debt secured by the taxpayer's second " + "qualified residence: a residence other than the principal residence, " + "such as a vacation home, that the taxpayer treats as a qualified " + "residence under 26 U.S.C. 163(h)(5)(A)(i)(II). Do not include it in " + "home_mortgage_interest. Federal law pools it with principal-residence " + "interest; some states allow only principal-residence interest." + ) + reference = [ + "https://www.law.cornell.edu/uscode/text/26/163#h_5_A", + "https://www.irs.gov/pub/irs-pdf/p936.pdf#page=4", + ] diff --git a/policyengine_us/variables/household/expense/person/second_residence_mortgage_points.py b/policyengine_us/variables/household/expense/person/second_residence_mortgage_points.py new file mode 100644 index 00000000000..33b66cd6542 --- /dev/null +++ b/policyengine_us/variables/household/expense/person/second_residence_mortgage_points.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class second_residence_mortgage_points(Variable): + value_type = float + entity = Person + label = "Deductible points paid on a second-residence mortgage" + unit = USD + definition_period = YEAR + uprating = "gov.bls.cpi.cpi_u" + documentation = ( + "This year's deductible points on debt secured by the taxpayer's " + "second qualified residence (a residence other than the principal " + "residence). Points on a second home are deducted ratably over the " + "life of the loan, because 26 U.S.C. 461(g)(2) allows a deduction in " + "the year paid only for the principal residence. Exclude points " + "already included in second_residence_mortgage_interest, and do not " + "include them in home_mortgage_points." + ) + reference = [ + "https://www.law.cornell.edu/uscode/text/26/461#g", + "https://www.irs.gov/pub/irs-pdf/p936.pdf#page=8", + ] diff --git a/policyengine_us/variables/household/expense/tax_unit/mortgage_interest_structure.py b/policyengine_us/variables/household/expense/tax_unit/mortgage_interest_structure.py index e4ecd49f3e0..8189bd8e17e 100644 --- a/policyengine_us/variables/household/expense/tax_unit/mortgage_interest_structure.py +++ b/policyengine_us/variables/household/expense/tax_unit/mortgage_interest_structure.py @@ -112,12 +112,13 @@ class home_mortgage_interest_tax_unit(Variable): unit = USD definition_period = YEAR documentation = ( - "Total home mortgage interest, summed from the person-level " - "home_mortgage_interest input — the single mortgage-interest input " - "(issue #9275). The per-mortgage balances and origination years remain " - "tax-unit inputs because the acquisition-debt caps need them per loan." + "Total home mortgage interest on the principal and second residences, " + "summed from the person-level home_mortgage_interest and " + "second_residence_mortgage_interest inputs. The per-mortgage balances " + "and origination years remain tax-unit inputs because the " + "acquisition-debt caps need them per loan." ) - adds = ["home_mortgage_interest"] + adds = ["home_mortgage_interest", "second_residence_mortgage_interest"] class deductible_mortgage_interest_tax_unit(Variable): @@ -127,12 +128,13 @@ class deductible_mortgage_interest_tax_unit(Variable): unit = USD definition_period = YEAR documentation = ( - "Federal deductible mortgage interest after applying the statutory " - "acquisition-debt caps to up to two mortgages. Grandfathered debt, " - "incurred on or before October 13, 1987, has no dollar limit but " + "Federal deductible mortgage interest and points after applying the " + "statutory acquisition-debt caps to up to two mortgages. Grandfathered " + "debt, incurred on or before October 13, 1987, has no dollar limit but " "reduces the limits for other debt; debt incurred on or before " "December 15, 2017 keeps the $1,000,000 limit and reduces the $750,000 " - "limit for later debt (IRS Publication 936, Table 1)." + "limit for later debt (IRS Publication 936, Table 1). Points take the " + "same deductible share as interest (Table 1, line 13 instructions)." ) reference = ( "https://www.law.cornell.edu/uscode/text/26/163#h_3", @@ -147,6 +149,11 @@ def formula(tax_unit, period, parameters): second_year = tax_unit("second_home_mortgage_origination_year", period) total_balance = first_balance + second_balance total_interest = tax_unit("home_mortgage_interest_tax_unit", period) + points = add( + tax_unit, + period, + ["home_mortgage_points", "second_residence_mortgage_points"], + ) filing_status = tax_unit("filing_status", period) p = parameters(period).gov.irs.deductions.itemized.interest.mortgage @@ -172,7 +179,9 @@ def formula(tax_unit, period, parameters): deductible_share[mask] = np.minimum( 1, limited_balance[mask] / total_balance[mask] ) - return total_interest * deductible_share + # Publication 936, Table 1, line 13: deductible points are multiplied + # by the same line 14 fraction as the interest on line 13. + return (total_interest + points) * deductible_share class non_deductible_mortgage_interest_tax_unit(Variable): @@ -182,11 +191,16 @@ class non_deductible_mortgage_interest_tax_unit(Variable): unit = USD definition_period = YEAR documentation = ( - "Home mortgage interest that is not deductible federally because it " - "exceeds the acquisition-debt caps." + "Home mortgage interest and points that are not deductible federally " + "because they exceed the acquisition-debt caps." ) def formula(tax_unit, period, parameters): total_interest = tax_unit("home_mortgage_interest_tax_unit", period) + points = add( + tax_unit, + period, + ["home_mortgage_points", "second_residence_mortgage_points"], + ) deductible_interest = tax_unit("deductible_mortgage_interest_tax_unit", period) - return max_(0, total_interest - deductible_interest) + return max_(0, total_interest + points - deductible_interest)