Skip to content
Open
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/statute-first-federal-income-tax.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Follow the statute where IRS forms differ: the refundable child tax credit bar applies only to a section 911 exclusion, not the housing deduction alone (with Form 2555 leaf inputs; a positive legacy aggregate still counts as an exclusion); a claimable spouse aged 65 or blind on a 2018-2025 joint return must file with income other than earned income over the section 63(c)(5)(A) amount; estate and trust royalties count as EITC investment income; and through 2020 former section 32(c)(1)(F) denies the EITC to a filer whose qualifying children all lack identification.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
description: The Internal Revenue Service denies the earned income tax credit to a filer who has qualifying children none of whom meets the identification requirement, when this is true.
values:
# Former 26 U.S.C. 32(c)(1)(F), in the 2015 Code edition; earlier years
# are not separately verified.
0000-01-01: true
# American Rescue Plan Act section 9622 struck subparagraph (F) for
# taxable years beginning after December 31, 2020.
2021-01-01: false
metadata:
unit: bool
period: year
label: EITC bar on filers whose qualifying children all lack identification
reference:
- title: 26 U.S.C. 32(c)(1)(F) (2015 edition)
href: https://www.govinfo.gov/content/pkg/USCODE-2015-title26/html/USCODE-2015-title26-subtitleA-chap1-subchapA-partIV-subpartC-sec32.htm
- title: Public Law 117-2, section 9622
href: https://www.congress.gov/117/plaws/publ2/PLAW-117publ2.pdf#page=151
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
Worksheet 1 lines 11 and 12 name (Schedule E lines 40, 34a column (d) and 34b
column (c)). `passive_estate_income`, the passive subset of `estate_income`,
defaults to zero; the draws leave it unset, or set it to all, half or none of
`estate_income`, or to an unrelated signed amount.
`estate_income`, or to an unrelated signed amount. Royalties received through
an estate or trust and not derived in a trade or business
(`estate_royalty_income`, Schedule K-1 (Form 1041) box 5) are their own
basket under 32(i)(2)(C), floored at zero, which Worksheet 1 line 8 leaves out
and the model counts under the statute.

Hypothesis draws batches of tax units (single, head of household with
dependents, joint with and without dependents), and a seeded population of 200
Expand All @@ -32,7 +36,8 @@
interest and dividends, plus their Schedule D gain with capital gain
distributions floored at zero (line 7), plus their net rental, farm rental,
passive partnership and passive estate and trust income less the signed part
also in earned income, floored at zero (line 13).
also in earned income, floored at zero (line 13), plus the estate and trust
royalty basket floored at zero (section 32(i)(2)(C)).
3. Bounds: it is at least the filers' interest and dividends (drawn
nonnegative), so never negative, and at least that plus their capital gains
floored at zero, so no passive loss (an estate or trust loss included)
Expand Down Expand Up @@ -81,6 +86,9 @@
ESTATE = "estate_income"
PASSIVE_ESTATE = "passive_estate_income"
OVERLAP = "eitc_passive_income_also_in_earned_income"
# Section 32(i)(2)(C) royalties received through an estate or trust: a basket
# of their own, floored at zero.
ROYALTY = "estate_royalty_income"
SE_EARNINGS = "partnership_self_employment_net_earnings"
# The filer's amounts, which a dependent's income must not change.
FILER_OUTPUTS = [
Expand Down Expand Up @@ -129,6 +137,7 @@ def investment_amounts(draw, dependent=False):
)
),
SE_EARNINGS: draw(signed),
ROYALTY: draw(signed),
**draw(estate_amounts()),
}

Expand Down Expand Up @@ -240,6 +249,19 @@ def _seeded_units(n=200):
],
}
)
# Royalties come from their own stream, so the draws above stay as they
# were before the royalty basket existed. The stream and the 30% rate
# leave every basket deciding eligibility for some units (the coverage
# assertions in test_seeded_population).
royalty_rng = np.random.default_rng(SEED + 5)
for unit in units:
for amounts in [unit["head"], unit["spouse"], *unit["dependents"]]:
if amounts is not None:
amounts[ROYALTY] = (
float(round(royalty_rng.uniform(-12_000, 12_000)))
if royalty_rng.random() < 0.3
else 0.0
)
return units


