diff --git a/changelog.d/eitc-passive-earned-overlap-farm-rent.fixed.md b/changelog.d/eitc-passive-earned-overlap-farm-rent.fixed.md new file mode 100644 index 00000000000..3069917c630 --- /dev/null +++ b/changelog.d/eitc-passive-earned-overlap-farm-rent.fixed.md @@ -0,0 +1 @@ +The EITC investment income test now includes farm rental income and losses in the passive basket, and leaves out passive partnership income or loss that is also earned income (26 U.S.C. 32(i)(2)(E)), entered through the new eitc_passive_income_also_in_earned_income input. diff --git a/policyengine_us/tests/core/test_dependent_eitc_investment_income_invariants.py b/policyengine_us/tests/core/test_dependent_eitc_investment_income_invariants.py index 2e1d60925d7..ef5baf2c040 100644 --- a/policyengine_us/tests/core/test_dependent_eitc_investment_income_invariants.py +++ b/policyengine_us/tests/core/test_dependent_eitc_investment_income_invariants.py @@ -1,4 +1,4 @@ -"""Invariants for dependents' income in the filer's EITC investment income test. +"""Invariants for the EITC investment income test. 26 USC 32(i)(1) denies the credit when the "disqualified income of the taxpayer" exceeds the limit. A tax unit dependent's interest, dividends, @@ -6,7 +6,10 @@ Publication 596 Worksheet 1 picks up a child's interest and dividends only through a Form 8814 election, which the model does not implement. So `eitc_relevant_investment_income` counts the head and spouse only, as -`irs_gross_income` does for AGI. +`irs_gross_income` does for AGI. 32(i)(2)(E) determines passive income and +losses without regard to amounts included in earned income, so the passive +basket removes the signed `eitc_passive_income_also_in_earned_income`, and it +includes farm rental income, which Worksheet 1 lines 11 and 12 name. Hypothesis draws batches of tax units (single, head of household with dependents, joint with and without dependents), and a seeded population of 200 @@ -14,7 +17,8 @@ or more, where float32 rounding of a tax unit total would show. Each batch runs as one vectorized simulation three times: as drawn; with the dependents' investment income inputs set to zero; and with $1,000 more of the head's -interest. For every tax unit: +interest and different partnership self-employment earnings for the head and +spouse. For every tax unit: 1. The dependents' investment income, of any size or sign, never changes the filer's `eitc_relevant_investment_income`, `eitc_investment_income_eligible`, @@ -22,16 +26,19 @@ 2. Differential: `eitc_relevant_investment_income` equals an independent numpy computation of Worksheet 1 over the head and spouse: interest, tax-exempt interest and dividends, plus their Schedule D gain with capital gain - distributions floored at zero (line 7), plus their net rental and passive - income floored at zero (line 13). + distributions floored at zero (line 7), plus their net rental, farm rental + and passive income less the signed part also in earned income, floored at + zero (line 13). 3. Bounds: it is at least the filers' interest and dividends (drawn nonnegative), so never negative, and $1,000 more of the head's interest - raises it by exactly $1,000. + raises it by exactly $1,000 even though the filers' partnership + self-employment earnings change too: only the explicit overlap input moves + passive income out of the basket. 4. For tax units without dependents and without a negative distributions - input, it equals the previous all-member formula, so leaving dependents out - changes nothing for them. (With filer gains in the drawn range. Past about - $16.8 million the previous formula's float32 tax unit total could be off by - a few dollars, which the direct sum corrects.) + input, it equals the same worksheet summed over every member, so leaving + dependents out changes nothing for them. (With filer gains in the drawn + range. Past about $16.8 million the previous formula's float32 tax unit + total could be off by a few dollars, which the direct sum corrects.) The formula reads the head's and spouse's person-level gains, never the tax unit's `net_capital_gains`; a YAML case pins that a supplied tax unit amount is @@ -57,7 +64,13 @@ DISTRIBUTIONS = "non_sch_d_capital_gains" # passive_partnership_s_corp_income is the passive subset of # partnership_s_corp_income; both are drawn together below. -PASSIVE_INPUTS = ["rental_income", "passive_partnership_s_corp_income"] +PASSIVE_INPUTS = [ + "rental_income", + "farm_rent_income", + "passive_partnership_s_corp_income", +] +OVERLAP = "eitc_passive_income_also_in_earned_income" +SE_EARNINGS = "partnership_self_employment_net_earnings" # The filer's amounts, which a dependent's income must not change. FILER_OUTPUTS = [ "eitc_relevant_investment_income", @@ -68,8 +81,10 @@ ] # Sums every member, for the previous formula in property 4. OUTPUTS = FILER_OUTPUTS + ["net_capital_gains"] -# More of the head's interest, in the third run. +# More of the head's interest, and different partnership self-employment +# earnings for the head and spouse, in the third run. INTEREST_SHIFT = 1_000.0 +SE_EARNINGS_SHIFT = 5_000.0 nonnegative = st.one_of(st.just(0.0), st.integers(1, 8_000).map(float)) signed = st.one_of(st.just(0.0), st.integers(-12_000, 12_000).map(float)) @@ -89,8 +104,20 @@ def investment_amounts(draw, dependent=False): # the model floors each person's amount at zero. DISTRIBUTIONS: draw(st.one_of(nonnegative, st.integers(-3_000, -1).map(float))), "rental_income": draw(signed), + "farm_rent_income": draw(signed), "partnership_s_corp_income": passive, "passive_partnership_s_corp_income": passive, + # None, all, half or an unrelated signed amount of the passive share + # is also earned income; the formula removes exactly this amount. + OVERLAP: draw( + st.one_of( + st.just(0.0), + st.just(passive), + st.just(float(round(passive / 2))), + signed, + ) + ), + SE_EARNINGS: draw(signed), } @@ -126,13 +153,22 @@ def capital(): return some(-12_000, 12_000) passive = some(-12_000, 12_000) + overlap = rng.choice(["none", "all", "half", "other"]) return { **{name: some(1, 8_000) for name in PORTFOLIO_INPUTS}, **{name: capital() for name in CAPITAL_INPUTS}, DISTRIBUTIONS: some(-3_000, 8_000), "rental_income": some(-12_000, 12_000), + "farm_rent_income": some(-12_000, 12_000, 0.3), "partnership_s_corp_income": passive, "passive_partnership_s_corp_income": passive, + OVERLAP: { + "none": 0.0, + "all": passive, + "half": float(round(passive / 2)), + "other": some(-12_000, 12_000), + }[overlap], + SE_EARNINGS: some(-12_000, 12_000), } @@ -161,7 +197,7 @@ def _seeded_units(n=200): return units -def _situation(units, year, *, zero_dependents=False, interest_shift=0.0): +def _situation(units, year, *, zero_dependents=False, shift=False): people, groups = {}, {"tax_units": {}, "households": {}, "marital_units": {}} for i, u in enumerate(units): head = f"head_{i}" @@ -171,10 +207,10 @@ def _situation(units, year, *, zero_dependents=False, interest_shift=0.0): "is_tax_unit_dependent": False, "employment_income": u["head_wages"], **u["head"], - "taxable_interest_income": ( - u["head"]["taxable_interest_income"] + interest_shift - ), } + if shift: + people[head]["taxable_interest_income"] += INTEREST_SHIFT + people[head][SE_EARNINGS] += SE_EARNINGS_SHIFT members, couple = [head], [head] if u["spouse"] is not None: spouse = f"spouse_{i}" @@ -184,6 +220,8 @@ def _situation(units, year, *, zero_dependents=False, interest_shift=0.0): "is_tax_unit_dependent": False, **u["spouse"], } + if shift: + people[spouse][SE_EARNINGS] -= SE_EARNINGS_SHIFT members.append(spouse) couple.append(spouse) groups["marital_units"][f"couple_{i}"] = {"members": couple} @@ -247,7 +285,7 @@ def _unit_sum(run, values): def _check(units, year): run = _run(units, year) zeroed = _run(units, year, zero_dependents=True) - shifted = _run(units, year, interest_shift=INTEREST_SHIFT) + shifted = _run(units, year, shift=True) filer = ~_by_person(units, "is_dependent") assert np.array_equal(~run["is_dependent"], filer) has_dependent = _unit_sum(run, (~filer).astype(float)) > 0 @@ -267,8 +305,23 @@ def filer_sum(names): run, filer * np.maximum(0, _by_person(units, DISTRIBUTIONS)) ) capital = np.maximum(0, filer_sum(CAPITAL_INPUTS) + distributions) - passive = np.maximum(0, filer_sum(PASSIVE_INPUTS)) + passive = np.maximum(0, filer_sum(PASSIVE_INPUTS) - filer_sum([OVERLAP])) expected = portfolio + capital + passive + # 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 + + 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)) + farm_rent_decides = eligible != (without_farm_rent <= run["limit"]) + overlap_decides = eligible != (without_overlap <= run["limit"]) investment_income = run["eitc_relevant_investment_income"] np.testing.assert_allclose(investment_income, expected, atol=TOLERANCE) np.testing.assert_array_equal( @@ -277,7 +330,8 @@ def filer_sum(names): ) assert not (run["eitc"][expected > run["limit"]] > 0).any() - # 3. Bounds and an exact shift in the filer's interest. + # 3. Bounds, and an exact shift in the head's interest while the filers' + # partnership self-employment earnings change too. assert (investment_income >= portfolio - TOLERANCE).all() assert (investment_income >= -TOLERANCE).all() np.testing.assert_allclose( @@ -286,8 +340,8 @@ def filer_sum(names): atol=TOLERANCE, ) - # 4. No dependents and no negative distributions input: the previous - # all-member formula, unchanged. + # 4. No dependents and no negative distributions input: the same worksheet + # summed over every member, unchanged. no_negative_distributions = ( _unit_sum(run, (_by_person(units, DISTRIBUTIONS) < 0).astype(float)) == 0 ) @@ -299,13 +353,18 @@ def filer_sum(names): run["net_capital_gains"] + _unit_sum(run, _by_person(units, DISTRIBUTIONS)), ) + np.maximum( - 0, _unit_sum(run, sum(_by_person(units, n) for n in PASSIVE_INPUTS)) + 0, + _unit_sum( + run, + sum(_by_person(units, n) for n in PASSIVE_INPUTS) + - _by_person(units, OVERLAP), + ), ) ) np.testing.assert_allclose( investment_income[unchanged], all_member[unchanged], atol=TOLERANCE ) - return has_dependent, unchanged + return has_dependent, unchanged, farm_rent_decides, overlap_decides # A batch's cost is mostly per-variable overhead, so each example is a large @@ -326,7 +385,11 @@ def test_dependent_investment_income_stays_off_the_filers_eitc_test(units): @pytest.mark.parametrize("year", [2021, 2025]) def test_seeded_population(year): - has_dependent, unchanged = _check(_seeded_units(), year) + has_dependent, unchanged, farm_rent_decides, overlap_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 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_relevant_investment_income.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_relevant_investment_income.yaml index 5499593bd30..6225bc6902a 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_relevant_investment_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_relevant_investment_income.yaml @@ -354,3 +354,440 @@ # Before: 12,000. eitc_relevant_investment_income: 0 adjusted_gross_income: 0 + +# Passive income that is also earned income, and farm rent (#9635 items 2 +# and 3). 26 USC 32(i)(2)(E) determines passive income and losses "without +# regard to any amount included in earned income under subsection (c)(2)", and +# the Worksheet 1 instructions for lines 11 and 12 leave out "any income (or +# loss) included in your earned income". eitc_passive_income_also_in_earned_income +# is that amount, signed. Lines 11 and 12 name Schedule E line 40, net farm +# rental income or loss from Form 4835, which the model treats as passive. +# +# Unless noted, the household is a 35-year-old head of household in TX for +# 2025 with two qualifying children aged 15 and 5. 2025 amounts (Rev. Proc. +# 2024-40 section 2.06): investment income limit 11,950; maximum credit with +# two children 7,152; phase-out start 23,350; phase-out rate 21.06%. + +- name: A general partner's passive share that is also earned income is left out + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 15_000 + taxable_interest_income: 5_000 + partnership_income: 8_000 + passive_partnership_s_corp_income: 8_000 + partnership_self_employment_net_earnings: 8_000 + eitc_passive_income_also_in_earned_income: 8_000 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + # Line 1 5,000 + line 13 max(0, 8,000 - 8,000) = 5,000. + eitc_relevant_investment_income: 5_000 + eitc_investment_income_eligible: true + # Self-employment tax 8,000 x 0.9235 x 0.153 = 1,130.364, half deducted. + # AGI 15,000 + 5,000 + 8,000 - 565.182 = 27,434.818 exceeds earned income + # 22,434.818, so 7,152 - 0.2106 x (27,434.818 - 23,350) = 6,291.74. + eitc: 6_291.74 + +- name: A general partner's passive loss that is also earned income does not offset rental income + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 20_000 + taxable_interest_income: 9_000 + rental_income: 4_000 + partnership_income: -4_000 + passive_partnership_s_corp_income: -4_000 + partnership_self_employment_net_earnings: -4_000 + eitc_passive_income_also_in_earned_income: -4_000 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + # 9,000 + max(0, 4,000 - 4,000 - (-4,000)) = 13,000. + eitc_relevant_investment_income: 13_000 + eitc_investment_income_eligible: false + eitc: 0 + +- name: A limited partner's passive share stays in the passive basket + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 15_000 + taxable_interest_income: 5_000 + partnership_income: 8_000 + passive_partnership_s_corp_income: 8_000 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + # 26 USC 1402(a)(13) keeps a limited partner's share out of net earnings + # from self-employment, so none of it is earned: 5,000 + 8,000 = 13,000. + eitc_relevant_investment_income: 13_000 + eitc_investment_income_eligible: false + eitc: 0 + +- name: Only the part of a passive share included in earned income is left out + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 15_000 + taxable_interest_income: 5_000 + partnership_income: 8_000 + passive_partnership_s_corp_income: 8_000 + partnership_self_employment_net_earnings: 5_000 + eitc_passive_income_also_in_earned_income: 5_000 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + # 5,000 + (8,000 - 5,000) = 8,000. + eitc_relevant_investment_income: 8_000 + eitc_investment_income_eligible: true + +- name: An unrelated self-employment loss does not change passive income + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 15_000 + taxable_interest_income: 5_000 + partnership_income: 5_000 + passive_partnership_s_corp_income: 8_000 + partnership_self_employment_net_earnings: -3_000 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + # An 8,000 passive limited partnership share and a separate active + # general partnership loss of 3,000; none of the passive share is earned. + # 5,000 + 8,000 = 13,000. + eitc_relevant_investment_income: 13_000 + eitc_investment_income_eligible: false + eitc: 0 + +- name: A spouse's self-employment earnings do not change the head's passive income + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 15_000 + taxable_interest_income: 5_000 + partnership_income: 8_000 + passive_partnership_s_corp_income: 8_000 + spouse: + age: 33 + partnership_income: 8_000 + partnership_self_employment_net_earnings: 8_000 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + marital_units: + couple: + members: [head, spouse] + child1_unit: + members: [child1] + child2_unit: + members: [child2] + households: + household: + members: [head, spouse, child1, child2] + state_code: TX + output: + # 5,000 + the head's 8,000 = 13,000. + eitc_relevant_investment_income: 13_000 + eitc_investment_income_eligible: false + eitc: 0 + +- name: Passive S corporation income is not offset by unrelated partnership earnings + period: 2025 + absolute_error_margin: 0.01 + input: + taxable_interest_income: 5_000 + s_corp_income: 8_000 + partnership_income: 8_000 + passive_partnership_s_corp_income: 8_000 + partnership_self_employment_net_earnings: 8_000 + output: + # The passive 8,000 is the S corporation share; the partnership's 8,000 + # of self-employment earnings is not passive. Overlap 0: 5,000 + 8,000. + eitc_relevant_investment_income: 13_000 + eitc_investment_income_eligible: false + +- name: Earned overlap can exceed net passive income + period: 2025 + absolute_error_margin: 0.01 + input: + taxable_interest_income: 9_000 + rental_income: 4_000 + partnership_income: 3_000 + passive_partnership_s_corp_income: 3_000 + partnership_self_employment_net_earnings: 8_000 + eitc_passive_income_also_in_earned_income: 8_000 + output: + # The passive 3,000 is an earned profit of 8,000 and an unrelated passive + # loss of 5,000: 9,000 + max(0, 4,000 + 3,000 - 8,000) = 9,000. + eitc_relevant_investment_income: 9_000 + eitc_investment_income_eligible: true + +- name: Removing an earned loss keeps an unrelated passive profit + period: 2025 + absolute_error_margin: 0.01 + input: + taxable_interest_income: 9_000 + partnership_income: -3_000 + passive_partnership_s_corp_income: -3_000 + partnership_self_employment_net_earnings: -8_000 + eitc_passive_income_also_in_earned_income: -8_000 + output: + # 9,000 + max(0, -3,000 - (-8,000)) = 14,000. + eitc_relevant_investment_income: 14_000 + eitc_investment_income_eligible: false + +- name: Farm rental income is passive income + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 20_000 + taxable_interest_income: 8_000 + farm_rent_income: 5_000 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + # Line 11 5,000: 8,000 + 5,000 = 13,000. + eitc_relevant_investment_income: 13_000 + eitc_investment_income_eligible: false + eitc: 0 + +- name: A farm rental loss nets against rental income in the passive basket + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 20_000 + taxable_interest_income: 9_000 + rental_income: 4_000 + farm_rent_income: -4_000 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + # Lines 11 and 12 are 4,000 and -4,000, so line 13 is 0: 9,000. + eitc_relevant_investment_income: 9_000 + eitc_investment_income_eligible: true + # AGI 20,000 + 9,000 + 4,000 - 4,000 = 29,000: + # 7,152 - 0.2106 x (29,000 - 23,350) = 5,962.11. + adjusted_gross_income: 29_000 + eitc: 5_962.11 + +- name: Farm rent at the 2025 investment income limit + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 20_000 + farm_rent_income: 11_950 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + eitc_relevant_investment_income: 11_950 + eitc_investment_income_eligible: true + # 7,152 - 0.2106 x (31,950 - 23,350) = 5,340.84. + eitc: 5_340.84 + +- name: Farm rent one dollar over the 2025 investment income limit + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 20_000 + farm_rent_income: 11_951 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + eitc_relevant_investment_income: 11_951 + eitc_investment_income_eligible: false + eitc: 0 + +- name: A spouse's passive share that is also earned income is left out on a joint return + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 15_000 + taxable_interest_income: 5_000 + spouse: + age: 33 + partnership_income: 8_000 + passive_partnership_s_corp_income: 8_000 + partnership_self_employment_net_earnings: 8_000 + eitc_passive_income_also_in_earned_income: 8_000 + child1: + age: 15 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + marital_units: + couple: + members: [head, spouse] + child1_unit: + members: [child1] + child2_unit: + members: [child2] + households: + household: + members: [head, spouse, child1, child2] + state_code: TX + output: + # The spouse's 8,000 is earned: 5,000 + max(0, 8,000 - 8,000) = 5,000. + eitc_relevant_investment_income: 5_000 + eitc_investment_income_eligible: true + # Self-employment tax 8,000 x 0.9235 x 0.153 = 1,130.364, half deducted. + # Earned income 22,434.818 reaches the 17,880 earned income amount, so the + # phase-in gives the 7,152 maximum; AGI 27,434.818 is under the 30,470 + # joint phase-out start, so there is no reduction. + eitc: 7_152 + +- name: A dependent's overlap input does not remove the filer's passive income + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 20_000 + taxable_interest_income: 9_000 + partnership_income: 4_000 + passive_partnership_s_corp_income: 4_000 + child1: + age: 15 + is_tax_unit_dependent: true + partnership_income: 4_000 + passive_partnership_s_corp_income: 4_000 + partnership_self_employment_net_earnings: 4_000 + eitc_passive_income_also_in_earned_income: 4_000 + child2: + age: 5 + tax_units: + tax_unit: + members: [head, child1, child2] + households: + household: + members: [head, child1, child2] + state_code: TX + output: + # The head is a limited partner with 4,000 of passive income; the child's + # amounts, overlap included, are on the child's own return: + # 9,000 + 4,000 = 13,000. Counting the child's overlap would give 9,000. + eitc_relevant_investment_income: 13_000 + eitc_investment_income_eligible: false + eitc: 0 diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/eitc_passive_income_also_in_earned_income.py b/policyengine_us/variables/gov/irs/credits/earned_income/eitc_passive_income_also_in_earned_income.py new file mode 100644 index 00000000000..00c5938d42e --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/earned_income/eitc_passive_income_also_in_earned_income.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class eitc_passive_income_also_in_earned_income(Variable): + value_type = float + entity = Person + label = "Passive partnership income or loss also included in EITC earned income" + unit = USD + documentation = ( + "The part of the person's passive_partnership_s_corp_income that is " + "also included in earned income under 26 USC 32(c)(2): a partnership " + "distributive share that is passive under section 469 and net earnings " + "from self-employment under section 1402(a), as for a general partner " + "who does not materially participate. The same amount must also be " + "included in partnership_self_employment_net_earnings, which is what " + "puts it in EITC earned income; nothing here checks that. An S " + "corporation share is never net earnings from self-employment, so it " + "has no such part. Enter income as a positive amount and a loss as a " + "negative amount. It is a sum over the person's activities, so it can " + "exceed the person's net passive income or net partnership " + "self-employment earnings, or have the opposite sign, because each of " + "those nets earned and unrelated passive amounts together; do not " + "limit it to them. 26 USC 32(i)(2)(E) determines passive income and losses without " + "regard to amounts included in earned income, so the head's and " + "spouse's amounts are removed from the passive basket of the EITC " + "investment income test; a dependent's are not read. It classifies " + "part of passive_partnership_s_corp_income and is not additional " + "income." + ) + definition_period = YEAR + default_value = 0 + reference = ( + "https://www.law.cornell.edu/uscode/text/26/32#i_2_E", + "https://www.irs.gov/pub/irs-prior/p596--2025.pdf#page=7", + "https://www.law.cornell.edu/uscode/text/26/1402#a", + ) diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/eitc_relevant_investment_income.py b/policyengine_us/variables/gov/irs/credits/earned_income/eitc_relevant_investment_income.py index b43210665ee..771a6369b24 100644 --- a/policyengine_us/variables/gov/irs/credits/earned_income/eitc_relevant_investment_income.py +++ b/policyengine_us/variables/gov/irs/credits/earned_income/eitc_relevant_investment_income.py @@ -18,7 +18,13 @@ class eitc_relevant_investment_income(Variable): "are the head's and spouse's own long_term_capital_gains and " "short_term_capital_gains; a net_capital_gains amount supplied for " "the tax unit, or a capital_gains amount set directly on a person, " - "is not read." + "is not read. The passive basket (lines 11-13) nets rental income, " + "farm rental income and passive partnership and S corporation income, " + "less eitc_passive_income_also_in_earned_income, then floors the " + "result at zero. All rental and farm rental income is treated as " + "passive: the model has no input for a real estate professional's " + "nonpassive farm rental, whose loss Form 4835 says is not subject to " + "the passive activity loss limitations." ) unit = USD definition_period = YEAR @@ -27,6 +33,7 @@ class eitc_relevant_investment_income(Variable): "https://www.irs.gov/pub/irs-prior/p596--2025.pdf#page=7", "https://www.irs.gov/pub/irs-prior/f8814--2025.pdf#page=1", "https://www.irs.gov/pub/irs-prior/i8814--2025.pdf#page=4", + "https://www.irs.gov/pub/irs-prior/f4835--2025.pdf#page=2", ) def formula(tax_unit, period, parameters): @@ -62,11 +69,26 @@ def formula(tax_unit, period, parameters): * max_(0, person("non_sch_d_capital_gains", period)) ) capital_gains = filer_gains + distributions - # The model's undifferentiated rental input is treated as passive - # rental income, consistently with its NIIT income mapping. Net the - # passive amounts across the head and spouse before applying the zero - # floor. + # Worksheet 1 lines 11-13. The model's undifferentiated rental input is + # treated as passive rental income, consistently with its NIIT income + # mapping. Farm rental income (Form 4835, Schedule E line 40, which + # lines 11 and 12 name) is treated as passive in the same way; Form + # 4835 is for rental activities under the passive activity rules, + # and the model has no input for a real estate professional's + # nonpassive farm rental. 32(i)(2)(E) determines passive income and + # losses without regard to amounts included in earned income, so the + # signed part of passive income also in earned income is removed. Net + # the passive amounts across the head and spouse before applying the + # zero floor. passive_income = tax_unit_non_dep_add( - tax_unit, period, ["rental_income", "passive_partnership_s_corp_income"] + tax_unit, + period, + [ + "rental_income", + "farm_rent_income", + "passive_partnership_s_corp_income", + ], + ) - tax_unit_non_dep_add( + tax_unit, period, ["eitc_passive_income_also_in_earned_income"] ) return portfolio_income + max_(0, capital_gains) + max_(0, passive_income)