Skip to content

Count Form 4835 farm rent in net investment income, and limit its losses under section 461(l) - #10076

Merged
MaxGhenis merged 15 commits into
mainfrom
fix-nii-farm-rent
Nov 5, 2026
Merged

MaxGhenis merged 15 commits into
mainfrom
fix-nii-farm-rent

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10075.

What changes

  • net_investment_income, the base of the net investment income tax (NIIT) and Minnesota's NIIT, now counts farm_rent_income, the Form 4835 net farm rental income or loss that filers carry to Schedule E line 40. AGI and NIIT MAGI already counted it, so on main a farm landlord's rent raised MAGI but never NII, and a Form 4835 loss never reduced NII.
  • NII now takes rental, farm rental, estate and passive pass-through losses only as far as section 461(l) allows them. A new variable, net_investment_income_excess_business_loss, computes the part of the section 461(l) excess business loss that falls on those losses, and NII adds it back.

Why (sources)

  • IRC 1411. In section 1411(c)(1)(A)(i), Congress puts gross income from rents in NII unless the filer derives it in the ordinary course of a trade or business that section 1411(c)(2) doesn't describe. Section 1411(c)(2) covers a passive activity under section 469 and a trading business, and section 1411(c)(6) excludes only items counted in self-employment income. https://www.law.cornell.edu/uscode/text/26/1411#c
  • Form 4835. The IRS limits the form to a rental activity under the passive activity rules, and its title reads "Income Not Subject to Self-Employment Tax". Filers carry its net amount to Schedule E line 40, then line 41, then Schedule 1 line 5. https://www.irs.gov/pub/irs-prior/f4835--2025.pdf
  • Form 8960 line 4a takes Schedule 1 lines 3, 5 and 6, so it includes the Form 4835 amount. Line 4b takes back out only rental income from a nonpassive activity that rises to a section 162 trade or business. The model has no line 4b input, so it counts all of it, as it does for rental_income. 2025 Instructions for Form 8960: https://www.irs.gov/pub/irs-prior/i8960--2025.pdf#page=8
  • Self-employment. The same instructions say on page 1 that income excepted from net earnings from self-employment under sections 1402(a)(1) through (17) "is subject to NIIT" (https://www.irs.gov/pub/irs-prior/i8960--2025.pdf#page=1). The tip under line 4a says income "not subject to self-employment tax under section 1401(b)" goes on line 4b as a negative, which would remove this rent. In 1411(c)(6), Congress excludes income counted in self-employment income, and the IRS repeats that exclusion in a line 4b bullet, so the model keeps the rent in NII. source_conflicts/candidates.yaml records this as us:niit-form-8960-line-4a-tip.
  • Section 461(l) in NII. 1411(c)(1)(B) allows only the deductions allowed by subtitle A. Treas. Reg. 1.1411-4(f)(2)(v), Example 1, applies the at-risk and passive loss limits first and lets NII take only the deductions left (https://www.ecfr.gov/current/title-26/section-1.1411-4#p-1.1411-4(f)). Treasury doesn't mention section 461(l) in that regulation. AGI already caps business losses at business income plus the threshold ($313,000, or $626,000 joint, in 2025) through limited_business_loss. On main, NII added the same losses in full: a single filer with $300,000 of wages, $500,000 of interest and a $600,000 rental loss had NII of −$100,000 and no NIIT. The limit gives NII of $187,000 and NIIT of $7,106. Form 461 line 16 puts the add-back on Schedule 1 line 8p, which Form 8960 line 4a doesn't take. Line 7 names line 8z but not line 8p, and it also takes modifications the form doesn't specify. The model follows the statute and adds the amount back, and source_conflicts/candidates.yaml records this as us:niit-excess-business-loss-addback.
  • Minnesota. Minn. Stat. 290.033(a) uses the section 1411(c) definition, excludes the net gain on class 2a property and adjusts for opportunity zone gains. Neither exception reaches rent or section 461(l). https://www.revisor.mn.gov/statutes/cite/290.033

The change

  • gov.irs.investment.income.sources adds farm_rent_income after rental_income, and net_investment_income_excess_business_loss. NII still sums sources over the head and spouse only (tax_unit_non_dep_add), so a dependent's farm rent stays on the dependent's own Form 8960.
  • net_investment_income_excess_business_loss takes excess_business_loss (Form 461 line 16, on main since Add the federal excess business loss back to Massachusetts income from 2022 #9908) times the share of gross business losses that NII counts. NII counts Schedule E rental, Form 4835 and Schedule E Part III estate and trust losses, and the passive part of partnership and S corporation losses. The variable adds limited_business_loss and excess_business_loss to reach gross business losses, which counts each Schedule C category's loss on its own, as main has since Offset SSTB and non-SSTB Schedule C results in federal AGI #9995.
  • farm_rent_income gains documentation and a Form 4835 reference.
  • estate_income_net_investment_income_adjustment documentation now names the only positive code H case, a section 1291 fund, CFC or QEF distribution that is NII but not taxable income, and cites the 2025 Form 1041 instructions (https://www.irs.gov/pub/irs-prior/i1041--2025.pdf#page=38).

This PR leaves limited_business_loss and excess_business_loss as main has them.

Decisions

  • Farm rent counts signed, like rental_income. The model has no Form 8960 line 4b input for nonpassive rentals, so it counts all Schedule E Part I rents and all Form 4835 amounts. A line 4b rental input would serve both, and the EITC passive basket needs the same split (EITC investment-income test after #9572: dependents' amounts, passive income that is also earned income, and farm rental #9635), so a later PR can add it.
  • No section 469 limit, as in AGI. The model doesn't apply the passive loss limits. AGI already takes the whole Form 4835 loss through loss_ald, and NII now uses the same amount.
  • Split of the excess between NII and other losses: provisional, proportional. When NII losses are the only business losses, the whole excess falls on them, and every reading agrees. When the filer also has non-NII losses (Schedule C, Schedule F, nonpassive pass-through), no statute, regulation or form instruction says how to split the excess. This PR splits it in proportion to the two kinds of loss, and the code, tests and documentation say "provisional". source_conflicts/candidates.yaml lists this as the open question us:niit-excess-business-loss-split. Taking NII losses first or non-NII losses first would each change one line in investment_share.
  • Code H doesn't change which estate losses count. A Schedule E Part III loss enters Form 8960 line 4a in full. The Schedule K-1 (Form 1041) box 14 code H adjustment is a separate line 7 item, so a positive code H never takes the loss out of NII.
  • Netted partnership amounts. The share reads each person's partnership_s_corp_income net, as limited_business_loss does, so a passive loss and nonpassive income of one person offset each other before the split.
  • One year at a time. The model carries no disallowed excess into later years, in AGI or NII.

Worked examples (hand-computed from the Form 8960 lines)

NIIT = 3.8% × min(max(0, NII), max(0, MAGI − threshold)); thresholds $200,000 single and $250,000 joint.

Case Year NII MAGI NIIT main
Single, wages 190,000, Form 4835 30,000 2025 30,000 220,000 760 0
Single, wages 250,000, Schedule E 40,000, Form 4835 −10,000 2025 30,000 280,000 1,140 1,520
Joint, head wages 240,000, spouse Form 4835 35,000 2024 35,000 275,000 950 0
Parent wages 210,000 + interest 5,000; dependent child Form 4835 10,000 2025 5,000 215,000 190 190
Minnesota, interest 800,000 + Form 4835 300,000 2025 1,100,000 MN NIIT 1% × 100,000 = 1,000 0
Single, wages 300,000, interest 500,000, Form 4835 −600,000 (allowed 313,000) 2025 500,000 − 600,000 + 287,000 = 187,000 487,000 7,106 10,906
Same with a Schedule E rental loss −600,000 2025 187,000 487,000 7,106 0
Wages 600,000, interest 200,000, Schedule C −300,000, Form 4835 −300,000 (share ½ of 287,000) 2025 43,500 487,000 1,653 7,600
Wages 300,000, interest 500,000, Schedule E Part III −600,000, code H +600,000 (MAGI adds the positive code H) 2025 787,000 1,087,000 29,906 19,000

main leaves Form 4835 amounts out of NII. This PR therefore raises NIIT for Form 4835 income and lowers it for a Form 4835 loss. It also raises NIIT for Schedule E rental, estate and passive losses beyond the section 461(l) limit.

The add-back variable's own cases include one with a non-SSTB Schedule C profit of 100,000, an SSTB loss of 1,000,000 and a rental loss of 200,000. Gross losses of 1,200,000 exceed the 413,000 limit by 787,000, so the add-back comes to 787,000 × 200,000 / 1,200,000 = 131,166.67. Netting the two Schedule C categories first would give 143,090.91.

Snapshot update: PolicyBench scenario 120

policybench_ira.yaml scenario 120 (single, TY2026, Connecticut) has farm_rent_income −2,446.59. Its federal target comes from the policyengine-us 2.37.2 evidence value (#10032), not an outside oracle. NII falls from 4,300 to 1,853.41, and NIIT from 163.40 to 70.43 (3.8% × 2,446.59 = 92.97). AGI already included the loss, so the MAGI limb doesn't bind. The Connecticut target stays the same. A comment in the YAML gives the old value and the reason.

Invariants

These hold for every input up to float32 rounding (a few dollars on nine-figure losses):

  1. Dollar for dollar while section 461(l) doesn't bind. NII moves dollar for dollar with a filer's farm_rent_income, of either sign. When the limit binds, NII takes only the allowed part of the loss, through the add-back.
  2. Dependents. A dependent's farm rent never changes the filer's NII, AGI, MAGI or NIIT.
  3. No dependents. For units without dependents, NII equals the all-member formula.
  4. NIIT bounds. 0 ≤ NIIT = 3.8% × min(max(0, NII), max(0, MAGI − threshold)).
  5. Add-back bounds. 0 ≤ net_investment_income_excess_business_loss ≤ min(excess_business_loss, NII losses), with equality to the excess when NII losses are the only business losses.
  6. Section 461(l) accounting. Whenever the excess is positive, limited_business_loss + excess_business_loss equals gross business losses.

The existing property tests in test_dependent_net_investment_income_invariants.py and test_estate_income_invariants.py check invariants 1 to 5 for rental, Form 4835, estate and partnership losses. They compare a seeded random population plus edge rows, one vectorized simulation per scenario, with an independent numpy reference from leaf inputs. They draw farm rent signed for every member, dependents included. Edge rows make section 461(l) bind for a single Form 4835 loss, a joint return with a nonpassive partnership loss, a dependent with a large loss, and an estate loss with a positive code H adjustment. YAML cases check the Schedule C category split, the cap on the passive loss, and invariant 6 on both sides of the threshold.

Tests

  • YAML:
    • net_investment_income_tax.yaml and mn_niit.yaml hold the worked examples, and mn_niit.yaml adds a case with the section 461(l) add-back;
    • excess_business_loss.yaml holds main's 4 cases and this PR's 5;
    • net_investment_income_excess_business_loss.yaml has 9 cases, including the Schedule C category, passive-cap and dependent cases.
  • Python: test_dependent_net_investment_income_invariants.py adds farm_rent_income to its signed inputs and reference sum. test_estate_income_invariants.py expects the add-back on its cap-binding rows and gains the code H edge row.
  • Mutants, each run on its own and then restored:
    • netting code H against the estate loss fails 2 estate property tests;
    • dividing by net_business_loss in place of gross losses fails the Schedule C category case;
    • dropping farm_rent_income from the sources fails 5 of 32 NIIT cases;
    • dropping not_dependent from the NII losses fails 6 dependent property tests;
    • removing the cap on the passive loss fails the passive-cap case;
    • having mn_niit ignore the add-back fails the Minnesota add-back case.
  • CI: the latest Pull request run, at 0cc5041957 (https://github.com/PolicyEngine/policyengine-us/actions/runs/42071455256), concluded success: all 36 jobs passed.

CI cost: the property tests keep their number of simulations and gain four edge rows, one input column, two output variables and one test function. The PR also adds 24 YAML cases, each with its own simulation. We ran no before-and-after timing.

Impact

We estimate that the net investment income tax rises $145.1 million in 2026 on the default dataset, comparing main at the merge base 0315452583 with this branch at 6a95037abe, before its merge of main at e53b426082. Each row groups tax units by the sign of their Form 4835 amount.

Tax units Records with NII changed (weighted) NII change Records with NIIT changed (weighted) NIIT change
Positive Form 4835 amount 254 (368,238) +$8.32 billion 48 (79,971) +$153.7 million
Form 4835 loss 203 (354,244) −$4.06 billion 29 (39,482) −$8.6 million
No Form 4835 amount; section 461(l) add-back on rental losses 11 (24,092) +$339 million 2 (0) +$136
All 468 (746,574) +$4.60 billion 79 (119,453) +$145.1 million

Household net income falls by the same $145.1 million. AGI, mn_niit and benefits don't move, and state income tax rises $0.04 on one record.

impact: we estimate that the net investment income tax rises $145.1 million in 2026, up $153.7 million on positive Form 4835 amounts and down $8.6 million on Form 4835 losses. Household net income falls by the same amount, and AGI, mn_niit and benefits don't move.
axiom: TheAxiomFoundation/rulespec-us#1643 queued

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 10, 2026 18:36
Form 8960 line 4a takes Schedule 1 line 5, which includes Schedule E line 40
(Form 4835 net farm rental income or loss). Form 4835 is only for a rental
activity under the passive activity rules, so it is rent under
26 USC 1411(c)(1)(A)(i). Add farm_rent_income to the NII sources, with
worked Form 8960 tests and the dependent invariants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its federal target was the policyengine-us 2.37.2 evidence value. The
-2,446.59 Form 4835 loss now reduces net investment income as it already
reduced AGI, so NIIT falls by 3.8% x 2,446.59.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 11, 2026 11:34
Section 1411(c)(1)(B) allows only deductions allowed by chapter 1, and
section 461(l) disallows business losses beyond business income plus the
threshold. NII added Schedule E rental, Form 4835, estate and passive
pass-through losses in full while AGI capped them. Add excess_business_loss
(Form 461 line 16) and net_investment_income_excess_business_loss, the part
of it that falls on losses NII counts (all of it when they are the only
business losses, a proportional share otherwise), and add it back in NII.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis changed the title Count Form 4835 farm rental income in net investment income (Form 8960 line 4a) Count Form 4835 farm rent in net investment income, and limit its losses under section 461(l) Oct 11, 2026
MaxGhenis and others added 3 commits October 11, 2026 13:35
The NII-follows-estate-income property now subtracts the estate losses that
section 461(l) disallows, computed independently from the leaf inputs; the
two edge rows with a 400,000 estate loss exercise it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Schedule K-1 (Form 1041) box 14 code H adjustment is positive only for
section 1291 fund, CFC and QEF distributions that are NII but not taxable
income (2025 Form 1041 instructions, Beneficiary reporting), so it is
separate line 7 income and never takes a Part III loss out of NII. Count the
whole Part III loss as an NII loss, add the review's counterexample (NIIT
29,906), and label the mixed-loss split provisional.

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

The authoring session hands this PR to the US + core hub, which adopted it and sent a cloud session (brief 888) to merge main.

  • Head: d1dec4b, which merges main at 065d0e9. main has since reached 4e71481, 510 commits on, and the PR conflicts with it in three files.
  • Review: APPROVE at d1dec4b (~/reviews/niit-farm-rent-2026-10/review/review-r4b.md, with earlier rounds beside it). The resolution below changes §461(l) code that review never saw, so the merged head needs a delta review before landing.
  • Passed: all 26 checks at d1dec4b.
  • Pending: the merge, CI on the merged head and the delta review.
  • Depends on: nothing.
  • Holds: none. d1363 stays open and the proportional split stays as built. A ruling changes one line, investment_share.

Resolving the conflicts

#9908 added excess_business_loss and net_business_loss to main. #9995 made limited_business_loss deduct each Schedule C category's loss on its own. This PR's helper, section_461l_business_income_and_loss, still reads the net total_self_employment_income, so drop it:

  1. limited_business_loss.py: take main's file.

  2. excess_business_loss.py: take main's file. Its max(0, net_business_loss - threshold) equals this PR's business_loss - limited_business_loss.

  3. excess_business_loss.yaml: keep main's 4 cases and this PR's 5. All 5 hold under main's formula, worked by hand.

  4. net_investment_income_excess_business_loss.py: remove the helper import and take the gross loss from the two variables:

    excess = tax_unit("excess_business_loss", period)
    # Allowed plus disallowed losses give the gross business losses.
    business_loss = tax_unit("limited_business_loss", period) + excess
    # investment_share keeps its current lines.
    return excess * investment_share
  5. PR body: update "Changes" and "Decisions", which still describe the helper and a new excess_business_loss.

Keeping this PR's limited_business_loss.py undoes #9995, and main's tests catch that. Keeping the old helper only for the add-back misstates the share when a filer has a profit in one Schedule C category and a loss in the other. Nothing in this PR sets sstb_self_employment_income, so its tests wouldn't catch that. So add this case to net_investment_income_excess_business_loss.yaml, with absolute_error_margin: 0.1:

Single, 2025, self_employment_income 100,000, sstb_self_employment_income −1,000,000, rental_income −200,000. Gross losses reach 1,200,000 against a 413,000 limit, which leaves an excess of 787,000 and an add-back of 787,000 × 200,000 / 1,200,000 = 131,166.67. The old helper gives 143,090.91.

~/reviews/niit-farm-rent-2026-10/handoff/resolution_differential.py checks these identities on numpy stand-ins for 1,000,000 tax units and doesn't import the model (output in resolution_differential.txt). In float64, main's excess matches this PR's, and limited plus excess matches gross loss, on every row. In float32, limited plus excess strays from gross loss by at most 2.5e-7 of its size, and no row breaks the invariant tests' tolerance (rtol=1e-6, atol=0.01).

This session ran no model tests on the resolution, to leave memory free for the Microcosm release booked on this host (microcosm-us-release-2026-11). CI gives the first real run.

Owed from review round 4

None of these blocks landing, and all three fit in the merge:

  1. test_estate_income_invariants.py: add the edge row {**edge, "head_wages": 600_000.0, "head_estate": -400_000.0, "head_adjustment": 400_000.0, "cap_binds": True}. No row today pairs a binding cap with code H, so the old code H netting would still pass the property test.
  2. test_dependent_net_investment_income_invariants.py:300: change "The NII share is proportional" to "provisionally proportional".
  3. estate_income_net_investment_income_adjustment.py:14: replace "can be positive" with the only positive case, a section 1291 fund, CFC or QEF distribution, and cite https://www.irs.gov/pub/irs-prior/i1041--2025.pdf#page=38. A user who enters +600,000 to back out a non-NII estate loss gets NII of 287,000 and not the 0 they meant.

Other checks on current main

  • No partner test names farm_rent_income or net_investment_income.
  • main added an NII reader, s770_nii_base (Add S.770 section 8 investment and active income tax options #10402). S.770's active residual takes only partnership and S corporation income, so farm rent can't count twice, and no S.770 test sets rents.
  • The body's microsim table names its commits (12ea587, 1def0db) and dataset. Nobody has rerun it on current main.
  • The authoring session wrote this PR without loading policyengine-model-development, which the worktree hook requires first. It loaded the skill on 2026-11-02 and read the new variables and tests against its references, and the logic follows them. Test names, person names and documentation fields follow the neighbouring files where those differ from the skill.

Follow-ups beyond this PR stay in ~/reviews/us-hub/FOLLOWUPS.md, and the Axiom line stays TheAxiomFoundation/rulespec-us#1643 queued.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

Main gained excess_business_loss and net_business_loss (#9908) and
deducts each Schedule C category's loss on its own in
limited_business_loss (#9995). Take main's versions of both files and
keep main's four excess_business_loss cases beside this branch's five.
The NII add-back drops this branch's section 461(l) helper, which netted
the two Schedule C categories, and takes the gross loss as allowed plus
disallowed. A new case with an SSTB loss and a non-SSTB profit shows the
difference: 131,166.67, not 143,090.91.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add an edge row pairing a binding section 461(l) cap with a positive
code H adjustment, so netting code H against the estate loss fails the
property test (it passed before). Name the only positive code H case in
the adjustment's documentation, and call the NII share provisionally
proportional in the reference helper's docstring.

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

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub, continuing an earlier session's work on this brief (that session pushed nothing). I'll post a hand-off comment here when it's ready.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: 4e5da2d96ba25aeddca4f2f88a1a10d7e8d20b2a, which merges origin/main at 0315452583. This continues an earlier session on this brief, which pushed nothing; both commits here are this session's.

What was asked and how it was resolved

Readings (judgment calls; I couldn't fetch the sources)

The network policy refuses irs.gov, web.archive.org, law.cornell.edu, govinfo, uscode.house.gov and ecfr, so I couldn't read IRC 1411(c) or the Form 8960 line 4a and 4b instructions myself.

The PR rests on these readings from the authoring session's quotes (issue #10075):

  • Form 4835 rent is section 1411(c)(1)(A)(i) rent from a passive activity.
  • Line 4a takes Schedule 1 lines 3, 5 and 6.
  • Line 4b removes only nonpassive section 162 rentals.

The hub should check them against the sources before landing. The add-back follows the statute (1411(c)(1)(B)) over Form 461's routing to Schedule 1 line 8p, which line 4a doesn't take. That is a statute-versus-form choice that CONTRIBUTING.md#source-conflicts would have recorded in the register. I added no # Source conflict marker or register entry, because I couldn't read either text to confirm the conflict.

Tests run (this head, one file per process)

File Result
excess_business_loss.yaml 9 passed
net_investment_income_excess_business_loss.yaml 7 passed
net_investment_income_tax.yaml 32 passed
mn_niit.yaml 3 passed
policybench_ira.yaml 8 passed
estate_net_investment_income.yaml 7 passed
niit_magi.yaml 8 passed
loss_ald.yaml 11 passed
dependent_losses.yaml 11 passed
dependent_net_investment_income.yaml 15 passed
ma_excess_business_loss_adjustment.yaml (from #9908) 9 passed
test_estate_income_invariants.py 22 passed
test_dependent_net_investment_income_invariants.py 14 passed
test_loss_ald_extraction_parity.py 3 passed
test_dependent_losses_agi_invariants.py 12 passed

test_az_property_tax_credit_income.py, named in the old body, isn't in the tree at this head.

Hand computations

NIIT = 3.8% × min(NII, MAGI − threshold).

  • Farm rent: 760 (3.8% × 20,000), 1,140 (× 30,000), 950 (× 25,000) and 190 (× 5,000).
  • Minnesota: 1% × (1,100,000 − 1,000,000) = 1,000.
  • Section 461(l): NII 500,000 − 600,000 + 287,000 = 187,000 gives 7,106.
  • Mixed: 200,000 − 300,000 + ½ × 287,000 = 43,500 gives 1,653.
  • Code H: 500,000 − 600,000 + 287,000 + 600,000 = 787,000 gives 29,906.
  • Schedule C categories: 1,200,000 − (100,000 + 313,000) = 787,000, and 787,000 × 200,000 / 1,200,000 = 131,166.67.

All match the YAML. The 313,000 threshold is the repo's 2025 gov.irs.ald.loss.max single value.

Mutants (each in its own process, restored after)

  • Estate loss netted with positive code H in the add-back: 2 of 22 estate property tests fail, so the new edge row catches it.
  • Denominator net_business_loss in place of limited + excess: the Schedule C category case fails (1 of 7).
  • farm_rent_income removed from gov.irs.investment.income.sources: 5 of 32 NIIT cases fail.

Invariants and property tests

The body states six invariants. The existing seeded property tests cover invariants 1–5, and the YAML covers invariant 6 at the boundary. I added no Hypothesis file, because each example would build a model. The seeded vectorized population already samples signed farm rent for every member, and its edge rows make section 461(l) bind.

CI

Pull request run https://github.com/PolicyEngine/policyengine-us/actions/runs/41969669487 at 4e5da2d96b: success, 36 of 36 jobs passed (completed 05:50Z on 11/05). This is the first CI run on the resolved section 461(l) code, which still needs the hub's delta review.

Other judgment calls

  • The axiom: line changed from TheAxiomFoundation/rulespec-us#1643 queued to the line the brief gives. The issue Relax dependency pins #1643 link is noted here so the hub can keep it.
  • I changed no partner test: none sets farm rent, NII or SSTB income.

Proposed follow-up

Record the section 461(l) NII add-back as a statute-over-form choice. Form 461 routes the excess to Schedule 1 line 8p, which Form 8960 line 4a doesn't take, and the model adds it back under 1411(c)(1)(B). Read both texts, add the register entry, and mark net_investment_income_excess_business_loss.py.


Generated by Claude Code

…icts

Point the add-back entry at the statute reading this PR implements, with the
Form 461 and Form 8960 lines it departs from. Add the open question of how to
split the excess, and the 2025 line 4a tip that the statute overrides.

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

Copy link
Copy Markdown
Contributor Author

Hub review in progress

Review head: 5ea9e6f718ced1f813a801854945a8df47ca104f (unchanged since the brief). This comment holds interim findings; the final review follows as a separate comment.

So far:

  • limited_business_loss.py, excess_business_loss.py and net_business_loss.py are byte-identical to origin/main (961d0ebb39). The PR merges cleanly with current main.
  • The changed YAML files pass at the head: excess_business_loss.yaml (9), net_investment_income_excess_business_loss.yaml (7), net_investment_income_tax.yaml (32), mn_niit.yaml (3) and policybench_ira.yaml (8). So do the Python files test_dependent_net_investment_income_invariants.py (14), test_estate_income_invariants.py (22) and test_source_conflicts.py (103).
  • Hand computations agree with the model at the head for all body rows and the 131,166.67 case.
  • Body table, main column, is wrong on two rows. Main ignores farm_rent_income in NII, so the Form 4835 −600,000 row gives NIIT 10,906 on main, not 0. The Schedule C −300,000 / Form 4835 −300,000 row gives 7,600 on main, not 0. Both are computed with the same situation at origin/main. Details follow in the final review.
  • Property-test draws never set self_employment_income/sstb_self_employment_income, and always draw passive income as 0, ½ or all of the partnership amount. So the Schedule C category split and the min_ in passive_loss aren't reached by the property tests.
  • I couldn't reach any primary source: irs.gov, law.cornell.edu, govinfo, uscode.house.gov, ecfr.gov and revisor.mn.gov are all refused by this VM's network policy.

Mutants and CI still running.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: REQUEST_CHANGES
5ea9e6f
METHODOLOGY_CHOICE: yes

Independent review, round 5, at the head above. The head hasn't moved since 5ea9e6f718. Main is now at 961d0ebb39, two commits past the merge base 0315452583. Neither touches a file this PR changes or the section 461(l) variables, and git merge-tree merges the PR into current main with no conflict.

Summary. Every value I computed by hand matches the model at this head: the body's table, the 131,166.67 case, PolicyBench 120's −92.97, and nine more situations of my own (dependents, a joint return, a capital loss, Minnesota, a passive loss inside a netted partnership amount, code H). limited + excess = gross loss holds by construction. The share's numerator and denominator count the same people and the same netted categories, so the share can't exceed 1 in exact arithmetic. I found no wrong result in the code. The changes I request are to the body, where the main column is false on two rows, and to the tests. One guard, the min_ on the passive loss, is untested: deleting it passes every test and can more than double the add-back. Minnesota's add-back path is also untested.

I couldn't reach any primary source. This VM's network policy refuses irs.gov, law.cornell.edu, govinfo.gov, uscode.house.gov, ecfr.gov and revisor.mn.gov, through both the proxy (HTTP 403 on CONNECT) and the fetch tool (DNS refused). So every legal reading below is unverified, and I quote no law.


Findings

P1. The body's main column is false on two rows

main leaves farm_rent_income out of NII entirely. A Form 4835 loss therefore never reduces NII on main, and NIIT there is the tax on the interest alone. It is not 0.

Row Body says main main actually gives Head gives
Single, wages 300,000, interest 500,000, Form 4835 −600,000 0 10,906 (NII 500,000; 3.8% × min(500,000, 287,000)) 7,106
Wages 600,000, interest 200,000, Schedule C −300,000, Form 4835 −300,000 0 7,600 (NII 200,000; 3.8% × 200,000) 1,653

Command: the same 2025 single-filer situation, run through Simulation(situation=...) at origin/main (a second worktree, with PYTHONPATH pointing at it) and at the head:

main  B 4835 -600k:  NII 500000.0  NIIT 10906.0
head  B 4835 -600k:  NII 187000.0  NIIT 7106.0
main  D SchC/4835:   NII 200000.0  NIIT 7600.0
head  D SchC/4835:   NII 43500.0   NIIT 1653.0

This matters beyond the table. For a Form 4835 loss, this PR lowers NIIT against main, because main ignored the loss, and it raises NIIT only for Schedule E, estate and passive losses. A reader of the table would expect the opposite sign for farm landlords with losses, and that changes what the impact comparison should look for.

Replacement for those two cells: 10,906 and 7,600. Add under the table: "On main, NII leaves out Form 4835 amounts entirely, so a Form 4835 loss never reduces NII there. For Form 4835 losses this PR lowers NIIT against main; for Schedule E rental, estate and passive losses beyond the section 461(l) limit it raises it."

P2. The min_ on the passive loss is untested; deleting it passes every test

net_investment_income_excess_business_loss.py:44-47. Mutant (b) replaces the min_(...) with max_(0, -person("passive_partnership_s_corp_income", period)). It passes all 7 add-back cases, all 32 NIIT cases, MN 3, excess_business_loss.yaml 9, PolicyBench 8, dependent invariants 14 and estate invariants 22.

The property draws can't reach it. test_dependent_net_investment_income_invariants.py:118-121 draws passive income as partnership × {0, 0.5, 1}, so it never exceeds the netted partnership loss and never has the opposite sign.

Failure scenario: the head has passive −1,000,000 inside partnership_s_corp_income −100,000 (nonpassive +900,000), plus a rental loss of −600,000. Gross losses are 700,000 and the excess is 387,000. Without the min_, the numerator becomes 1,600,000 and the share 2.29, so the add-back is 884,571.43, more than the excess (invariant 5 broken). At the head the model gives:

G passive -1M, total -100k, rental -600k: lim 313000.0 exc 387000.0 add 387000.0 NII -313000.0

Add this case to net_investment_income_excess_business_loss.yaml:

- name: A passive loss counts only as far as the netted partnership loss
  # Passive -1,000,000 within partnership/S corporation income of -100,000
  # (nonpassive +900,000), and a rental loss of 600,000. Section 461(l) sees
  # losses of 100,000 + 600,000 = 700,000 and an excess of 700,000 - 313,000
  # = 387,000. The NII losses are 600,000 + min(1,000,000, 100,000) = 700,000,
  # so the whole excess is the add-back. Without the cap the share would be
  # 1,600,000 / 700,000 and the add-back 884,571.43.
  period: 2025
  input:
    people:
      person1:
        age: 50
        is_tax_unit_head: true
        employment_income: 300_000
        taxable_interest_income: 900_000
        partnership_s_corp_income: -100_000
        passive_partnership_s_corp_income: -1_000_000
        rental_income: -600_000
    tax_units:
      tax_unit:
        members: [person1]
    households:
      household:
        members: [person1]
        state_code: TX
  output:
    excess_business_loss: 387_000
    net_investment_income_excess_business_loss: 387_000
    net_investment_income: -313_000

At this head the case passes, and under mutant (b) it fails: net_investment_income_excess_business_loss@2025: [884571.44] differs from 387000.0. Optionally, also draw passive income independently of the partnership amount in _draw_amounts, so the property test reaches the cap.

P2. Minnesota's add-back path is untested

Mutant (c) changes mn_niit.py:27 to net_investment_income - net_investment_income_excess_business_loss (Minnesota ignores the add-back), and it passes every file listed above. mn_niit.yaml's only new case has a positive Form 4835 amount, so section 461(l) never binds. Add:

- name: Minnesota net investment income takes the section 461(l) add-back
  # Interest 2,000,000 and a Form 4835 loss of 600,000. Section 461(l) allows
  # 313,000 of the loss: NII 2,000,000 - 600,000 + 287,000 = 1,687,000;
  # 1% x (1,687,000 - 1,000,000) = 6,870. Without the add-back: 4,000.
  period: 2025
  absolute_error_margin: 0.01
  input:
    people:
      person1:
        age: 60
        is_tax_unit_head: true
        taxable_interest_income: 2_000_000
        farm_rent_income: -600_000
    tax_units:
      tax_unit:
        members: [person1]
    households:
      household:
        members: [person1]
        state_code: MN
  output:
    net_investment_income: 1_687_000
    mn_niit: 6_870

The case passes at the head, and under mutant (c) it fails: mn_niit@2025: [4000.] differs from 6870.0. On main, mn_niit is 10,000, because NII is 2,000,000 there.

Whether Minnesota conforms to section 461(l) for its NIIT base is a reading I couldn't check (revisor.mn.gov is unreachable). The hub should confirm it before the case asserts it.

P3. A dependent's loss in the add-back is caught only by the property tests

Mutant (a) drops not_dependent * at net_investment_income_excess_business_loss.py:49. It passes all YAML and fails 6 property tests in test_dependent_net_investment_income_invariants.py (test_dependents_income_stays_off_filer_form_8960, test_net_investment_income_matches_reference and test_section_461l_addback_bounds, in 2024 and 2025). That is enough to kill it. A YAML case would still document the rule next to the variable. The model gives, at the head, a filer with wages 300,000, interest 500,000 and rental −600,000, plus a dependent with Form 4835 −1,000,000: excess 287,000, add-back 287,000, NII 187,000, NIIT 7,106 (F dependent 4835 -1M in my probe).

P3. Invariants 5 and 6 hold only up to float32 rounding

The body says "These hold for every input". limited_business_loss + excess_business_loss is summed in a different order from the numerator, so in float32 the share can land above 1. Witness: a single filer in 2025, self_employment_income 12,345.67, interest 1,000,000,000, swept over rental_income from −1,000,000 to −900,000,000 in 5,000 steps:

se=12345.67: rows add>exc: 371 of 5000; max add-exc 16.0; worst row rental=-134618128.00 excess=134292768.00 addback=134292784.00; max |limited+excess-gross| 16.0
se=98765.43: rows add>exc: 374 of 5000; max add-exc 16.0; ... max |limited+excess-gross| 32.0

That is one float32 step at that size, and the property tests' rtol=1e-6 absorbs it. Either clip the share in code:

        investment_share = min_(
            1,
            np.divide(
                investment_loss,
                business_loss,
                out=np.zeros_like(investment_loss),
                where=business_loss > 0,
            ),
        )

or change the body's line to "These hold for every input up to float32 rounding (a few dollars on nine-figure losses)."

P3. Other stale or overstated body statements

  1. "The existing property tests … check invariants 1 to 5." Their draws set no self_employment_income or sstb_self_employment_income, and the passive draw never binds the cap (above). Replace with: "… check invariants 1 to 5 for rental, Form 4835, estate and partnership losses. The Schedule C category split is checked only by the YAML case, and the passive-loss cap by [the new YAML case]."
  2. "The property tests keep their population size …" Not quite: the dependent file gains 3 edge rows and the estate file 1. Replace with "… gain four edge rows, one input column, two output variables and one test function."
  3. "The YAML cases check invariant 6 at the boundary." The five new excess_business_loss.yaml cases sit on both sides of the threshold, not at it. Replace with "on both sides of the threshold".
  4. The 9th table row's MAGI of 1,087,000 isn't explained in the body. It comes from main's niit_magi adding a positive code H (the YAML comment says so). Add "(MAGI adds the positive code H)".
  5. "Mutants" list: add the three in this review and what kills them (table below).

P3. The provisional split uses netted partnership amounts (methodology note, no change requested)

Numerator and denominator agree, because both see each person's partnership_s_corp_income netted as main's limited_business_loss sees it. But when a passive loss sits beside nonpassive income in one person's amount, the split doesn't follow the gross losses. Witness, 2025 single: wages 500,000, interest 900,000, partnership_s_corp_income −100,000 with passive −1,000,000, Schedule C −900,000. The model gives excess 687,000, add-back 68,700, NII −31,300 and NIIT 0. A proportional split over gross losses (1,000,000 of 1,900,000) would give 361,578.95, NII 261,578.95 and NIIT 9,940. The excess itself is the same either way, since it depends only on the net. This belongs with the open question us:niit-excess-business-loss-split, not as a blocker.


Mutants (each in its own process on a scratch worktree; all three worktrees restored, git status clean)

Files run per mutant: net_investment_income_excess_business_loss.yaml, net_investment_income_tax.yaml, mn_niit.yaml, excess_business_loss.yaml, policybench_ira.yaml, test_dependent_net_investment_income_invariants.py, test_estate_income_invariants.py.

Mutant Result Killed by
Hand-off 1: estate loss netted with code H killed add-back YAML 1/7 (code H case), NIIT YAML 1/32, test_net_investment_income_adds_estate_income_and_code_h [2024, 2026]
Hand-off 2: denominator net_business_loss killed add-back YAML 1/7 (Schedule C category case) only
Hand-off 3: drop farm_rent_income from sources killed NIIT YAML 5/32, mn_niit.yaml 1/3, PolicyBench 1/8, dependent invariants 4/14
(a) investment_loss without not_dependent killed dependent invariants 6/14 only; no YAML
(b) passive_loss without the min_ survives nothing
(c) Minnesota: mn_niit subtracts the add-back survives nothing
(d) denominator limited_business_loss alone killed add-back YAML 6/7, NIIT YAML 4/32, dependent invariants 6/14, estate invariants 4/22

The hand-off's counts for its three mutants are right.


Checked and sound

  • The merge. limited_business_loss.py, excess_business_loss.py and net_business_loss.py are identical to origin/main (git diff --quiet origin/main pr-10076 -- <file>). Comparing the PR's net change at d1dec4b4cf (against 065d0e9560) with the one at 4e5da2d96b (against 0315452583):

    • dropped from the diff: limited_business_loss.py, excess_business_loss.py;
    • added: estate_income_net_investment_income_adjustment.py (documentation);
    • patch changed: net_investment_income_excess_business_loss.py, its YAML, excess_business_loss.yaml, test_dependent_net_investment_income_invariants.py and test_estate_income_invariants.py;
    • 5ea9e6f718 changes only source_conflicts/candidates.yaml.
  • The add-back, derived from main's variables (I couldn't read Form 461 itself). Let L be the head and spouse's gross business losses, I their gross business income (each Schedule C category on its own) and T the threshold. Then limited = min(L, I + T). excess = max(0, net_loss − T), where net_loss = L − I because total_self_employment_income adds the two categories. When L > I + T, limited + excess = (I + T) + (L − I − T) = L; otherwise excess is 0 and limited is L. So the denominator is L exactly. Every numerator term (rental, Form 4835, estate, passive capped at the netted partnership loss) is a per-person, non-dependent piece of L, so 0 ≤ share ≤ 1. Then add-back = excess × share ≤ excess, and since excess ≤ L, add-back ≤ the numerator. Category by category:

    • an SSTB or Schedule C loss is in L and not in the numerator;
    • a capital loss is in neither (I capital loss probe: add-back 287,000, NII 184,000, NIIT 6,992, matching my hand value);
    • a dependent is in neither (probe F);
    • a passive loss offset by nonpassive income in the same person is in neither (probe H: no excess, NII −100,000).
  • Years. gov.irs.ald.loss.max is infinite through 2020 (CARES Act), so the add-back is 0 before 2021 and the register entry's from: 2021 fits.

  • Hand computations, all matching the model at this head (probe output above and in the interim comment):

    • Body rows: 760; 1,140; 950; 190; MN 1,000; 7,106; 7,106; 1,653; 29,906.
    • The Schedule C category case: 787,000 × 200,000 / 1,200,000 = 131,166.67. The old netting gives 787,000 × 200,000 / 1,100,000 = 143,090.91.
    • PolicyBench 120: 40,423.17578125 − 40,330.20703125 = 92.97, matching 3.8% × 2,446.59 = 92.970.
    • The defect reproduces at main: wages 190,000 and Form 4835 30,000 give NII 0 and NIIT 0 on main, against 30,000 and 760 at the head.
  • Conflict entries.

    • us:niit-excess-business-loss-addback: the example (NII 187,000, NIIT 7,106; in full, NII −100,000 and no NIIT) matches probe C.
    • us:niit-excess-business-loss-split: 143,500 proportional, 287,000 NII-first, 0 other-first; NII 43,500 / 187,000 / −100,000, all correct by hand.
    • us:niit-form-8960-line-4a-tip: 760 against 0 matches probe A.
    • No # Source conflict markers were added, which is right for a ruling PR under CONTRIBUTING step 1.
    • test_source_conflicts.py: 103 passed.
  • Readers of net_investment_income: net_investment_income_tax, mn_niit, the Biden budget 2025 reform's net_investment_income_tax, and s770_nii_base. The only reader of gov.irs.investment.income.sources is net_investment_income.

    • The first three should take both farm rent and the add-back: all read NII as the section 1411 base, Minnesota subject to the conformity question above.
    • s770_nii_base takes both. Its active residual (s770_active_business_income) reads only partnership amounts, so nothing counts twice.
    • The new path net_investment_income → net_investment_income_excess_business_loss → {excess_business_loss → net_business_loss, limited_business_loss} reads only income inputs and other_net_gain, and nothing on it reads NII. Every probe, including Minnesota and S.770 with the reform on, computed without a cycle error.
  • Property references. Both are independent numpy code from leaf inputs and import nothing from the formula. They mirror its structure, including the passive cap and the proportional rule, so they check the implementation against the stated rule, not the rule against the law. The draws reach a dependent's loss (random rows plus the HoH edge row) and a binding limit with both kinds of loss (the joint edge row with a nonpassive partnership loss). They don't reach a Schedule C category split or a binding passive cap.

  • Tests at this head, one file per process:

    • YAML: excess_business_loss.yaml 9, net_investment_income_excess_business_loss.yaml 7, net_investment_income_tax.yaml 32, mn_niit.yaml 3, policybench_ira.yaml 8;
    • Python: test_dependent_net_investment_income_invariants.py 14, test_estate_income_invariants.py 22, test_source_conflicts.py 103.
    • All passed.
  • Scope. Nothing under .github/ or tests/policy/baseline/partners/. No open PR touches the same files except source_conflicts/candidates.yaml, which many open PRs append to (Follow state statutes where forms differ: NM medical exemption, VT CDCC, NY/NYC household credit, PR EITC #10300, Follow the statute for the NYC school tax rate reduction, IN supplemental homestead, VA 529 and WA senior/disabled property tax exemption #10330, Net the premium tax credit and leave out unreported payroll tax in Oregon's federal tax subtraction #10093 and others), so expect a routine text conflict there. Fill IRS SOI uprating series gaps with Publication 1304 actuals #10128 and Extend IRS SOI uprating series to tax year 2023 #10132 touch farm_rent_income calibration and tests only, with no overlap.

  • Body lines. impact: exists and is pending, as the brief expects. axiom: exists and I left it alone. The changelog fragment changelog.d/niit-farm-rent.fixed.md is top-level and matches the change.

  • Baseline movement. Yes, the diff moves baseline outputs:

    • NII, NIIT and mn_niit for every unit with nonzero head or spouse farm_rent_income;
    • the same three for every unit whose excess_business_loss is positive and which has rental, Form 4835, estate or passive losses.

    The comparison should report the count and NIIT change for each group separately, with sign, because the two move in opposite directions for Form 4835 losses (P1).

  • CI. Pull request run https://github.com/PolicyEngine/policyengine-us/actions/runs/41978104913 at this head: success, 36 of 36 jobs passed (completed 07:07Z on 11/05).

Follow-ups (identical on main or outside this PR)

  • S.770's active base takes no share of the section 461(l) excess. With the active-business option on (2026, single): wages 300,000, interest 900,000, nonpassive partnership −600,000 and rental −600,000. The model gives excess 944,000, add-back 472,000, NII 772,000, s770_active_business_income −600,000 in full, and a combined NII base of 172,000. Main gives 300,000, −600,000 and 0. By the PR's own proportional rule, the active loss would be cut to −128,000 and the combined base would be 644,000. Default-off reform; the PR moves it toward the rule, not away.
  • No later-year carryover. The model has no federal NOL variable, so section 461(l)'s disallowed excess never returns in a later year in AGI or NII. I couldn't read Treas. Reg. 1.1411-4(h) or section 461(l)(2) to say whether that carryover reaches the section 1411 NOL. The body should say the treatment is single-year: "The model carries no disallowed excess into later years, in AGI or NII."
  • Netted partnership amounts in main's limited_business_loss (P3 above): a passive loss and nonpassive income of one person cancel before section 461(l) sees them.

What I couldn't check

  • Every primary source. I couldn't confirm or refute any of these: 26 U.S.C. 1411(c)(1)(A)(i), (c)(1)(B), (c)(2), (c)(6) and 461(l); the 2025 Form 4835 title and purpose; the 2025 Form 8960 instructions (page 1, the line 4a tip, line 4b's list, line 7's list); 2025 Form 461 line 16; Treas. Reg. 1.1411-4(f)(2)(ii), (f)(2)(v) Example 1 and (h); Minn. Stat. 290.033. I also couldn't search eCFR for a regulation naming section 461(l).
  • The line 4a tip. I can't confirm the hub's quote. If it reads as quoted ("not subject to self-employment tax under section 1401(b)", entered as a negative on line 4b), then taken literally it would also remove ordinary Schedule E rent, which self-employment tax doesn't reach either. That would undo line 4a's rental inclusion. So, on the hub's quotes alone, I agree the narrower reading tied to 1411(c)(6) is the coherent one. That is an inference from the quoted words, not a reading of the text.
  • Population impact, which needs the hub's data.

Generated by Claude Code

MaxGhenis and others added 2 commits November 5, 2026 02:20
…-back

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

DTrim99 commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Count Form 4835 farm rent in net investment income, and limit its losses under section 469

Summary

The PR makes two changes to net_investment_income, which is the base for the federal NIIT and Minnesota's NIIT:

  • Farm rent. gov.irs.investment.income.sources adds farm_rent_income, the Form 4835 net farm rental income or loss on Schedule E line 40. AGI and NIIT MAGI already counted it. NII did not.
  • Section 461(l). A new TaxUnit variable, net_investment_income_excess_business_loss, works out the part of main's excess_business_loss (Form 461 line 16) that falls on losses NII counts: Schedule E rental, Form 4835, Schedule E Part III estate and trust losses, and the passive part of partnership/S corporation losses. NII adds that amount back. When the filer also has business losses NII doesn't count, the PR splits the excess in proportion and labels the split provisional.

I read the law and the forms first. The code follows them.

Farm rent is NII.

  • 26 U.S.C. 1411(c)(1)(A)(i) counts "gross income from ... rents, other than such income which is derived in the ordinary course of a trade or business not described in paragraph (2)". Paragraph (2)(A) is "a passive activity (within the meaning of section 469)". Treas. Reg. 1.1411-4(a)(1)(i) and (b) say the same.
  • The 2025 Form 4835 is titled "Income Not Subject to Self-Employment Tax". Its Purpose of Form says: "Use this form only if the activity was a rental activity for purposes of the passive activity loss limitations."
  • Line 32 (income) or line 34c (deductible loss) goes to Schedule E line 40. Schedule E line 41 combines line 40 with the other parts and goes to Schedule 1 line 5.
  • 2025 Form 8960 line 4a takes Schedule 1 lines 3, 5 and 6. The 2024 form takes lines 3 and 5, so it also includes Form 4835 amounts.
  • Line 4b removes rentals only when they are nonpassive and derived in a section 162 trade or business. Example: "Nonpassive net rental income or loss of a real estate professional where the rental activity rises to a section 162 trade or business."
  • 1411(c)(6) excludes only items "taken into account in determining self-employment income ... on which a tax is imposed by section 1401(b)". Form 4835 rent is excepted under 1402(a)(1). Page 1 of the 2025 instructions says income excepted under 1402(a)(1)–(17) "is subject to NIIT".

Section 469 and Form 4835.

  • A Form 4835 activity is a rental activity, so it is passive per se under 469(c)(2). The exception is a real estate professional who materially participates (469(c)(7)); Form 4835's line 34 instructions route that loss straight to line 34c, outside the passive loss limits.
  • The 469(i) $25,000 offset for rental real estate with active participation applies to Form 4835. Line A asks "Did you actively participate...?". The Form 8582 instructions route a qualifying line 32 loss to line 34c under Schedule E's "Exception for Certain Rental Real Estate Activities". The offset phases out by 50% of AGI over $100,000, so it is gone at $150,000 (469(i)(3)(A)).
  • The PR models none of this, and correctly so for its input. farm_rent_income is the Schedule E line 40 amount. For a loss, that is line 34c's deductible loss "after applying" the at-risk rules (Form 6198) and the passive loss rules (Form 8582), and prior-year suspended losses released this year are also netted into lines 32 and 34c ("PAL").
  • That is the order the law sets. Section 461(l)(6) says "This subsection shall be applied after the application of section 469". Treas. Reg. 1.1411-4(f)(2)(v), Examples 1 and 2, has NII take only the deductions sections 465 and 469 allow, plus released suspended losses as current-year deductions.
  • So NII rightly takes the same line 40 amount that AGI takes. The model carries no section 469 carryforward because the input is already post-limit. It carries no section 461(l)(2) NOL carryover either; the body says the model works one year at a time.

Section 461(l) in NII.

  • 1411(c)(1)(B) subtracts only "the deductions allowed by this subtitle". 461(l)(1)(B) says "any excess business loss ... shall not be allowed" for years after 2020. CARES Act § 2304 suspended it for 2018–2020, and gov.irs.ald.loss.max is infinite before 2021, so the add-back is 0 then.
  • When every business loss is an NII loss, the whole excess must come off NII deductions, and every reading agrees.
  • Form 461 line 16 sends the add-back to Schedule 1 line 8p. Form 8960 line 4a doesn't take line 8p, and line 7 names line 8z but not 8p. So following the statute over the form is a recorded statute-first choice: us:niit-excess-business-loss-addback in candidates.yaml, follows: statute.
  • Neither 1.1411-4 nor 1.1411-5 mentions section 461(l), and no form says how to split the excess when non-NII losses are also present. The PR records that gap as us:niit-excess-business-loss-split (state: open).

Minnesota. Minn. Stat. 290.033(a) uses "the meaning given in section 1411(c)", with two exceptions: class 2a gains and opportunity zone gains. Neither touches rent or section 461(l), so mn_niit correctly takes both changes through net_investment_income.

Line 4a tip. The 2025 tip under line 4a says income "that is not subject to self-employment tax under section 1401(b) is also entered as a negative on line 4b". Taken literally, that would also remove ordinary Schedule E rent, and it contradicts page 1 and 1411(c)(6). It is new in 2025; the 2024 instructions have no such sentence. The PR follows the statute and records the conflict as us:niit-form-8960-line-4a-tip (2025 only).

I hand-checked 18 expectations, and all match. Every new term is pinned by a test. CI is green at the head (36 of 36), and the branch is level with main and merges cleanly. I have no critical or should-address findings. The suggestions cover documenting the post-section 469 input convention, the statutory wording, a companion case for the rulespec-us issue, and two follow-ups.

Critical

None.

Should address

None.

Suggestions

  1. Document the post-section 469 input convention (farm_rent_income.py documentation; the body's "No section 469 limit, as in AGI").

    • The PR's documentation says "Net farm rental income or loss from Form 4835 ... reported on Schedule E line 40", but it doesn't say the loss has already passed the at-risk and passive loss limits. That is what makes leaving out a section 469 calculation correct.
    • The body's "The model doesn't apply the passive loss limits" can read as a gap. Suggested documentation sentence: "A loss is the deductible loss after the at-risk and passive activity loss limits (Form 4835 line 34c, from Forms 6198 and 8582), which is the amount Schedule E line 40 takes."
    • Suggested body wording: "The input is the Schedule E line 40 amount, which already reflects sections 465 and 469, so neither AGI nor NII applies them again (section 461(l)(6); Treas. Reg. 1.1411-4(f)(2)(v), Example 1)."
  2. Quote 1411(c)(1)(B) as written. The statute says "the deductions allowed by this subtitle". The PR paraphrases this as "deductions allowed by chapter 1" in five places:

    • the sources.yaml comment above net_investment_income_excess_business_loss;
    • the variable's documentation;
    • the header of net_investment_income_excess_business_loss.yaml;
    • property 6 in test_dependent_net_investment_income_invariants.py;
    • property 2 in test_estate_income_invariants.py.

    The conclusion holds, because section 461(l) sits in chapter 1 inside subtitle A. Still, "allowed by this subtitle (subtitle A), which includes section 461(l)" would match the statute and the body.

  3. rulespec-us#1643: add a non-farm companion case.

    • The issue is OPEN, labelled pe-parity and dispatch-ready (module path, corpus row ids, verbatim law, hand-computed F1–F4, and the signed-manifest blocker recorded). Its review finding states the general rule: allocable_investment_deductions are the deductions allowed after sections 461(l), 465 and 469. So it covers this head.
    • Its title and F4 cover only Form 4835. The head's add-back also reaches Schedule E Part I rentals, Part III estate losses and passive pass-through losses.
    • Suggest adding an F5 for TY2026: wages 300,000, interest 500,000, a Schedule E rental loss of 600,000, threshold 256,000. The excess is 344,000 and the allowed deductions 256,000. AGI = 300,000 + 500,000 − 256,000 = 544,000. NII = 500,000 − 256,000 = 244,000. NIIT = 3.8% × min(244,000, 344,000) = 9,272.
    • Also add one line saying the split with non-NII losses is open (us:niit-excess-business-loss-split) and shouldn't be encoded as law.
  4. Section 461(l) reaches only trade-or-business items (follow-up; the same on main for AGI).

    • Form 461 Part II, lines 10–12, removes income and losses "not attributable to a trade or business". The instructions define that as an activity carried on "with continuity and regularity".
    • Main's limited_business_loss and net_business_loss treat every Schedule E rental, Form 4835 and estate loss as a business item. The add-back now carries that assumption into NII.
    • For a rental that isn't a section 162 business, section 461(l) wouldn't apply, and both AGI and NII should take the whole loss.
    • For Form 4835 the assumption is the most defensible: the form itself points net farm rental income to Schedule J farm income averaging.
    • Suggest one sentence in the add-back documentation and a follow-up issue for a trade-or-business input. It could share the planned Form 8960 line 4b rental input (EITC investment-income test after #9572: dependents' amounts, passive income that is also earned income, and farm rental #9635).
  5. The open split and CONTRIBUTING's tie-break. CONTRIBUTING says that where text and canons don't settle a reading, the model follows the administering agency's reading. The IRS has no stated reading on the split. Its forms take the full loss on line 4a and give the line 8p add-back nowhere. Of the three options, "other losses first" departs least from the form while still honouring 1411(c)(1)(B). Suggest naming that consideration in the us:niit-excess-business-loss-split note, so the ruling weighs it against the provisional proportional rule.

  6. CI cost line and stale CI note.

    • CLAUDE.md asks expanded model-building Python tests to report before/after time and memory, or to say they weren't measured.
    • The body says no simulation was added, which is true: four edge rows join an existing vectorized simulation, and the new test function reuses the runs fixture.
    • Suggest citing the two property-test files' durations from this head's CI run, or adding "not measured".
    • The "CI" bullet ("hasn't finished") is stale: 36 of 36 checks now pass.

Verified correct

  • Hand-checks. 18 expectations, all matching. Thresholds: 2025 section 461(l) $313,000 single and $626,000 joint (Form 461 line 15); NIIT $200,000 single and $250,000 joint (1411(b)).

    # Case Arithmetic Expected
    1 Wages 190,000 + Form 4835 30,000 (2025) NII 30,000; MAGI 220,000; 3.8% × min(30,000, 20,000) 760
    2 Wages 250,000, Schedule E 40,000, Form 4835 −10,000 NII 30,000; MAGI 280,000; 3.8% × min(30,000, 80,000) 1,140 (main 1,520)
    3 Joint 2024, spouse Form 4835 35,000 MAGI 275,000 − 250,000 = 25,000; 3.8% × 25,000 950
    4 Parent wages 210,000 + interest 5,000, child Form 4835 10,000 NII 5,000; MAGI 215,000; 3.8% × 5,000 190
    5 Wages 300,000, interest 500,000, Form 4835 −600,000 limited min(600,000, 0 + 313,000) = 313,000; AGI 487,000; excess 287,000, all on NII losses; NII 500,000 − 600,000 + 287,000 = 187,000; 3.8% × min(187,000, 287,000) 7,106 (main 10,906)
    6 Same with a Schedule E rental loss same lines 7,106 (main 0)
    7 Wages 600,000, interest 200,000, Schedule C −300,000, Form 4835 −300,000 excess 287,000 × 300/600 = 143,500; NII 200,000 − 300,000 + 143,500 = 43,500; 3.8% × 43,500 1,653 (main 7,600)
    8 Estate −600,000, code H +600,000 AGI 487,000; MAGI 1,087,000; NII 500,000 − 600,000 + 600,000 + 287,000 = 787,000; 3.8% × min(787,000, 887,000) 29,906
    9 Partnership −400,000, of which 100,000 passive (400,000 − 313,000) × 100/400 21,750
    10 Joint: rental −500,000; spouse partnership −400,000 (200,000 passive) (900,000 − 626,000) × 700/900 213,111.11
    11 Non-SSTB +100,000, SSTB −1,000,000, rental −200,000 limited min(1,200,000, 413,000) = 413,000; net loss 1,100,000 − 313,000 = 787,000; 413,000 + 787,000 = 1,200,000; 787,000 × 200,000 / 1,200,000 131,166.67
    12 Passive −1,000,000 inside partnership −100,000, rental −600,000, interest 900,000 losses 700,000; excess 387,000; NII losses 600,000 + min(1,000,000, 100,000) = 700,000, so share 1; NII 900,000 − 600,000 − 1,000,000 + 387,000 add-back 387,000; NII −313,000
    13 Case 5 plus a dependent with Form 4835 −1,000,000 the filer's excess stays 287,000 NII 187,000; NIIT 7,106
    14 MN: interest 800,000 + Form 4835 300,000 1% × (1,100,000 − 1,000,000) 1,000
    15 MN: interest 2,000,000, Form 4835 −600,000 NII 2,000,000 − 600,000 + 287,000 = 1,687,000; 1% × 687,000 6,870
    16 Rental +100,000, Form 4835 −600,000 limited 413,000; 600,000 − 413,000 excess 187,000
    17 Joint: head Schedule C −400,000, spouse Form 4835 −400,000 800,000 − 626,000 excess 174,000
    18 PolicyBench 120, Form 4835 −2,446.59 3.8% × 2,446.59 = 92.97; 40,423.18 − 40,330.21 = 92.97 (float32) 40,330.21
  • The add-back's arithmetic.

    • Let L be the head and spouse's gross business losses (each Schedule C category separately), I their business income and T the threshold.
    • limited_business_loss = min(L, I + T), and excess_business_loss = max(0, L − I − T). That holds because total_self_employment_income is exactly the sum of self_employment_income and sstb_self_employment_income.
    • So the denominator limited + excess equals L whenever the excess is positive.
    • Every numerator term is a non-dependent, per-person piece of L, so 0 ≤ share ≤ 1 in exact arithmetic. The body now notes the float32 caveat.
    • The where=business_loss > 0 guard prevents 0/0. Before 2021, the infinite threshold gives an excess of 0 and no NaN.
  • Mutation coverage. Each of these fails at least one test if deleted or changed:

    • farm_rent_income and the add-back in sources;
    • not_dependent (the YAML dependent case and the property tests);
    • each of the four loss terms;
    • the min_ cap on the passive loss (the passive-cap case);
    • the limited + excess denominator (the Schedule C category case);
    • Minnesota taking the add-back (the mn_niit case).
  • Entity, period and vectorization.

    • The new variable is a TaxUnit YEAR variable that uses tax_unit.sum over person arrays and np.divide with where.
    • tax_unit_non_dep_add adds a TaxUnit source as is, and this one already leaves dependents out, as the sources.yaml header requires.
    • The new chain (NII → add-back → excess_business_loss / limited_business_loss) never reads NII, so there is no cycle.
  • Readers of net_investment_income: net_investment_income_tax, mn_niit, the Biden 2025 budget reform's NIIT, and S.770's s770_nii_base. Only net_investment_income reads the sources parameter.

  • No reinvention. No existing variable splits the section 461(l) excess. The PR reuses main's limited_business_loss, excess_business_loss and net_business_loss without changing them.

  • References.

    • The new sources.yaml entries corroborate their claims:
      • 2025 Form 8960 instructions: page 4 (passive activity rules) and page 8 (lines 4a and 4b), titled #page 4,8 and quoted, as CLAUDE.md requires;
      • 2025 Form 4835 page 2 (Purpose of Form);
      • 2025 Schedule E page 2 (lines 40 and 41);
      • 1411(c)(1)(B) and (c)(2);
      • 461(l);
      • Treas. Reg. 1.1411-4(f).
    • The 2025 Form 1041 instructions #page=38 (file page 38) say: "The only instance where code H will be a positive number is when ... an (a) interest in a section 1291 fund, or (b) interest in a controlled foreign corporation or qualified electing fund ... [and] the distribution ... is (a) NII to the estate or trust, but not included in its taxable income". This matches the new estate_income_net_investment_income_adjustment documentation and the code H test cases.
  • Source conflicts.

    • All three statute-versus-form or gap choices are recorded in source_conflicts/candidates.yaml, each with two or more sources, readings, pe_us and state, and no premature # Source conflict marker (CONTRIBUTING step 1).
    • Facts in the entries that I checked against the texts:
      • Form 461 line 16 → Schedule 1 line 8p;
      • line 7 names 8z, not 8p;
      • Example 1 applies sections 465 and 469 first;
      • the tip's quoted words;
      • the page 1 "is subject to NIIT" sentence;
      • from: 2021 for the add-back, and 2025 only for the tip.
  • Tests.

    • The YAML cases are in variable-named files. The policybench_ira.yaml scenario 120 update is explained in a comment and is not a partner test.
    • No file under tests/policy/baseline/partners/ or .github/ changes.
    • The Python changes extend two existing property tests that compare against independent numpy references. They add no simulation build.
  • Changelog. changelog.d/niit-farm-rent.fixed.md is top-level, and fixed fits.

  • Merged-tree guards. No income-source list touches unemployment, military retirement or social security. CI ran on a head that already contains main's source-conflict ratchet (Tie source-conflict markers to the register shared with Axiom #10287) and passed.

Axiom line assessment

axiom: TheAxiomFoundation/rulespec-us#1643 queued. The issue is valid:

  • It is OPEN and labelled pe-parity, last updated 2026-10-11.
  • It names the module (us/statutes/26/1411.yaml and its test file, plus a Form 8960 line 4 module) and gives the corpus row ids for 1411(c)(1), (c)(2) and (c)(6).
  • It quotes the law and the 2025 forms verbatim.
  • It sets out the required outputs and hand-computed companion cases F1–F4. F4 is the TY2026 version of the section 461(l) Form 4835 case: NII 244,000, NIIT 9,272, which this head reproduces with the $256,000 threshold.
  • It records the blocker: a signed manifest that needs a billed re-encode with approval.

Its review finding states the general section 1411(c)(1)(B) rule, so it covers this head's add-back. Suggestion 3 asks for a non-farm companion case and a note on the open split.

CI status

All 36 checks pass at 950dd51a86. I re-checked near the end, and the Contrib other-shard-1 job that was pending earlier has finished green.

Branch status

  • The branch is 0 commits behind main; its last commit merges main at e53b426082.
  • git merge-tree --write-tree PolicyEngine/main 950dd51a86 merges cleanly.
  • Branch note: many open PRs append to source_conflicts/candidates.yaml, so whichever lands second may need a routine text merge there.

Verdict: APPROVE

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving: 0 critical, 0 should. Form 4835 rent is passive rent under § 1411(c)(1)(A)(i)/(c)(2)(A), farm_rent_income is already the post-§ 469 amount, and the § 461(l) add-back is a recorded statute-first choice. Every new term is pinned; CI 36/36. Full report in the comment above.

…ment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit bf86e75 into main Nov 5, 2026
36 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10076 at 0cc50419574fe821a3fc393865d3bdcf1a00e9b2 (squash): Count Form 4835 farm rent in net investment income, and limit its losses under section 461(l).

  • Review: a GPT-6.1 Sol reviewer approved 950dd51a86 in a delta review (round 6), with METHODOLOGY_CHOICE yes, and found no code defect. At this head the hub made the six text corrections the reviewer asked for, to:
    • the impact table's third row and weighted count;
    • the statement on Treasury's regulation, in the body and the register;
    • the statement on line 4b, in the body and the register;
    • the NIIT equation's zero floors;
    • the Minnesota test comment;
    • the CI cost note.
  • Earlier review: in round 5, the reviewer requested changes at 5ea9e6f718. The hub answered in commit 6a95037abe with table cells of 10,906 and 7,600, the passive-cap, dependent and Minnesota cases, the float32 wording, five corrected statements and the methodology note under Decisions. The round 6 reviewer checked each one. S.770's active-loss allocation and the carryover stay as follow-ups.
  • Readings: the PR follows the statute on us:niit-excess-business-loss-addback and us:niit-form-8960-line-4a-tip. On us:niit-excess-business-loss-split, Congress and the IRS leave the split open, and the body states the PR's choice.
  • Gates checked live at merge:
    • gh pr checks exits 0;
    • the latest Pull request run at the head concluded success against main e53b426082 (42071455256, 36 of 36 jobs);
    • MERGEABLE;
    • not a draft;
    • no reviewer's latest review requests changes;
    • the semantic-overlap guard, which accepts main cc8f4c9d90 on the combination check below;
    • the merge pins the head.
  • Combination: main changed source_conflicts/candidates.yaml after that run. The hub merged main cc8f4c9d90 with this head locally and ran the register test (103 passed) and the NIIT and Minnesota YAML files (45 cases passed).
  • Impact: we estimate that net investment income tax revenue rises $145.1 million in 2026: $153.7 million more on positive Form 4835 amounts and $8.6 million less on Form 4835 losses. Household net income falls by the same $145.1 million, and AGI, mn_niit and benefits don't move. We compared main at 0315452583 with 6a95037abe. Since then the hub merged main and corrected one test comment and two register notes, and the round 6 reviewer checked main's commits against these outputs.
  • Axiom: Pin Form 4835 farm rent (Schedule E line 40) as §1411(c)(1)(A)(i) rents in NIIT, and encode Form 8960 line 4 [pe-parity] TheAxiomFoundation/rulespec-us#1643 tracks the encoding.

🤖 Generated with Claude Code

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.

Net investment income leaves out Form 4835 farm rental income (Schedule E line 40)

3 participants