Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/sch-d-cg-distributions-agi.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Net capital gain distributions against Schedule D capital losses before the 26 U.S.C. 1211(b) $3,000 loss limit in adjusted gross income, net investment income, loss-limited capital gains (Michigan, Mississippi, Delaware and Georgia), a dependent's Social Security modified AGI and the capital gain tax worksheet test, and net spouses' gains and losses on a joint return.
4 changes: 3 additions & 1 deletion policyengine_us/parameters/gov/irs/gross_income/sources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ values:
- farm_rent_income
- other_net_gain_gross_income
- capital_gains
# Capital gain distributions reported on Form 1040 line 7a. With capital
# losses they go on Schedule D line 13; loss_ald nets the losses against
# them (capital_losses_allowed_against_gains).
- non_sch_d_capital_gains
# Capital gain distributions received without filing Schedule D (Form 1040 line 7).
- taxable_interest_income
- rental_income
# Royalties included in rental income
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ values:
# code H input says otherwise, and a dependent's amounts stay out of the
# filer's net investment income.
- estate_net_investment_income
# Schedule D gain or loss of the head and spouse, limited under § 1211(b)
# (the Schedule D part of Form 8960 line 5a).
# Form 8960 line 5a: the head and spouse's Form 1040 line 7a capital gain
# or loss, Schedule D gains and losses with capital gain distributions
# (line 13), limited under § 1211(b). Distributions reported without
# Schedule D are the same amount.
- filer_loss_limited_net_capital_gains
- non_sch_d_capital_gains
# Capital gain distributions received without filing Schedule D (Form 1040 line 7).
metadata:
unit: currency-USD
label: net investment income sources
Expand Down
28 changes: 17 additions & 11 deletions policyengine_us/tests/core/test_dependent_losses_agi_invariants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
2. 0 <= limited_capital_loss <= the filing-status cap, and it never exceeds
the non-dependents' own capital losses (26 USC 1211(b)).
3. loss_ald >= limited_capital_loss >= 0.
4. For tax units without dependents, both variables equal the pre-change
all-member formula (a reference implementation below), so filers without
dependents see no change.
4. For tax units without dependents, both variables equal the all-member
formula (a reference implementation below), so leaving dependents out
changes nothing for filers without dependents. The capital part follows
26 USC 1211(b): losses are allowed up to the gains
(`capital_losses_allowed_against_gains`), plus the excess up to the cap
(`limited_capital_loss`).
"""

import numpy as np
Expand Down Expand Up @@ -268,8 +271,8 @@ def test_loss_ald_at_least_limited_capital_loss(runs):
)


def test_units_without_dependents_match_pre_change_formula(runs, units):
"""Differential check against the all-member formula used before."""
def test_units_without_dependents_match_all_member_formula(runs, units):
"""Differential check against the all-member formula."""
p = runs["irs"]
filing_status = runs["with"]["filing_status"]
se = _by_person(units, "self_employment_income") + _by_person(
Expand All @@ -279,12 +282,13 @@ def test_units_without_dependents_match_pre_change_formula(runs, units):
income = sum(_unit_sum(runs, np.maximum(x, 0)) for x in person_business)
loss = sum(_unit_sum(runs, np.maximum(-x, 0)) for x in person_business)
limited_business_loss = np.minimum(loss, income + p.ald.loss.max[filing_status])
capital_losses = _unit_sum(
runs,
np.maximum(0, -sum(_by_person(units, name) for name in CAPITAL_INPUTS)),
)
person_capital = sum(_by_person(units, name) for name in CAPITAL_INPUTS)
capital_losses = _unit_sum(runs, np.maximum(0, -person_capital))
capital_gains = _unit_sum(runs, np.maximum(0, person_capital))
allowed_against_gains = np.minimum(capital_losses, capital_gains)
limited_capital_loss = np.minimum(
p.ald.loss.capital.max[filing_status], capital_losses
p.ald.loss.capital.max[filing_status],
capital_losses - allowed_against_gains,
)
no_dependents = np.array([not u["dependents"] for u in units])
assert no_dependents.sum() > 100
Expand All @@ -295,6 +299,8 @@ def test_units_without_dependents_match_pre_change_formula(runs, units):
)
np.testing.assert_allclose(
runs["with"]["loss_ald"][no_dependents],
(limited_business_loss + limited_capital_loss)[no_dependents],
(limited_business_loss + allowed_against_gains + limited_capital_loss)[
no_dependents
],
atol=TOLERANCE,
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
AGI, NIIT MAGI or `net_investment_income_tax`.
2. `net_investment_income` equals an independent numpy computation over the
head and spouse: their interest, dividends, rents, passive pass-through,
estate and trust income and capital gain distributions, plus their netted
Schedule D gain or loss, limited under 26 USC 1211(b).
estate and trust income, plus their netted Schedule D gain or loss with
capital gain distributions on line 13, limited under 26 USC 1211(b).
3. -loss limit <= `filer_loss_limited_net_capital_gains`, and it equals
`loss_limited_net_capital_gains` when the unit has no dependents.
4. For tax units without dependents, `net_investment_income` equals the
pre-change all-member formula, so filers without dependents see no change.
all-member formula (person-level sources plus
`loss_limited_net_capital_gains`), so leaving dependents out changes
nothing for filers without dependents.
5. 0 <= NIIT = 3.8% x min(max(0, NII), max(0, MAGI - threshold)).
"""

Expand Down Expand Up @@ -56,7 +58,12 @@
+ NONNEGATIVE_INPUTS
+ ["partnership_s_corp_income", "passive_partnership_s_corp_income"]
)
CAPITAL_INPUTS = ["short_term_capital_gains", "long_term_capital_gains"]
# Schedule D gains and losses, with capital gain distributions (line 13).
CAPITAL_INPUTS = [
"short_term_capital_gains",
"long_term_capital_gains",
"non_sch_d_capital_gains",
]
# Every person-level amount Form 8960 counts, other than Schedule D gains.
NII_PERSON_INPUTS = [
"taxable_interest_income",
Expand All @@ -65,7 +72,6 @@
"rental_income",
"passive_partnership_s_corp_income",
"estate_income",
"non_sch_d_capital_gains",
]
OUTPUTS = [
"net_investment_income",
Expand Down Expand Up @@ -305,8 +311,8 @@ def test_filer_capital_gains_within_loss_limit(runs, units):
)


def test_units_without_dependents_match_pre_change_formula(runs, units):
"""Differential check against the all-member formula used before."""
def test_units_without_dependents_match_all_member_formula(runs, units):
"""Differential check against the all-member formula."""
person_nii = sum(_by_person(units, name) for name in NII_PERSON_INPUTS)
old = _unit_sum(runs, person_nii) + runs["with"]["loss_limited_net_capital_gains"]
no_dependents = np.array([not u["dependents"] for u in units])
Expand Down
Loading
Loading