Skip to content

Count car loan interest on the return of the taxpayer who incurred it - #10406

Merged
MaxGhenis merged 9 commits into
mainfrom
auto-loan-interest-per-tax-unit
Oct 31, 2026
Merged

MaxGhenis merged 9 commits into
mainfrom
auto-loan-interest-per-tax-unit

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Status: GPT-6.1 Sol approved b611bfef with one documentation nit, the cost report (now below); the Opus 5.5 review approved an earlier head. That head includes main at f679eedd57, and all 33 CI checks pass at it. The impact below runs on that merged base for both years.

Problem

qualified_passenger_vehicle_loan_interest is a Household input, and auto_loan_interest_deduction read it with add(tax_unit, period, [...]). For a group-to-group add, policyengine-core (commons/formulas.py, for_each_variable, 3.33.12) does three things:

  • it projects the household value to every member;
  • it divides by the household's member count;
  • it sums those shares over the tax unit.

So a household's car loan interest was split across its tax units by member count. On main:

  • two single roommates sharing $5,000 of household interest each deducted $2,500;
  • a 3-person tax unit and a 1-person roommate unit with $8,000 deducted $6,000 and $2,000.

This only arises in households with more than one tax unit. On the default dataset those hold 31.3% of the $12.16bn of weighted 2024 input, and $1.64bn of it was split to tax units other than the household head's.

The law ties the interest to the borrower:

  • 26 U.S.C. 163(h)(4)(B)(i) counts interest "on indebtedness incurred by the taxpayer".
  • 26 CFR 1.163-16(d)(1) (T.D. 10054, 91 FR 57237) repeats it.
  • 1.163-16(h)(1) (91 FR 57240–41) limits the amount to $10,000 "per Federal tax return regardless of filing status".
  • The payee statement must tell the payor of record that they "may deduct QPVLI only to the extent the SPVL was incurred by, and the QPVLI was actually paid by, the payor of record" (§1.6050AA-1(h)(2)(iv), 91 FR 57243).

Splitting one loan over several returns also multiplied the per-return limit.

Fix

  • New Person input qualified_passenger_vehicle_loan_interest_person: interest on loans the person incurred. It counts on the return of the tax unit the person heads or is the spouse in. A tax-unit dependent's own loans don't reach the filer's return.
  • New TaxUnit variable tax_unit_qualified_passenger_vehicle_loan_interest: the head's and spouse's own interest, plus the household-level amount in full for the one tax unit holding the household head. When nobody carries the head flag (API, YAML), that is the oldest member; with several flagged heads, the oldest flagged one; ties go to the first listed (get_rank, as in is_hud_dependent).
  • auto_loan_interest_deduction reads the new variable. The limit and the phase-out don't change.
  • Unchanged: the Household input keeps its entity and name, so the app, the API and Microcosm's household column still work. In a household with one tax unit, the result doesn't change.

Why the household head

The data can't say who took out the loan. Microcosm draws the household amount from the 2022 SCF, whose debts belong to the "primary economic unit": "an economically dominant single individual or couple ... and all other individuals in the household who are financially interdependent with that individual or couple" (2022 SCF codebook, "Unit of analysis"). It then scales by Treasury's six million qualifying loans a year.

The household head is the CPS householder, "the person (or one of the people) in whose name the housing unit is owned or rented". It is the model's closest observed stand-in for that economic unit. The default build sets exactly one head per household, and 98.9% are CPS line 1. The same Microcosm SCF stage also names it as the carrier of its household asset draws: head_carry, condition is_household_head, in us/source_stages.json.

Microcosm picks the person whose traits drive the imputation by its own rule: the one adult, the man of a married couple, otherwise the oldest adult. In multi-tax-unit households with this interest, that person is in the head's tax unit in 70.5% of records; aligning the two is a data follow-up, noted on PolicyEngine/microcosm#1215. us-hub/source-conflicts/RULINGS.md logs the ruling (us:car-loan-interest-household-attribution).

Invariants

These hold for every household. Hypothesis and seeded mixed batches test them in tests/core/test_auto_loan_interest_attribution_invariants.py:

  1. Conservation: a household's tax units together hold the household amount plus every head's and spouse's own interest. Only a dependent's own interest drops out.
  2. One holder: the household amount lands on exactly one tax unit, the one holding the designated (or fallback) head.
  3. Locality: a tax unit's deduction depends only on its own filers' loans, and on the household amount when it holds the head. Zeroing other tax units' loans leaves it unchanged.
  4. Differential against the statute: the deduction is max(min(interest, 10,000) − 200 × ceil(max(MAGI − threshold, 0) / 1,000), 0), with a $200,000 threshold for a joint return and $100,000 otherwise. The test computes it from the statute's numbers.
  5. Bounds: 0 ≤ deduction ≤ min(tax unit interest, 10,000).

The tests mix households with and without flagged heads (the fallback runs beside flagged ones), include ties, adult dependents and married people filing separately, and list people out of household order. Eight mutants each fail the property tests and at least one YAML case (mutants/results-r4.txt, run in a scratch copy of the merged tree, where the unmutated code passes all of them):

  • the old member-count split;
  • counting dependents;
  • counting every adult's own loan;
  • giving every flagged head the amount;
  • a first-member fallback;
  • an oldest-filer fallback;
  • the youngest flagged head;
  • zero for separate filers.

The round-1 review found that the oldest-filer and every-adult mutants survived, and the Sol review that nothing covered separate filers. The tests now catch all three.

Tests

  • tax_unit_qualified_passenger_vehicle_loan_interest.yaml (15 cases):

    • head attribution and the oldest-member fallback;
    • a parent's unit beside a roommate (was 6,000/2,000, now 8,000/0);
    • each filer's own loan;
    • a dependent's loan, and adult dependents' loans (a student and a grandparent);
    • the fallback when the oldest member is a dependent (it follows that member to the claiming return, not to the oldest filer);
    • person plus household amounts;
    • the per-return limit on the holder (was 7,500 + 7,500, now 10,000);
    • several flagged heads;
    • a head claimed as a dependent;
    • one tax unit (unchanged);
    • married people filing separately (each return has its own $10,000 limit);
    • §1.163-16(h)(3) Example 1 (spouses' $6,000 and $5,000 loans on a joint return, deduction $10,000) and Example 2 (single, $7,000, MAGI $124,200, deduction $2,000).
  • in_auto_loan_interest_deduction.yaml: Indiana follows the federal deduction to the head's return.

  • Revert check: with main's add() deduction formula over the rest of this PR, 13 of the new cases fail: 12 of the 15 here, plus Indiana's (revert-check/old-formula-r3.txt). The 3 that still pass read only the new tax-unit variable (several flagged heads; a head claimed as a dependent) or cover one tax unit, which doesn't change.

  • Test cost: the new Python file builds no dataset; each Hypothesis example builds two small simulations. It joins the existing Rest Python core group (process core-2, shard 2); this PR adds no job or matrix entry.

    • Linux CI at b611bfef: 8.96 s of test time across its 4 tests, in a process that ran 504 tests in 1,077 s. CI doesn't report this group's peak memory, so this PR states none.
    • Local, a Mac at load 66–97 on core 3.34.2: 24.2 s elapsed including startup and model import, and 1.28 GB peak resident memory (1.25 GB footprint). The file is new, so the before figure is zero.
  • Other checks at b611bfef: the full suite passes in CI on core 3.34.2. The separate affected-file batch passed its first 6 of 26 targets at afc7741f; I stopped it there because that CI run covers the rest.

Consumers

  • Indiana (in_auto_loan_interest_deduction), South Carolina (sc_obbba_addback), Montana (mt_federal_obbba_subtraction), and the Idaho and Arizona subtraction lists read the federal auto_loan_interest_deduction per tax unit, so they follow the fix unchanged.
  • No other variable reads the Household input.

Impact

This is a real microsimulation on the default dataset (populace_us_2024, sha 6496cc43…). It compares main at the merge base f679eedd57 with this PR at afc7741f8b, for 2025 and 2026. The runs went one at a time under the hub heavy lock. The final head b611bfef differs from afc7741f8b only by the order of one test YAML's cases. All sums use the dataset's weights.

Output 2025 2026
auto_loan_interest_deduction (baseline $10.72bn; $10.84bn) −$118.1M −$127.5M
tax units whose deduction rises: the holder of the household amount 4,888 (9.12M weighted), +$1,448M 4,822 (9.07M), +$1,474M
tax units whose deduction falls: other filers 7,766 (13.37M weighted), −$1,566M 7,716 (13.33M), −$1,602M
taxable_income +$2.5M +$16.6M
income_tax −$10.9M −$9.6M
state_income_tax +$0.67M +$0.34M
household_net_income +$10.2M +$9.3M
household_benefits 0 0

Why the total deduction falls. The interest total doesn't change: every household amount still lands on exactly one return. The largest household amount in the data is $2,456, so the $10,000 limit never binds. The whole net fall therefore comes from the phase-out: more of the moved interest now sits on returns whose MAGI exceeds the $100,000 or $200,000 threshold.

Why federal income tax falls even though the deduction falls.

  • In 2025, taxable income rises $1,103M on 9.65M weighted returns and falls $1,101M on 7.11M, a net of +$2.5M.
  • Federal tax rises $132.8M on 9.43M weighted returns and falls $143.7M on 6.67M. That is about 12.0% and 13.1% of the taxable income moved.
  • So the returns that take the household amount face slightly higher rates than the roommates, adult children and others who lose their share.
  • taxable_income floors at zero (max_(0, agi - exemptions - deductions)). So the $1,566M of deduction taken off other returns raises taxable income by only $1,103M in all, and the $1,448M added to the holders lowers it by $1,101M.
  • 2026 follows the same pattern: taxable income +$1,147M and −$1,130M, federal tax +$136.2M and −$145.8M (about 11.9% and 12.9%).

States. State income tax moves mostly in Colorado (+$647K in 2025, +$648K in 2026), which starts from federal taxable income. Arizona is +$152K in 2025, when it subtracts the deduction. Indiana is −$108K in 2026, the year in_deductions lists its conformity deduction. Idaho is −$87K/−$89K. Every other state's change is under $70K. South Carolina's add-back falls $1.5M, but SC tax barely moves. Montana's subtraction moves less than $100 in total; it shifts between people.

Comparison files are in fixes/10406-impact/ on the hub host (compare.<year>.json, by_state.<year>.json), with copies in the evidence folder ~/reviews/auto-loan-attribution-2026-10-23/impact/r2/.

Axiom

axiom: us:statutes/26/163/h/4/B encoded-correct

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 23, 2026 09:22
26 U.S.C. 163(h)(4)(B)(i) counts interest on indebtedness incurred by the
taxpayer, and 26 CFR 1.163-16(h)(1) applies the $10,000 limit per return.
The household-level input was split across a household's tax units by
head count (core's group-to-group add()), so roommates and adult children
each deducted a share of one loan's interest.

Add a person-level input for each filer's own loans (head and spouse count;
a dependent's loans do not), and give a household-level amount in full to
the household head's tax unit (oldest member when no head is designated).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Multi-tax-unit households, each filer's own loans, a dependent's loan,
the per-return limit, several or no flagged heads, and 26 CFR
1.163-16(h)(3) Examples 1 and 2; Indiana follows the federal deduction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conservation of the household total, one holder per household amount,
locality across tax units, and a differential against the statute's
limit and phase-out.

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

Test an adult dependent's own loan and the oldest-member fallback when the
oldest member is a dependent, and draw adult dependents in the property
tests. Document the several-heads and dependent-head rules on the household
input, cite the payor statement legend (91 FR 57243) and point Cornell at
163(h)(4)(B)(i).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 23, 2026 14:51
MaxGhenis and others added 4 commits October 23, 2026 11:04
Keeps auto_loan_interest_deduction.yaml as on main, so this PR no longer
conflicts with #10395, which appends cases to that file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aper locality check

Cover married people filing separately in YAML and in the property
generator, keep one Federal Register reference per variable with the
pages in a comment, and reuse the first simulation in the locality check
so each property example builds two simulations instead of three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
So the case also fails a first-listed-member fallback, which only the
property tests caught.

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

Copy link
Copy Markdown
Contributor Author

Merge audit. GitHub still requires one approving review, which no one on this PR can give, so this merge uses --admin, pinned to the reviewed head.

  • Head: b611bfef45. gh pr checks exits 0 with 33 of 33 passing at this head. mergeable reads MERGEABLE. The PR is not a draft and has no CHANGES_REQUESTED review.
  • Independent reviews: GPT-6.1 Sol approved this head with nits against f679eedd57 and found no executable model defect; the PR body now carries its one nit, the cost report. Sol's first pass and the Opus 5.5 review approved earlier heads with nits, and commits since then resolve them. The verdicts live in ~/reviews/auto-loan-attribution-2026-10-23/review/.
  • Main since the reviewed base: two commits, Leave dependents' tips and overtime out of Georgia's HB463 exclusions #10405 (Georgia tip and overtime exclusions) and a version bump. Neither touches this PR's files, and git merge-tree reports no conflict.
  • Impact (real microsimulation, both years, base f679eedd57): the deduction falls $118.1M in 2025 and $127.5M in 2026; federal income tax falls $10.9M and $9.6M. The PR body has the full table.
  • Attribution ruling: logged as us:car-loan-interest-household-attribution in the hub's source-conflict rulings. The fix corrects the model to the statute and regulation, so it merges on these gates without a separate approval.
  • Axiom: us:statutes/26/163/h/4/B encoded-correct; Add companion cases for IRC §163(h)(4)(B)(i) and (C)(i): another person's car loan is not the taxpayer's interest; spouses' loans share one $10,000 limit (PE-US #10406) TheAxiomFoundation/rulespec-us#1886 queues the companion cases.

@MaxGhenis
MaxGhenis merged commit 3bce333 into main Oct 31, 2026
33 checks passed
@MaxGhenis
MaxGhenis deleted the auto-loan-interest-per-tax-unit branch October 31, 2026 17:40
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.

1 participant