Skip to content

Apply federal mortgage debt limits by loan vintage, including grandfathered pre-1987 debt - #10025

Open
MaxGhenis wants to merge 4 commits into
mainfrom
mortgage-grandfathered-debt
Open

MaxGhenis wants to merge 4 commits into
mainfrom
mortgage-grandfathered-debt

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10011.

What and why

The federal limit on deductible mortgage interest depends on when each debt was incurred. The old helper picked each loan's cap by origination year (everything through 2017 got $1,000,000) and then combined the two loans by balance size. That got two cases wrong:

  1. Grandfathered debt was capped. Debt incurred on or before October 13, 1987 has no dollar limit. A 1986 mortgage with a $1,500,000 balance and $15,000 of interest gave $10,000 deductible; the law gives $15,000.
  2. Older debt above $750,000 was cut to $750,000 when a newer loan was larger (issue Mortgage debt cap loses grandfathered debt when equal balances put the newer loan first #10011). $800,000 from 2016 plus $900,000 from 2020 gave a $750,000 qualified balance; the law gives $800,000. Two $775,000 loans from 2017 and 2018 gave $750,000 or $775,000 depending on which slot held which loan.

This PR computes the qualified loan limit by vintage, line by line as IRS Publication 936 Table 1 does, so it no longer depends on loan order or relative size.

Montana already counts this federal amount (#10004), and Alabama and North Carolina will (branch al-nc-mortgage-interest-federal-cap, which waits on this PR), so they inherit the fix. Puerto Rico's pr_mortgage_deduction also reads it.

Law

26 U.S.C. 163(h)(3)(D):

(i) In general In the case of any pre-October 13, 1987, indebtedness— (I) such indebtedness shall be treated as acquisition indebtedness, and (II) the limitation of subparagraph (B)(ii) shall not apply. (ii) Reduction in $1,000,000 limitation The limitation of subparagraph (B)(ii) shall be reduced (but not below zero) by the aggregate amount of outstanding pre-October 13, 1987, indebtedness.

26 U.S.C. 163(h)(3)(F)(i), for taxable years beginning after December 31, 2017:

(II) Limitation on acquisition indebtedness Subparagraph (B)(ii) shall be applied by substituting "$750,000 ($375,000" for "$1,000,000 ($500,000". … (IV) Treatment of indebtedness incurred on or before December 15, 2017 Subclause (II) shall not apply to any indebtedness incurred on or before December 15, 2017, and, in applying such subclause to any indebtedness incurred after such date, the limitation under such subclause shall be reduced (but not below zero) by the amount of any indebtedness incurred on or before December 15, 2017, which is treated as acquisition indebtedness for purposes of this subsection for the taxable year.

IRS Publication 936 (2025), PDF p. 13: "Grandfathered debt isn't limited. All of the interest you paid on grandfathered debt is fully deductible home mortgage interest. However, the amount of your grandfathered debt reduces the limit for home acquisition debt." Table 1, PDF p. 14 computes the qualified loan limit:

Line Entry
1 grandfathered debt (G)
2 home acquisition debt incurred after October 13, 1987 and before December 16, 2017 (P)
3 $1,000,000 ($500,000 separate)
4 larger of lines 1 and 3
5 lines 1 + 2
6 smaller of lines 4 and 5
7 home acquisition debt incurred after December 15, 2017 (N)
8 $750,000 ($375,000 separate)
9 larger of lines 6 and 8
10 lines 6 + 7
11 smaller of lines 9 and 10: the qualified loan limit

Line 6 counts grandfathered debt once against the $750,000 limit, matching p. 12: "The limits above are reduced (but not below zero) by the amount of your grandfathered debt." For 2017 and earlier, both limit parameters are $1,000,000 ($500,000 separate), so all non-grandfathered debt shares one $1,000,000 limit reduced by grandfathered debt; the 2017 edition says the same ("Grandfathered debt isn't limited", PDF p. 10; Table 1, PDF p. 11).

Inputs and the 1987 convention

The model stores a balance and an origination year for up to two loans, so the October 13, 1987 cutoff falls inside a year. A new parameter gov.irs.deductions.itemized.interest.mortgage.grandfathered_origination_year = 1987 treats a 1987 loan as grandfathered. It mirrors the existing pre_tcja_origination_year = 2017 convention, under which a 2017 loan counts as incurred on or before December 15, 2017. January 1 to October 13 is 286 of 365 days of 1987. A year of 0 means unknown and counts as post-2017 debt, as before. Both origination-year inputs now document this. No new input is added; exact cutoff dates would require an origination-date input.

The three cap parameter descriptions now say which debt each applies to; the old ones called all pre-2018 debt "grandfathered", which Pub. 936 reserves for pre-October 14, 1987 debt.

Households (main vs this branch)

Measured with evidence/households.py on gh/main fdd70629054a24a0a53077c358d13b9a17d96b4d and this branch 9e5342c844145868e917f9a92d0c06e5185fb3d2. Each of the 14 households uses one tax unit; both JSON outputs record the imported module. Dollar pairs show main → branch.

Household Loans (balance/year); interest paid Deductible interest Federal income tax MT itemized deduction MT income tax
1986 debt; zero wages (2024, joint; wages $0) $1,500,000/1986; interest $15,000 $10,000 → $15,000 $0 → $0 — —
1986 debt; itemizer (2024, joint; wages $400,000) $1,500,000/1986; interest $90,000 $60,000 → $90,000 $67,159.12 → $59,959.11 — —
Grandfathered; separate (2024, separate; wages $250,000) $600,000/1985; interest $36,000 $30,000 → $36,000 $47,598.05 → $45,678.05 — —
1987 convention (2024, single; wages $400,000) $1,200,000/1987; interest $72,000 $60,000 → $72,000 $88,714.94 → $84,514.94 — —
1988 boundary (2024, single; wages $400,000) $1,200,000/1988; interest $72,000 $60,000 → $60,000 $88,714.94 → $88,714.94 — —
Grandfathered + 2010 (2024, joint; wages $400,000) $1,200,000/1985; $300,000/2010; interest $75,000 $50,000 → $60,000 $69,559.12 → $67,159.12 — —
Grandfathered + 2020 (2024, joint; wages $400,000) $400,000/2020; $1,100,000/1986; interest $75,000 $50,000 → $55,000 $69,559.12 → $68,359.12 — —
#10011; older loan first (2024, joint; wages $350,000) $800,000/2016; $900,000/2020; interest $85,000 $37,500 → $40,000 $60,559.11 → $59,959.11 — —
#10011; newer loan first (2024, joint; wages $350,000) $900,000/2020; $800,000/2016; interest $85,000 $37,500 → $40,000 $60,559.11 → $59,959.11 — —
Equal balances; newer first (2024, joint; wages $350,000) $775,000/2018; $775,000/2017; interest $62,000 $30,000 → $31,000 $62,359.11 → $62,119.11 — —
Equal balances; older first (2024, joint; wages $350,000) $775,000/2017; $775,000/2018; interest $62,000 $31,000 → $31,000 $62,119.11 → $62,119.11 — —
Montana grandfathered; 2022 (2022, joint; wages $300,000) $1,200,000/1986; interest $72,000 $60,000 → $72,000 $42,871 → $39,991 $70,000 → $82,000 $14,556.65 → $13,746.65
Montana grandfathered; 2025 (2025, joint; wages $300,000) $1,200,000/1986; interest $72,000 $60,000 → $72,000 $39,185.21 → $36,305.21 $60,000 → $72,000 $13,653.60 → $12,945.60
Post-2017 only (2024, joint; wages $400,000) $900,000/2019; interest $54,000 $45,000 → $45,000 $70,759.12 → $70,759.12 — —

Deductible interest is unchanged for 1988 boundary, Equal balances; older first, Post-2017 only.
The following cases use the standard deduction on both versions: 1986 debt; zero wages. A changed mortgage-interest allowance does not change federal income tax in these nonitemizing cases.
Federal income tax is unchanged for 1986 debt; zero wages, 1988 boundary, Equal balances; older first, Post-2017 only.

Tests

  • deductible_mortgage_interest_tax_unit.yaml (new, named for the variable it checks): 18 cases. Each shows its Table 1 arithmetic and covers:

    • grandfathered debt alone (joint, separate, 2017);
    • the 1987/1988 boundary;
    • two grandfathered loans;
    • grandfathered plus pre-2018 or post-2017 debt, both below and above each limit;
    • Pub. 936's refinanced 1986 balloon note, with a larger balance so the year entered matters;
    • Mortgage debt cap loses grandfathered debt when equal balances put the newer loan first #10011's two examples, in both slot orders;
    • an unknown year;
    • two vectorized cases that mix every filing status and vintage in 2024 and 2017, checking that deductible plus non-deductible interest equals interest paid.

    The model prorates by the exact ratio; Pub. 936 line 14 rounds it to three decimals, which would move some expected amounts by up to $20.

  • mt_itemized_deductions_joint.yaml: one case where 1986 debt above $1,000,000 is fully deductible in Montana.

  • tests/core/test_mortgage_debt_limit_invariants.py (new). It tests the pure helpers only and builds no Simulation; it's Python because YAML can't state for-all properties. Hypothesis (400 derandomized examples) draws two loans around every cutoff year and limit, and checks the helpers against two independent restatements: Table 1 line by line, with its "stop here" branch, and the statute in its own order. Local passing run: 2 tests passed in 177.502 seconds elapsed, with 812.73 MiB peak RSS (852,213,760 bytes), measured locally on macOS using time.monotonic() for elapsed time and os.wait4 child resource usage for RSS (Darwin ru_maxrss is in bytes). These are local measurements, not Linux CI measurements.

Branch verification

Measured verification at 9e5342c844145868e917f9a92d0c06e5185fb3d2: 84 YAML cases across 10 files and 8 Python tests across 2 files passed. The YAML files ran in one bounded foreground batch of 10 files; each Python file ran in its own foreground process.

Test file Result
core/test_mortgage_debt_limit_invariants.py 2 passed
core/test_mt_mortgage_interest_federal_limit_properties.py 6 passed
policy/baseline/gov/irs/income/taxable_income/deductions/deductible_mortgage_interest.yaml 10 passed
policy/baseline/gov/irs/income/taxable_income/deductions/deductible_mortgage_interest_tax_unit.yaml 18 passed
policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization.yaml 5 passed
policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.yaml 2 passed
policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml 5 passed
policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml 7 passed
policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml 5 passed
policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml 8 passed
policy/baseline/gov/states/mt/tax/income/integration.yaml 20 passed
policy/baseline/gov/territories/pr/tax/income/taxable_income/deductions/pr_mortgage_deduction.yaml 4 passed

Per-file passing counts in a batch come from the YAML case counts only after its real pytest summary and collected count confirm every case in the complete batch passed.

No partner test files were edited. A read-only search found no partner cases supplying mortgage balances, origination years, mortgage interest, or deductible mortgage interest.

Regression evidence on main

The branch's new cases were also run against gh/main fdd70629054a24a0a53077c358d13b9a17d96b4d:

Cases copied onto main Observed result
deductible_mortgage_interest_tax_unit.yaml; mt_grandfathered_case.yaml (2-file batch) 5 passed, 14 failed (aggregate)
deductible_mortgage_interest_tax_unit.yaml 5 passed, 13 failed (observed verbose lines)
mt_grandfathered_case.yaml 0 passed, 1 failed (observed verbose lines)

Per-file rows count the recorded verbose result lines before the failure report. Every file's case count, the total number of result lines, and their combined outcomes match the batch's collected count and pytest summary; no per-file results are inferred.

These are preserved assertion failures demonstrating the pre-fix behavior; the same federal and Montana cases pass on the branch.

Invariants (asserted in the property test)

For every pair of loans, filing status, and pre- or post-2018 year:

  • Differential: the qualified loan limit equals both restatements.
  • Bounds: grandfathered debt ≤ limit ≤ total debt; limit ≥ min(total debt, post-2017 limit); limit ≤ max(grandfathered debt, $1,000,000 limit).
  • Order: swapping the two loans leaves the limit unchanged.
  • Monotone: a larger balance never lowers the limit, and neither does an older vintage.
  • Accounting (vectorized YAML cases): deductible + non-deductible = interest paid, for every filing status.
  • Conservation: the vintage split sums to the total balance.

Hub integration notes

Hub microsimulation impact

Pending: the hub runs it.

Not changed (known limitations)

  • Pub. 936 rounds the line 14 ratio to three decimals; the model uses the exact ratio, as before.
  • Pre-2018 home-equity debt (up to $100,000) is still not modeled.
  • Refinancing has no inputs. Refinanced grandfathered debt stays grandfathered up to the refinanced principal, for the term left on the original debt. For a balloon loan, it stays grandfathered for the first refinancing's term, up to 30 years (163(h)(3)(D)(iii)(II) and (iv); Pub. 936 p. 13). The origination-year inputs now say to enter the original debt's year for such a refinancing. Any amount borrowed beyond that, and the expiry of the term, are left to the person entering the inputs.
  • The October 13, 1987 and December 15, 2017 cutoffs are approximated by calendar year.
  • The (F)(i)(V) binding-contract exception is not modeled (§163(h)(3)(F)(i)(V); Pub. 936 p. 12). It needs contract facts the model doesn't have: a written contract signed before December 15, 2017 to close before 2018, and a purchase before April 1, 2018. So every 2018 loan takes the $750,000 limit.

axiom: TheAxiomFoundation/rulespec-us#1673 queued

The rest of 26 U.S.C. 163(h)(3) and Montana's mortgage interest line are already queued as TheAxiomFoundation/rulespec-us#1413 and #1512; #1673 covers the pre-October 13, 1987 grandfathered debt this PR adds (filed under d1142).

Independent review: round 2 APPROVE at 248d68cdb8; its two nits were corrected in 8929b0d709 (the 2017 YAML arithmetic comment and pre-2018 cap explanation). Confirmation at the merged head 9e5342c844145868e917f9a92d0c06e5185fb3d2 is pending with the hub. The PR remains a draft.

🤖 Generated with Claude Code

MaxGhenis and others added 4 commits October 9, 2026 08:35
Grandfathered debt incurred on or before October 13, 1987 has no dollar
limit and reduces the other limits (26 U.S.C. 163(h)(3)(D)). Debt incurred
on or before December 15, 2017 keeps the $1,000,000 limit and reduces the
$750,000 limit for later debt ((h)(3)(F)). Compute the qualified loan limit
as in IRS Publication 936 Table 1, so it no longer depends on which loan is
larger or which slot it is in (fixes #10011).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address review: the origination-year inputs now say a refinancing that
still counts as grandfathered debt takes the original year (163(h)(3)(D)
(iii)-(iv)); the Pub. 936 balloon-note case distinguishes 1986 from 1996;
the YAML header and property test note the exact ratio (Table 1 line 14
rounds to three decimals); the Simulation grid becomes two vectorized YAML
cases covering every filing status in 2017 and 2024; cap.yaml cites
(B)(ii) and (F)(i)(II), (IV) and says the reduction stops at zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Hand-off from the original session to the US + core hub, which adopted this PR. I'm not pushing to this branch. My last head was 8929b0d709, and it is contained in the hub's 9e5342c844. Below is context the PR body doesn't carry yet.

Expected microsim impact: zero on the certified default dataset. On 2026-10-09 I read the default h5 directly (populace_us_2024.h5, sha256 6496cc4393d4…, the DEFAULT_DATASET_SHA256 pin). It has 79,729 tax units:

  • 14,106 have first_home_mortgage_balance > 0, with origination years 2005–2021 only; none is 1987 or earlier, and 2017 is absent.
  • None has a second_home_mortgage_balance.
  • None has a positive balance with origination year 0.

So neither the grandfathered rule nor the two-loan vintage rule binds on any record. A non-zero diff in the hub's impact run would point to something other than these two rules. My own main-vs-branch microsims never got the heavy lock before that session ended; their driver is ~/reviews/us-hub/mortgage-grandfathered/evidence/microsim.sh, which uses lockrun labels mgd-m and mgd-b.

Reviews: GPT-6.1 Sol round 1 requested changes, and all five findings were fixed: ~/reviews/us-hub/mortgage-grandfathered/review-out.md. Round 2 APPROVED at 248d68cdb8, with two nits fixed in 8929b0d709 (comment-only): review-r2-out.md in the same folder.

Axiom: the held d1071 draft at ~/reviews/us-hub/state-head-fallbacks/axiom/mt-mortgage-163h3-issue.md now has the verbatim 163(h)(3)(D) text, Pub. 936 Table 1, a review_finding covering all three vintages, and companion cases F–M that mirror deductible_mortgage_interest_tax_unit.yaml. The pre-extension copy is .bak-2026-10-09-pre-grandfathered. The d1142 issue can reuse those cases rather than restating them.

Follow-ups owed:

  • Hawaii: spawned as chip task_1736c219. Apply Hawaii's pre-TCJA mortgage debt limits to the interest deduction #9950 (held under d1119) still caps grandfathered debt; once this lands, it should reuse _debt_by_vintage with Hawaii's caps.
  • AL/NC: the AL/NC session asked that this PR fix Mortgage debt cap loses grandfathered debt when equal balances put the newer loan first #10011 too, which it does. That session wasn't live on 10/09, so its note is at ~/reviews/us-hub/al-nc-mortgage/FROM-mortgage-grandfathered.md. Its branch al-nc-mortgage-interest-federal-cap depends on this PR. If its property-test oracle uses "$1M if year ≤ 2017 else $750K", that is only right for origination years ≥ 1988.
  • Pre-2018 home-equity allowance: a stalled worktree ~/PolicyEngine/_worktrees/pe-us-state-mortgage-limits (branch state-pre-tcja-mortgage-limits, uncommitted, no PR) adds 2017 YAML cases to deductible_mortgage_interest.yaml that expect the $100,000 home-equity allowance. Those cases will need to build on this PR's _qualified_loan_limit. This PR's own 2017 cases deliberately stay within the acquisition limit, so they don't depend on that allowance.

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mortgage debt cap loses grandfathered debt when equal balances put the newer loan first

1 participant