Skip to content

Commit 2440379

Browse files
authored
Merge pull request #7875 from PolicyEngine/codex/mid-modeling
Add structural federal mortgage interest deduction
2 parents eb96d4a + e3ff221 commit 2440379

10 files changed

Lines changed: 338 additions & 14 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add structural federal mortgage interest deduction modeling using mortgage balances, interest amounts, and origination years.
Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
1-
description: The IRS allows an itemized deduction for interest paid on mortgages up to this amount, based on filing status.
1+
description: The IRS allows an itemized deduction for interest paid on non-grandfathered home acquisition debt up to this mortgage balance, based on filing status.
22
SINGLE:
3-
2017-01-01: 1_000_000
3+
1900-01-01: 1_000_000
44
2018-01-01: 750_000
55
JOINT:
6-
2017-01-01: 1_000_000
6+
1900-01-01: 1_000_000
77
2018-01-01: 750_000
88
SEPARATE:
9-
2017-01-01: 500_000
9+
1900-01-01: 500_000
1010
2018-01-01: 375_000
1111
HEAD_OF_HOUSEHOLD:
12-
2017-01-01: 1_000_000
12+
1900-01-01: 1_000_000
1313
2018-01-01: 750_000
1414
SURVIVING_SPOUSE:
15-
2017-01-01: 1_000_000
15+
1900-01-01: 1_000_000
1616
2018-01-01: 750_000
1717
metadata:
1818
breakdown: filing_status
1919
unit: currency-USD
2020
period: year
2121
label: IRS home mortgage value cap
2222
reference:
23-
# OBBB extends TCJA limits on mortgage interested deduction.
23+
# OBBB extends TCJA limits on mortgage interest deduction.
2424
- title: H.R.1 - One Big Beautiful Bill Act
2525
href: https://www.congress.gov/bill/119th-congress/house-bill/1/text
26-
# Hawaii applies this AGI threshold in the state itemized deductions computation
27-
- title: Hawaii Income Tax Law, Chapter 235, Section 235-2.4, (j)(3)
28-
href: https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=10
29-
# TCJA adjustemnts described in (h)(3)(F)(i)(II)
26+
# TCJA adjustments described in (h)(3)(F)(i)(II)
3027
- title: 26 U.S. Code § 163 - Interest, (h)(3)(b)(ii)
3128
href: https://www.law.cornell.edu/uscode/text/26/163
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
description: The IRS allows an itemized deduction for interest paid on pre-TCJA grandfathered home acquisition debt up to this mortgage balance, based on filing status.
2+
SINGLE:
3+
1900-01-01: 1_000_000
4+
JOINT:
5+
1900-01-01: 1_000_000
6+
SEPARATE:
7+
1900-01-01: 500_000
8+
HEAD_OF_HOUSEHOLD:
9+
1900-01-01: 1_000_000
10+
SURVIVING_SPOUSE:
11+
1900-01-01: 1_000_000
12+
metadata:
13+
breakdown: filing_status
14+
unit: currency-USD
15+
period: year
16+
label: IRS pre-TCJA home mortgage value cap
17+
reference:
18+
- title: 26 U.S. Code § 163 - Interest, (h)(3)(F)
19+
href: https://www.law.cornell.edu/uscode/text/26/163
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
description: Home acquisition debt originated on or before this calendar year is treated as pre-TCJA grandfathered debt for the federal mortgage interest deduction.
2+
values:
3+
1900-01-01: 2017
4+
metadata:
5+
unit: year
6+
label: IRS pre-TCJA mortgage origination year cutoff
7+
reference:
8+
- title: 26 U.S. Code § 163 - Interest, (h)(3)(F)
9+
href: https://www.law.cornell.edu/uscode/text/26/163
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
- name: Grandfathered mortgage remains fully deductible
2+
period: 2024
3+
input:
4+
filing_status: SINGLE
5+
tax_unit_itemizes: true
6+
standard_deduction: 0
7+
first_home_mortgage_balance: 900_000
8+
first_home_mortgage_interest: 45_000
9+
first_home_mortgage_origination_year: 2017
10+
output:
11+
deductible_mortgage_interest_tax_unit: 45_000
12+
interest_deduction: 45_000
13+
mortgage_interest: 45_000
14+
15+
- name: Post-TCJA mortgage is limited by the lower cap
16+
period: 2024
17+
input:
18+
filing_status: SINGLE
19+
tax_unit_itemizes: true
20+
standard_deduction: 0
21+
first_home_mortgage_balance: 900_000
22+
first_home_mortgage_interest: 45_000
23+
first_home_mortgage_origination_year: 2018
24+
output:
25+
deductible_mortgage_interest_tax_unit: 37_500
26+
interest_deduction: 37_500
27+
mortgage_interest: 45_000
28+
29+
- name: Mixed-vintage mortgages use the combined statutory balance limit
30+
period: 2024
31+
absolute_error_margin: 0.01
32+
input:
33+
filing_status: JOINT
34+
tax_unit_itemizes: true
35+
standard_deduction: 0
36+
first_home_mortgage_balance: 800_000
37+
first_home_mortgage_interest: 40_000
38+
first_home_mortgage_origination_year: 2017
39+
second_home_mortgage_balance: 300_000
40+
second_home_mortgage_interest: 15_000
41+
second_home_mortgage_origination_year: 2018
42+
output:
43+
deductible_mortgage_interest_tax_unit: 40_000
44+
interest_deduction: 40_000
45+
mortgage_interest: 55_000
46+
47+
- name: Married filing separately uses the lower post-TCJA cap
48+
period: 2024
49+
input:
50+
filing_status: SEPARATE
51+
tax_unit_itemizes: true
52+
standard_deduction: 0
53+
first_home_mortgage_balance: 500_000
54+
first_home_mortgage_interest: 25_000
55+
first_home_mortgage_origination_year: 2018
56+
output:
57+
deductible_mortgage_interest_tax_unit: 18_750
58+
interest_deduction: 18_750
59+
mortgage_interest: 25_000
60+
61+
- name: No structural mortgage inputs yields zero deductible interest
62+
period: 2024
63+
input:
64+
filing_status: SINGLE
65+
tax_unit_itemizes: true
66+
standard_deduction: 0
67+
output:
68+
deductible_mortgage_interest_tax_unit: 0
69+
non_deductible_mortgage_interest_tax_unit: 0

