From a0c589dbb62b1dd109d7583f18e9a05bf3334185 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 19:18:29 -0400 Subject: [PATCH 1/9] WIP: failing-on-main tests for Hawaii mortgage and investment interest limits Co-Authored-By: Claude Opus 5.5 --- .../itemized/hi_interest_deduction.yaml | 76 +++++ .../hi_investment_interest_deduction.yaml | 210 ++++++++++++++ .../hi_mortgage_interest_deduction.yaml | 267 ++++++++++++++++++ .../hi_reduced_itemized_deductions.yaml | 35 +++ 4 files changed, 588 insertions(+) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_interest_deduction.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml 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..1dfe46d07ad --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_interest_deduction.yaml @@ -0,0 +1,76 @@ +- name: Case 1, a $1.5 million mortgage deducts interest on $1.1 million of debt. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 90_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [person1] + state_code: HI + output: + # $1,100,000 / $1,500,000 * $90,000 = $66,000. + hi_interest_deduction: 66_000 + +- name: Case 2, mortgage and investment interest are each limited. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 65_000 + investment_interest_expense: 5_000 + taxable_interest_income: 2_000 + person2: + age: 45 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + first_home_mortgage_balance: 800_000 + first_home_mortgage_origination_year: 2017 + second_home_mortgage_balance: 500_000 + second_home_mortgage_origination_year: 2020 + households: + household: + members: [person1, person2] + state_code: HI + output: + # Mortgage: $1,100,000 / $1,300,000 * $65,000 = $55,000. + hi_mortgage_interest_deduction: 55_000 + # Investment interest: limited to $2,000 of net investment income. + hi_investment_interest_deduction: 2_000 + hi_interest_deduction: 57_000 + hi_total_itemized_deductions: 57_000 + +- name: Case 3, interest within both limits is fully deductible. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 20_000 + investment_interest_expense: 1_000 + qualified_dividend_income: 4_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + first_home_mortgage_balance: 400_000 + first_home_mortgage_origination_year: 2021 + households: + household: + members: [person1] + state_code: HI + output: + hi_interest_deduction: 21_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.yaml new file mode 100644 index 00000000000..611d4c748f4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.yaml @@ -0,0 +1,210 @@ +- name: Case 1, qualified dividends count as investment income in Hawaii. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + investment_interest_expense: 5_000 + taxable_interest_income: 2_000 + qualified_dividend_income: 1_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + # N-158 line 4a: $2,000 interest + $1,000 ordinary dividends = $3,000. + hi_investment_interest_deduction: 3_000 + +- name: Case 2, interest expense below investment income is fully deductible. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + investment_interest_expense: 1_000 + taxable_interest_income: 10_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + hi_investment_interest_deduction: 1_000 + +- name: Case 3, net short-term gain counts as investment income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + investment_interest_expense: 5_000 + short_term_capital_gains: 4_000 + long_term_capital_gains_before_response: 10_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + # Line 4b $14,000; line 4c $10,000; line 4d $4,000. + hi_investment_interest_deduction: 4_000 + +- name: Case 4, net capital gain alone is not investment income without the election. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + investment_interest_expense: 5_000 + long_term_capital_gains_before_response: 10_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + hi_investment_interest_deduction: 0 + +- name: Case 5, investment expenses reduce net investment income. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + investment_interest_expense: 5_000 + taxable_interest_income: 6_000 + investment_expenses: 2_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + hi_investment_interest_deduction: 4_000 + +- name: Case 6, a net short-term loss does not reduce other investment income. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + investment_interest_expense: 5_000 + taxable_interest_income: 2_000 + short_term_capital_gains: -3_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + hi_investment_interest_deduction: 2_000 + +- name: Case 7, net investment income is floored at zero. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + investment_interest_expense: 2_000 + taxable_interest_income: 1_000 + investment_expenses: 5_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + hi_investment_interest_deduction: 0 + +- name: Case 8, a long-term loss leaves the short-term gain as investment income. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + investment_interest_expense: 5_000 + short_term_capital_gains: 6_000 + long_term_capital_gains_before_response: -2_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + # Line 4b $4,000; net capital gain $0, so line 4d is $4,000. + hi_investment_interest_deduction: 4_000 + +- name: Case 9, joint filers pool investment income and interest expense. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + investment_interest_expense: 3_000 + taxable_interest_income: 1_000 + person2: + age: 45 + non_qualified_dividend_income: 1_500 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: HI + output: + hi_investment_interest_deduction: 2_500 + +- name: Case 10, no investment interest expense means no deduction. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + taxable_interest_income: 10_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + hi_investment_interest_deduction: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml new file mode 100644 index 00000000000..33abff22d1c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml @@ -0,0 +1,267 @@ +- name: Case 1, a 2023 mortgage over the limits keeps the pre-TCJA limits in Hawaii. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 90_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [person1] + state_code: HI + output: + # Federal: $750,000 / $1,500,000 * $90,000 = $45,000. + deductible_mortgage_interest_tax_unit: 45_000 + # Hawaii (2017 Pub 936 Table 1): $1,000,000 acquisition debt plus + # $100,000 of the excess as home equity debt. + # $1,100,000 / $1,500,000 * $90,000 = $66,000. + hi_mortgage_interest_deduction: 66_000 + +- name: Case 2, a post-TCJA mortgage under $1 million is fully deductible in Hawaii. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 45_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + first_home_mortgage_balance: 900_000 + first_home_mortgage_origination_year: 2018 + households: + household: + members: [person1] + state_code: HI + output: + # Federal: $750,000 / $900,000 * $45,000 = $37,500. + deductible_mortgage_interest_tax_unit: 37_500 + hi_mortgage_interest_deduction: 45_000 + +- name: Case 3, married filing separately uses the $500,000 and $50,000 limits. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 30_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SEPARATE + first_home_mortgage_balance: 600_000 + first_home_mortgage_origination_year: 2019 + households: + household: + members: [person1] + state_code: HI + output: + # Federal: $375,000 / $600,000 * $30,000 = $18,750. + deductible_mortgage_interest_tax_unit: 18_750 + # Hawaii: ($500,000 + $50,000) / $600,000 * $30,000 = $27,500. + hi_mortgage_interest_deduction: 27_500 + +- name: Case 4, joint filers with two mortgages apply the limits to the combined balance. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 40_000 + person2: + age: 45 + home_mortgage_interest: 25_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + first_home_mortgage_balance: 800_000 + first_home_mortgage_origination_year: 2017 + second_home_mortgage_balance: 500_000 + second_home_mortgage_origination_year: 2020 + households: + household: + members: [person1, person2] + state_code: HI + output: + # Federal: $800,000 / $1,300,000 * $65,000 = $40,000. + deductible_mortgage_interest_tax_unit: 40_000 + # Hawaii: $1,100,000 / $1,300,000 * $65,000 = $55,000. + hi_mortgage_interest_deduction: 55_000 + +- name: Case 5, debt exactly at the combined $1.1 million limit is fully deductible. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 55_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + first_home_mortgage_balance: 1_100_000 + first_home_mortgage_origination_year: 2024 + households: + household: + members: [person1] + state_code: HI + output: + hi_mortgage_interest_deduction: 55_000 + +- name: Case 6, debt between $1 million and $1.1 million is fully deductible. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 52_500 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + first_home_mortgage_balance: 1_050_000 + first_home_mortgage_origination_year: 2024 + households: + household: + members: [person1] + state_code: HI + output: + hi_mortgage_interest_deduction: 52_500 + +- name: Case 7, head of household in 2018, the first year Hawaii decoupled. + period: 2018 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 100_000 + person2: + age: 10 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: HEAD_OF_HOUSEHOLD + first_home_mortgage_balance: 2_000_000 + first_home_mortgage_origination_year: 2018 + households: + household: + members: [person1, person2] + state_code: HI + output: + # $1,100,000 / $2,000,000 * $100,000 = $55,000. + hi_mortgage_interest_deduction: 55_000 + +- name: Case 8, surviving spouse uses the $1 million and $100,000 limits. + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 60 + home_mortgage_interest: 60_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SURVIVING_SPOUSE + first_home_mortgage_balance: 1_200_000 + first_home_mortgage_origination_year: 2019 + households: + household: + members: [person1] + state_code: HI + output: + # $1,100,000 / $1,200,000 * $60,000 = $55,000. + hi_mortgage_interest_deduction: 55_000 + +- name: Case 9, interest without a reported balance is fully deductible. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 10_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + hi_mortgage_interest_deduction: 10_000 + +- name: Case 10, a directly supplied deductible mortgage interest input is kept. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + deductible_mortgage_interest: 20_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + hi_mortgage_interest_deduction: 20_000 + +- name: Case 11, no mortgage means no deduction. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + households: + household: + members: [person1] + state_code: HI + output: + hi_mortgage_interest_deduction: 0 + +- name: Case 12, a very large mortgage is limited to $1.1 million of debt. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + home_mortgage_interest: 150_000 + tax_units: + tax_unit: + members: [person1] + filing_status: JOINT + first_home_mortgage_balance: 3_000_000 + first_home_mortgage_origination_year: 2010 + households: + household: + members: [person1] + state_code: HI + output: + # $1,100,000 / $3,000,000 * $150,000 = $55,000. + hi_mortgage_interest_deduction: 55_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml index 912c803624b..3ee95aaec3d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml @@ -4,8 +4,10 @@ people: person1: investment_interest_expense: 100 + taxable_interest_income: 100 person2: investment_interest_expense: 100 + taxable_interest_income: 100 tax_units: tax_unit: filing_status: SEPARATE @@ -28,8 +30,10 @@ people: person1: investment_interest_expense: 100 + taxable_interest_income: 100 person2: investment_interest_expense: 100 + taxable_interest_income: 100 tax_units: tax_unit: filing_status: JOINT @@ -51,8 +55,10 @@ people: person1: investment_interest_expense: 100 + taxable_interest_income: 100 person2: investment_interest_expense: 100 + taxable_interest_income: 100 tax_units: tax_unit: filing_status: JOINT @@ -67,3 +73,32 @@ state_code: HI output: hi_reduced_itemized_deductions: 0 + +- name: Only investment interest allowed after the investment income limit is excluded from the reduction. + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + investment_interest_expense: 1_000 + taxable_interest_income: 200 + person2: + age: 45 + tax_units: + tax_unit: + filing_status: JOINT + hi_medical_expense_deduction: 300 + hi_casualty_loss_deduction: 400 + hi_agi: 500_000 + members: [person1, person2] + hi_total_itemized_deductions: 10_000 + households: + household: + members: [person1, person2] + state_code: HI + output: + # Excluded deductions: $300 + $200 + $400 = $900. + # ($10,000 - $900) * 80% = $7,280, less than + # ($500,000 - $166,800) * 3% = $9,996. + # $10,000 - $7,280 = $2,720. + hi_reduced_itemized_deductions: 2_720 From a393506315163e657250e7d42f691b2412be164d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 19:37:21 -0400 Subject: [PATCH 2/9] Apply Hawaii's pre-TCJA mortgage debt limits and the 163(d) limit to the interest deduction Hawaii makes IRC 163(h)(3)(F) inoperative (HRS 235-2.4(j)(3), (i)(3) after Act 35, SLH 2026), so mortgage interest is deductible on up to $1,000,000 of home acquisition debt plus $100,000 of home equity debt ($500,000/$50,000 MFS), with acquisition debt over the limit counting as home equity debt (2017 Pub 936). hi_interest_deduction previously added uncapped mortgage_interest. Investment interest is now limited to net investment income per Form N-158, counting qualified dividends since Hawaii makes 163(d)(4)(B) inoperative; the Pease exclusion uses the limited amount. Co-Authored-By: Claude Opus 5.5 --- changelog.d/hi-mortgage-interest-cap.fixed.md | 1 + .../interest/home_acquisition_debt_limit.yaml | 33 +++++++++++ .../interest/home_equity_debt_limit.yaml | 33 +++++++++++ .../itemized/hi_interest_deduction.yaml | 1 - .../itemized/hi_interest_deduction.py | 9 +-- .../hi_investment_interest_deduction.py | 54 ++++++++++++++++++ .../hi_mortgage_interest_deduction.py | 55 +++++++++++++++++++ .../hi_reduced_itemized_deductions.py | 2 +- 8 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 changelog.d/hi-mortgage-interest-cap.fixed.md create mode 100644 policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_acquisition_debt_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_equity_debt_limit.yaml create mode 100644 policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.py create mode 100644 policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py diff --git a/changelog.d/hi-mortgage-interest-cap.fixed.md b/changelog.d/hi-mortgage-interest-cap.fixed.md new file mode 100644 index 00000000000..20b3d47d43d --- /dev/null +++ b/changelog.d/hi-mortgage-interest-cap.fixed.md @@ -0,0 +1 @@ +Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt) and the IRC 163(d) net investment income limit to the Hawaii itemized interest deduction. diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_acquisition_debt_limit.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_acquisition_debt_limit.yaml new file mode 100644 index 00000000000..932e73ede63 --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_acquisition_debt_limit.yaml @@ -0,0 +1,33 @@ +description: Hawaii limits the home acquisition debt on which mortgage interest is deductible to this amount, based on filing status. +SINGLE: + 2018-01-01: 1_000_000 +JOINT: + 2018-01-01: 1_000_000 +HEAD_OF_HOUSEHOLD: + 2018-01-01: 1_000_000 +SEPARATE: + 2018-01-01: 500_000 +SURVIVING_SPOUSE: + 2018-01-01: 1_000_000 +metadata: + propagate_metadata_to_children: true + breakdown: + - filing_status + unit: currency-USD + period: year + label: Hawaii home acquisition debt limit + reference: + # Hawaii makes the TCJA limit in IRC 163(h)(3)(F) inoperative, so the + # pre-TCJA limit in IRC 163(h)(3)(B)(ii) applies. + - title: Hawaii Revised Statutes 235-2.4(j)(3) + href: https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm + - title: Act 35, SLH 2026 (HB2329 CD1), HRS 235-2.4(i)(3) + href: https://data.capitol.hawaii.gov/sessions/session2026/bills/HB2329_CD1_.pdf#page=20 + - title: 26 U.S. Code § 163(h)(3)(B)(ii) (2016 edition) + href: https://www.govinfo.gov/content/pkg/USCODE-2016-title26/html/USCODE-2016-title26-subtitleA-chap1-subchapB-partVII-sec163.htm + - title: 2018 Hawaii N-11 Instructions, Line 21c + href: https://files.hawaii.gov/tax/forms/2018/n11ins.pdf#page=19 + - title: 2025 Hawaii N-11 Instructions, Line 21c + href: https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=17 + - title: 2017 IRS Publication 936, Home acquisition debt limit + href: https://www.irs.gov/pub/irs-prior/p936--2017.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_equity_debt_limit.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_equity_debt_limit.yaml new file mode 100644 index 00000000000..52a3e241d66 --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_equity_debt_limit.yaml @@ -0,0 +1,33 @@ +description: Hawaii limits the home equity debt on which mortgage interest is deductible to this amount, based on filing status. +SINGLE: + 2018-01-01: 100_000 +JOINT: + 2018-01-01: 100_000 +HEAD_OF_HOUSEHOLD: + 2018-01-01: 100_000 +SEPARATE: + 2018-01-01: 50_000 +SURVIVING_SPOUSE: + 2018-01-01: 100_000 +metadata: + propagate_metadata_to_children: true + breakdown: + - filing_status + unit: currency-USD + period: year + label: Hawaii home equity debt limit + reference: + # Hawaii makes the TCJA suspension of home equity interest in IRC + # 163(h)(3)(F) inoperative, so the limit in IRC 163(h)(3)(C)(ii) applies. + - title: Hawaii Revised Statutes 235-2.4(j)(3) + href: https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm + - title: Act 35, SLH 2026 (HB2329 CD1), HRS 235-2.4(i)(3) + href: https://data.capitol.hawaii.gov/sessions/session2026/bills/HB2329_CD1_.pdf#page=20 + - title: 26 U.S. Code § 163(h)(3)(C)(ii) (2016 edition) + href: https://www.govinfo.gov/content/pkg/USCODE-2016-title26/html/USCODE-2016-title26-subtitleA-chap1-subchapB-partVII-sec163.htm + - title: 2018 Hawaii N-11 Instructions, Line 21c + href: https://files.hawaii.gov/tax/forms/2018/n11ins.pdf#page=19 + - title: 2025 Hawaii N-11 Instructions, Line 21c + href: https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=17 + - title: 2017 IRS Publication 936, Home equity debt limit + href: https://www.irs.gov/pub/irs-prior/p936--2017.pdf#page=10 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 index 1dfe46d07ad..e598ea74d56 100644 --- 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 @@ -50,7 +50,6 @@ # Investment interest: limited to $2,000 of net investment income. hi_investment_interest_deduction: 2_000 hi_interest_deduction: 57_000 - hi_total_itemized_deductions: 57_000 - name: Case 3, interest within both limits is fully deductible. period: 2025 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 4a58a37629d..172d1416f24 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 @@ -6,11 +6,12 @@ class hi_interest_deduction(Variable): entity = TaxUnit label = "Hawaii interest deduction" unit = USD - documentation = ( - "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=17\n" - "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=32" # total itemized deduction worksheet + documentation = "Worksheet A-3 line 14: home mortgage and investment interest." + reference = ( + "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=17", + "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=34", ) definition_period = YEAR defined_for = StateCode.HI - adds = ["investment_interest_expense", "mortgage_interest"] + adds = ["hi_mortgage_interest_deduction", "hi_investment_interest_deduction"] diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.py new file mode 100644 index 00000000000..f8b8e507d62 --- /dev/null +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.py @@ -0,0 +1,54 @@ +from policyengine_us.model_api import * + + +class hi_investment_interest_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii investment interest deduction" + unit = USD + documentation = ( + "Form N-158 line 8. IRC 163(d) limits investment interest to net " + "investment income. Hawaii makes IRC 163(d)(4)(B) inoperative, so " + "qualified dividends count as investment income without an election." + ) + reference = ( + "https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm", + "https://files.hawaii.gov/tax/forms/2025/n158_i.pdf", + ) + definition_period = YEAR + defined_for = StateCode.HI + + def formula(tax_unit, period, parameters): + # Line 3. The line 2 carryforward from the prior year is not modeled. + interest_expense = add(tax_unit, period, ["investment_interest_expense"]) + # Line 4a: interest and ordinary dividends. Annuities and royalties are + # not modeled separately. + gross_investment_income = add( + tax_unit, + period, + ["taxable_interest_income", "ordinary_dividend_income"], + ) + long_term_gain = add( + tax_unit, + period, + ["long_term_capital_gains", "non_sch_d_capital_gains"], + ) + short_term_gain = add(tax_unit, period, ["short_term_capital_gains"]) + # Line 4b. + net_gain = max_(0, long_term_gain + short_term_gain) + # Line 4c. + net_capital_gain = max_(0, long_term_gain + min_(0, short_term_gain)) + # Line 4d. The line 4e election to include net capital gain is not + # modeled, matching hi_capital_gain_for_alternative_tax. + net_gain_less_net_capital_gain = net_gain - min_(net_gain, net_capital_gain) + # Line 5. + investment_expenses = add(tax_unit, period, ["investment_expenses"]) + # Line 6. + net_investment_income = max_( + 0, + gross_investment_income + + net_gain_less_net_capital_gain + - investment_expenses, + ) + # Line 8. + return min_(interest_expense, net_investment_income) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py new file mode 100644 index 00000000000..fbbad5735b4 --- /dev/null +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py @@ -0,0 +1,55 @@ +from policyengine_us.model_api import * + + +class hi_mortgage_interest_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii home mortgage interest deduction" + unit = USD + documentation = ( + "Home mortgage interest Hawaii allows on Worksheet A-3. Hawaii makes " + "IRC 163(h)(3)(F) inoperative, so the pre-TCJA home acquisition and " + "home equity debt limits apply regardless of when the debt arose." + ) + reference = ( + "https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm", + "https://data.capitol.hawaii.gov/sessions/session2026/bills/HB2329_CD1_.pdf#page=20", + "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=17", + "https://www.irs.gov/pub/irs-prior/p936--2017.pdf#page=11", + ) + definition_period = YEAR + defined_for = StateCode.HI + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.hi.tax.income.deductions.itemized.interest + filing_status = tax_unit("filing_status", period) + # 2017 Publication 936, Table 1. Grandfathered debt from before + # October 14, 1987 (line 1) is not modeled, so every balance is home + # acquisition debt (line 2). + debt = add( + tax_unit, + period, + ["first_home_mortgage_balance", "second_home_mortgage_balance"], + ) + # Line 6. + acquisition_debt = min_(debt, p.home_acquisition_debt_limit[filing_status]) + # Line 7. Debt over the home acquisition debt limit may qualify as home + # equity debt. The fair market value limit on home equity debt is not + # modeled. + home_equity_debt = min_( + debt - acquisition_debt, p.home_equity_debt_limit[filing_status] + ) + # Line 8. + qualified_loan_limit = acquisition_debt + home_equity_debt + # Line 11. Without a reported balance, the interest is treated as + # within the limits, as in the federal calculation. + deductible_share = np.divide( + qualified_loan_limit, + debt, + out=np.ones_like(debt), + where=debt > 0, + ) + # Line 10, which also keeps directly supplied deductible interest. + interest = add(tax_unit, period, ["mortgage_interest"]) + # Line 12. + return interest * deductible_share diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py index c68fabcfe61..4326a76fd50 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py @@ -22,7 +22,7 @@ def formula(tax_unit, period, parameters): period, [ "hi_medical_expense_deduction", - "investment_interest_expense", + "hi_investment_interest_deduction", "hi_casualty_loss_deduction", ], ) From 265b2e54e30840d1dc5b420dc51120ea8365b7f5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 7 Oct 2026 10:04:13 -0400 Subject: [PATCH 3/9] Split held Hawaii investment-interest limit from mortgage PR --- changelog.d/hi-mortgage-interest-cap.fixed.md | 2 +- .../itemized/hi_interest_deduction.yaml | 32 +- .../hi_investment_interest_deduction.yaml | 575 ------------------ .../hi_reduced_itemized_deductions.yaml | 35 -- .../itemized/hi_interest_deduction.py | 2 +- .../hi_investment_interest_deduction.py | 87 --- .../hi_reduced_itemized_deductions.py | 2 +- 7 files changed, 30 insertions(+), 705 deletions(-) delete mode 100644 policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.yaml delete mode 100644 policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.py diff --git a/changelog.d/hi-mortgage-interest-cap.fixed.md b/changelog.d/hi-mortgage-interest-cap.fixed.md index 20b3d47d43d..bab6305f323 100644 --- a/changelog.d/hi-mortgage-interest-cap.fixed.md +++ b/changelog.d/hi-mortgage-interest-cap.fixed.md @@ -1 +1 @@ -Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt) and the IRC 163(d) net investment income limit to the Hawaii itemized interest deduction. +Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt) and preserve supplied deductible mortgage interest when gross interest is unavailable. 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 index e598ea74d56..9bd9bced9b2 100644 --- 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 @@ -20,7 +20,7 @@ # $1,100,000 / $1,500,000 * $90,000 = $66,000. hi_interest_deduction: 66_000 -- name: Case 2, mortgage and investment interest are each limited. +- name: Case 2, mortgage interest is capped and the bundled investment-interest input is preserved. period: 2026 absolute_error_margin: 0.01 input: @@ -47,11 +47,10 @@ output: # Mortgage: $1,100,000 / $1,300,000 * $65,000 = $55,000. hi_mortgage_interest_deduction: 55_000 - # Investment interest: limited to $2,000 of net investment income. - hi_investment_interest_deduction: 2_000 - hi_interest_deduction: 57_000 + # Preserve the bundled $5,000 input pending the dataset leaf-input split. + hi_interest_deduction: 60_000 -- name: Case 3, interest within both limits is fully deductible. +- name: Case 3, mortgage interest within the debt limit and the bundled input are deductible. period: 2025 absolute_error_margin: 0.01 input: @@ -73,3 +72,26 @@ state_code: HI output: hi_interest_deduction: 21_000 + +- name: Case 4, supplied deductible mortgage interest with a balance is kept alongside the bundled input. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + deductible_mortgage_interest: 45_000 + investment_interest_expense: 1_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [person1] + state_code: HI + output: + hi_mortgage_interest_deduction: 45_000 + hi_interest_deduction: 46_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.yaml deleted file mode 100644 index 5e6b577add6..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.yaml +++ /dev/null @@ -1,575 +0,0 @@ -- name: Case 1, qualified dividends count as investment income in Hawaii. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - investment_interest_expense: 5_000 - taxable_interest_income: 2_000 - qualified_dividend_income: 1_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SINGLE - households: - household: - members: [person1] - state_code: HI - output: - # N-158 line 4a: $2,000 interest + $1,000 ordinary dividends = $3,000. - hi_investment_interest_deduction: 3_000 - -- name: Case 2, interest expense below investment income is fully deductible. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - investment_interest_expense: 1_000 - taxable_interest_income: 10_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SINGLE - households: - household: - members: [person1] - state_code: HI - output: - hi_investment_interest_deduction: 1_000 - -- name: Case 3, net short-term gain counts as investment income. - period: 2026 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - investment_interest_expense: 5_000 - short_term_capital_gains: 4_000 - long_term_capital_gains_before_response: 10_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SINGLE - households: - household: - members: [person1] - state_code: HI - output: - # Line 4b $14,000; line 4c $10,000; line 4d $4,000. - hi_investment_interest_deduction: 4_000 - -- name: Case 4, net capital gain alone is not investment income without the election. - period: 2026 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - investment_interest_expense: 5_000 - long_term_capital_gains_before_response: 10_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SINGLE - households: - household: - members: [person1] - state_code: HI - output: - hi_investment_interest_deduction: 0 - -- name: Case 5, allowed investment expenses after the two-percent floor reduce income. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - investment_interest_expense: 5_000 - taxable_interest_income: 6_000 - investment_expenses: 2_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SINGLE - households: - household: - members: [person1] - state_code: HI - output: - # HI AGI is $6,000; allowed expenses are $2,000 - $120 = $1,880. - hi_investment_interest_deduction: 4_120 - -- name: Case 6, a net short-term loss does not reduce other investment income. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - investment_interest_expense: 5_000 - taxable_interest_income: 2_000 - short_term_capital_gains: -3_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SINGLE - households: - household: - members: [person1] - state_code: HI - output: - hi_investment_interest_deduction: 2_000 - -- name: Case 7, net investment income is floored at zero. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - investment_interest_expense: 2_000 - taxable_interest_income: 1_000 - investment_expenses: 5_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SINGLE - households: - household: - members: [person1] - state_code: HI - output: - hi_investment_interest_deduction: 0 - -- name: Case 8, a long-term loss leaves the short-term gain as investment income. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - investment_interest_expense: 5_000 - short_term_capital_gains: 6_000 - long_term_capital_gains_before_response: -2_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SINGLE - households: - household: - members: [person1] - state_code: HI - output: - # Line 4b $4,000; net capital gain $0, so line 4d is $4,000. - hi_investment_interest_deduction: 4_000 - -- name: Case 9, joint filers pool investment income and interest expense. - period: 2024 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - investment_interest_expense: 3_000 - taxable_interest_income: 1_000 - person2: - age: 45 - non_qualified_dividend_income: 1_500 - tax_units: - tax_unit: - members: [person1, person2] - filing_status: JOINT - households: - household: - members: [person1, person2] - state_code: HI - output: - hi_investment_interest_deduction: 2_500 - -- name: Case 10, no investment interest expense means no deduction. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 45 - taxable_interest_income: 10_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SINGLE - households: - household: - members: [person1] - state_code: HI - output: - hi_investment_interest_deduction: 0 - -- name: Case 11, investment expenses below the shared two-percent floor are not allowed. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 5000 - investment_expenses: 1000 - tax_units: - tax_unit: - members: [parent] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent] - state_code: HI - output: - # $1,000 expenses are below the $2,000 floor, so N-158 line 5 is zero. - hi_investment_interest_deduction: 5000 - -- name: Case 12, dependent interest income does not belong on the parent return. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - child: - age: 12 - is_tax_unit_dependent: true - taxable_interest_income: 5000 - tax_units: - tax_unit: - members: [parent, child] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent, child] - state_code: HI - output: - hi_investment_interest_deduction: 0 - -- name: Case 13, dependent capital losses do not erase the parent investment income. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - short_term_capital_gains: 5000 - child: - age: 12 - is_tax_unit_dependent: true - short_term_capital_gains: -10000 - long_term_capital_gains_before_response: -10000 - tax_units: - tax_unit: - members: [parent, child] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent, child] - state_code: HI - output: - hi_investment_interest_deduction: 5000 - -- name: Case 14, dependent investment expenses are excluded from the parent return. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 5000 - investment_expenses: 1000 - child: - age: 12 - is_tax_unit_dependent: true - investment_expenses: 2000 - tax_units: - tax_unit: - members: [parent, child] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent, child] - state_code: HI - output: - hi_investment_interest_deduction: 5000 - -- name: Case 15, dependent interest expense does not create a deduction for the parent. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - taxable_interest_income: 10000 - child: - age: 12 - is_tax_unit_dependent: true - investment_interest_expense: 5000 - tax_units: - tax_unit: - members: [parent, child] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent, child] - state_code: HI - output: - hi_investment_interest_deduction: 0 - -- name: Case 16, person-level Treasury interest is exempt from Hawaii investment income. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 5000 - us_govt_interest_person: 5000 - tax_units: - tax_unit: - members: [parent] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent] - state_code: HI - output: - hi_investment_interest_deduction: 0 - -- name: Case 17, an existing tax-unit Treasury-interest input is also excluded. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 5000 - tax_units: - tax_unit: - members: [parent] - filing_status: SINGLE - hi_agi: 100000 - us_govt_interest: 5000 - households: - household: - members: [parent] - state_code: HI - output: - hi_investment_interest_deduction: 0 - -- name: Case 18, dependent Treasury interest does not erase the parent taxable interest. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 5000 - child: - age: 12 - is_tax_unit_dependent: true - taxable_interest_income: 5000 - us_govt_interest_person: 5000 - tax_units: - tax_unit: - members: [parent, child] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent, child] - state_code: HI - output: - hi_investment_interest_deduction: 5000 - -- name: Case 19, employee expenses share the two-percent floor with investment expenses. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 5000 - investment_expenses: 1000 - unreimbursed_business_employee_expenses: 2000 - tax_units: - tax_unit: - members: [parent] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent] - state_code: HI - output: - # A-6 line 29 is $3,000 - $2,000 = $1,000; N-158 line 5 is $1,000. - hi_investment_interest_deduction: 4000 - -- name: Case 20, tax-preparation fees share the floor but cannot raise allowed investment expenses. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 5000 - investment_expenses: 800 - tax_preparation_fees: 2500 - tax_units: - tax_unit: - members: [parent] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent] - state_code: HI - output: - # A-6 line 29 is $1,300; N-158 line 5 uses the smaller $800 investment expense. - hi_investment_interest_deduction: 4200 - -- name: Case 21, dependent employee expenses do not consume the parent shared floor. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 5000 - investment_expenses: 1000 - child: - age: 12 - is_tax_unit_dependent: true - unreimbursed_business_employee_expenses: 2000 - tax_preparation_fees: 1000 - tax_units: - tax_unit: - members: [parent, child] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent, child] - state_code: HI - output: - hi_investment_interest_deduction: 5000 - -- name: Case 22, dependent positive capital gains do not create parent investment income. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - child: - age: 12 - is_tax_unit_dependent: true - short_term_capital_gains: 5000 - non_sch_d_capital_gains: 1000 - tax_units: - tax_unit: - members: [parent, child] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent, child] - state_code: HI - output: - hi_investment_interest_deduction: 0 - -- name: Case 23, dependent ordinary dividends do not create parent investment income. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - child: - age: 12 - is_tax_unit_dependent: true - qualified_dividend_income: 3000 - non_qualified_dividend_income: 2000 - tax_units: - tax_unit: - members: [parent, child] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent, child] - state_code: HI - output: - hi_investment_interest_deduction: 0 - -- name: Case 24, Hawaii AGI below zero does not create a negative expense floor. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 5000 - investment_expenses: 1000 - tax_units: - tax_unit: - members: [parent] - filing_status: SINGLE - hi_agi: -100000 - households: - household: - members: [parent] - state_code: HI - output: - hi_investment_interest_deduction: 4000 - -- name: Case 25, exempt interest cannot offset ordinary dividends when it exceeds reported taxable interest. - period: 2025 - absolute_error_margin: 0.01 - input: - people: - parent: - age: 45 - investment_interest_expense: 5000 - taxable_interest_income: 0 - us_govt_interest_person: 5000 - non_qualified_dividend_income: 5000 - tax_units: - tax_unit: - members: [parent] - filing_status: SINGLE - hi_agi: 100000 - households: - household: - members: [parent] - state_code: HI - output: - # Only the taxable-interest component is reduced, with a zero floor. - hi_investment_interest_deduction: 5000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml index 3ee95aaec3d..912c803624b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml @@ -4,10 +4,8 @@ people: person1: investment_interest_expense: 100 - taxable_interest_income: 100 person2: investment_interest_expense: 100 - taxable_interest_income: 100 tax_units: tax_unit: filing_status: SEPARATE @@ -30,10 +28,8 @@ people: person1: investment_interest_expense: 100 - taxable_interest_income: 100 person2: investment_interest_expense: 100 - taxable_interest_income: 100 tax_units: tax_unit: filing_status: JOINT @@ -55,10 +51,8 @@ people: person1: investment_interest_expense: 100 - taxable_interest_income: 100 person2: investment_interest_expense: 100 - taxable_interest_income: 100 tax_units: tax_unit: filing_status: JOINT @@ -73,32 +67,3 @@ state_code: HI output: hi_reduced_itemized_deductions: 0 - -- name: Only investment interest allowed after the investment income limit is excluded from the reduction. - period: 2022 - absolute_error_margin: 0.01 - input: - people: - person1: - investment_interest_expense: 1_000 - taxable_interest_income: 200 - person2: - age: 45 - tax_units: - tax_unit: - filing_status: JOINT - hi_medical_expense_deduction: 300 - hi_casualty_loss_deduction: 400 - hi_agi: 500_000 - members: [person1, person2] - hi_total_itemized_deductions: 10_000 - households: - household: - members: [person1, person2] - state_code: HI - output: - # Excluded deductions: $300 + $200 + $400 = $900. - # ($10,000 - $900) * 80% = $7,280, less than - # ($500,000 - $166,800) * 3% = $9,996. - # $10,000 - $7,280 = $2,720. - hi_reduced_itemized_deductions: 2_720 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 172d1416f24..d453c8b53a3 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 @@ -14,4 +14,4 @@ class hi_interest_deduction(Variable): definition_period = YEAR defined_for = StateCode.HI - adds = ["hi_mortgage_interest_deduction", "hi_investment_interest_deduction"] + adds = ["hi_mortgage_interest_deduction", "investment_interest_expense"] diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.py deleted file mode 100644 index fed1987f8ae..00000000000 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_investment_interest_deduction.py +++ /dev/null @@ -1,87 +0,0 @@ -from policyengine_us.model_api import * - - -class hi_investment_interest_deduction(Variable): - value_type = float - entity = TaxUnit - label = "Hawaii investment interest deduction" - unit = USD - documentation = ( - "Form N-158 line 8. IRC 163(d) limits investment interest to net " - "investment income. Hawaii makes IRC 163(d)(4)(B) inoperative, so " - "qualified dividends count as investment income without an election." - ) - reference = ( - "https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm", - "https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0007.htm", - "https://files.hawaii.gov/tax/forms/2025/n158_i.pdf", - "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=34", - ) - definition_period = YEAR - defined_for = StateCode.HI - - def formula(tax_unit, period, parameters): - # Line 3. The line 2 carryforward from the prior year is not modeled. - interest_expense = tax_unit_non_dep_add( - tax_unit, period, ["investment_interest_expense"] - ) - # Line 4a excludes dependents unless an N-814 election is filed. - # That election is not modeled. Federal-obligation interest is exempt - # under HRS 235-7(a)(1), so it is not Hawaii investment income. - taxable_interest = tax_unit_non_dep_add( - tax_unit, period, ["taxable_interest_income"] - ) - dependent_government_interest = tax_unit.sum( - tax_unit.members("us_govt_interest_person", period) - * tax_unit.members("is_tax_unit_dependent", period) - ) - # us_govt_interest aggregates all members' person-level inputs by - # default. Remove dependents' share while retaining a supplied - # tax-unit government-interest amount. - government_interest = max_( - 0, - tax_unit("us_govt_interest", period) - dependent_government_interest, - ) - gross_investment_income = max_(0, taxable_interest - government_interest) - gross_investment_income += tax_unit_non_dep_add( - tax_unit, period, ["ordinary_dividend_income"] - ) - # Annuities and royalties are not modeled separately. - long_term_gain = tax_unit_non_dep_add( - tax_unit, - period, - ["long_term_capital_gains", "non_sch_d_capital_gains"], - ) - short_term_gain = tax_unit_non_dep_add( - tax_unit, period, ["short_term_capital_gains"] - ) - # Line 4b. - net_gain = max_(0, long_term_gain + short_term_gain) - # Line 4c. - net_capital_gain = max_(0, long_term_gain + min_(0, short_term_gain)) - # Line 4d. The line 4e election to include net capital gain is not - # modeled, matching hi_capital_gain_for_alternative_tax. - net_gain_less_net_capital_gain = net_gain - min_(net_gain, net_capital_gain) - # Line 5. Modeled investment expenses are miscellaneous expenses on - # Worksheet A-6 line 25. N-158 requires the smaller of those expenses - # and the deduction allowed on Worksheet A-6 line 29. The two-percent - # floor is shared with employee business expenses and tax-prep fees. - p = parameters(period).gov.irs.deductions.itemized.misc - investment_expenses = tax_unit_non_dep_add( - tax_unit, period, ["investment_expenses"] - ) - misc_expenses = investment_expenses + tax_unit_non_dep_add( - tax_unit, period, p.sources - ) - misc_floor = p.floor * max_(0, tax_unit("hi_agi", period)) - allowed_misc_expenses = max_(0, misc_expenses - misc_floor) - allowed_investment_expenses = min_(investment_expenses, allowed_misc_expenses) - # Line 6. - net_investment_income = max_( - 0, - gross_investment_income - + net_gain_less_net_capital_gain - - allowed_investment_expenses, - ) - # Line 8. - return min_(interest_expense, net_investment_income) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py index 4326a76fd50..c68fabcfe61 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py @@ -22,7 +22,7 @@ def formula(tax_unit, period, parameters): period, [ "hi_medical_expense_deduction", - "hi_investment_interest_deduction", + "investment_interest_expense", "hi_casualty_loss_deduction", ], ) From a8e3f806a131c982696bed32444c003a96205833 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 7 Oct 2026 12:44:14 -0400 Subject: [PATCH 4/9] Remove broken statute citations from Hawaii mortgage limits --- .../itemized/interest/home_acquisition_debt_limit.yaml | 2 -- .../deductions/itemized/interest/home_equity_debt_limit.yaml | 2 -- 2 files changed, 4 deletions(-) diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_acquisition_debt_limit.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_acquisition_debt_limit.yaml index 932e73ede63..6b9baedb9d9 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_acquisition_debt_limit.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_acquisition_debt_limit.yaml @@ -23,8 +23,6 @@ metadata: href: https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm - title: Act 35, SLH 2026 (HB2329 CD1), HRS 235-2.4(i)(3) href: https://data.capitol.hawaii.gov/sessions/session2026/bills/HB2329_CD1_.pdf#page=20 - - title: 26 U.S. Code § 163(h)(3)(B)(ii) (2016 edition) - href: https://www.govinfo.gov/content/pkg/USCODE-2016-title26/html/USCODE-2016-title26-subtitleA-chap1-subchapB-partVII-sec163.htm - title: 2018 Hawaii N-11 Instructions, Line 21c href: https://files.hawaii.gov/tax/forms/2018/n11ins.pdf#page=19 - title: 2025 Hawaii N-11 Instructions, Line 21c diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_equity_debt_limit.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_equity_debt_limit.yaml index 52a3e241d66..72cb63ef0e1 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_equity_debt_limit.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/interest/home_equity_debt_limit.yaml @@ -23,8 +23,6 @@ metadata: href: https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm - title: Act 35, SLH 2026 (HB2329 CD1), HRS 235-2.4(i)(3) href: https://data.capitol.hawaii.gov/sessions/session2026/bills/HB2329_CD1_.pdf#page=20 - - title: 26 U.S. Code § 163(h)(3)(C)(ii) (2016 edition) - href: https://www.govinfo.gov/content/pkg/USCODE-2016-title26/html/USCODE-2016-title26-subtitleA-chap1-subchapB-partVII-sec163.htm - title: 2018 Hawaii N-11 Instructions, Line 21c href: https://files.hawaii.gov/tax/forms/2018/n11ins.pdf#page=19 - title: 2025 Hawaii N-11 Instructions, Line 21c From d16ee798d8989fb3c840064fa965c6f9bb3ecb35 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 8 Oct 2026 11:49:11 -0400 Subject: [PATCH 5/9] Exclude dependent mortgage interest from filer deductions --- changelog.d/hi-mortgage-interest-cap.fixed.md | 2 +- .../hi_mortgage_interest_deduction.yaml | 198 ++++++++++++++++++ .../test_hi_mortgage_interest_dependents.py | 174 +++++++++++++++ .../hi_mortgage_interest_deduction.py | 9 +- .../tax_unit/mortgage_interest_structure.py | 16 +- 5 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 policyengine_us/tests/test_hi_mortgage_interest_dependents.py diff --git a/changelog.d/hi-mortgage-interest-cap.fixed.md b/changelog.d/hi-mortgage-interest-cap.fixed.md index bab6305f323..8682589f948 100644 --- a/changelog.d/hi-mortgage-interest-cap.fixed.md +++ b/changelog.d/hi-mortgage-interest-cap.fixed.md @@ -1 +1 @@ -Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt) and preserve supplied deductible mortgage interest when gross interest is unavailable. +Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt) and preserve supplied deductible mortgage interest when gross interest is unavailable. Exclude dependents' mortgage interest from filers' deductions. diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml index 2401a47ca84..96ef7cc6bd1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml @@ -445,3 +445,201 @@ state_code: HI output: hi_mortgage_interest_deduction: [66000, 45000] + +# 2017 Publication 936, page 2: deduct interest paid on a home owned by +# the filer (or spouse on a joint return), excluding a dependent's own home. +- name: Case 21, canonical priority depends on whether the filer paid the interest. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + legacy_filer: + age: 45 + mortgage_interest: 90_000 + dependent_owner: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + canonical_filer: + age: 45 + mortgage_interest: 90_000 + home_mortgage_interest: 12_000 + control_dependent: + age: 20 + is_tax_unit_dependent: true + tax_units: + dependent_interest_unit: + members: [legacy_filer, dependent_owner] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + filer_interest_unit: + members: [canonical_filer, control_dependent] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [legacy_filer, dependent_owner, canonical_filer, control_dependent] + state_code: HI + output: + # The same $12,000 is excluded for the dependent, but has canonical + # priority when paid by the filer. The Hawaii debt ratio is 11/15. + hi_mortgage_interest_deduction: [66_000, 8_800] + +- name: Case 22, joint filers combine both spouses' interest without dependent interest. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + is_tax_unit_head: true + home_mortgage_interest: 40_000 + spouse: + age: 44 + is_tax_unit_spouse: true + home_mortgage_interest: 25_000 + dependent: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, spouse, dependent] + filing_status: JOINT + first_home_mortgage_balance: 800_000 + first_home_mortgage_origination_year: 2017 + second_home_mortgage_balance: 500_000 + second_home_mortgage_origination_year: 2020 + households: + household: + members: [head, spouse, dependent] + state_code: HI + output: + # $65,000 * $1,100,000 / $1,300,000 = $55,000. + hi_mortgage_interest_deduction: 55_000 + +- name: Case 23, a dependent's canonical interest does not replace structured filer interest. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + dependent: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, dependent] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + first_home_mortgage_interest: 90_000 + households: + household: + members: [head, dependent] + state_code: HI + output: + hi_mortgage_interest_deduction: 66_000 + +- name: Case 24, supplied deductible interest excludes dependent amounts. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + deductible_mortgage_interest: 45_000 + dependent: + age: 20 + is_tax_unit_dependent: true + deductible_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, dependent] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [head, dependent] + state_code: HI + output: + hi_mortgage_interest_deduction: 45_000 + +- name: Case 25, legacy gross interest excludes dependent amounts. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + mortgage_interest: 90_000 + dependent: + age: 20 + is_tax_unit_dependent: true + mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, dependent] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [head, dependent] + state_code: HI + output: + hi_mortgage_interest_deduction: 66_000 + +- name: Case 26, dependent-only canonical interest cannot create a filer deduction. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + dependent: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, dependent] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [head, dependent] + state_code: HI + output: + hi_mortgage_interest_deduction: 0 + +- name: Case 27, dependent canonical interest cannot override the supplied deductible fallback. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + deductible_mortgage_interest: 45_000 + dependent: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, dependent] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [head, dependent] + state_code: HI + output: + hi_mortgage_interest_deduction: 45_000 diff --git a/policyengine_us/tests/test_hi_mortgage_interest_dependents.py b/policyengine_us/tests/test_hi_mortgage_interest_dependents.py new file mode 100644 index 00000000000..dbb035dfb56 --- /dev/null +++ b/policyengine_us/tests/test_hi_mortgage_interest_dependents.py @@ -0,0 +1,174 @@ +import random + +import numpy as np + +from policyengine_us import Simulation + + +PERIOD = "2026" +MORTGAGE_INPUTS = ( + "home_mortgage_interest", + "mortgage_interest", + "deductible_mortgage_interest", + "non_deductible_mortgage_interest", +) +INTEREST_SOURCES = ( + "canonical", + "canonical_with_supplied_deduction", + "legacy", + "reconstructed", + "supplied_deduction", + "structured", + "none", +) + + +def _filer_interest(source, amount): + if source in ("canonical", "canonical_with_supplied_deduction"): + inputs = {"home_mortgage_interest": amount} + if source == "canonical_with_supplied_deduction": + inputs["deductible_mortgage_interest"] = amount / 2 + return inputs + if source == "legacy": + return {"mortgage_interest": amount} + if source == "reconstructed": + return { + "deductible_mortgage_interest": amount / 2, + "non_deductible_mortgage_interest": amount / 2, + } + if source == "supplied_deduction": + return {"deductible_mortgage_interest": amount} + return {} + + +def _add_tax_unit(situation, name, joint, filers, mortgage, dependents): + members = [] + filer_names = [] + for index, interest in enumerate(filers): + person_name = f"{name}_filer_{index}" + members.append(person_name) + filer_names.append(person_name) + inputs = { + "age": 45 - index, + "is_tax_unit_head": index == 0, + "is_tax_unit_spouse": index == 1, + "is_tax_unit_dependent": False, + **interest, + } + situation["people"][person_name] = { + variable: {PERIOD: value} for variable, value in inputs.items() + } + + for index, interest in enumerate(dependents): + person_name = f"{name}_dependent_{index}" + members.append(person_name) + inputs = { + "age": 15 - index, + "is_tax_unit_head": False, + "is_tax_unit_spouse": False, + "is_tax_unit_dependent": True, + **interest, + } + situation["people"][person_name] = { + variable: {PERIOD: value} for variable, value in inputs.items() + } + situation["marital_units"][person_name] = {"members": [person_name]} + + situation["tax_units"][name] = { + "members": members, + "filing_status": {PERIOD: "JOINT" if joint else "SINGLE"}, + **{variable: {PERIOD: value} for variable, value in mortgage.items()}, + } + situation["households"][name] = { + "members": members, + "state_code": {PERIOD: "HI"}, + } + situation["families"][name] = {"members": members} + situation["spm_units"][name] = {"members": members} + situation["marital_units"][name] = {"members": filer_names} + + +def test_dependent_mortgage_inputs_never_change_hawaii_deduction(): + """Generate matched returns across every mortgage-interest source path.""" + rng = random.Random(9950) + situation = { + entity: {} + for entity in ( + "people", + "tax_units", + "households", + "families", + "spm_units", + "marital_units", + ) + } + labels = [] + + for joint in (False, True): + for source in INTEREST_SOURCES: + # Exercise each person input alone, then arbitrary combinations. + for sample in range(len(MORTGAGE_INPUTS) + 2): + filers = [ + _filer_interest(source, rng.randint(1_000, 150_000)) + for _ in range(2 if joint else 1) + ] + debt = (0, 550_000, 1_100_000, 1_500_000, 3_000_000)[sample % 5] + mortgage = { + "first_home_mortgage_balance": debt * 0.75, + "second_home_mortgage_balance": debt * 0.25, + "first_home_mortgage_origination_year": rng.choice( + (2010, 2017, 2023) + ), + "second_home_mortgage_origination_year": 2023, + } + if source == "structured": + mortgage["first_home_mortgage_interest"] = rng.randint( + 1_000, 150_000 + ) + mortgage["second_home_mortgage_interest"] = rng.randint( + 1_000, 150_000 + ) + + variables = ( + (MORTGAGE_INPUTS[sample],) + if sample < len(MORTGAGE_INPUTS) + else MORTGAGE_INPUTS + ) + dependents = [ + {variable: rng.randint(1, 200_000) for variable in variables} + for _ in range(1 + sample % 3) + ] + label = f"{'joint' if joint else 'single'}-{source}-{sample}" + labels.append(label) + # Membership, filer inputs, and tax-unit debt remain identical. + # Only the dependents' person-level mortgage inputs change. + _add_tax_unit( + situation, + f"{label}-before", + joint, + filers, + mortgage, + [{} for _ in dependents], + ) + _add_tax_unit( + situation, + f"{label}-after", + joint, + filers, + mortgage, + dependents, + ) + + # One vectorized calculation keeps the generated cases inexpensive. + deduction = Simulation(situation=situation).calculate( + "hi_mortgage_interest_deduction", PERIOD + ) + before, after = deduction[::2], deduction[1::2] + failures = [ + f"{label}: {old} -> {new}" + for label, old, new in zip(labels, before, after) + if abs(old - new) > 0.01 + ] + np.testing.assert_allclose( + after, before, rtol=0, atol=0.01, err_msg="\n".join(failures) + ) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py index 6052a6c7e31..73a9c06d2ab 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py @@ -15,6 +15,7 @@ class hi_mortgage_interest_deduction(Variable): "https://data.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm", "https://data.capitol.hawaii.gov/sessions/session2026/bills/HB2329_CD1_.pdf#page=20", "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=17", + "https://www.irs.gov/pub/irs-prior/p936--2017.pdf#page=2", "https://www.irs.gov/pub/irs-prior/p936--2017.pdf#page=11", ) definition_period = YEAR @@ -50,11 +51,13 @@ def formula(tax_unit, period, parameters): where=debt > 0, ) # Line 10 uses total interest paid, before the federal debt cap. - # This also preserves the canonical person input's priority over + # This also preserves the canonical filer input's priority over # the deprecated first/second-home interest inputs. gross_interest = tax_unit("home_mortgage_interest_tax_unit", period) - legacy_interest = add(tax_unit, period, ["mortgage_interest"]) - supplied_deduction = add(tax_unit, period, ["deductible_mortgage_interest"]) + legacy_interest = tax_unit_non_dep_add(tax_unit, period, ["mortgage_interest"]) + supplied_deduction = tax_unit_non_dep_add( + tax_unit, period, ["deductible_mortgage_interest"] + ) # Legacy gross interest can be supplied directly or reconstructed # from deductible and non-deductible components. Deductible interest # alone has already been limited and must not receive a second cap. 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 fed7ca75751..9640709583c 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 @@ -62,7 +62,7 @@ class first_home_mortgage_interest(Variable): documentation = ( "DEPRECATED (issue #9275): use the person-level home_mortgage_interest " "input instead; the deduction only ever uses the first+second sum, and " - "this input is read only when no person-level interest is reported. " + "this input is read only when no filer interest is reported. " "Kept temporarily so existing datasets that supply it keep working; " "removal is scheduled once certified microdata stops exporting it." ) @@ -78,7 +78,7 @@ class second_home_mortgage_interest(Variable): documentation = ( "DEPRECATED (issue #9275): use the person-level home_mortgage_interest " "input instead; the deduction only ever uses the first+second sum, and " - "this input is read only when no person-level interest is reported. " + "this input is read only when no filer interest is reported. " "Kept temporarily so existing datasets that supply it keep working; " "removal is scheduled once certified microdata stops exporting it." ) @@ -111,14 +111,20 @@ class home_mortgage_interest_tax_unit(Variable): unit = USD definition_period = YEAR documentation = ( - "Total home mortgage interest. The person-level home_mortgage_interest " + "Total home mortgage interest paid by the tax unit's head and spouse. " + "The person-level home_mortgage_interest " "input is canonical; the deprecated structured first/second interest " - "inputs are used only when no person-level interest is reported " + "inputs are used only when no filer interest is reported " "(existing datasets still supply them — see issue #9275)." ) + reference = "https://www.irs.gov/pub/irs-prior/p936--2017.pdf#page=2" + def formula(tax_unit, period, parameters): - reported_interest = add(tax_unit, period, ["home_mortgage_interest"]) + # Only filers' own interest belongs on their return (Pub. 936, p. 2). + reported_interest = tax_unit_non_dep_add( + tax_unit, period, ["home_mortgage_interest"] + ) structured_interest = add( tax_unit, period, From 099c3b66be3a036daca758f76390c6f2e3923739 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 8 Oct 2026 20:20:57 -0400 Subject: [PATCH 6/9] Add federal mortgage regressions and disclose national scope --- changelog.d/hi-mortgage-interest-cap.fixed.md | 2 +- .../deductible_mortgage_interest.yaml | 126 ++++++++++ .../hi_mortgage_interest_deduction.yaml | 24 ++ ...st_federal_mortgage_interest_dependents.py | 238 ++++++++++++++++++ 4 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 policyengine_us/tests/test_federal_mortgage_interest_dependents.py diff --git a/changelog.d/hi-mortgage-interest-cap.fixed.md b/changelog.d/hi-mortgage-interest-cap.fixed.md index 8682589f948..0408fe28acd 100644 --- a/changelog.d/hi-mortgage-interest-cap.fixed.md +++ b/changelog.d/hi-mortgage-interest-cap.fixed.md @@ -1 +1 @@ -Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt) and preserve supplied deductible mortgage interest when gross interest is unavailable. Exclude dependents' mortgage interest from filers' deductions. +Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt) and preserve supplied deductible mortgage interest when gross interest is unavailable. Exclude dependents' canonical mortgage-interest inputs from the shared federal aggregate nationwide, changing federal deductions and state deductions that use federal mortgage-interest or itemized-deduction amounts. Exclude dependents' legacy gross interest and supplied deductible interest from Hawaii's mortgage deduction. 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 c437e3030f3..1f3b9593cfb 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 @@ -144,3 +144,129 @@ output: # Post-TCJA cap: 45,000 x 750,000 / 900,000 = 37,500 deductible_mortgage_interest_tax_unit: 37_500 + +# Publication 936 (2017), pages 2 and 8: only the taxpayer's paid share +# belongs on the return, including a spouse's share on a joint return. +# https://www.irs.gov/pub/irs-prior/p936--2017.pdf#page=2 +- name: Dependent-only canonical interest cannot create a federal filer deduction + period: 2024 + input: + people: + head: + age: 45 + is_tax_unit_head: true + dependent: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, dependent] + filing_status: SINGLE + households: + household: + members: [head, dependent] + state_code: CA + output: + home_mortgage_interest_tax_unit: 0 + deductible_mortgage_interest_tax_unit: 0 + non_deductible_mortgage_interest_tax_unit: 0 + deductible_mortgage_interest: [0, 0] + interest_deduction: 0 + +- name: Federal mortgage interest includes the single filer's payment only + period: 2024 + input: + people: + head: + age: 45 + is_tax_unit_head: true + home_mortgage_interest: 10_000 + dependent: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, dependent] + filing_status: SINGLE + households: + household: + members: [head, dependent] + state_code: CA + output: + home_mortgage_interest_tax_unit: 10_000 + deductible_mortgage_interest_tax_unit: 10_000 + non_deductible_mortgage_interest_tax_unit: 0 + deductible_mortgage_interest: [10_000, 0] + interest_deduction: 10_000 + +- name: Capped federal interest allocates both spouses' payments without dependent interest + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + is_tax_unit_head: true + home_mortgage_interest: 30_000 + spouse: + age: 44 + is_tax_unit_spouse: true + home_mortgage_interest: 15_000 + dependent: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, spouse, dependent] + filing_status: JOINT + first_home_mortgage_balance: 900_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [head, spouse, dependent] + state_code: CA + output: + # $45,000 * $750,000 / $900,000 = $37,500, split 2:1. + home_mortgage_interest_tax_unit: 45_000 + deductible_mortgage_interest_tax_unit: 37_500 + non_deductible_mortgage_interest_tax_unit: 7_500 + deductible_mortgage_interest: [25_000, 12_500, 0] + non_deductible_mortgage_interest: [5_000, 2_500, 0] + interest_deduction: 37_500 + +- name: Dependent canonical interest preserves the federal structured-interest fallback + period: 2024 + input: + people: + head: + age: 45 + is_tax_unit_head: true + spouse: + age: 44 + is_tax_unit_spouse: true + dependent: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, spouse, dependent] + filing_status: JOINT + first_home_mortgage_interest: 45_000 + first_home_mortgage_balance: 900_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [head, spouse, dependent] + state_code: CA + output: + # With no filer canonical payments, each spouse gets half the deduction. + home_mortgage_interest_tax_unit: 45_000 + deductible_mortgage_interest_tax_unit: 37_500 + non_deductible_mortgage_interest_tax_unit: 7_500 + deductible_mortgage_interest: [18_750, 18_750, 0] + non_deductible_mortgage_interest: [3_750, 3_750, 0] + interest_deduction: 37_500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml index 96ef7cc6bd1..b980fbc7e51 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml @@ -643,3 +643,27 @@ state_code: HI output: hi_mortgage_interest_deduction: 45_000 + +- name: Case 28, dependent-only supplied deductible interest cannot create a Hawaii filer deduction. + period: 2026 + input: + people: + head: + age: 45 + dependent: + age: 20 + is_tax_unit_dependent: true + deductible_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, dependent] + filing_status: SINGLE + first_home_mortgage_balance: 1_500_000 + first_home_mortgage_origination_year: 2023 + households: + household: + members: [head, dependent] + state_code: HI + output: + # Pub. 936 (2017), p. 2: the dependent's paid share is not the filer's. + hi_mortgage_interest_deduction: 0 diff --git a/policyengine_us/tests/test_federal_mortgage_interest_dependents.py b/policyengine_us/tests/test_federal_mortgage_interest_dependents.py new file mode 100644 index 00000000000..854b7ef223f --- /dev/null +++ b/policyengine_us/tests/test_federal_mortgage_interest_dependents.py @@ -0,0 +1,238 @@ +import random +from itertools import product + +import numpy as np + +from policyengine_us import Simulation + + +PERIOD = "2026" +MORTGAGE_CASES = ( + ("no-debt", 0, 2023), + ("post-tcja-uncapped", 600_000, 2023), + ("post-tcja-capped", 900_000, 2023), + ("pre-tcja-uncapped", 900_000, 2017), + ("pre-tcja-capped", 1_500_000, 2017), +) + + +def _add_tax_unit(situation, name, joint, filers, mortgage, dependent_interest): + members = [] + filer_names = [] + filer_indices = [] + dependent_indices = [] + + for index, interest in enumerate(filers): + person_name = f"{name}_filer_{index}" + members.append(person_name) + filer_names.append(person_name) + filer_indices.append(len(situation["people"])) + inputs = { + "age": 45 - index, + "is_tax_unit_head": index == 0, + "is_tax_unit_spouse": index == 1, + "is_tax_unit_dependent": False, + **interest, + } + situation["people"][person_name] = { + variable: {PERIOD: value} for variable, value in inputs.items() + } + + for index, interest in enumerate(dependent_interest): + person_name = f"{name}_dependent_{index}" + members.append(person_name) + dependent_indices.append(len(situation["people"])) + inputs = { + "age": 20 - index, + "is_tax_unit_head": False, + "is_tax_unit_spouse": False, + "is_tax_unit_dependent": True, + "home_mortgage_interest": interest, + } + situation["people"][person_name] = { + variable: {PERIOD: value} for variable, value in inputs.items() + } + situation["marital_units"][person_name] = {"members": [person_name]} + + situation["tax_units"][name] = { + "members": members, + "filing_status": {PERIOD: "JOINT" if joint else "SINGLE"}, + **{variable: {PERIOD: value} for variable, value in mortgage.items()}, + } + situation["households"][name] = { + "members": members, + "state_code": {PERIOD: "CA"}, + } + situation["families"][name] = {"members": members} + situation["spm_units"][name] = {"members": members} + situation["marital_units"][name] = {"members": filer_names} + return filer_indices, dependent_indices + + +def test_dependents_do_not_change_federal_mortgage_interest_or_allocations(): + """Matched returns conserve interest and allocate it only to filers. + + Varying dependents' own interest cannot change federal aggregates or + filer allocations. Canonical payments determine proportional shares; + structured fallback uses equal shares. No deduction outputs are supplied. + """ + rng = random.Random(9950) + situation = { + entity: {} + for entity in ( + "people", + "tax_units", + "households", + "families", + "spm_units", + "marital_units", + ) + } + records = [] + + for joint, source, mortgage_case, dependent_count in product( + (False, True), + ("canonical", "structured", "none"), + MORTGAGE_CASES, + (1, 2, 3), + ): + debt_label, balance, origination_year = mortgage_case + filer_count = 2 if joint else 1 + filers = [ + {"home_mortgage_interest": rng.randint(1_000, 60_000)} + if source == "canonical" + else {} + for _ in range(filer_count) + ] + if joint and source == "canonical" and dependent_count < 3: + # Include head-only, spouse-only, and two-payer joint returns. + filers[dependent_count - 1] = {} + payments = np.array( + [filer.get("home_mortgage_interest", 0) for filer in filers] + ) + mortgage = { + "first_home_mortgage_balance": balance, + "first_home_mortgage_origination_year": origination_year, + } + if source == "structured": + mortgage["first_home_mortgage_interest"] = rng.randint(1_000, 120_000) + gross_interest = ( + payments.sum() + if source == "canonical" + else mortgage.get("first_home_mortgage_interest", 0) + ) + expected_shares = ( + payments / payments.sum() + if source == "canonical" + else np.full(filer_count, 1 / filer_count) + ) + label = ( + f"{'joint' if joint else 'single'}-{source}-{debt_label}" + f"-{dependent_count}-dependents" + ) + dependent_payments = [rng.randint(1, 200_000) for _ in range(dependent_count)] + for suffix, dependent_interest in ( + ("before", [0] * dependent_count), + ("after", dependent_payments), + ): + name = f"{label}-{suffix}" + filer_indices, dependent_indices = _add_tax_unit( + situation, name, joint, filers, mortgage, dependent_interest + ) + records.append( + ( + name, + filer_indices, + dependent_indices, + gross_interest, + expected_shares, + ) + ) + + # One model construction covers 90 matched pairs, including both vintages. + simulation = Simulation(situation=situation) + gross = simulation.calculate("home_mortgage_interest_tax_unit", PERIOD) + deductible = simulation.calculate("deductible_mortgage_interest_tax_unit", PERIOD) + non_deductible = simulation.calculate( + "non_deductible_mortgage_interest_tax_unit", PERIOD + ) + person_deductible = simulation.calculate("deductible_mortgage_interest", PERIOD) + person_non_deductible = simulation.calculate( + "non_deductible_mortgage_interest", PERIOD + ) + shares = simulation.calculate("home_mortgage_interest_share", PERIOD) + + for index, (name, filers, dependents, expected_gross, expected_shares) in enumerate( + records + ): + np.testing.assert_allclose( + gross[index], expected_gross, rtol=0, atol=0.01, err_msg=name + ) + np.testing.assert_allclose( + deductible[index] + non_deductible[index], + gross[index], + rtol=0, + atol=0.01, + err_msg=name, + ) + assert deductible[index] >= 0, name + assert non_deductible[index] >= 0, name + np.testing.assert_allclose( + shares[filers], expected_shares, rtol=0, atol=1e-7, err_msg=name + ) + np.testing.assert_allclose(shares[dependents], 0, rtol=0, atol=0, err_msg=name) + for person_values, tax_unit_values in ( + (person_deductible, deductible), + (person_non_deductible, non_deductible), + ): + np.testing.assert_allclose( + person_values[dependents], 0, rtol=0, atol=0, err_msg=name + ) + np.testing.assert_allclose( + person_values[filers].sum(), + tax_unit_values[index], + rtol=0, + atol=0.01, + err_msg=name, + ) + np.testing.assert_allclose( + person_values[filers], + expected_shares * tax_unit_values[index], + rtol=0, + atol=0.01, + err_msg=name, + ) + np.testing.assert_allclose( + person_deductible[filers] + person_non_deductible[filers], + expected_shares * expected_gross, + rtol=0, + atol=0.01, + err_msg=name, + ) + + for index in range(0, len(records), 2): + name, before_filers, *_ = records[index] + after_filers = records[index + 1][1] + for variable, values in ( + ("gross", gross), + ("deductible", deductible), + ("non-deductible", non_deductible), + ): + np.testing.assert_allclose( + values[index + 1], + values[index], + rtol=0, + atol=0.01, + err_msg=f"{name}: dependent inputs changed federal {variable}", + ) + for variable, values in ( + ("deductible", person_deductible), + ("non-deductible", person_non_deductible), + ): + np.testing.assert_allclose( + values[after_filers], + values[before_filers], + rtol=0, + atol=0.01, + err_msg=f"{name}: dependent inputs changed filer {variable}", + ) From 87124017799b80d6f5fddc568fa153bd1c341900 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 02:06:33 -0400 Subject: [PATCH 7/9] Prevent dependent mortgage fallback leaks and round Hawaii debt ratio --- changelog.d/hi-mortgage-interest-cap.fixed.md | 2 +- .../deductible_mortgage_interest.yaml | 33 ++++++- .../itemized/hi_interest_deduction.yaml | 10 +- .../hi_mortgage_interest_deduction.yaml | 63 ++++++------ ...st_federal_mortgage_interest_dependents.py | 51 +++++++--- .../test_hi_mortgage_interest_dependents.py | 95 +++++++++++++++---- .../hi_mortgage_interest_deduction.py | 10 +- .../tax_unit/mortgage_interest_structure.py | 18 ++-- 8 files changed, 205 insertions(+), 77 deletions(-) diff --git a/changelog.d/hi-mortgage-interest-cap.fixed.md b/changelog.d/hi-mortgage-interest-cap.fixed.md index 0408fe28acd..7b88b368039 100644 --- a/changelog.d/hi-mortgage-interest-cap.fixed.md +++ b/changelog.d/hi-mortgage-interest-cap.fixed.md @@ -1 +1 @@ -Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt) and preserve supplied deductible mortgage interest when gross interest is unavailable. Exclude dependents' canonical mortgage-interest inputs from the shared federal aggregate nationwide, changing federal deductions and state deductions that use federal mortgage-interest or itemized-deduction amounts. Exclude dependents' legacy gross interest and supplied deductible interest from Hawaii's mortgage deduction. +Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt), round the qualified-debt ratio to three decimal places, and preserve supplied deductible mortgage interest when gross interest is unavailable. Exclude dependents' canonical mortgage-interest inputs from the shared federal aggregate nationwide, changing federal deductions and state deductions that use federal mortgage-interest or itemized-deduction amounts. Use deprecated structured interest only when no tax-unit member reports canonical interest, and prevent Hawaii's legacy gross-interest and supplied-deduction fallbacks from restoring dependent-only canonical interest. Exclude dependents' legacy gross interest and supplied deductible interest from Hawaii's mortgage deduction. 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 1f3b9593cfb..fa36031f481 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 @@ -174,6 +174,34 @@ deductible_mortgage_interest: [0, 0] interest_deduction: 0 +- name: Dataset-like structured totals cannot restore dependent-only canonical interest + period: 2024 + input: + people: + head: + age: 45 + is_tax_unit_head: true + dependent: + age: 20 + is_tax_unit_dependent: true + home_mortgage_interest: 12_000 + tax_units: + tax_unit: + members: [head, dependent] + filing_status: SINGLE + # Dataset exports duplicate canonical interest in deprecated inputs. + first_home_mortgage_interest: 12_000 + households: + household: + members: [head, dependent] + state_code: CA + output: + home_mortgage_interest_tax_unit: 0 + deductible_mortgage_interest_tax_unit: 0 + non_deductible_mortgage_interest_tax_unit: 0 + deductible_mortgage_interest: [0, 0] + interest_deduction: 0 + - name: Federal mortgage interest includes the single filer's payment only period: 2024 input: @@ -237,7 +265,7 @@ non_deductible_mortgage_interest: [5_000, 2_500, 0] interest_deduction: 37_500 -- name: Dependent canonical interest preserves the federal structured-interest fallback +- name: No member canonical interest preserves the federal structured-interest fallback period: 2024 input: people: @@ -250,7 +278,6 @@ dependent: age: 20 is_tax_unit_dependent: true - home_mortgage_interest: 12_000 tax_units: tax_unit: members: [head, spouse, dependent] @@ -263,7 +290,7 @@ members: [head, spouse, dependent] state_code: CA output: - # With no filer canonical payments, each spouse gets half the deduction. + # With no member canonical payments, each spouse gets half the deduction. home_mortgage_interest_tax_unit: 45_000 deductible_mortgage_interest_tax_unit: 37_500 non_deductible_mortgage_interest_tax_unit: 7_500 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 index 9bd9bced9b2..bd1948563f3 100644 --- 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 @@ -17,8 +17,8 @@ members: [person1] state_code: HI output: - # $1,100,000 / $1,500,000 * $90,000 = $66,000. - hi_interest_deduction: 66_000 + # Table 1, line 11: round($1,100,000 / $1,500,000, 3) * $90,000. + hi_interest_deduction: 65_970 - name: Case 2, mortgage interest is capped and the bundled investment-interest input is preserved. period: 2026 @@ -45,10 +45,10 @@ members: [person1, person2] state_code: HI output: - # Mortgage: $1,100,000 / $1,300,000 * $65,000 = $55,000. - hi_mortgage_interest_deduction: 55_000 + # Mortgage: round($1,100,000 / $1,300,000, 3) * $65,000 = $54,990. + hi_mortgage_interest_deduction: 54_990 # Preserve the bundled $5,000 input pending the dataset leaf-input split. - hi_interest_deduction: 60_000 + hi_interest_deduction: 59_990 - name: Case 3, mortgage interest within the debt limit and the bundled input are deductible. period: 2025 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml index b980fbc7e51..e5798fd314c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml @@ -21,8 +21,9 @@ deductible_mortgage_interest_tax_unit: 45_000 # Hawaii (2017 Pub 936 Table 1): $1,000,000 acquisition debt plus # $100,000 of the excess as home equity debt. - # $1,100,000 / $1,500,000 * $90,000 = $66,000. - hi_mortgage_interest_deduction: 66_000 + # Line 11 rounds $1,100,000 / $1,500,000 to 0.733. + # 0.733 * $90,000 = $65,970. + hi_mortgage_interest_deduction: 65_970 - name: Case 2, a post-TCJA mortgage under $1 million is fully deductible in Hawaii. period: 2026 @@ -68,8 +69,9 @@ output: # Federal: $375,000 / $600,000 * $30,000 = $18,750. deductible_mortgage_interest_tax_unit: 18_750 - # Hawaii: ($500,000 + $50,000) / $600,000 * $30,000 = $27,500. - hi_mortgage_interest_deduction: 27_500 + # Hawaii: round(($500,000 + $50,000) / $600,000, 3) = 0.917. + # 0.917 * $30,000 = $27,510. + hi_mortgage_interest_deduction: 27_510 - name: Case 4, joint filers with two mortgages apply the limits to the combined balance. period: 2024 @@ -97,8 +99,8 @@ output: # Federal: $800,000 / $1,300,000 * $65,000 = $40,000. deductible_mortgage_interest_tax_unit: 40_000 - # Hawaii: $1,100,000 / $1,300,000 * $65,000 = $55,000. - hi_mortgage_interest_deduction: 55_000 + # Hawaii: round($1,100,000 / $1,300,000, 3) * $65,000 = $54,990. + hi_mortgage_interest_deduction: 54_990 - name: Case 5, debt exactly at the combined $1.1 million limit is fully deductible. period: 2026 @@ -185,8 +187,8 @@ members: [person1] state_code: HI output: - # $1,100,000 / $1,200,000 * $60,000 = $55,000. - hi_mortgage_interest_deduction: 55_000 + # round($1,100,000 / $1,200,000, 3) * $60,000 = $55,020. + hi_mortgage_interest_deduction: 55_020 - name: Case 9, interest without a reported balance is fully deductible. period: 2026 @@ -263,8 +265,8 @@ members: [person1] state_code: HI output: - # $1,100,000 / $3,000,000 * $150,000 = $55,000. - hi_mortgage_interest_deduction: 55_000 + # round($1,100,000 / $3,000,000, 3) * $150,000 = $55,050. + hi_mortgage_interest_deduction: 55_050 - name: Case 13, supplied deductible interest is not capped again when only a balance is supplied. period: 2026 @@ -307,7 +309,7 @@ members: [person1] state_code: HI output: - hi_mortgage_interest_deduction: 8800 + hi_mortgage_interest_deduction: 8796 - name: Case 15, deprecated structured gross interest still receives the Hawaii debt cap. period: 2026 @@ -328,7 +330,7 @@ members: [person1] state_code: HI output: - hi_mortgage_interest_deduction: 66000 + hi_mortgage_interest_deduction: 65970 - name: Case 16, direct legacy gross interest still receives the Hawaii debt cap. period: 2026 @@ -349,7 +351,7 @@ members: [person1] state_code: HI output: - hi_mortgage_interest_deduction: 66000 + hi_mortgage_interest_deduction: 65970 - name: Case 17, supplied deductible and nondeductible components reconstruct gross interest. period: 2026 @@ -371,7 +373,7 @@ members: [person1] state_code: HI output: - hi_mortgage_interest_deduction: 66000 + hi_mortgage_interest_deduction: 65970 - name: Case 18, canonical gross interest is used with a supplied federal deduction. period: 2026 @@ -393,7 +395,7 @@ members: [person1] state_code: HI output: - hi_mortgage_interest_deduction: 66000 + hi_mortgage_interest_deduction: 65970 - name: Case 19, canonical gross interest takes priority over deprecated structured interest. period: 2026 @@ -415,7 +417,7 @@ members: [person1] state_code: HI output: - hi_mortgage_interest_deduction: 8800 + hi_mortgage_interest_deduction: 8796 - name: Case 20, gross interest and deductible-only fallbacks are handled per tax unit. period: 2026 @@ -444,11 +446,11 @@ members: [person1, person2] state_code: HI output: - hi_mortgage_interest_deduction: [66000, 45000] + hi_mortgage_interest_deduction: [65970, 45000] # 2017 Publication 936, page 2: deduct interest paid on a home owned by # the filer (or spouse on a joint return), excluding a dependent's own home. -- name: Case 21, canonical priority depends on whether the filer paid the interest. +- name: Case 21, dependent canonical interest blocks the legacy gross-interest fallback. period: 2026 absolute_error_margin: 0.01 input: @@ -483,9 +485,10 @@ members: [legacy_filer, dependent_owner, canonical_filer, control_dependent] state_code: HI output: - # The same $12,000 is excluded for the dependent, but has canonical - # priority when paid by the filer. The Hawaii debt ratio is 11/15. - hi_mortgage_interest_deduction: [66_000, 8_800] + # Canonical presence selects the filers' own amount, zero for the + # dependent-only unit, rather than falling back to legacy gross interest. + # The other unit deducts $12,000 * 0.733 = $8,796. + hi_mortgage_interest_deduction: [0, 8_796] - name: Case 22, joint filers combine both spouses' interest without dependent interest. period: 2026 @@ -517,10 +520,10 @@ members: [head, spouse, dependent] state_code: HI output: - # $65,000 * $1,100,000 / $1,300,000 = $55,000. - hi_mortgage_interest_deduction: 55_000 + # $65,000 * round($1,100,000 / $1,300,000, 3) = $54,990. + hi_mortgage_interest_deduction: 54_990 -- name: Case 23, a dependent's canonical interest does not replace structured filer interest. +- name: Case 23, dependent-only canonical interest suppresses matching dataset structured interest. period: 2026 absolute_error_margin: 0.01 input: @@ -537,13 +540,15 @@ filing_status: SINGLE first_home_mortgage_balance: 1_500_000 first_home_mortgage_origination_year: 2023 - first_home_mortgage_interest: 90_000 + first_home_mortgage_interest: 12_000 households: household: members: [head, dependent] state_code: HI output: - hi_mortgage_interest_deduction: 66_000 + home_mortgage_interest_tax_unit: 0 + deductible_mortgage_interest_tax_unit: 0 + hi_mortgage_interest_deduction: 0 - name: Case 24, supplied deductible interest excludes dependent amounts. period: 2026 @@ -593,7 +598,7 @@ members: [head, dependent] state_code: HI output: - hi_mortgage_interest_deduction: 66_000 + hi_mortgage_interest_deduction: 65_970 - name: Case 26, dependent-only canonical interest cannot create a filer deduction. period: 2026 @@ -619,7 +624,7 @@ output: hi_mortgage_interest_deduction: 0 -- name: Case 27, dependent canonical interest cannot override the supplied deductible fallback. +- name: Case 27, dependent canonical interest blocks the supplied deductible fallback. period: 2026 absolute_error_margin: 0.01 input: @@ -642,7 +647,7 @@ members: [head, dependent] state_code: HI output: - hi_mortgage_interest_deduction: 45_000 + hi_mortgage_interest_deduction: 0 - name: Case 28, dependent-only supplied deductible interest cannot create a Hawaii filer deduction. period: 2026 diff --git a/policyengine_us/tests/test_federal_mortgage_interest_dependents.py b/policyengine_us/tests/test_federal_mortgage_interest_dependents.py index 854b7ef223f..482af2a5688 100644 --- a/policyengine_us/tests/test_federal_mortgage_interest_dependents.py +++ b/policyengine_us/tests/test_federal_mortgage_interest_dependents.py @@ -74,7 +74,14 @@ def test_dependents_do_not_change_federal_mortgage_interest_or_allocations(): Varying dependents' own interest cannot change federal aggregates or filer allocations. Canonical payments determine proportional shares; - structured fallback uses equal shares. No deduction outputs are supplied. + zero filer payments use equal shares. Dataset-like structured totals track + all members' canonical payments, including dependents, without changing + filer aggregates. No deduction outputs are supplied. + + A fixed structured fallback is eligible only while nobody reports canonical + interest. Adding the first canonical payment intentionally changes its + eligibility; the structured pairs keep dependent canonical payments positive + on both sides, and YAML covers the eligible fallback separately. """ rng = random.Random(9950) situation = { @@ -92,7 +99,13 @@ def test_dependents_do_not_change_federal_mortgage_interest_or_allocations(): for joint, source, mortgage_case, dependent_count in product( (False, True), - ("canonical", "structured", "none"), + ( + "canonical", + "dataset-like-canonical", + "dataset-like-dependent-only", + "structured", + "none", + ), MORTGAGE_CASES, (1, 2, 3), ): @@ -100,11 +113,15 @@ def test_dependents_do_not_change_federal_mortgage_interest_or_allocations(): filer_count = 2 if joint else 1 filers = [ {"home_mortgage_interest": rng.randint(1_000, 60_000)} - if source == "canonical" + if source in ("canonical", "dataset-like-canonical") else {} for _ in range(filer_count) ] - if joint and source == "canonical" and dependent_count < 3: + if ( + joint + and source in ("canonical", "dataset-like-canonical") + and dependent_count < 3 + ): # Include head-only, spouse-only, and two-payer joint returns. filers[dependent_count - 1] = {} payments = np.array( @@ -116,14 +133,10 @@ def test_dependents_do_not_change_federal_mortgage_interest_or_allocations(): } if source == "structured": mortgage["first_home_mortgage_interest"] = rng.randint(1_000, 120_000) - gross_interest = ( - payments.sum() - if source == "canonical" - else mortgage.get("first_home_mortgage_interest", 0) - ) + gross_interest = payments.sum() expected_shares = ( payments / payments.sum() - if source == "canonical" + if payments.sum() > 0 else np.full(filer_count, 1 / filer_count) ) label = ( @@ -131,13 +144,25 @@ def test_dependents_do_not_change_federal_mortgage_interest_or_allocations(): f"-{dependent_count}-dependents" ) dependent_payments = [rng.randint(1, 200_000) for _ in range(dependent_count)] + before_dependent_payments = ( + [rng.randint(1, 1_000) for _ in range(dependent_count)] + if source in ("structured", "dataset-like-dependent-only") + else [0] * dependent_count + ) for suffix, dependent_interest in ( - ("before", [0] * dependent_count), + ("before", before_dependent_payments), ("after", dependent_payments), ): name = f"{label}-{suffix}" + pair_mortgage = mortgage.copy() + if source.startswith("dataset-like"): + # Dataset exports duplicate the all-member canonical total. + # Its dependent component must not enter the filers' return. + all_member_interest = payments.sum() + sum(dependent_interest) + pair_mortgage["first_home_mortgage_interest"] = all_member_interest / 2 + pair_mortgage["second_home_mortgage_interest"] = all_member_interest / 2 filer_indices, dependent_indices = _add_tax_unit( - situation, name, joint, filers, mortgage, dependent_interest + situation, name, joint, filers, pair_mortgage, dependent_interest ) records.append( ( @@ -149,7 +174,7 @@ def test_dependents_do_not_change_federal_mortgage_interest_or_allocations(): ) ) - # One model construction covers 90 matched pairs, including both vintages. + # One model construction covers 150 matched pairs, including both vintages. simulation = Simulation(situation=situation) gross = simulation.calculate("home_mortgage_interest_tax_unit", PERIOD) deductible = simulation.calculate("deductible_mortgage_interest_tax_unit", PERIOD) diff --git a/policyengine_us/tests/test_hi_mortgage_interest_dependents.py b/policyengine_us/tests/test_hi_mortgage_interest_dependents.py index dbb035dfb56..8d55dfbc18a 100644 --- a/policyengine_us/tests/test_hi_mortgage_interest_dependents.py +++ b/policyengine_us/tests/test_hi_mortgage_interest_dependents.py @@ -19,12 +19,18 @@ "reconstructed", "supplied_deduction", "structured", + "dataset_like", + "dataset_like_dependent_only", "none", ) def _filer_interest(source, amount): - if source in ("canonical", "canonical_with_supplied_deduction"): + if source in ( + "canonical", + "canonical_with_supplied_deduction", + "dataset_like", + ): inputs = {"home_mortgage_interest": amount} if source == "canonical_with_supplied_deduction": inputs["deductible_mortgage_interest"] = amount / 2 @@ -89,7 +95,13 @@ def _add_tax_unit(situation, name, joint, filers, mortgage, dependents): def test_dependent_mortgage_inputs_never_change_hawaii_deduction(): - """Generate matched returns across every mortgage-interest source path.""" + """Dependent payments cannot change deductions within a fixed source path. + + Dataset-like structured inputs equal all members' canonical payments; + changing dependent payments and these matching totals leaves the filers' + deduction unchanged. Canonical presence selects the filers' own amount, + including zero, before any legacy gross or supplied-deduction fallback. + """ rng = random.Random(9950) situation = { entity: {} @@ -103,6 +115,7 @@ def test_dependent_mortgage_inputs_never_change_hawaii_deduction(): ) } labels = [] + expected = [] for joint in (False, True): for source in INTEREST_SOURCES: @@ -140,24 +153,68 @@ def test_dependent_mortgage_inputs_never_change_hawaii_deduction(): ] label = f"{'joint' if joint else 'single'}-{source}-{sample}" labels.append(label) - # Membership, filer inputs, and tax-unit debt remain identical. - # Only the dependents' person-level mortgage inputs change. - _add_tax_unit( - situation, - f"{label}-before", - joint, - filers, - mortgage, - [{} for _ in dependents], + before_dependents = [{} for _ in dependents] + canonical_guard = "home_mortgage_interest" in variables and source in ( + "legacy", + "reconstructed", + "supplied_deduction", + "structured", ) - _add_tax_unit( - situation, - f"{label}-after", - joint, - filers, - mortgage, - dependents, + if canonical_guard: + # Both sides already have canonical interest. Crossing + # from no canonical input to a positive one intentionally + # changes eligibility for the lower-priority fallbacks. + before_dependents = [ + {"home_mortgage_interest": 1} for _ in dependents + ] + + filer_total = sum(sum(inputs.values()) for inputs in filers) + if source == "canonical_with_supplied_deduction": + filer_total = sum( + inputs["home_mortgage_interest"] for inputs in filers + ) + if source == "structured": + filer_total = ( + mortgage["first_home_mortgage_interest"] + + mortgage["second_home_mortgage_interest"] + ) + share = round(min(debt, 1_100_000) / debt, 3) if debt else 1 + expected.append( + 0 + if canonical_guard + else ( + filer_total + if source == "supplied_deduction" + else filer_total * share + ) ) + # Membership, filer inputs, and tax-unit debt remain identical. + # Dataset-like exports additionally mirror all members' + # canonical interest into the deprecated structured totals. + for suffix, dependent_inputs in ( + ("before", before_dependents), + ("after", dependents), + ): + tax_unit_inputs = mortgage.copy() + if source in ("dataset_like", "dataset_like_dependent_only"): + all_member_total = sum( + inputs.get("home_mortgage_interest", 0) + for inputs in filers + dependent_inputs + ) + tax_unit_inputs["first_home_mortgage_interest"] = ( + all_member_total * 0.75 + ) + tax_unit_inputs["second_home_mortgage_interest"] = ( + all_member_total * 0.25 + ) + _add_tax_unit( + situation, + f"{label}-{suffix}", + joint, + filers, + tax_unit_inputs, + dependent_inputs, + ) # One vectorized calculation keeps the generated cases inexpensive. deduction = Simulation(situation=situation).calculate( @@ -172,3 +229,5 @@ def test_dependent_mortgage_inputs_never_change_hawaii_deduction(): np.testing.assert_allclose( after, before, rtol=0, atol=0.01, err_msg="\n".join(failures) ) + np.testing.assert_allclose(before, expected, rtol=0, atol=0.02) + np.testing.assert_allclose(after, expected, rtol=0, atol=0.02) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py index 73a9c06d2ab..0e15ce5ee0e 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py @@ -50,10 +50,16 @@ def formula(tax_unit, period, parameters): out=np.ones_like(debt), where=debt > 0, ) + # Pub. 936 (2017), Table 1, line 11 rounds the ratio to three places. + deductible_share = np.round(deductible_share, 3) # Line 10 uses total interest paid, before the federal debt cap. # This also preserves the canonical filer input's priority over # the deprecated first/second-home interest inputs. gross_interest = tax_unit("home_mortgage_interest_tax_unit", period) + # Any member's canonical input selects the filers' amount, even zero, + # so legacy or supplied amounts cannot restore dependent interest. + canonical_interest = add(tax_unit, period, ["home_mortgage_interest"]) + has_canonical_interest = (canonical_interest > 0) | (gross_interest > 0) legacy_interest = tax_unit_non_dep_add(tax_unit, period, ["mortgage_interest"]) supplied_deduction = tax_unit_non_dep_add( tax_unit, period, ["deductible_mortgage_interest"] @@ -61,10 +67,10 @@ def formula(tax_unit, period, parameters): # Legacy gross interest can be supplied directly or reconstructed # from deductible and non-deductible components. Deductible interest # alone has already been limited and must not receive a second cap. - has_gross_interest = (gross_interest > 0) | ( + has_gross_interest = has_canonical_interest | ( legacy_interest > supplied_deduction ) - interest = where(gross_interest > 0, gross_interest, legacy_interest) + interest = where(has_canonical_interest, gross_interest, legacy_interest) # Line 12. Retain the supplied-deductible fallback when gross interest # is unavailable, even if a mortgage balance has been reported. return where( 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 9640709583c..2af48bd9a2a 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 @@ -62,7 +62,8 @@ class first_home_mortgage_interest(Variable): documentation = ( "DEPRECATED (issue #9275): use the person-level home_mortgage_interest " "input instead; the deduction only ever uses the first+second sum, and " - "this input is read only when no filer interest is reported. " + "this input is read only when no tax-unit member, including a " + "dependent, reports person-level home_mortgage_interest. " "Kept temporarily so existing datasets that supply it keep working; " "removal is scheduled once certified microdata stops exporting it." ) @@ -78,7 +79,8 @@ class second_home_mortgage_interest(Variable): documentation = ( "DEPRECATED (issue #9275): use the person-level home_mortgage_interest " "input instead; the deduction only ever uses the first+second sum, and " - "this input is read only when no filer interest is reported. " + "this input is read only when no tax-unit member, including a " + "dependent, reports person-level home_mortgage_interest. " "Kept temporarily so existing datasets that supply it keep working; " "removal is scheduled once certified microdata stops exporting it." ) @@ -114,7 +116,8 @@ class home_mortgage_interest_tax_unit(Variable): "Total home mortgage interest paid by the tax unit's head and spouse. " "The person-level home_mortgage_interest " "input is canonical; the deprecated structured first/second interest " - "inputs are used only when no filer interest is reported " + "inputs are used only when no tax-unit member, including a dependent, " + "reports person-level interest " "(existing datasets still supply them — see issue #9275)." ) @@ -122,6 +125,7 @@ class home_mortgage_interest_tax_unit(Variable): def formula(tax_unit, period, parameters): # Only filers' own interest belongs on their return (Pub. 936, p. 2). + all_member_reported_interest = add(tax_unit, period, ["home_mortgage_interest"]) reported_interest = tax_unit_non_dep_add( tax_unit, period, ["home_mortgage_interest"] ) @@ -130,7 +134,9 @@ def formula(tax_unit, period, parameters): period, ["first_home_mortgage_interest", "second_home_mortgage_interest"], ) - return where(reported_interest > 0, reported_interest, structured_interest) + return where( + all_member_reported_interest > 0, reported_interest, structured_interest + ) class deductible_mortgage_interest_tax_unit(Variable): @@ -151,8 +157,8 @@ def formula(tax_unit, period, parameters): first_year = tax_unit("first_home_mortgage_origination_year", period) second_year = tax_unit("second_home_mortgage_origination_year", period) total_balance = first_balance + second_balance - # Falls back to reported person-level interest when the structured - # first/second inputs are absent. + # Canonical payments take priority; structured first/second interest + # is used only when no member reports person-level interest. total_interest = tax_unit("home_mortgage_interest_tax_unit", period) filing_status = tax_unit("filing_status", period) From e88eafef5f042766480419bfa3c2e0f0d08fcd13 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 02:16:30 -0400 Subject: [PATCH 8/9] Isolate reconstructed mortgage inputs in Hawaii differential tests --- .../test_hi_mortgage_interest_dependents.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/policyengine_us/tests/test_hi_mortgage_interest_dependents.py b/policyengine_us/tests/test_hi_mortgage_interest_dependents.py index 8d55dfbc18a..1f507fee8f6 100644 --- a/policyengine_us/tests/test_hi_mortgage_interest_dependents.py +++ b/policyengine_us/tests/test_hi_mortgage_interest_dependents.py @@ -1,6 +1,7 @@ import random import numpy as np +import pytest from policyengine_us import Simulation @@ -94,7 +95,8 @@ def _add_tax_unit(situation, name, joint, filers, mortgage, dependents): situation["marital_units"][name] = {"members": filer_names} -def test_dependent_mortgage_inputs_never_change_hawaii_deduction(): +@pytest.mark.parametrize("reconstructed", (False, True)) +def test_dependent_mortgage_inputs_never_change_hawaii_deduction(reconstructed): """Dependent payments cannot change deductions within a fixed source path. Dataset-like structured inputs equal all members' canonical payments; @@ -116,9 +118,14 @@ def test_dependent_mortgage_inputs_never_change_hawaii_deduction(): } labels = [] expected = [] + sources = ( + ("reconstructed",) + if reconstructed + else tuple(source for source in INTEREST_SOURCES if source != "reconstructed") + ) for joint in (False, True): - for source in INTEREST_SOURCES: + for source in sources: # Exercise each person input alone, then arbitrary combinations. for sample in range(len(MORTGAGE_INPUTS) + 2): filers = [ @@ -151,6 +158,17 @@ def test_dependent_mortgage_inputs_never_change_hawaii_deduction(): {variable: rng.randint(1, 200_000) for variable in variables} for _ in range(1 + sample % 3) ] + if reconstructed: + # Supplying a variable for one person installs a complete + # input vector, defaulting omitted persons to zero. Keep + # mortgage_interest calculated in this separate batch. + for inputs in dependents: + legacy_gross = inputs.pop("mortgage_interest", None) + if legacy_gross is not None and len(inputs) == 0: + inputs["deductible_mortgage_interest"] = legacy_gross / 2 + inputs["non_deductible_mortgage_interest"] = ( + legacy_gross / 2 + ) label = f"{'joint' if joint else 'single'}-{source}-{sample}" labels.append(label) before_dependents = [{} for _ in dependents] From 3b42b27278af367e116116884126fc3f3b7836d1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 03:15:07 -0400 Subject: [PATCH 9/9] Preserve Hawaii filer mortgage fallbacks with dependent interest --- changelog.d/hi-mortgage-interest-cap.fixed.md | 2 +- .../hi_mortgage_interest_deduction.yaml | 18 +++++--- .../test_hi_mortgage_interest_dependents.py | 45 ++++++++----------- .../hi_mortgage_interest_deduction.py | 9 ++-- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/changelog.d/hi-mortgage-interest-cap.fixed.md b/changelog.d/hi-mortgage-interest-cap.fixed.md index 7b88b368039..bb9765576c4 100644 --- a/changelog.d/hi-mortgage-interest-cap.fixed.md +++ b/changelog.d/hi-mortgage-interest-cap.fixed.md @@ -1 +1 @@ -Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt), round the qualified-debt ratio to three decimal places, and preserve supplied deductible mortgage interest when gross interest is unavailable. Exclude dependents' canonical mortgage-interest inputs from the shared federal aggregate nationwide, changing federal deductions and state deductions that use federal mortgage-interest or itemized-deduction amounts. Use deprecated structured interest only when no tax-unit member reports canonical interest, and prevent Hawaii's legacy gross-interest and supplied-deduction fallbacks from restoring dependent-only canonical interest. Exclude dependents' legacy gross interest and supplied deductible interest from Hawaii's mortgage deduction. +Apply Hawaii's pre-TCJA mortgage debt limits ($1 million acquisition plus $100,000 home equity debt), round the qualified-debt ratio to three decimal places, and preserve supplied deductible mortgage interest when gross interest is unavailable. Exclude dependents' canonical mortgage-interest inputs from the shared federal aggregate nationwide, changing federal deductions and state deductions that use federal mortgage-interest or itemized-deduction amounts. Use deprecated structured interest only when no tax-unit member reports canonical interest. Preserve filers' own legacy gross interest and supplied deductible interest in Hawaii even when dependents report canonical interest, while excluding dependents' legacy gross interest and supplied deductible interest from Hawaii's mortgage deduction. diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml index e5798fd314c..47323dbb83c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.yaml @@ -450,7 +450,7 @@ # 2017 Publication 936, page 2: deduct interest paid on a home owned by # the filer (or spouse on a joint return), excluding a dependent's own home. -- name: Case 21, dependent canonical interest blocks the legacy gross-interest fallback. +- name: Case 21, dependent canonical interest preserves the filer's legacy gross-interest fallback. period: 2026 absolute_error_margin: 0.01 input: @@ -485,10 +485,10 @@ members: [legacy_filer, dependent_owner, canonical_filer, control_dependent] state_code: HI output: - # Canonical presence selects the filers' own amount, zero for the - # dependent-only unit, rather than falling back to legacy gross interest. - # The other unit deducts $12,000 * 0.733 = $8,796. - hi_mortgage_interest_deduction: [0, 8_796] + # The dependent's payment cannot erase the filer's own legacy interest: + # $90,000 * 0.733 = $65,970. The other unit's canonical filer interest + # takes priority over legacy gross: $12,000 * 0.733 = $8,796. + hi_mortgage_interest_deduction: [65_970, 8_796] - name: Case 22, joint filers combine both spouses' interest without dependent interest. period: 2026 @@ -546,6 +546,8 @@ members: [head, dependent] state_code: HI output: + # The federal aggregate already excludes the dependent's payment and its + # matching structured export, so the calculated filer fallbacks are zero. home_mortgage_interest_tax_unit: 0 deductible_mortgage_interest_tax_unit: 0 hi_mortgage_interest_deduction: 0 @@ -624,7 +626,7 @@ output: hi_mortgage_interest_deduction: 0 -- name: Case 27, dependent canonical interest blocks the supplied deductible fallback. +- name: Case 27, dependent canonical interest preserves the filer's supplied deductible fallback. period: 2026 absolute_error_margin: 0.01 input: @@ -647,7 +649,9 @@ members: [head, dependent] state_code: HI output: - hi_mortgage_interest_deduction: 0 + # The head's supplied deduction belongs to the filer, and has already + # been limited. Neither the dependent's payment nor a second cap reduces it. + hi_mortgage_interest_deduction: 45_000 - name: Case 28, dependent-only supplied deductible interest cannot create a Hawaii filer deduction. period: 2026 diff --git a/policyengine_us/tests/test_hi_mortgage_interest_dependents.py b/policyengine_us/tests/test_hi_mortgage_interest_dependents.py index 1f507fee8f6..14586538e91 100644 --- a/policyengine_us/tests/test_hi_mortgage_interest_dependents.py +++ b/policyengine_us/tests/test_hi_mortgage_interest_dependents.py @@ -19,7 +19,7 @@ "legacy", "reconstructed", "supplied_deduction", - "structured", + "structured_with_filer_legacy", "dataset_like", "dataset_like_dependent_only", "none", @@ -97,12 +97,14 @@ def _add_tax_unit(situation, name, joint, filers, mortgage, dependents): @pytest.mark.parametrize("reconstructed", (False, True)) def test_dependent_mortgage_inputs_never_change_hawaii_deduction(reconstructed): - """Dependent payments cannot change deductions within a fixed source path. + """Dependent payments cannot change deductions with filer payments fixed. Dataset-like structured inputs equal all members' canonical payments; changing dependent payments and these matching totals leaves the filers' - deduction unchanged. Canonical presence selects the filers' own amount, - including zero, before any legacy gross or supplied-deduction fallback. + deduction unchanged, including when dependent payments go from zero to + positive. Structured exports also have their totals explicitly attributed + to filers through legacy gross inputs, so disabling the federal structured + fallback cannot erase those filers' payments from the Hawaii deduction. """ rng = random.Random(9950) situation = { @@ -141,13 +143,21 @@ def test_dependent_mortgage_inputs_never_change_hawaii_deduction(reconstructed): ), "second_home_mortgage_origination_year": 2023, } - if source == "structured": + if source == "structured_with_filer_legacy": mortgage["first_home_mortgage_interest"] = rng.randint( 1_000, 150_000 ) mortgage["second_home_mortgage_interest"] = rng.randint( 1_000, 150_000 ) + structured_total = ( + mortgage["first_home_mortgage_interest"] + + mortgage["second_home_mortgage_interest"] + ) + filers = [ + {"mortgage_interest": structured_total / len(filers)} + for _ in filers + ] variables = ( (MORTGAGE_INPUTS[sample],) @@ -172,39 +182,22 @@ def test_dependent_mortgage_inputs_never_change_hawaii_deduction(reconstructed): label = f"{'joint' if joint else 'single'}-{source}-{sample}" labels.append(label) before_dependents = [{} for _ in dependents] - canonical_guard = "home_mortgage_interest" in variables and source in ( - "legacy", - "reconstructed", - "supplied_deduction", - "structured", - ) - if canonical_guard: - # Both sides already have canonical interest. Crossing - # from no canonical input to a positive one intentionally - # changes eligibility for the lower-priority fallbacks. - before_dependents = [ - {"home_mortgage_interest": 1} for _ in dependents - ] filer_total = sum(sum(inputs.values()) for inputs in filers) if source == "canonical_with_supplied_deduction": filer_total = sum( inputs["home_mortgage_interest"] for inputs in filers ) - if source == "structured": + if source == "structured_with_filer_legacy": filer_total = ( mortgage["first_home_mortgage_interest"] + mortgage["second_home_mortgage_interest"] ) share = round(min(debt, 1_100_000) / debt, 3) if debt else 1 expected.append( - 0 - if canonical_guard - else ( - filer_total - if source == "supplied_deduction" - else filer_total * share - ) + filer_total + if source == "supplied_deduction" + else filer_total * share ) # Membership, filer inputs, and tax-unit debt remain identical. # Dataset-like exports additionally mirror all members' diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py index 0e15ce5ee0e..732a8f60beb 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_mortgage_interest_deduction.py @@ -56,10 +56,7 @@ def formula(tax_unit, period, parameters): # This also preserves the canonical filer input's priority over # the deprecated first/second-home interest inputs. gross_interest = tax_unit("home_mortgage_interest_tax_unit", period) - # Any member's canonical input selects the filers' amount, even zero, - # so legacy or supplied amounts cannot restore dependent interest. - canonical_interest = add(tax_unit, period, ["home_mortgage_interest"]) - has_canonical_interest = (canonical_interest > 0) | (gross_interest > 0) + # These fallbacks already include only the filers' own payments. legacy_interest = tax_unit_non_dep_add(tax_unit, period, ["mortgage_interest"]) supplied_deduction = tax_unit_non_dep_add( tax_unit, period, ["deductible_mortgage_interest"] @@ -67,10 +64,10 @@ def formula(tax_unit, period, parameters): # Legacy gross interest can be supplied directly or reconstructed # from deductible and non-deductible components. Deductible interest # alone has already been limited and must not receive a second cap. - has_gross_interest = has_canonical_interest | ( + has_gross_interest = (gross_interest > 0) | ( legacy_interest > supplied_deduction ) - interest = where(has_canonical_interest, gross_interest, legacy_interest) + interest = where(gross_interest > 0, gross_interest, legacy_interest) # Line 12. Retain the supplied-deductible fallback when gross interest # is unavailable, even if a mortgage balance has been reported. return where(