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-atomic-stage3.removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed the deprecated first_home_mortgage_interest and second_home_mortgage_interest inputs; the person-level home_mortgage_interest input is now the only mortgage-interest input, while the per-mortgage balance and origination-year inputs are unchanged. API callers that pass either removed input now receive an unknown-variable error and should supply home_mortgage_interest on the filer(s) instead. Datasets that still carry the removed columns load with policyengine-core's unknown-column warning, and any tax unit whose interest appeared only in those columns now computes zero mortgage interest; the certified default dataset has no such tax units.
5 changes: 2 additions & 3 deletions policyengine_us/tests/core/test_uprating_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,8 @@
# Benefits adjusted by the Social Security cost-of-living index.
"veterans_benefits": COLA,
# Consumption expenses, and the interest on debts, follow prices. The
# structured first and second mortgage inputs stay on the default: two are
# deprecated (issue #9275) and the file's formulas are outside the
# selective coverage job.
# per-mortgage balance and origination-year inputs stay on the default:
# the file's formulas are outside the selective coverage job.
"pre_subsidy_rent": CPI_U,
"pre_subsidy_care_expenses": CPI_U,
"spm_unit_pre_subsidy_childcare_expenses": CPI_U,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
Datasets built before issue #9275's stage 3 can still carry the removed
tax-unit ``first_home_mortgage_interest`` / ``second_home_mortgage_interest``
columns. Loading one must name the ignored columns (policyengine-core's
unknown-column warning) and compute mortgage interest from the person-level
``home_mortgage_interest`` input alone.
"""

import logging

import pandas as pd

from policyengine_us import Microsimulation
from policyengine_us.data.dataset_schema import USSingleYearDataset


def _dataset_with_removed_columns() -> USSingleYearDataset:
return USSingleYearDataset(
person=pd.DataFrame(
{
"person_id": [1, 2],
"age": [40.0, 40.0],
"employment_income": [50_000.0, 0.0],
"person_household_id": [1, 1],
"person_tax_unit_id": [1, 1],
"person_spm_unit_id": [1, 1],
"person_family_id": [1, 1],
"person_marital_unit_id": [1, 1],
"home_mortgage_interest": [900.0, 100.0],
}
),
household=pd.DataFrame(
{"household_id": [1], "household_weight": [1.0], "state_fips": [6]}
),
tax_unit=pd.DataFrame(
{
"tax_unit_id": [1],
# Deliberately different from the person-level total so the
# test proves the removed columns are ignored.
"first_home_mortgage_interest": [5_000.0],
"second_home_mortgage_interest": [2_000.0],
}
),
spm_unit=pd.DataFrame({"spm_unit_id": [1]}),
family=pd.DataFrame({"family_id": [1]}),
marital_unit=pd.DataFrame({"marital_unit_id": [1]}),
time_period=2024,
)


def test_removed_mortgage_interest_columns_warn_and_are_ignored(caplog):
with caplog.at_level(logging.WARNING):
sim = Microsimulation(dataset=_dataset_with_removed_columns())
interest = sim.calculate("home_mortgage_interest_tax_unit", 2024)

warnings = [
record.getMessage()
for record in caplog.records
if "do not match any variable" in record.getMessage()
]
assert len(warnings) == 1
assert "first_home_mortgage_interest" in warnings[0]
assert "second_home_mortgage_interest" in warnings[0]
assert interest.values.tolist() == [1_000.0]
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_balance: 900_000
first_home_mortgage_interest: 45_000
home_mortgage_interest: 45_000
first_home_mortgage_origination_year: 2017
output:
deductible_mortgage_interest_tax_unit: 45_000
Expand All @@ -19,12 +19,14 @@
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_balance: 900_000
first_home_mortgage_interest: 45_000
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
# 45,000 x (1 - 750,000 / 900,000)
non_deductible_mortgage_interest_tax_unit: 7_500

- name: Mixed-vintage mortgages use the combined statutory balance limit
period: 2024
Expand All @@ -34,10 +36,9 @@
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_balance: 800_000
first_home_mortgage_interest: 40_000
home_mortgage_interest: 55_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
Expand All @@ -51,14 +52,14 @@
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_balance: 500_000
first_home_mortgage_interest: 25_000
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
- name: No mortgage inputs yields zero deductible interest
period: 2024
input:
filing_status: SINGLE
Expand All @@ -68,7 +69,7 @@
deductible_mortgage_interest_tax_unit: 0
non_deductible_mortgage_interest_tax_unit: 0

- name: Person-level home mortgage interest is deductible when no structural inputs are provided
- name: Person-level home mortgage interest is the single interest input
period: 2024
input:
people:
Expand All @@ -85,62 +86,96 @@
interest_deduction: 10_000
mortgage_interest: 10_000

- name: Structural interest with no reported balance is fully deductible
period: 2024
input:
filing_status: SINGLE
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_interest: 10_000
output:
deductible_mortgage_interest_tax_unit: 10_000

- name: Regression guard - post-TCJA balance above the cap is still limited
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

- name: Person-level input is canonical when both input styles are supplied (issue #9275)
- name: Joint filers' interest sums across spouses and is allocated back by reported shares
period: 2024
input:
people:
head:
home_mortgage_interest: 12_000
age: 45
home_mortgage_interest: 30_000
spouse:
age: 45
home_mortgage_interest: 15_000
tax_units:
tax_unit:
members: [head]
filing_status: SINGLE
members: [head, spouse]
filing_status: JOINT
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_interest: 10_000
first_home_mortgage_balance: 900_000
first_home_mortgage_origination_year: 2018
output:
# Person-level wins; the deprecated structured input is ignored when
# person-level interest is reported.
home_mortgage_interest_tax_unit: 12_000
deductible_mortgage_interest_tax_unit: 12_000
interest_deduction: 12_000
home_mortgage_interest_tax_unit: 45_000
# 45,000 x 750,000 / 900,000 = 37,500, split 2:1 by reported interest
deductible_mortgage_interest_tax_unit: 37_500
non_deductible_mortgage_interest_tax_unit: 7_500
deductible_mortgage_interest: [25_000, 12_500]
non_deductible_mortgage_interest: [5_000, 2_500]

- name: Person-level interest still passes through the acquisition-debt caps
- name: Interest reported only by a dependent is claimed by the filers in equal shares
period: 2024
input:
people:
head:
home_mortgage_interest: 45_000
age: 45
spouse:
age: 45
child:
age: 10
home_mortgage_interest: 6_000
tax_units:
tax_unit:
members: [head]
filing_status: SINGLE
members: [head, spouse, child]
filing_status: JOINT
tax_unit_itemizes: true
standard_deduction: 0
first_home_mortgage_balance: 900_000
first_home_mortgage_origination_year: 2018
output:
# Post-TCJA cap: 45,000 x 750,000 / 900,000 = 37,500
home_mortgage_interest_tax_unit: 6_000
deductible_mortgage_interest_tax_unit: 6_000
deductible_mortgage_interest: [3_000, 3_000, 0]

- name: Smaller grandfathered loan reduces the post-TCJA limit on a larger new loan
period: 2024
input:
filing_status: JOINT
tax_unit_itemizes: true
standard_deduction: 0
home_mortgage_interest: 45_000
first_home_mortgage_balance: 200_000
first_home_mortgage_origination_year: 2017
second_home_mortgage_balance: 700_000
second_home_mortgage_origination_year: 2018
output:
# 26 USC 163(h)(3)(F)(iii): 200,000 grandfathered + min(700,000, 750,000 - 200,000)
# = 750,000 qualified of 900,000; 45,000 x 750 / 900 = 37,500
deductible_mortgage_interest_tax_unit: 37_500

- name: Married filing separately keeps the 500,000 grandfathered cap
period: 2024
input:
filing_status: SEPARATE
tax_unit_itemizes: true
standard_deduction: 0
home_mortgage_interest: 30_000
first_home_mortgage_balance: 600_000
first_home_mortgage_origination_year: 2016
output:
# 30,000 x 500,000 / 600,000
deductible_mortgage_interest_tax_unit: 25_000
non_deductible_mortgage_interest_tax_unit: 5_000

- name: Combined grandfathered balances above 1,000,000 are limited in total
period: 2024
input:
filing_status: JOINT
tax_unit_itemizes: true
standard_deduction: 0
home_mortgage_interest: 60_000
first_home_mortgage_balance: 800_000
first_home_mortgage_origination_year: 2015
second_home_mortgage_balance: 400_000
second_home_mortgage_origination_year: 2016
output:
# 60,000 x 1,000,000 / 1,200,000
deductible_mortgage_interest_tax_unit: 50_000
non_deductible_mortgage_interest_tax_unit: 10_000
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@
- head
- spouse
first_home_mortgage_balance: 1000000.0
first_home_mortgage_interest: 2743.0
takes_up_aca_if_eligible: true
takes_up_dc_ptc: true
takes_up_eitc: true
Expand Down Expand Up @@ -587,7 +586,6 @@
members: &id007
- head
first_home_mortgage_balance: 273000.0
first_home_mortgage_interest: 7566.0
takes_up_aca_if_eligible: true
takes_up_dc_ptc: true
takes_up_eitc: true
Expand Down Expand Up @@ -660,9 +658,7 @@
members: &id008
- head
first_home_mortgage_balance: 794797.6875
first_home_mortgage_interest: 2975.722412109375
second_home_mortgage_balance: 205202.3125
second_home_mortgage_interest: 768.277587890625
takes_up_aca_if_eligible: true
takes_up_dc_ptc: true
takes_up_eitc: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ class home_mortgage_interest_share(Variable):
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."
"Allocates the tax unit's mortgage interest (and its deductible and "
"non-deductible parts) to the filers, who claim the deduction: in "
"proportion to the head's and spouse's own reported interest, or "
"evenly between them when only other members (such as dependents) "
"report interest. Shares sum to one across head and spouse, so the "
"person amounts always add back to the tax-unit totals."
)

def formula(person, period, parameters):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,38 +52,6 @@ class second_home_mortgage_balance(Variable):
)


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 = (
"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. "
"Kept temporarily so existing datasets that supply it keep working; "
"removal is scheduled once certified microdata stops exporting it."
)


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 = (
"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. "
"Kept temporarily so existing datasets that supply it keep working; "
"removal is scheduled once certified microdata stops exporting it."
)


class first_home_mortgage_origination_year(Variable):
value_type = int
entity = TaxUnit
Expand Down Expand Up @@ -111,20 +79,12 @@ class home_mortgage_interest_tax_unit(Variable):
unit = USD
definition_period = YEAR
documentation = (
"Total home mortgage interest. 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 "
"(existing datasets still supply them — see issue #9275)."
"Total home mortgage interest, summed from the person-level "
"home_mortgage_interest input — the single mortgage-interest input "
"(issue #9275). The per-mortgage balances and origination years remain "
"tax-unit inputs because the acquisition-debt caps need them per loan."
)

def formula(tax_unit, period, parameters):
reported_interest = add(tax_unit, period, ["home_mortgage_interest"])
structured_interest = add(
tax_unit,
period,
["first_home_mortgage_interest", "second_home_mortgage_interest"],
)
return where(reported_interest > 0, reported_interest, structured_interest)
adds = ["home_mortgage_interest"]


class deductible_mortgage_interest_tax_unit(Variable):
Expand All @@ -145,8 +105,6 @@ 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.
total_interest = tax_unit("home_mortgage_interest_tax_unit", period)

filing_status = tax_unit("filing_status", period)
Expand Down
Loading