policyengine_us/variables/household/expense/person/deductible_mortgage_interest.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@ class deductible_mortgage_interest(Variable):
55
value_type = float
66
entity = Person
77
label = "Deductible mortgage interest"
8-
documentation = "Under the interest deduction, the US caps the mortgage value to which interest is applied which based on the year of purchase not tax year."
8+
documentation = (
9+
"Federal deductible mortgage interest. When structural mortgage inputs "
10+
"are provided at the tax-unit level, PolicyEngine applies the "
11+
"acquisition-debt caps and allocates the resulting deduction across "
12+
"filers."
13+
)
914
unit = USD
1015
definition_period = YEAR
16+
reference = "https://www.law.cornell.edu/uscode/text/26/163"
1117

12-
# This is a placeholder variable until we can implement the full mortgage interest deduction logic
18+
def formula(person, period, parameters):
19+
deductible_interest = person.tax_unit(
20+
"deductible_mortgage_interest_tax_unit", period
21+
)
22+
share = person("home_mortgage_interest_share", period)
23+
return deductible_interest * share
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from policyengine_us.model_api import *
2+
3+
4+
class home_mortgage_interest_share(Variable):
5+
value_type = float
6+
entity = Person
7+
label = "Share of tax-unit home mortgage interest"
8+
definition_period = YEAR
9+
documentation = (
10+
"Allocates tax-unit mortgage interest across filers using reported "
11+
"person-level home mortgage interest when available, otherwise evenly "
12+
"across head and spouse."
13+
)
14+
15+
def formula(person, period, parameters):
16+
head_or_spouse = person("is_tax_unit_head_or_spouse", period)
17+
reported_interest = head_or_spouse * person("home_mortgage_interest", period)
18+
total_reported_interest = person.tax_unit.sum(reported_interest)
19+
20+
reported_share = np.zeros_like(total_reported_interest)
21+
reported_mask = total_reported_interest > 0
22+
reported_share[reported_mask] = (
23+
reported_interest[reported_mask] / total_reported_interest[reported_mask]
24+
)
25+
26+
filer_count = person.tax_unit.sum(head_or_spouse)
27+
equal_share = np.zeros_like(filer_count)
28+
filer_mask = filer_count > 0
29+
equal_share[filer_mask] = head_or_spouse[filer_mask] / filer_count[filer_mask]
30+
31+
return where(total_reported_interest > 0, reported_share, equal_share)

