diff --git a/changelog.d/fix-uc-exclusion-2020.fixed.md b/changelog.d/fix-uc-exclusion-2020.fixed.md new file mode 100644 index 00000000000..7483ab4ceff --- /dev/null +++ b/changelog.d/fix-uc-exclusion-2020.fixed.md @@ -0,0 +1 @@ +Fixed the 2020 unemployment compensation exclusion: the $150,000 test no longer subtracts the compensation twice, each spouse on a joint return excludes up to $10,200 of their own compensation, and a dependent's compensation is excluded on the dependent's own return instead of using the filers' exclusion. The tax unit's taxable unemployment compensation now leaves out dependents' compensation in every year, so Indiana's unemployment compensation deduction no longer deducts compensation that is not in the filers' federal AGI. A dependent's own estate or trust loss now reduces the modified AGI of the dependent's own return. diff --git a/policyengine_us/parameters/gov/irs/unemployment_compensation/exemption/amount.yaml b/policyengine_us/parameters/gov/irs/unemployment_compensation/exemption/amount.yaml index e26a94f6b66..708b70dc87f 100644 --- a/policyengine_us/parameters/gov/irs/unemployment_compensation/exemption/amount.yaml +++ b/policyengine_us/parameters/gov/irs/unemployment_compensation/exemption/amount.yaml @@ -1,7 +1,14 @@ -description: Unemployment Insurance exemption amount -metadata: - unit: currency-USD +description: The IRS excludes up to this amount of the unemployment compensation each person received from gross income, for each spouse on a joint return, when adjusted gross income is under the cutoff. values: 2013-01-01: 0 2020-01-01: 10_200 2021-01-01: 0 +metadata: + unit: currency-USD + period: year + label: Unemployment compensation exclusion amount per recipient + reference: + - title: 26 U.S. Code § 85(c)(1) - Special rule for 2020 + href: https://www.law.cornell.edu/uscode/text/26/85#c_1 + - title: 2020 Instructions for Form 1040 (rev. 4-2021), Unemployment Compensation Exclusion Worksheet, lines 8 and 9 + href: https://www.irs.gov/pub/irs-prior/i1040gi--2020.pdf#page=88 diff --git a/policyengine_us/parameters/gov/irs/unemployment_compensation/exemption/cutoff.yaml b/policyengine_us/parameters/gov/irs/unemployment_compensation/exemption/cutoff.yaml index ca7a83e4906..eac52054675 100644 --- a/policyengine_us/parameters/gov/irs/unemployment_compensation/exemption/cutoff.yaml +++ b/policyengine_us/parameters/gov/irs/unemployment_compensation/exemption/cutoff.yaml @@ -1,4 +1,4 @@ -description: Unemployment Insurance exemption cutoff +description: The IRS allows the unemployment compensation exclusion when adjusted gross income, figured without unemployment compensation, is under this amount. HEAD_OF_HOUSEHOLD: 2013-01-01: 0 2020-01-01: 150_000 @@ -21,7 +21,13 @@ SURVIVING_SPOUSE: 2021-01-01: 0 metadata: propagate_metadata_to_children: true - label: Unemployment compensation exemption cutoff + label: Unemployment compensation exclusion cutoff + period: year breakdown: - filing_status unit: currency-USD + reference: + - title: 26 U.S. Code § 85(c) - Special rule for 2020 + href: https://www.law.cornell.edu/uscode/text/26/85#c + - title: 2020 Instructions for Form 1040 (rev. 4-2021), Unemployment Compensation Exclusion Worksheet, lines 6 and 7 + href: https://www.irs.gov/pub/irs-prior/i1040gi--2020.pdf#page=88 diff --git a/policyengine_us/tests/core/test_unemployment_compensation_exclusion_invariants.py b/policyengine_us/tests/core/test_unemployment_compensation_exclusion_invariants.py new file mode 100644 index 00000000000..d32544ec140 --- /dev/null +++ b/policyengine_us/tests/core/test_unemployment_compensation_exclusion_invariants.py @@ -0,0 +1,297 @@ +"""Invariants for the 2020 unemployment compensation exclusion, IRC 85(c). + +IRC 85(c)(1): for a taxable year beginning in 2020, a taxpayer whose adjusted +gross income is less than $150,000 excludes up to $10,200 of the unemployment +compensation they received, "or, in the case of a joint return, received by +each spouse". IRC 85(c)(2): that adjusted gross income is figured after +section 86 and without regard to section 85, so without any unemployment +compensation (2020 Unemployment Compensation Exclusion Worksheet, line 6). +A dependent's compensation is on the dependent's own return. + +Hypothesis draws batches of tax units (single, joint, joint with a child or +student dependent, single parent) with income near the $150,000 cutoff. Each +drawn unit appears three times in one vectorized 2020 simulation: as drawn, +with its dependents' compensation set to zero, and with the head's and +spouse's compensation swapped. A 2021 simulation of the drawn units checks +that the exclusion ends. For every person and tax unit: + +1. Bounds: taxable compensation is never negative, and the exempt part + (compensation less taxable) is at most the lesser of what the person + received and $10,200. A net repayment is neither taxable nor excluded; + the exempt part carries it. Nothing is excluded in 2021. +2. Cutoff: the head's and spouse's compensation is excluded only when the + tax unit's `taxable_uc_agi` is under $150,000, a dependent's only when + their own `dependent_taxable_uc_agi` is, and then each person excludes + the lesser of their own compensation and $10,200. +3. Differential: `taxable_uc_agi` equals adjusted gross income less the + taxable compensation on the return, floored at zero. The model figures + AGI without the compensation once from gross income sources and once, + here, from AGI itself; the two must agree, so the compensation is left + out exactly once. +4. Return boundary: the tax unit's taxable compensation is the sum over its + head and spouse, and dependents' compensation never changes the filers' + taxable compensation, `taxable_uc_agi` or AGI. +5. Symmetry: swapping the head's and spouse's compensation swaps their + taxable compensation and leaves the return's total unchanged. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +CUTOFF = 150_000 +AMOUNT = 10_200 +TOLERANCE = dict(rtol=1e-6, atol=0.01) + +PERSON_OUTPUTS = [ + "unemployment_compensation", + "taxable_unemployment_compensation", + "tax_exempt_unemployment_compensation", + "is_tax_unit_dependent", + "dependent_taxable_uc_agi", +] +TAX_UNIT_OUTPUTS = [ + "taxable_uc_agi", + "tax_unit_taxable_unemployment_compensation", + "adjusted_gross_income", +] +VARIANTS = ["drawn", "no_dependent_uc", "swapped"] + +# Amounts that land on and around the cutoff and the exclusion cap. +money = st.one_of( + st.just(0), + st.integers(1, 200_000), + st.sampled_from([CUTOFF - 1, CUTOFF, CUTOFF + 1]), +) +compensation = st.one_of( + st.just(0), + st.integers(1, 30_000), + st.sampled_from([AMOUNT - 1, AMOUNT, AMOUNT + 1]), + # A net repayment. + st.integers(-2_000, -1), +) + + +@st.composite +def adults(draw): + age = draw(st.integers(25, 80)) + return { + "age": age, + "employment_income": draw(money), + "taxable_pension_income": draw(st.sampled_from([0, 20_000, 140_000])), + "social_security_retirement": draw(st.sampled_from([0, 20_000])) + if age >= 62 + else 0, + "early_withdrawal_penalty": draw(st.sampled_from([0, 500])), + "unemployment_compensation": draw(compensation), + } + + +@st.composite +def dependents(draw, students_allowed): + # Qualifying children: their income never decides whether they are + # dependents, so zeroing their compensation keeps the tax unit's shape. + student = students_allowed and draw(st.booleans()) + return { + "age": draw(st.integers(19, 23)) if student else draw(st.integers(0, 17)), + "is_full_time_student": student, + "employment_income": draw(st.sampled_from([0, 8_000])), + "dividend_income": draw(st.sampled_from([0, 160_000])), + "unemployment_compensation": draw(compensation), + } + + +@st.composite +def tax_units(draw): + kind = draw(st.sampled_from(["single", "joint", "joint_child", "parent"])) + head = draw(adults()) + spouse = draw(adults()) if kind.startswith("joint") else None + # A single parent's adult student would be taken for a spouse. + children = ( + [draw(dependents(students_allowed=kind == "joint_child"))] + if kind in ("joint_child", "parent") + else [] + ) + return {"head": head, "spouse": spouse, "children": children} + + +def _variant(unit, variant): + adults_ = [dict(unit["head"])] + ([dict(unit["spouse"])] if unit["spouse"] else []) + children = [dict(child) for child in unit["children"]] + if variant == "no_dependent_uc": + for child in children: + child["unemployment_compensation"] = 0 + if variant == "swapped" and len(adults_) == 2: + first, second = adults_ + first["unemployment_compensation"], second["unemployment_compensation"] = ( + second["unemployment_compensation"], + first["unemployment_compensation"], + ) + return adults_, children + + +def _situation(units, year, variants): + """Each variant holds every drawn unit, in the same order, so a person's + position within a variant is the same in all of them.""" + people, tax_units_, marital_units, households = {}, {}, {}, {} + for variant in variants: + for i, unit in enumerate(units): + adults_, children = _variant(unit, variant) + key = f"{variant}_{i}" + adult_names = [f"{key}_adult{j}" for j in range(len(adults_))] + child_names = [f"{key}_child{j}" for j in range(len(children))] + for name, inputs in zip(adult_names + child_names, adults_ + children): + people[name] = {k: {year: v} for k, v in inputs.items()} + members = adult_names + child_names + tax_units_[key] = {"members": members} + marital_units[key] = {"members": adult_names} + for name in child_names: + marital_units[name] = {"members": [name]} + households[key] = {"members": members, "state_code": {year: "TX"}} + return { + "people": people, + "tax_units": tax_units_, + "marital_units": marital_units, + "households": households, + } + + +def _couples(units): + """Positions, within a variant, of each joint unit's two adults.""" + pairs, position = [], 0 + for unit in units: + if unit["spouse"]: + pairs.append((position, position + 1)) + position += 1 + bool(unit["spouse"]) + len(unit["children"]) + return np.array(pairs, dtype=int).reshape(-1, 2), position + + +def _run(units, year, variants): + sim = Simulation(situation=_situation(units, year, variants)) + run = { + name: np.asarray(sim.calculate(name, year)) + for name in PERSON_OUTPUTS + TAX_UNIT_OUTPUTS + } + run["tax_unit_index"] = sim.populations["tax_unit"].members_entity_id + return run + + +def _check_2020(units): + run = _run(units, 2020, VARIANTS) + received = run["unemployment_compensation"] + exempt = run["tax_exempt_unemployment_compensation"] + taxable = run["taxable_unemployment_compensation"] + dependent = run["is_tax_unit_dependent"] + filer = ~dependent + magi = run["taxable_uc_agi"] + + # 1. Bounds. + cap = np.minimum(np.maximum(received, 0), AMOUNT) + assert (taxable >= 0).all() + assert (exempt <= cap + TOLERANCE["atol"]).all() + np.testing.assert_allclose(taxable + exempt, received, **TOLERANCE) + repayment = received < 0 + np.testing.assert_allclose(exempt[repayment], received[repayment]) + + # 2. Cutoff: the filers' return, and each dependent's own return. + filer_under = magi[run["tax_unit_index"]] < CUTOFF + dependent_under = run["dependent_taxable_uc_agi"] < CUTOFF + under = np.where(dependent, dependent_under, filer_under) + excluded = under * cap + np.testing.assert_allclose(taxable, np.maximum(0, received - excluded), **TOLERANCE) + + # 3. Differential: AGI without the compensation, two ways. + np.testing.assert_allclose( + magi, + np.maximum( + 0, + run["adjusted_gross_income"] + - run["tax_unit_taxable_unemployment_compensation"], + ), + **TOLERANCE, + ) + + # 4. Return boundary: the return sums the filers only, and dependents' + # compensation changes nothing on it. + np.testing.assert_allclose( + run["tax_unit_taxable_unemployment_compensation"], + np.bincount( + run["tax_unit_index"], weights=taxable * filer, minlength=len(magi) + ), + **TOLERANCE, + ) + pairs, people_per_variant = _couples(units) + n = len(units) + drawn, no_dependent_uc, swapped = ( + slice(k * n, (k + 1) * n) for k in range(len(VARIANTS)) + ) + for name in TAX_UNIT_OUTPUTS: + np.testing.assert_allclose( + run[name][no_dependent_uc], run[name][drawn], err_msg=name, **TOLERANCE + ) + people = [ + slice(k * people_per_variant, (k + 1) * people_per_variant) + for k in range(len(VARIANTS)) + ] + drawn_filer = filer[people[0]] + np.testing.assert_allclose( + taxable[people[1]][drawn_filer], taxable[people[0]][drawn_filer], **TOLERANCE + ) + + # 5. Symmetry between spouses. + np.testing.assert_allclose( + run["tax_unit_taxable_unemployment_compensation"][swapped], + run["tax_unit_taxable_unemployment_compensation"][drawn], + **TOLERANCE, + ) + if len(pairs): + for k in (0, 1): + # The swapped variant's adult k received the other adult's + # compensation, so is taxed on it as the other adult was. + np.testing.assert_allclose( + received[people[2]][pairs[:, k]], received[people[0]][pairs[:, 1 - k]] + ) + np.testing.assert_allclose( + taxable[people[2]][pairs[:, k]], + taxable[people[0]][pairs[:, 1 - k]], + **TOLERANCE, + ) + + +def _check_2021(units): + run = _run(units, 2021, ["drawn"]) + received = run["unemployment_compensation"] + # Nothing is excluded: all compensation is taxable, and only a net + # repayment is left in the exempt part. + np.testing.assert_allclose( + run["taxable_unemployment_compensation"], + np.maximum(0, received), + **TOLERANCE, + ) + np.testing.assert_allclose( + run["tax_exempt_unemployment_compensation"], np.minimum(0, received) + ) + + +# A batch's cost is mostly per-variable overhead, so each example is a large +# batch and there are few examples. +SETTINGS = dict( + max_examples=5, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +@settings(**SETTINGS) +@given(st.lists(tax_units(), min_size=10, max_size=30)) +def test_exclusion_invariants_2020(units): + _check_2020(units) + + +@settings(**dict(SETTINGS, max_examples=2)) +@given(st.lists(tax_units(), min_size=10, max_size=30)) +def test_exclusion_ends_after_2020(units): + _check_2021(units) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.yaml index f4eca3c333c..8668b160dca 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.yaml @@ -170,3 +170,63 @@ state_code: TX output: dependent_taxable_ss_magi: [0, 24_000] + +- name: A dependent's own estate or trust loss reduces their modified AGI + # Schedule E Part III carries the loss to Schedule 1 line 5, which AGI + # includes (Pub. 915 Worksheet 1 line 3): 20,000 of wages less a 2,000 + # loss gives modified AGI of 18,000. + period: 2025 + input: + people: + parent: + age: 50 + employment_income: 80_000 + is_tax_unit_head: true + student: + age: 20 + is_full_time_student: true + is_tax_unit_dependent: true + employment_income: 20_000 + estate_income: -2_000 + tax_units: + tax_unit: + members: [parent, student] + households: + household: + members: [parent, student] + state_code: TX + output: + dependent_taxable_ss_magi: [0, 18_000] + +- name: A dependent's business losses are limited by IRC 461(l) on their own return + # 2025 Form 461: business losses are deductible up to business income + # (none here) plus the $313,000 single threshold, so $313,000 of the + # $700,000 estate business loss is allowed. Modified AGI: 400,000 of + # dividends + 1,000 of wages - 313,000 = 88,000. With $20,000 of benefits + # the combined income of 98,000 is far over the adjusted base amount, so + # 85% of the benefits, 17,000, is taxable. + period: 2025 + input: + people: + parent: + age: 50 + employment_income: 80_000 + is_tax_unit_head: true + student: + age: 20 + is_full_time_student: true + is_tax_unit_dependent: true + employment_income: 1_000 + dividend_income: 400_000 + estate_income: -700_000 + social_security_survivors: 20_000 + tax_units: + tax_unit: + members: [parent, student] + households: + household: + members: [parent, student] + state_code: TX + output: + dependent_taxable_ss_magi: [0, 88_000] + dependent_taxable_social_security: [0, 17_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_unemployment_compensation_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_unemployment_compensation_deduction.yaml index 20988c0e20b..632019a676a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_unemployment_compensation_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_unemployment_compensation_deduction.yaml @@ -7,3 +7,60 @@ tax_unit_taxable_unemployment_compensation: 3_000 output: in_unemployment_compensation_deduction: 2_000 + +- name: Case 2, a spouse's compensation in federal AGI is deductible. + period: 2024 + input: + people: + person1: + age: 45 + employment_income: 10_000 + person2: + age: 43 + unemployment_compensation: 5_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: IN + output: + # IC 6-3-2-10(a): federal AGI 15,000 less 18,000 is below zero, so the + # Indiana-taxable part is 0 and all 5,000 in federal AGI is deducted. + adjusted_gross_income: 15_000 + tax_unit_taxable_unemployment_compensation: 5_000 + in_unemployment_compensation_deduction: 5_000 + +- name: Case 3, a dependent's compensation is not in the filers' federal AGI, so it is not deducted. + period: 2024 + input: + people: + person1: + age: 45 + employment_income: 10_000 + person2: + age: 43 + person3: + age: 17 + unemployment_compensation: 5_000 + tax_units: + tax_unit: + members: [person1, person2, person3] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2, person3] + state_code: IN + output: + # The dependent's 5,000 is on the dependent's own return, not in the + # filers' federal AGI of 10,000, so the filers deduct none of it. + is_tax_unit_dependent: [false, false, true] + adjusted_gross_income: 10_000 + tax_unit_taxable_unemployment_compensation: 0 + in_unemployment_compensation_deduction: 0 diff --git a/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/dependent_taxable_uc_agi.yaml b/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/dependent_taxable_uc_agi.yaml new file mode 100644 index 00000000000..4b4020dc626 --- /dev/null +++ b/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/dependent_taxable_uc_agi.yaml @@ -0,0 +1,132 @@ +# A dependent's IRC 85(c)(2) adjusted gross income on their own return: line 6 +# of the 2020 Unemployment Compensation Exclusion Worksheet (Instructions for +# Form 1040, rev. 4-2021, p. 88), figured with the dependent's own income. +- name: Case 1, the dependent's own income less their own adjustments, without the compensation. + period: 2020 + input: + people: + person1: + age: 45 + employment_income: 50_000 + person2: + age: 19 + is_full_time_student: true + employment_income: 12_000 + early_withdrawal_penalty: 100 + unemployment_compensation: 4_000 + person3: + age: 43 + tax_units: + tax_unit: + members: [person1, person3, person2] + marital_units: + marital_unit: + members: [person1, person3] + households: + household: + members: [person1, person3, person2] + state_code: TX + output: + # Line 1: 12,000 of wages. Line 5: 100. Line 6: 11,900. The head and + # spouse are not dependents and are tested on the tax unit's return. + is_tax_unit_dependent: [false, true, false] + dependent_taxable_uc_agi: [0, 11_900, 0] + +- name: Case 2, the dependent's Social Security is taxable on the full compensation. + period: 2020 + input: + people: + person1: + age: 45 + employment_income: 50_000 + person2: + age: 19 + is_full_time_student: true + employment_income: 30_000 + social_security_survivors: 10_000 + unemployment_compensation: 6_000 + person3: + age: 43 + tax_units: + tax_unit: + members: [person1, person3, person2] + marital_units: + marital_unit: + members: [person1, person3] + households: + household: + members: [person1, person3, person2] + state_code: TX + output: + # Social Security Benefits Worksheet (p. 31), unmarried: line 3, 30,000 + # + 6,000 = 36,000; line 5, 36,000 + 5,000 = 41,000. Taxable: the lesser + # of 85% x (41,000 - 34,000) + 4,500 = 10,450 and 85% x 10,000 = 8,500. + # Exclusion worksheet line 6: 30,000 + 8,500 = 38,500. + is_tax_unit_dependent: [false, true, false] + dependent_taxable_social_security: [0, 8_500, 0] + dependent_taxable_uc_agi: [0, 38_500, 0] + +- name: Case 3, an estate or trust loss reduces the dependent's own AGI below the cutoff. + period: 2020 + input: + people: + person1: + age: 45 + employment_income: 50_000 + person2: + age: 20 + is_full_time_student: true + dividend_income: 150_000 + estate_income: -1_000 + unemployment_compensation: 5_000 + person3: + age: 43 + tax_units: + tax_unit: + members: [person1, person3, person2] + marital_units: + marital_unit: + members: [person1, person3] + households: + household: + members: [person1, person3, person2] + state_code: TX + output: + # Line 1: Form 1040 line 3b dividends, 150,000. Line 2: Schedule 1 line + # 5, the Schedule E Part III loss, -1,000. Line 6: 149,000, under the + # cutoff, so the dependent excludes all 5,000. + is_tax_unit_dependent: [false, true, false] + dependent_taxable_uc_agi: [0, 149_000, 0] + taxable_unemployment_compensation: [0, 0, 0] + tax_exempt_unemployment_compensation: [0, 5_000, 0] + +- name: Case 4, the IRC 461(l) limit is suspended for 2020, so the whole loss counts. + period: 2020 + input: + people: + person1: + age: 45 + employment_income: 50_000 + person2: + age: 20 + is_full_time_student: true + dividend_income: 150_000 + estate_income: -200_000 + unemployment_compensation: 5_000 + person3: + age: 43 + tax_units: + tax_unit: + members: [person1, person3, person2] + marital_units: + marital_unit: + members: [person1, person3] + households: + household: + members: [person1, person3, person2] + state_code: TX + output: + # CARES Act section 2304 suspends the excess business loss limit through + # 2020. Line 6: 150,000 - 200,000 = -50,000, under the cutoff. + dependent_taxable_uc_agi: [0, -50_000, 0] + taxable_unemployment_compensation: [0, 0, 0] diff --git a/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/tax_unit_taxable_unemployment_compensation.yaml b/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/tax_unit_taxable_unemployment_compensation.yaml index bb05654b3f4..fd48121bc12 100644 --- a/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/tax_unit_taxable_unemployment_compensation.yaml +++ b/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/tax_unit_taxable_unemployment_compensation.yaml @@ -1,37 +1,52 @@ # 26 USC 85(c)(1) excludes up to $10,200 of 2020 unemployment compensation -# only if AGI (determined without regard to unemployment compensation) is -# strictly less than $150,000. -- name: No exclusion in 2020 when AGI over UC equals the $150,000 cutoff exactly +# only if AGI, determined without regard to section 85 and so without any +# unemployment compensation (85(c)(2)(B)), is strictly less than $150,000. +# 2020 Unemployment Compensation Exclusion Worksheet (Instructions for Form +# 1040, rev. 4-2021, p. 88), lines 6 and 7. +- name: Case 1, no exclusion when AGI without the compensation equals the cutoff. period: 2020 input: people: head: age: 40 - employment_income: 155_000 + employment_income: 150_000 unemployment_compensation: 5_000 tax_units: tax_unit: members: [head] filing_status: SINGLE - marital_units: - marital_unit: - members: [head] - families: - family: + households: + household: members: [head] - spm_units: - spm_unit: + state_code: TX + output: + # Line 6: 150,000 of wages. Line 7: $150,000 or more, so all of the + # compensation is taxable. + taxable_uc_agi: 150_000 + tax_unit_taxable_unemployment_compensation: 5_000 + +- name: Case 2, full exclusion when AGI without the compensation is one dollar below the cutoff. + period: 2020 + input: + people: + head: + age: 40 + employment_income: 149_999 + unemployment_compensation: 5_000 + tax_units: + tax_unit: members: [head] + filing_status: SINGLE households: household: members: [head] state_code: TX output: - # AGI over UC = 155,000 - 5,000 = 150,000, not below the cutoff, - # so all unemployment compensation is taxable. - tax_unit_taxable_unemployment_compensation: 5_000 + # Line 6: 149,999, under the cutoff. Line 8: 5,000. + taxable_uc_agi: 149_999 + tax_unit_taxable_unemployment_compensation: 0 -- name: Full exclusion in 2020 when AGI over UC is one dollar below the cutoff +- name: Case 3, the compensation itself does not count toward the cutoff. period: 2020 input: people: @@ -43,20 +58,45 @@ tax_unit: members: [head] filing_status: SINGLE + households: + household: + members: [head] + state_code: TX + output: + # Line 6: 154,999, the wages alone. Subtracting the 5,000 of + # compensation from it (149,999) would wrongly allow the exclusion. + taxable_uc_agi: 154_999 + tax_unit_taxable_unemployment_compensation: 5_000 + +- name: Case 4, the joint return sums each spouse's taxable compensation and leaves out a dependent's. + period: 2020 + input: + people: + head: + age: 45 + employment_income: 40_000 + unemployment_compensation: 15_000 + spouse: + age: 43 + unemployment_compensation: 12_000 + child: + age: 17 + unemployment_compensation: 11_000 + tax_units: + tax_unit: + members: [head, spouse, child] marital_units: marital_unit: - members: [head] - families: - family: - members: [head] - spm_units: - spm_unit: - members: [head] + members: [head, spouse] households: household: - members: [head] + members: [head, spouse, child] state_code: TX output: - # AGI over UC = 154,999 - 5,000 = 149,999, below the $150,000 cutoff, - # so unemployment compensation up to $10,200 is excluded. - tax_unit_taxable_unemployment_compensation: 0 + # Line 6: 40,000. Line 8: 10,200. Line 9: 10,200. Line 10: 20,400 of + # the couple's 27,000, so 6,600 is taxable on their return. The child's + # 11,000 (800 taxable after the child's own exclusion) is on the + # child's return. + taxable_unemployment_compensation: [4_800, 1_800, 800] + tax_unit_taxable_unemployment_compensation: 6_600 + adjusted_gross_income: 46_600 diff --git a/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_unemployment_compensation.yaml b/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_unemployment_compensation.yaml index 964a4f25cc0..43479af7874 100644 --- a/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_unemployment_compensation.yaml +++ b/policyengine_us/tests/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_unemployment_compensation.yaml @@ -1,4 +1,16 @@ -- name: Taxable UC allocated to spouse when only spouse received UC +# Each person's taxable unemployment compensation is their own compensation +# less the part IRC 85(c) excludes on the return it is reported on. The 2020 +# cases follow the 2020 Unemployment Compensation Exclusion Worksheet +# (Instructions for Form 1040, rev. 4-2021, p. 88): +# lines 1-4: total income without unemployment compensation (Form 1040 +# lines 1-7, Schedule 1 lines 1-6 and 8) +# line 5: adjustments (Form 1040 line 10c) +# line 6: modified AGI = line 4 - line 5 +# line 7: no exclusion when line 6 is $150,000 or more +# lines 8-9: compensation paid to you, and on a joint return to your +# spouse, each up to $10,200 +# line 10: exclusion = line 8 + line 9 +- name: Case 1, taxable compensation stays with the spouse who received it. period: 2025 input: people: @@ -27,7 +39,7 @@ output: taxable_unemployment_compensation: [0, 19_000] -- name: Taxable UC split in proportion to person-level UC +- name: Case 2, outside 2020 each spouse's compensation is fully taxable. period: 2025 input: people: @@ -56,7 +68,7 @@ output: taxable_unemployment_compensation: [5_000, 15_000] -- name: Zero UC produces zero allocation +- name: Case 3, no compensation means no taxable compensation. period: 2025 input: people: @@ -82,3 +94,339 @@ state_code: CA output: taxable_unemployment_compensation: [0] + +- name: Case 4, modified AGI of exactly 150,000 without the compensation allows no exclusion. + period: 2020 + input: + people: + person1: + age: 66 + taxable_pension_income: 150_000 + unemployment_compensation: 8_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Worksheet line 1: Form 1040 line 5b pensions, 150,000; lines 2, 3 and + # 5: 0; line 6: 150,000. Line 7: 150,000 or more, so nothing is + # excluded. The compensation is not subtracted from line 6 again. + taxable_uc_agi: 150_000 + taxable_unemployment_compensation: [8_000] + tax_exempt_unemployment_compensation: [0] + tax_unit_taxable_unemployment_compensation: 8_000 + adjusted_gross_income: 158_000 + +- name: Case 5, modified AGI one dollar under 150,000 allows the exclusion. + period: 2020 + input: + people: + person1: + age: 66 + taxable_pension_income: 149_999 + unemployment_compensation: 8_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Line 6: 149,999, under 150,000. Line 8: 8,000 (under 10,200). Line 10: + # 8,000, all of Schedule 1 line 7. + taxable_uc_agi: 149_999 + taxable_unemployment_compensation: [0] + tax_exempt_unemployment_compensation: [8_000] + tax_unit_taxable_unemployment_compensation: 0 + adjusted_gross_income: 149_999 + +- name: Case 6, each spouse on a joint return excludes up to 10,200 of their own compensation. + period: 2020 + input: + people: + person1: + age: 45 + employment_income: 60_000 + unemployment_compensation: 20_000 + person2: + age: 43 + unemployment_compensation: 5_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # The example in the 2020 Form 1040 instructions (p. 87): "You were paid + # $20,000 of unemployment compensation and your spouse was paid $5,000. + # Report the $25,000 ... on line 7 and report $15,200 on line 8 as a + # negative amount." Line 6: 60,000. Line 8: min(20,000, 10,200) = + # 10,200. Line 9: min(5,000, 10,200) = 5,000. Line 10: 15,200. + filing_status: JOINT + taxable_uc_agi: 60_000 + taxable_unemployment_compensation: [9_800, 0] + tax_exempt_unemployment_compensation: [10_200, 5_000] + tax_unit_taxable_unemployment_compensation: 9_800 + adjusted_gross_income: 69_800 + +- name: Case 7, two spouses over 10,200 each exclude 10,200. + period: 2020 + input: + people: + person1: + age: 45 + unemployment_compensation: 20_000 + person2: + age: 43 + unemployment_compensation: 20_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # Line 6: 0. Lines 8 and 9: 10,200 each. Line 10: 20,400 of 40,000. + taxable_uc_agi: 0 + taxable_unemployment_compensation: [9_800, 9_800] + tax_exempt_unemployment_compensation: [10_200, 10_200] + tax_unit_taxable_unemployment_compensation: 19_600 + adjusted_gross_income: 19_600 + +- name: Case 8, a dependent's compensation does not use the filers' exclusion. + period: 2020 + input: + people: + person1: + age: 45 + employment_income: 30_000 + person2: + age: 43 + unemployment_compensation: 8_000 + person3: + age: 20 + is_full_time_student: true + unemployment_compensation: 4_000 + tax_units: + tax_unit: + members: [person1, person2, person3] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # The couple's worksheet: line 6, 30,000; line 8, 0; line 9, 8,000 (the + # spouse's own compensation); line 10, 8,000, so none of their 8,000 is + # taxable. The dependent's 4,000 is on the dependent's own return, where + # the dependent excludes all of it (own modified AGI 0). + is_tax_unit_dependent: [false, false, true] + taxable_uc_agi: 30_000 + dependent_taxable_uc_agi: [0, 0, 0] + taxable_unemployment_compensation: [0, 0, 0] + tax_exempt_unemployment_compensation: [0, 8_000, 4_000] + tax_unit_taxable_unemployment_compensation: 0 + adjusted_gross_income: 30_000 + +- name: Case 9, a dependent excludes up to 10,200 on their own return. + period: 2020 + input: + people: + person1: + age: 45 + employment_income: 30_000 + person2: + age: 43 + person3: + age: 20 + is_full_time_student: true + unemployment_compensation: 15_000 + tax_units: + tax_unit: + members: [person1, person2, person3] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + # The dependent's own worksheet: line 6, 0; line 8, min(15,000, 10,200) + # = 10,200; 4,800 is taxable on the dependent's return. None of it is on + # the filers' return. + is_tax_unit_dependent: [false, false, true] + taxable_unemployment_compensation: [0, 0, 4_800] + tax_exempt_unemployment_compensation: [0, 0, 10_200] + tax_unit_taxable_unemployment_compensation: 0 + adjusted_gross_income: 30_000 + +- name: Case 10, a dependent with modified AGI of 150,000 excludes nothing. + period: 2020 + input: + people: + person1: + age: 45 + employment_income: 30_000 + unemployment_compensation: 5_000 + person2: + age: 43 + person3: + age: 20 + is_full_time_student: true + dividend_income: 150_000 + unemployment_compensation: 5_000 + tax_units: + tax_unit: + members: [person1, person2, person3] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + # The dependent's own worksheet: line 1, Form 1040 line 3b dividends, + # 150,000; line 6, 150,000, so the dependent excludes nothing. The + # filers' worksheet: line 6, 30,000 (the dependent's dividends are on + # the dependent's return); line 8, 5,000; nothing taxable. + is_tax_unit_dependent: [false, false, true] + taxable_uc_agi: 30_000 + dependent_taxable_uc_agi: [0, 0, 150_000] + taxable_unemployment_compensation: [0, 0, 5_000] + tax_exempt_unemployment_compensation: [5_000, 0, 0] + tax_unit_taxable_unemployment_compensation: 0 + adjusted_gross_income: 30_000 + +- name: Case 11, modified AGI counts Social Security made taxable by the full compensation. + period: 2020 + input: + people: + person1: + age: 66 + taxable_pension_income: 140_000 + social_security_retirement: 20_000 + unemployment_compensation: 10_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + # IRC 85(c)(2)(A): AGI after section 86. Social Security Benefits + # Worksheet (p. 31) line 3 includes the full compensation: 140,000 + + # 10,000 = 150,000; line 5: 150,000 + 10,000 (half the benefits) = + # 160,000, so 85% of the benefits, 17,000, is taxable. Exclusion + # worksheet line 1: 140,000 + 17,000 = 157,000 = line 6, 150,000 or + # more, so nothing is excluded. + tax_unit_taxable_social_security: 17_000 + taxable_uc_agi: 157_000 + taxable_unemployment_compensation: [10_000] + tax_unit_taxable_unemployment_compensation: 10_000 + adjusted_gross_income: 167_000 + +- name: Case 12, adjustments to income reduce modified AGI. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 150_400 + early_withdrawal_penalty: 500 + unemployment_compensation: 5_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + # Line 4: 150,400. Line 5: Schedule 1 line 17 penalty on early + # withdrawal of savings, 500. Line 6: 149,900, under 150,000. Line 8: + # 5,000. + taxable_uc_agi: 149_900 + taxable_unemployment_compensation: [0] + adjusted_gross_income: 149_900 + +- name: Case 13, the exclusion ends after 2020. + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 30_000 + unemployment_compensation: 8_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + taxable_unemployment_compensation: [8_000] + tax_exempt_unemployment_compensation: [0] + tax_unit_taxable_unemployment_compensation: 8_000 + +- name: Case 14, a net repayment is neither taxable nor excluded. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 30_000 + unemployment_compensation: -500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + # irs_gross_income counts positive amounts only, so a net repayment + # never reduces gross income and none of it is taxable. The exempt + # amount carries it, as states that subtract repayments read it. + taxable_unemployment_compensation: [0] + tax_exempt_unemployment_compensation: [-500] + adjusted_gross_income: 30_000 + +- name: Case 15, a net repayment outside 2020 is not taxable either. + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 30_000 + unemployment_compensation: -500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + taxable_unemployment_compensation: [0] + tax_exempt_unemployment_compensation: [-500] + tax_unit_taxable_unemployment_compensation: 0 + adjusted_gross_income: 30_000 diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.py index 1b712f483cb..aa51a392aad 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.py @@ -11,6 +11,8 @@ "farm_operations_income", "farm_rent_income", "rental_income", + # Schedule E Part III estate and trust losses (limited_business_loss). + "estate_income", ] # Above-the-line deductions the model can attribute to one person, keyed by @@ -32,8 +34,9 @@ def dependent_income_and_deductions(person, period, parameters): """A dependent's own-return income before above-the-line deductions, and the person-level above-the-line deductions to subtract from it. - Income nets business losses and capital losses (within the IRC 1211(b) - limit) and leaves out Social Security, as IRC 86(b)(2) requires. + Income nets business losses (within the IRC 461(l) limit) and capital + losses (within the IRC 1211(b) limit) and leaves out Social Security, as + IRC 86(b)(2) requires. """ p = parameters(period).gov.irs sources = p.gross_income.sources @@ -47,7 +50,13 @@ def dependent_income_and_deductions(person, period, parameters): "capital_gains" in sources and "non_sch_d_capital_gains" in sources ) + # A dependent who lives with their spouse files separately; any other + # files as single. + lives_with_spouse = person("dependent_lives_with_spouse", period) + income = 0 + business_income = 0 + business_loss = 0 for source in sources: components = DEPENDENT_GROSS_INCOME_SOURCE_OVERRIDES.get(source, [source]) for component in components: @@ -55,14 +64,26 @@ def dependent_income_and_deductions(person, period, parameters): continue amount = add(person, period, [component]) if deduct_losses and component in BUSINESS_LOSS_SOURCES: - income += amount + business_income += max_(0, amount) + business_loss += max_(0, -amount) else: income += max_(0, amount) + if deduct_losses: + # IRC 461(l), as limited_business_loss applies it to the filers: + # business losses are deductible up to business income plus the + # threshold, which is infinite while the limit is suspended (2018 + # through 2020). + threshold = where( + lives_with_spouse, + p.ald.loss.max["SEPARATE"], + p.ald.loss.max["SINGLE"], + ) + income += business_income - min_(business_loss, business_income + threshold) + if "capital_gains" in sources: # IRC 1211(b): net capital losses are deductible up to the limit, # which is lower for a married individual filing separately. - lives_with_spouse = person("dependent_lives_with_spouse", period) capital_loss_limit = p.ald.loss.capital.max loss_limit = deduct_losses * where( lives_with_spouse, @@ -97,8 +118,8 @@ class dependent_taxable_ss_magi(Variable): relative gross income test, losses and above-the-line deductions reduce it. Above-the-line deductions the model records only for the tax unit (health savings account contributions) are not attributed to - dependents, and the IRC 461(l) excess business loss limit is not - applied. + dependents. The IRC 461(l) excess business loss limit applies to the + dependent's own business income and losses. """ definition_period = YEAR defined_for = "is_tax_unit_dependent" diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/dependent_taxable_uc_agi.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/dependent_taxable_uc_agi.py new file mode 100644 index 00000000000..64468c4dbc9 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/dependent_taxable_uc_agi.py @@ -0,0 +1,46 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.income.taxable_income.adjusted_gross_income.irs_gross_income.social_security.dependent_taxable_ss_magi import ( + dependent_income_and_deductions, +) + + +class dependent_taxable_uc_agi(Variable): + value_type = float + entity = Person + label = ( + "Dependent's adjusted gross income for the unemployment compensation exclusion" + ) + unit = USD + documentation = """ + The adjusted gross income of IRC 85(c)(2), figured on a dependent's own + return: adjusted gross income after taxable Social Security and the + dependent's own above-the-line deductions, without any unemployment + compensation. It is taxable_uc_agi for a dependent, whose income + irs_gross_income leaves off the tax unit's return. + """ + definition_period = YEAR + defined_for = "is_tax_unit_dependent" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/85#c_2", + # 2020 Instructions for Form 1040 (rev. 4-2021), Unemployment + # Compensation Exclusion Worksheet, lines 1 through 6. + "https://www.irs.gov/pub/irs-prior/i1040gi--2020.pdf#page=88", + ) + + def formula(person, period, parameters): + # The dependent's own income, with unemployment compensation in full + # and without Social Security, and their own above-the-line + # deductions. Of the deductions that list leaves out (IRC 86(b)(2)), + # IRC 85(c)(2)(A) applies sections 135, 137 and 221, but none of them + # is the dependent's: the model records the section 135 and 137 + # exclusions for the filer (gov.irs.ald.filer_amounts_recorded_on_ + # dependents), and IRC 221(c) denies a dependent the student loan + # interest deduction. + income, deductions = dependent_income_and_deductions(person, period, parameters) + # IRC 85(c)(2)(B): without regard to section 85, so without the + # compensation (worksheet line 2 leaves out Schedule 1, line 7). + income -= max_(0, person("unemployment_compensation", period)) + # IRC 85(c)(2)(A): after section 86 (worksheet line 1 includes Form + # 1040, line 6b). + income += person("dependent_taxable_social_security", period) + return income - add(person, period, deductions) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/tax_unit_taxable_unemployment_compensation.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/tax_unit_taxable_unemployment_compensation.py index 9cbaae0d4ff..661a0770ce2 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/tax_unit_taxable_unemployment_compensation.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/tax_unit_taxable_unemployment_compensation.py @@ -6,19 +6,12 @@ class tax_unit_taxable_unemployment_compensation(Variable): entity = TaxUnit label = "Taxable unemployment compensation" unit = USD - documentation = "Unemployment compensation included in AGI." + documentation = ( + "Unemployment compensation included in AGI: the taxable unemployment " + "compensation of the members who are not dependents. A dependent's " + "compensation is on the dependent's own return." + ) definition_period = YEAR reference = "https://www.law.cornell.edu/uscode/text/26/85" - def formula(tax_unit, period, parameters): - ui = parameters(period).gov.irs.unemployment_compensation - uc_amount = tax_unit("tax_unit_unemployment_compensation", period) - agi = tax_unit("taxable_uc_agi", period) - agi_over_uc = agi - uc_amount - filing_status = tax_unit("filing_status", period) - uc_excluded = where( - agi_over_uc < ui.exemption.cutoff[filing_status], - min_(uc_amount, ui.exemption.amount), - 0, - ) - return uc_amount - uc_excluded + formula = sum_among_non_dependents("taxable_unemployment_compensation") diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_uc_agi.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_uc_agi.py index 20045f03cf3..1da3faf0b9e 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_uc_agi.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_uc_agi.py @@ -4,16 +4,33 @@ class taxable_uc_agi(Variable): value_type = float entity = TaxUnit - label = "Taxable unemployment compensation for SS adjusted gross income" + label = "Adjusted gross income for the unemployment compensation exclusion" unit = USD - documentation = "Income used to determine taxability of unemployment compensation." + documentation = ( + "The adjusted gross income IRC 85(c) compares with the cutoff: " + "adjusted gross income after taxable Social Security and the " + "above-the-line deductions, without any unemployment compensation " + "(IRC 85(c)(2)). It is line 6 of the 2020 Unemployment Compensation " + "Exclusion Worksheet." + ) definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/85" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/85#c_2", + # 2020 Instructions for Form 1040 (rev. 4-2021), Unemployment + # Compensation Exclusion Worksheet, lines 1 through 6. + "https://www.irs.gov/pub/irs-prior/i1040gi--2020.pdf#page=88", + ) def formula(tax_unit, period, parameters): irs = parameters(period).gov.irs gross_income_sources = irs.gross_income.sources - income_sources_without_ss = [ + # IRC 85(c)(2)(B): without regard to section 85, so without any + # unemployment compensation. Taxable Social Security, the IRA + # deduction and the student loan interest and tuition and fees + # deductions, which this counts (85(c)(2)(A)), are figured with all + # of it (IRC 86(b)(2)(A), 219(g)(3)(A)(ii), 221(b)(2)(C); 2020 + # Instructions for Form 1040, p. 87, line 7 caution). + income_sources_without_uc = [ income_source for income_source in gross_income_sources if income_source != "taxable_unemployment_compensation" @@ -21,7 +38,7 @@ def formula(tax_unit, period, parameters): gross_income = 0 person = tax_unit.members not_dependent = ~person("is_tax_unit_dependent", period) - for source in income_sources_without_ss: + for source in income_sources_without_uc: # Add positive values only - losses are deducted later. gross_income += not_dependent * max_(0, add(person, period, [source])) gross_income = tax_unit.sum(gross_income) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_unemployment_insurance.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_unemployment_insurance.py index 707baf719c0..ef4957c3cba 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_unemployment_insurance.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/unemployment_insurance/taxable_unemployment_insurance.py @@ -6,18 +6,45 @@ class taxable_unemployment_compensation(Variable): entity = Person label = "Taxable unemployment compensation" unit = USD + documentation = ( + "Unemployment compensation a person received less the part IRC 85(c) " + "excludes from gross income on the return it is reported on: the tax " + "unit's return for the head and spouse, the dependent's own return " + "for a dependent." + ) definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/85", + # 2020 Instructions for Form 1040 (rev. 4-2021), Unemployment + # Compensation Exclusion Worksheet, Schedule 1, line 8. + "https://www.irs.gov/pub/irs-prior/i1040gi--2020.pdf#page=88", + ) def formula(person, period, parameters): - # The taxable amount of unemployment compensation is decided at the tax - # unit level, but gross income (which contains taxable UI) is person-level. - # Allocate the tax unit's taxable UC to each person in proportion to their - # unemployment_compensation, so that per-person AGI reflects the actual - # recipient (required by states with combined-return optimization). - tax_unit_taxable_uc = person.tax_unit( - "tax_unit_taxable_unemployment_compensation", period + p = parameters(period).gov.irs.unemployment_compensation.exemption + received = person("unemployment_compensation", period) + # IRC 85(c)(1) tests the adjusted gross income of the return the + # compensation is on. Members who are not dependents are on the tax + # unit's return, as in irs_gross_income. + filing_status = person.tax_unit("filing_status", period) + filer_agi = person.tax_unit("taxable_uc_agi", period) + filer_eligible = filer_agi < p.cutoff[filing_status] + # A dependent's compensation is on the dependent's own return, filed + # separately if they live with their spouse and single otherwise + # (a dependent can't file jointly or claim dependents). + lives_with_spouse = person("dependent_lives_with_spouse", period) + dependent_cutoff = where( + lives_with_spouse, p.cutoff["SEPARATE"], p.cutoff["SINGLE"] ) - person_uc = person("unemployment_compensation", period) - tax_unit_uc = person.tax_unit.sum(person_uc) - share = where(tax_unit_uc > 0, person_uc / tax_unit_uc, 0) - return tax_unit_taxable_uc * share + dependent_agi = person("dependent_taxable_uc_agi", period) + dependent_eligible = dependent_agi < dependent_cutoff + is_dependent = person("is_tax_unit_dependent", period) + eligible = where(is_dependent, dependent_eligible, filer_eligible) + # Each person excludes up to the amount of the compensation they + # received, so on a joint return each spouse does (worksheet lines 8 + # and 9). + excluded = eligible * min_(max_(0, received), p.amount) + # A net repayment adds nothing to gross income (irs_gross_income + # counts positive amounts only), so none of it is taxable; + # tax_exempt_unemployment_compensation carries it. + return max_(0, received - excluded)