Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/mortgage-interest-structure.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add structural federal mortgage interest deduction modeling using mortgage balances, interest amounts, and origination years.
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
description: The IRS allows an itemized deduction for interest paid on mortgages up to this amount, based on filing status.
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.
SINGLE:
2017-01-01: 1_000_000
1900-01-01: 1_000_000
2018-01-01: 750_000
JOINT:
2017-01-01: 1_000_000
1900-01-01: 1_000_000
2018-01-01: 750_000
SEPARATE:
2017-01-01: 500_000
1900-01-01: 500_000
2018-01-01: 375_000
HEAD_OF_HOUSEHOLD:
2017-01-01: 1_000_000
1900-01-01: 1_000_000
2018-01-01: 750_000
SURVIVING_SPOUSE:
2017-01-01: 1_000_000
1900-01-01: 1_000_000
2018-01-01: 750_000
metadata:
breakdown: filing_status
unit: currency-USD
period: year
label: IRS home mortgage value cap
reference:
# OBBB extends TCJA limits on mortgage interested deduction.
# OBBB extends TCJA limits on mortgage interest deduction.
- title: H.R.1 - One Big Beautiful Bill Act
href: https://www.congress.gov/bill/119th-congress/house-bill/1/text
# Hawaii applies this AGI threshold in the state itemized deductions computation
- title: Hawaii Income Tax Law, Chapter 235, Section 235-2.4, (j)(3)
href: https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=10
# TCJA adjustemnts described in (h)(3)(F)(i)(II)
# TCJA adjustments described in (h)(3)(F)(i)(II)
- title: 26 U.S. Code § 163 - Interest, (h)(3)(b)(ii)
href: https://www.law.cornell.edu/uscode/text/26/163
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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.
SINGLE:
1900-01-01: 1_000_000
JOINT:
1900-01-01: 1_000_000
SEPARATE:
1900-01-01: 500_000
HEAD_OF_HOUSEHOLD:
1900-01-01: 1_000_000
SURVIVING_SPOUSE:
1900-01-01: 1_000_000
metadata:
breakdown: filing_status
unit: currency-USD
period: year
label: IRS pre-TCJA home mortgage value cap
reference:
- title: 26 U.S. Code § 163 - Interest, (h)(3)(F)
href: https://www.law.cornell.edu/uscode/text/26/163
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Home acquisition debt originated on or before this calendar year is treated as pre-TCJA grandfathered debt for the federal mortgage interest deduction.
values:
1900-01-01: 2017
metadata:
unit: year
label: IRS pre-TCJA mortgage origination year cutoff
reference:
- title: 26 U.S. Code § 163 - Interest, (h)(3)(F)
href: https://www.law.cornell.edu/uscode/text/26/163
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
- name: Grandfathered mortgage remains fully deductible
period: 2024
input:
filing_status: SINGLE
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_balance: 900_000
first_home_mortgage_interest: 45_000
first_home_mortgage_origination_year: 2017
output:
deductible_mortgage_interest_tax_unit: 45_000
interest_deduction: 45_000
mortgage_interest: 45_000

- name: Post-TCJA mortgage is limited by the lower cap
period: 2024
input:
filing_status: SINGLE
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_balance: 900_000
first_home_mortgage_interest: 45_000
first_home_mortgage_origination_year: 2018
output:
deductible_mortgage_interest_tax_unit: 37_500
interest_deduction: 37_500
mortgage_interest: 45_000

- name: Mixed-vintage mortgages use the combined statutory balance limit
period: 2024
absolute_error_margin: 0.01
input:
filing_status: JOINT
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_balance: 800_000
first_home_mortgage_interest: 40_000
first_home_mortgage_origination_year: 2017
second_home_mortgage_balance: 300_000
second_home_mortgage_interest: 15_000
second_home_mortgage_origination_year: 2018
output:
deductible_mortgage_interest_tax_unit: 40_000
interest_deduction: 40_000
mortgage_interest: 55_000

