Repository navigation
Apply federal mortgage debt limits by loan vintage, including grandfathered pre-1987 debt - #10025
Conversation
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>
|
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 Expected microsim impact: zero on the certified default dataset. On 2026-10-09 I read the default h5 directly (
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: GPT-6.1 Sol round 1 requested changes, and all five findings were fixed: Axiom: the held d1071 draft at Follow-ups owed:
🤖 Generated with Claude Code |
Preserve the removal of deprecated mortgage-interest inputs and person-level interest aggregation while retaining the vintage debt limits and origination-year documentation.
|
US + core hub merge audit, us#10025 at
|
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:
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'spr_mortgage_deductionalso reads it.Law
26 U.S.C. 163(h)(3)(D):
26 U.S.C. 163(h)(3)(F)(i), for taxable years beginning after December 31, 2017:
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 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 existingpre_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.pyongh/mainfdd70629054a24a0a53077c358d13b9a17d96b4dand this branch9e5342c844145868e917f9a92d0c06e5185fb3d2. Each of the 14 households uses one tax unit; both JSON outputs record the imported module. Dollar pairs show main → branch.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: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 usingtime.monotonic()for elapsed time andos.wait4child resource usage for RSS (Darwinru_maxrssis in bytes). These are local measurements, not Linux CI measurements.Branch verification
Measured verification at
b2ff589e37fd80eb1d48f07097cde6ba93282f78, after merginggh/mainde3815f4ccec7c698f4c6c6a5e024acd79f3579b(including #9605): 37 YAML cases across 3 files and 8 Python tests across 2 files passed.Each file ran in its own foreground process, sequentially, with
PYTHONPATHset to this checkout andOMP_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 MKL_NUM_THREADS=4 VECLIB_MAXIMUM_THREADS=4 NUMEXPR_NUM_THREADS=4 OMP_WAIT_POLICY=PASSIVE.core/test_mortgage_debt_limit_invariants.pycore/test_mt_mortgage_interest_federal_limit_properties.pypolicy/baseline/gov/irs/income/taxable_income/deductions/deductible_mortgage_interest_tax_unit.yamlpolicy/baseline/gov/irs/income/taxable_income/deductions/deductible_mortgage_interest.yamlpolicy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yamlPer-file counts come from each process's pytest summary. Every test file added or changed by this PR was run. The exact IRS
deductions/itemizing/subtree contains no mortgage-interest YAML references; the existing mortgage file lives one level above it. The invariant test is the only Python file referencingmortgage_interest_structureor the vintage parameters; the Montana property test was also run for state integration coverage.make formatpassed: 6,901 files left unchanged and all lint checks passed. No folder runs, full suites, or microsimulations were run; hub microsimulation verification remains pending.Both removed-input names were searched with
git grepacross the whole tree. Their only remaining references are historicalCHANGELOG.mdtext and the intentional legacy-dataset warning/ignored-column test. The merged formula uses only person-levelhome_mortgage_interest, existing tax-unit balances, and existing origination years; it restores neither deprecated input nor the structured-interest fallback.No partner test files were changed relative to current main.
Regression evidence on main
The branch's new cases were also run against
gh/mainfdd70629054a24a0a53077c358d13b9a17d96b4d:deductible_mortgage_interest_tax_unit.yaml;mt_grandfathered_case.yaml(2-file batch)deductible_mortgage_interest_tax_unit.yamlmt_grandfathered_case.yamlPer-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:
Hub integration notes
_limited_mortgage_balanceand_mortgage_balance_caphelpers and must migrate those imports.Not changed (known limitations)
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 in8929b0d709(the 2017 YAML arithmetic comment and pre-2018 cap explanation). Confirmation at the merged head9e5342c844145868e917f9a92d0c06e5185fb3d2is pending with the hub. The PR remains a draft.🤖 Generated with Claude Code
Hub microsimulation impact (current head)
A real microsimulation on the default dataset, main at the merge base
de3815f4ccagainst this PR's headb2ff589e37, for 2024 and 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.deductible_mortgage_interestinterest_deductionitemized_taxable_income_deductionsmt_income_taxal_income_taxnc_income_taxstate_income_taxincome_taxhousehold_net_incomehousehold_benefitsNo record in the default dataset changes in either year for any output above.
Comparison files are in
fixes/10025-impact/on the hub host.