Expand Down Expand Up @@ -355,29 +377,36 @@ def filer_sum(names):
)
capital = np.maximum(0, filer_sum(CAPITAL_INPUTS) + distributions)
passive = np.maximum(0, filer_sum(PASSIVE_INPUTS) - filer_sum([OVERLAP]))
expected = portfolio + capital + passive
royalties = np.maximum(0, filer_sum([ROYALTY]))
expected = portfolio + capital + passive + royalties
# Units whose eligibility turns on farm rent alone, or on the overlap input
# alone, so the seeded population shows each term is exercised.
eligible = expected <= run["limit"]
without_farm_rent = (
portfolio
+ capital
+ royalties
+ np.maximum(
0,
filer_sum([n for n in PASSIVE_INPUTS if n != "farm_rent_income"])
- filer_sum([OVERLAP]),
)
)
without_overlap = portfolio + capital + np.maximum(0, filer_sum(PASSIVE_INPUTS))
without_overlap = (
portfolio + capital + royalties + np.maximum(0, filer_sum(PASSIVE_INPUTS))
)
without_estate = (
portfolio
+ capital
+ royalties
+ np.maximum(
0,
filer_sum([n for n in PASSIVE_INPUTS if n != PASSIVE_ESTATE])
- filer_sum([OVERLAP]),
)
)
without_royalties = portfolio + capital + passive
royalty_decides = eligible != (without_royalties <= run["limit"])
farm_rent_decides = eligible != (without_farm_rent <= run["limit"])
overlap_decides = eligible != (without_overlap <= run["limit"])
estate_decides = eligible != (without_estate <= run["limit"])
Expand Down Expand Up @@ -422,11 +451,19 @@ def filer_sum(names):
- _by_person(units, OVERLAP),
),
)
+ np.maximum(0, _unit_sum(run, _by_person(units, ROYALTY)))
)
np.testing.assert_allclose(
investment_income[unchanged], all_member[unchanged], atol=TOLERANCE
)
return has_dependent, unchanged, farm_rent_decides, overlap_decides, estate_decides
return (
has_dependent,
unchanged,
farm_rent_decides,
overlap_decides,
estate_decides,
royalty_decides,
)


# A batch's cost is mostly per-variable overhead, so each example is a large
Expand All @@ -453,10 +490,12 @@ def test_seeded_population(year):
farm_rent_decides,
overlap_decides,
estate_decides,
royalty_decides,
) = _check(_seeded_units(), year)
# The population exercises both branches of each property.
assert has_dependent.sum() > 50
assert unchanged.sum() > 20
assert farm_rent_decides.sum() > 2
assert overlap_decides.sum() > 2
assert estate_decides.sum() > 2
assert royalty_decides.sum() > 2
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
unearned income test. YAML cases pin the published amounts one case at a time;
these tests check what must hold for every input, which YAML cannot state.

Where the statute and the IRS charts differ (decision d1395: the age and
blindness addition in the 2018-2025 joint test, a dependent filing
On a 2018-2025 joint return, section 6012(f)(2)(D) tests a claimable
spouse's income other than earned income against the section 63(c)(5)(A)
amount alone, while Table 2 adds the additional standard deduction for age or
blindness; the model follows the statute, and property 10 states it. Where
the statute and the IRS charts differ otherwise (a dependent filing
separately, and in 2026 blindness and the exact dollar), the tests below
assert nothing that depends on the difference.

Expand All @@ -29,10 +32,11 @@
3. For a filer nobody can claim, the result depends on gross income alone: it
is the same however that income is split between wages and interest.
4. On a 2025 joint return with no other income, where the spouse who can be
claimed is under 65 and not blind, the result is the unearned income test
(over $1,350), whatever the other spouse's age and blindness. (For a
claimed spouse who is 65 or older or blind, section 6012(f)(2)(D) and
Table 2 differ; that is d1395.)
claimed has only interest, the result is section 6012(f)(2)(D)'s unearned
income test (over $1,350), whatever either spouse's age and blindness.
Table 2 would add $1,600 for each of the claimed spouse's age 65 and
blindness; the grid's amounts between $1,350 and those sums tell the two
apart.

`test_filing_requirement_properties` draws batches of tax units with
Hypothesis (single, head of household, surviving spouse, separate and joint;
Expand All @@ -54,11 +58,11 @@
income belongs on the dependent's own return.
9. Table 2 and the Medicaid implementation, as in 1 and 2, on the drawn
single filers who can be claimed and have no self-employment income.
10. Lower bound (2025): on a single, separate or joint return, a head or
spouse who can be claimed and whose interest and dividends are over
Table 2's unearned income amount for them always makes the return
required. Table 2's amount is never below the statute's, so this holds
under either reading of d1395.
10. Lower bound (2025): on a single or separate return, a head or spouse
who can be claimed and whose interest and dividends are over Table 2's
unearned income amount for them always makes the return required. On a
joint return the amount is section 6012(f)(2)(D)'s: the section
63(c)(5)(A) amount, $1,350, with no addition for age or blindness.

`test_earned_income_sources_are_gross_income_sources`:

Expand Down Expand Up @@ -215,16 +219,18 @@ def head_medicaid_requirement(simulation, units, year):


def unearned_test_met(units):
"""Whether Table 2's unearned income test is met by a head or spouse who
can be claimed, for single, separate and joint returns (2025 amounts)."""
"""Whether the unearned income test is met by a head or spouse who can be
claimed (2025 amounts): Table 2's for single and separate returns, and
section 6012(f)(2)(D)'s, with no addition for age or blindness, for joint
returns."""
met = []
for unit in units:
married = unit["filing_status"] in ("SEPARATE", "JOINT")
per_condition = (
TABLE_2_MARRIED_AGED_OR_BLIND_ADDITION
if married
else TABLE_2_AGED_OR_BLIND_ADDITION
)
if unit["filing_status"] == "JOINT":
per_condition = 0
elif unit["filing_status"] == "SEPARATE":
per_condition = TABLE_2_MARRIED_AGED_OR_BLIND_ADDITION
else:
per_condition = TABLE_2_AGED_OR_BLIND_ADDITION
met.append(
unit["filing_status"] in ("SINGLE", "SEPARATE", "JOINT")
and any(
Expand Down Expand Up @@ -292,8 +298,9 @@ def test_table_2_boundary_grid():
single_filer(age, blind, False, wages, gross - wages)
for age, blind, gross, wages in splits
]
# Joint returns whose younger spouse, under 65 and not blind, can be
# claimed and has only interest.
# Joint returns whose spouse can be claimed and has only interest, with
# and without age 65 and blindness: section 6012(f)(2)(D) tests every
# such spouse at the section 63(c)(5)(A) amount alone.
joint = [
{
"filing_status": "JOINT",
Expand All @@ -305,8 +312,8 @@ def test_table_2_boundary_grid():
"taxable_interest_income": 0,
},
{
"age": 40,
"is_blind": False,
"age": spouse_age,
"is_blind": spouse_blind,
"claimed_as_dependent_on_another_return": True,
"taxable_interest_income": interest,
},
Expand All @@ -315,6 +322,8 @@ def test_table_2_boundary_grid():
}
for head_age in (45, 70, 80)
for head_blind in (False, True)
for spouse_age in (40, 65)
for spouse_blind in (False, True)
for interest in UNEARNED_GRID
]
units = claimable + unclaimable + joint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,21 @@
foreign_earned_income_exclusion: 0
ctc_credit_limit_worksheet_b_applies: false

- name: Housing deduction filer cannot use Worksheet B when worksheet deductions offset the stacking amount.
- name: A filer claiming only the housing deduction is not barred, so Worksheet B applies.
# Section 24(d)(3) bars a taxpayer who "elects to exclude any amount from
# gross income under section 911"; the section 911(c)(4)(A) housing
# deduction is a deduction, not an exclusion. The Schedule 8812
# instructions would bar every Form 2555 filer; the model follows the
# statute, and Worksheet B reads the statutory bar.
period: 2025
input:
ctc_qualifying_children: 2
foreign_housing_deduction: 500
foreign_earned_income_exclusion_disallowed_deductions: 500
output:
foreign_earned_income_exclusion: 0
ctc_credit_limit_worksheet_b_applies: false
refundable_ctc_barred_by_section_911_exclusion: false
ctc_credit_limit_worksheet_b_applies: true

- name: Does not apply in 2021, when the CTC was fully refundable.
period: 2021
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,20 @@
section_911_excluded_income: 0
# Without the section 911 bar, the same phase-in gives a $3,400 refund.
refundable_ctc: 0

- name: A filer claiming only the foreign housing deduction gets the refundable credit
# Section 24(d)(3) bars only an election to exclude an amount under section
# 911; the section 911(c)(4)(A) housing deduction is not an exclusion.
# Phase-in: 15% x ($12,500 - $2,500) = $1,500, below the $1,700 refundable
# maximum and the $2,200 credit, with no tax to absorb the credit.
period: 2025
input:
foreign_earned_income_exclusion_amount: 0
foreign_housing_exclusion: 0
foreign_housing_deduction: 1_000
ctc: 2_200
ctc_refundable_maximum: 1_700
ctc_phase_in: 1_500
ctc_limiting_tax_liability: 0
output:
refundable_ctc: 1_500
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,45 @@
output:
foreign_earned_income_exclusion: 0
refundable_ctc_barred_by_section_911_exclusion: true

# Section 911(c)(4)(A) treats the housing amount of a filer without
# employer-provided housing as "a deduction allowable in computing adjusted
# gross income". A filer who claims only that deduction (Form 2555 line 50)
# excludes nothing, so section 24(d)(3) does not bar the refundable credit,
# although the Schedule 8812 instructions bar every Form 2555 filer. The model
# follows the statute.
- name: A housing deduction alone does not bar the refundable credit
period: 2025
input:
foreign_earned_income_exclusion_amount: 0
foreign_housing_exclusion: 0
foreign_housing_deduction: 1_000
output:
refundable_ctc_barred_by_section_911_exclusion: false

- name: A housing deduction alone did not bar the credit in 2015 either
period: 2015
input:
foreign_earned_income_exclusion_amount: 0
foreign_housing_exclusion: 0
foreign_housing_deduction: 1_000
output:
refundable_ctc_barred_by_section_911_exclusion: false

- name: A housing exclusion with a housing deduction is still barred
period: 2025
input:
foreign_earned_income_exclusion_amount: 0
foreign_housing_exclusion: 2_000
foreign_housing_deduction: 1_000
output:
refundable_ctc_barred_by_section_911_exclusion: true

# The legacy aggregates include the housing deduction, so without Form 2555
# leaves a positive aggregate is treated as an exclusion.
- name: A positive legacy aggregate is still treated as an exclusion
period: 2025
input:
foreign_earned_income_exclusion: 1_000
output:
refundable_ctc_barred_by_section_911_exclusion: true
Loading
Loading