- name: Married filing separately uses the lower post-TCJA cap
period: 2024
input:
filing_status: SEPARATE
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_balance: 500_000
first_home_mortgage_interest: 25_000
first_home_mortgage_origination_year: 2018
output:
deductible_mortgage_interest_tax_unit: 18_750
interest_deduction: 18_750
mortgage_interest: 25_000

- name: No structural mortgage inputs yields zero deductible interest
period: 2024
input:
filing_status: SINGLE
tax_unit_itemizes: true
standard_deduction: 0
output:
deductible_mortgage_interest_tax_unit: 0
non_deductible_mortgage_interest_tax_unit: 0
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@ class deductible_mortgage_interest(Variable):
value_type = float
entity = Person
label = "Deductible mortgage interest"
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."
documentation = (
"Federal deductible mortgage interest. When structural mortgage inputs "
"are provided at the tax-unit level, PolicyEngine applies the "
"acquisition-debt caps and allocates the resulting deduction across "
"filers."
)
unit = USD
definition_period = YEAR
reference = "https://www.law.cornell.edu/uscode/text/26/163"

# This is a placeholder variable until we can implement the full mortgage interest deduction logic
def formula(person, period, parameters):
deductible_interest = person.tax_unit(
"deductible_mortgage_interest_tax_unit", period
)
share = person("home_mortgage_interest_share", period)
return deductible_interest * share
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from policyengine_us.model_api import *


class home_mortgage_interest_share(Variable):
value_type = float
entity = Person
label = "Share of tax-unit home mortgage interest"
definition_period = YEAR
documentation = (
"Allocates tax-unit mortgage interest across filers using reported "
"person-level home mortgage interest when available, otherwise evenly "
"across head and spouse."
)

def formula(person, period, parameters):
head_or_spouse = person("is_tax_unit_head_or_spouse", period)
reported_interest = head_or_spouse * person("home_mortgage_interest", period)
total_reported_interest = person.tax_unit.sum(reported_interest)

reported_share = np.zeros_like(total_reported_interest)
reported_mask = total_reported_interest > 0
reported_share[reported_mask] = (
reported_interest[reported_mask] / total_reported_interest[reported_mask]
)

filer_count = person.tax_unit.sum(head_or_spouse)
equal_share = np.zeros_like(filer_count)
filer_mask = filer_count > 0
equal_share[filer_mask] = head_or_spouse[filer_mask] / filer_count[filer_mask]

return where(total_reported_interest > 0, reported_share, equal_share)
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,14 @@ class non_deductible_mortgage_interest(Variable):
label = "Non-deductible mortgage interest"
unit = USD
definition_period = YEAR
documentation = (
"Home mortgage interest that is not deductible federally after "
"applying the acquisition-debt caps."
)

def formula(person, period, parameters):
non_deductible_interest = person.tax_unit(
"non_deductible_mortgage_interest_tax_unit", period
)
share = person("home_mortgage_interest_share", period)
return non_deductible_interest * share
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
from policyengine_us.model_api import *


def _mortgage_balance_cap(
origination_year, pre_tcja_cap, post_tcja_cap, pre_tcja_origination_year
):
# An origination year of 0 means no mortgage; treat as post-TCJA so the
# lower cap applies (harmless because the balance will also be 0).
return where(
(origination_year > 0) & (origination_year <= pre_tcja_origination_year),
pre_tcja_cap,
post_tcja_cap,
)


def _limited_mortgage_balance(first_balance, second_balance, first_cap, second_cap):
# Under §163(h)(3)(F), pre-TCJA debt keeps the $1M cap and post-TCJA debt
# gets max(0, $750K − pre_TCJA_debt). This is equivalent to computing the
# combined deductible balance as:
# min(larger_cap, max(larger_balance, smaller_cap))
# where "larger/smaller" refer to the balance sizes (not vintages).
first_is_smaller = first_balance < second_balance
smaller_cap = where(first_is_smaller, first_cap, second_cap)
larger_cap = where(first_is_smaller, second_cap, first_cap)
larger_balance = max_(first_balance, second_balance)
return min_(larger_cap, max_(larger_balance, smaller_cap))