policyengine_us/variables/household/expense/person/non_deductible_mortgage_interest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,14 @@ class non_deductible_mortgage_interest(Variable):
77
label = "Non-deductible mortgage interest"
88
unit = USD
99
definition_period = YEAR
10+
documentation = (
11+
"Home mortgage interest that is not deductible federally after "
12+
"applying the acquisition-debt caps."
13+
)
14+
15+
def formula(person, period, parameters):
16+
non_deductible_interest = person.tax_unit(
17+
"non_deductible_mortgage_interest_tax_unit", period
18+
)
19+
share = person("home_mortgage_interest_share", period)
20+
return non_deductible_interest * share
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
from policyengine_us.model_api import *
2+
3+
4+
def _mortgage_balance_cap(
5+
origination_year, pre_tcja_cap, post_tcja_cap, pre_tcja_origination_year
6+
):
7+
# An origination year of 0 means no mortgage; treat as post-TCJA so the
8+
# lower cap applies (harmless because the balance will also be 0).
9+
return where(
10+
(origination_year > 0) & (origination_year <= pre_tcja_origination_year),
11+
pre_tcja_cap,
12+
post_tcja_cap,
13+
)
14+
15+
16+
def _limited_mortgage_balance(first_balance, second_balance, first_cap, second_cap):
17+
# Under §163(h)(3)(F), pre-TCJA debt keeps the $1M cap and post-TCJA debt
18+
# gets max(0, $750K − pre_TCJA_debt). This is equivalent to computing the
19+
# combined deductible balance as:
20+
# min(larger_cap, max(larger_balance, smaller_cap))
21+
# where "larger/smaller" refer to the balance sizes (not vintages).
22+
first_is_smaller = first_balance < second_balance
23+
smaller_cap = where(first_is_smaller, first_cap, second_cap)
24+
larger_cap = where(first_is_smaller, second_cap, first_cap)
25+
larger_balance = max_(first_balance, second_balance)
26+
return min_(larger_cap, max_(larger_balance, smaller_cap))
27+
28+
29+
class first_home_mortgage_balance(Variable):
30+
value_type = float
31+
entity = TaxUnit
32+
label = "First home mortgage balance"
33+
unit = USD
34+
definition_period = YEAR
35+
default_value = 0
36+
documentation = (
37+
"Outstanding balance on the first home acquisition mortgage used to "
38+
"calculate the federal mortgage interest deduction."
39+
)
40+
41+
42+
class second_home_mortgage_balance(Variable):
43+
value_type = float
44+
entity = TaxUnit
45+
label = "Second home mortgage balance"
46+
unit = USD
47+
definition_period = YEAR
48+
default_value = 0
49+
documentation = (
50+
"Outstanding balance on the second home acquisition mortgage used to "
51+
"calculate the federal mortgage interest deduction."
52+
)
53+
54+
55+
class first_home_mortgage_interest(Variable):
56+
value_type = float
57+
entity = TaxUnit
58+
label = "First home mortgage interest"
59+
unit = USD
60+
definition_period = YEAR
61+
default_value = 0
62+
documentation = (
63+
"Interest paid on the first home acquisition mortgage used to "
64+
"calculate the federal mortgage interest deduction."
65+
)
66+
67+
68+
class second_home_mortgage_interest(Variable):
69+
value_type = float
70+
entity = TaxUnit
71+
label = "Second home mortgage interest"
72+
unit = USD
73+
definition_period = YEAR
74+
default_value = 0
75+
documentation = (
76+
"Interest paid on the second home acquisition mortgage used to "
77+
"calculate the federal mortgage interest deduction."
78+
)
79+
80+
81+
class first_home_mortgage_origination_year(Variable):
82+
value_type = int
83+
entity = TaxUnit
84+
label = "First home mortgage origination year"
85+
definition_period = YEAR
86+
default_value = 0
87+
documentation = "Calendar year when the first home acquisition mortgage originated."
88+
89+
90+
class second_home_mortgage_origination_year(Variable):
91+
value_type = int
92+
entity = TaxUnit
93+
label = "Second home mortgage origination year"
94+
definition_period = YEAR
95+
default_value = 0
96+
documentation = (
97+
"Calendar year when the second home acquisition mortgage originated."
98+
)
99+
100+
101+
class home_mortgage_interest_tax_unit(Variable):
102+
value_type = float
103+
entity = TaxUnit
104+
label = "Tax unit home mortgage interest"
105+
unit = USD
106+
definition_period = YEAR
107+
adds = ["first_home_mortgage_interest", "second_home_mortgage_interest"]
108+
109+
110+
class deductible_mortgage_interest_tax_unit(Variable):
111+
value_type = float
112+
entity = TaxUnit
113+
label = "Tax unit deductible mortgage interest"
114+
unit = USD
115+
definition_period = YEAR
116+
documentation = (
117+
"Federal deductible mortgage interest after applying the statutory "
118+
"acquisition-debt caps to up to two mortgages."
119+
)
120+
reference = "https://www.law.cornell.edu/uscode/text/26/163"
121+
122+
def formula(tax_unit, period, parameters):
123+
first_balance = tax_unit("first_home_mortgage_balance", period)
124+
second_balance = tax_unit("second_home_mortgage_balance", period)
125+
first_interest = tax_unit("first_home_mortgage_interest", period)
126+
second_interest = tax_unit("second_home_mortgage_interest", period)
127+
first_year = tax_unit("first_home_mortgage_origination_year", period)
128+
second_year = tax_unit("second_home_mortgage_origination_year", period)
129+
total_balance = first_balance + second_balance
130+
total_interest = first_interest + second_interest
131+
132+
filing_status = tax_unit("filing_status", period)
133+
p = parameters(period).gov.irs.deductions.itemized.interest.mortgage
134+
pre_tcja_cap = p.pre_tcja_cap[filing_status]
135+
post_tcja_cap = p.cap[filing_status]
136+
pre_tcja_origination_year = p.pre_tcja_origination_year
137+
138+
first_cap = _mortgage_balance_cap(
139+
first_year,
140+
pre_tcja_cap,
141+
post_tcja_cap,
142+
pre_tcja_origination_year,
143+
)
144+
second_cap = _mortgage_balance_cap(
145+
second_year,
146+
pre_tcja_cap,
147+
post_tcja_cap,
148+
pre_tcja_origination_year,
149+
)
150+
limited_balance = _limited_mortgage_balance(
151+
first_balance, second_balance, first_cap, second_cap
152+
)
153+
154+
deductible_share = np.zeros_like(total_balance)
155+
mask = total_balance > 0
156+
deductible_share[mask] = np.minimum(
157+
1, limited_balance[mask] / total_balance[mask]
158+
)
159+
return total_interest * deductible_share
160+
161+
162+
class non_deductible_mortgage_interest_tax_unit(Variable):
163+
value_type = float
164+
entity = TaxUnit
165+
label = "Tax unit non-deductible mortgage interest"
166+
unit = USD
167+
definition_period = YEAR
168+
documentation = (
169+
"Home mortgage interest that is not deductible federally because it "
170+
"exceeds the acquisition-debt caps."
171+
)
172+
173+
def formula(tax_unit, period, parameters):
174+
total_interest = tax_unit("home_mortgage_interest_tax_unit", period)
175+
deductible_interest = tax_unit("deductible_mortgage_interest_tax_unit", period)
176+
return max_(0, total_interest - deductible_interest)

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)