class first_home_mortgage_balance(Variable):
value_type = float
entity = TaxUnit
label = "First home mortgage balance"
unit = USD
definition_period = YEAR
default_value = 0
documentation = (
"Outstanding balance on the first home acquisition mortgage used to "
"calculate the federal mortgage interest deduction."
)


class second_home_mortgage_balance(Variable):
value_type = float
entity = TaxUnit
label = "Second home mortgage balance"
unit = USD
definition_period = YEAR
default_value = 0
documentation = (
"Outstanding balance on the second home acquisition mortgage used to "
"calculate the federal mortgage interest deduction."
)


class first_home_mortgage_interest(Variable):
value_type = float
entity = TaxUnit
label = "First home mortgage interest"
unit = USD
definition_period = YEAR
default_value = 0
documentation = (
"Interest paid on the first home acquisition mortgage used to "
"calculate the federal mortgage interest deduction."
)


class second_home_mortgage_interest(Variable):
value_type = float
entity = TaxUnit
label = "Second home mortgage interest"
unit = USD
definition_period = YEAR
default_value = 0
documentation = (
"Interest paid on the second home acquisition mortgage used to "
"calculate the federal mortgage interest deduction."
)


class first_home_mortgage_origination_year(Variable):
value_type = int
entity = TaxUnit
label = "First home mortgage origination year"
definition_period = YEAR
default_value = 0
documentation = "Calendar year when the first home acquisition mortgage originated."


class second_home_mortgage_origination_year(Variable):
value_type = int
entity = TaxUnit
label = "Second home mortgage origination year"
definition_period = YEAR
default_value = 0
documentation = (
"Calendar year when the second home acquisition mortgage originated."
)


class home_mortgage_interest_tax_unit(Variable):
value_type = float
entity = TaxUnit
label = "Tax unit home mortgage interest"
unit = USD
definition_period = YEAR
adds = ["first_home_mortgage_interest", "second_home_mortgage_interest"]


class deductible_mortgage_interest_tax_unit(Variable):
value_type = float
entity = TaxUnit
label = "Tax unit deductible mortgage interest"
unit = USD
definition_period = YEAR
documentation = (
"Federal deductible mortgage interest after applying the statutory "
"acquisition-debt caps to up to two mortgages."
)
reference = "https://www.law.cornell.edu/uscode/text/26/163"

def formula(tax_unit, period, parameters):
first_balance = tax_unit("first_home_mortgage_balance", period)
second_balance = tax_unit("second_home_mortgage_balance", period)
first_interest = tax_unit("first_home_mortgage_interest", period)
second_interest = tax_unit("second_home_mortgage_interest", period)
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
total_interest = first_interest + second_interest

filing_status = tax_unit("filing_status", period)
p = parameters(period).gov.irs.deductions.itemized.interest.mortgage
pre_tcja_cap = p.pre_tcja_cap[filing_status]
post_tcja_cap = p.cap[filing_status]
pre_tcja_origination_year = p.pre_tcja_origination_year

first_cap = _mortgage_balance_cap(
first_year,
pre_tcja_cap,
post_tcja_cap,
pre_tcja_origination_year,
)
second_cap = _mortgage_balance_cap(
second_year,
pre_tcja_cap,
post_tcja_cap,
pre_tcja_origination_year,
)
limited_balance = _limited_mortgage_balance(
first_balance, second_balance, first_cap, second_cap
)

deductible_share = np.zeros_like(total_balance)
mask = total_balance > 0
deductible_share[mask] = np.minimum(
1, limited_balance[mask] / total_balance[mask]
)
return total_interest * deductible_share


class non_deductible_mortgage_interest_tax_unit(Variable):
value_type = float
entity = TaxUnit
label = "Tax unit non-deductible mortgage interest"
unit = USD
definition_period = YEAR
documentation = (
"Home mortgage interest that is not deductible federally because it "
"exceeds the acquisition-debt caps."
)

def formula(tax_unit, period, parameters):
total_interest = tax_unit("home_mortgage_interest_tax_unit", period)
deductible_interest = tax_unit("deductible_mortgage_interest_tax_unit", period)
return max_(0, total_interest - deductible_interest)
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading