diff --git a/changelog.d/fix-separate-filer-standard-deduction-and-dcap-wages.fixed.md b/changelog.d/fix-separate-filer-standard-deduction-and-dcap-wages.fixed.md new file mode 100644 index 00000000000..8db9f59baca --- /dev/null +++ b/changelog.d/fix-separate-filer-standard-deduction-and-dcap-wages.fixed.md @@ -0,0 +1 @@ +Zero the whole standard deduction, including the aged and blind amount, for a married separate filer whose spouse itemizes (IRC 63(c)(6)), and leave employer dependent care benefits out of income tax, FICA and FUTA wages, adding back any part above the IRC 129 exclusion and counting it as EITC earned income. diff --git a/policyengine_us/parameters/gov/irs/gross_income/fica_pre_tax_contributions.yaml b/policyengine_us/parameters/gov/irs/gross_income/fica_pre_tax_contributions.yaml index 24db48c5bc4..6834c47c44f 100644 --- a/policyengine_us/parameters/gov/irs/gross_income/fica_pre_tax_contributions.yaml +++ b/policyengine_us/parameters/gov/irs/gross_income/fica_pre_tax_contributions.yaml @@ -3,11 +3,17 @@ values: 2010-01-01: - pre_tax_health_insurance_premiums - health_savings_account_payroll_contributions + # Dependent care assistance the employer reasonably believes excludable + # under IRC 129 is not FICA wages (IRC 3121(a)(18)) or FUTA wages + # (IRC 3306(b)(13)). Form 2441 line 12 holds only those amounts. + - dependent_care_employer_benefits metadata: label: FICA pre-tax contributions reference: - - title: IRC §3121(a) — FICA wages definition - href: https://www.law.cornell.edu/uscode/text/26/3121 + - title: IRC §3121(a) — FICA wages definition, including (a)(18), dependent care assistance + href: https://www.law.cornell.edu/uscode/text/26/3121#a + - title: IRC §3306(b)(13) — dependent care assistance excluded from FUTA wages + href: https://www.law.cornell.edu/uscode/text/26/3306#b_13 - title: IRS Publication 15 (Employer's Tax Guide), Section 5 href: https://www.irs.gov/publications/p15 - title: IRS cafeteria plan FAQ diff --git a/policyengine_us/parameters/gov/irs/gross_income/pre_tax_contributions.yaml b/policyengine_us/parameters/gov/irs/gross_income/pre_tax_contributions.yaml index 83b25bfef22..568a2406fb2 100644 --- a/policyengine_us/parameters/gov/irs/gross_income/pre_tax_contributions.yaml +++ b/policyengine_us/parameters/gov/irs/gross_income/pre_tax_contributions.yaml @@ -10,6 +10,10 @@ values: - pre_tax_health_insurance_premiums # HSA contributions can be either through pre-tax. - health_savings_account_payroll_contributions + # Dependent care assistance (W-2 box 10, Form 2441 line 12) is excluded + # from gross income under IRC 129(a) and left out of box 1. Any part + # above the filer's own exclusion returns as taxable_dependent_care_benefits. + - dependent_care_employer_benefits metadata: label: Pre-tax contributions unit: list diff --git a/policyengine_us/parameters/gov/irs/gross_income/sources.yaml b/policyengine_us/parameters/gov/irs/gross_income/sources.yaml index 69b959c1392..a2474e69e74 100644 --- a/policyengine_us/parameters/gov/irs/gross_income/sources.yaml +++ b/policyengine_us/parameters/gov/irs/gross_income/sources.yaml @@ -2,6 +2,8 @@ description: Income sources counted as gross income for tax purposes. values: 2010-01-01: - irs_employment_income + # Dependent care benefits above the IRC 129 exclusion (Form 1040 line 1e). + - taxable_dependent_care_benefits - self_employment_income - sstb_self_employment_income - partnership_s_corp_income @@ -54,6 +56,8 @@ metadata: reference: - title: 26 U.S. Code § 61 - Gross income defined href: https://www.law.cornell.edu/uscode/text/26/61 + - title: 26 U.S. Code § 129 - Dependent care assistance programs + href: https://www.law.cornell.edu/uscode/text/26/129 - title: 26 U.S. Code § 111 - Recovery of tax benefit items href: https://www.law.cornell.edu/uscode/text/26/111 - title: 26 U.S. Code § 104(a)(3) - Amounts received through accident or health insurance diff --git a/policyengine_us/parameters/gov/irs/income/filing_requirement/dependent/earned_income_sources.yaml b/policyengine_us/parameters/gov/irs/income/filing_requirement/dependent/earned_income_sources.yaml index f685a64bcce..46c4877a2f0 100644 --- a/policyengine_us/parameters/gov/irs/income/filing_requirement/dependent/earned_income_sources.yaml +++ b/policyengine_us/parameters/gov/irs/income/filing_requirement/dependent/earned_income_sources.yaml @@ -1,9 +1,11 @@ description: The Internal Revenue Service counts these gross income sources as earned income of a filer who can be claimed as a dependent, both for the standard deduction worksheet for dependents and for the unearned income filing test. The earned portion of taxable disability benefits is counted separately; other gross income is unearned income. values: 2010-01-01: - # Form 1040 line 1z (wages). Line 1a is the total of Form(s) W-2, box 1, - # which leaves out elective deferrals. + # Form 1040 line 1z (wages): line 1a, the total of Form(s) W-2, box 1, + # which leaves out elective deferrals and dependent care benefits, - irs_employment_income + # plus line 1e, taxable dependent care benefits (Form 2441 line 26). + - taxable_dependent_care_benefits # Schedule 1 line 3 (business income, Schedule C). - self_employment_income - sstb_self_employment_income diff --git a/policyengine_us/tests/core/test_dependent_care_assistance_exclusion_invariants.py b/policyengine_us/tests/core/test_dependent_care_assistance_exclusion_invariants.py index 024d1338a81..baf6d715cc2 100644 --- a/policyengine_us/tests/core/test_dependent_care_assistance_exclusion_invariants.py +++ b/policyengine_us/tests/core/test_dependent_care_assistance_exclusion_invariants.py @@ -24,10 +24,15 @@ 1. Differential: dependent_care_assistance_exclusion equals an independent numpy min of the benefits, the cap printed on Form 2441 line 21 for the year (not the model's parameter) and the lesser of the filers' earnings. + Earnings are box 1 wages: Form 2441 line 18 earned income leaves out the + line 12 benefits, which the fixtures add on top to form employment_income. + Raising benefits therefore raises employment_income by the same amount. 2. Bound: 0 <= exclusion <= benefits, the year/status cap and earned income. 3. Monotone: raising benefits never lowers the exclusion. 4. California: ca_dependent_care_assistance_exclusion equals the same min with - the FTB 3506 line 22 cap, and never exceeds the federal exclusion. + the FTB 3506 line 22 cap and California wages (which the model does not + reduce by the benefits), and doesn't exceed the federal exclusion where + the federal earned income limit doesn't bind below California's cap. Separate filers treated as unmarried use the full cap in both systems. 5. Section 21(c): cdcc_limit equals max(Form 2441 line 27 amount x the number of qualifying persons (at most two) - exclusion, 0). @@ -82,7 +87,7 @@ @st.composite def filers(draw): return { - "employment_income": draw(wages), + "box_1_wages": draw(wages), "benefits": draw(benefits), "increase": draw(increases), } @@ -109,7 +114,7 @@ def _seeded_units(n=200): def filer(): level = rng.choice([0, 1, 2]) return { - "employment_income": float(rng.integers(0, 150_001)), + "box_1_wages": float(rng.integers(0, 150_001)), "benefits": float( [0, rng.integers(1, 5_001), rng.integers(5_000, 12_001)][level] ), @@ -142,7 +147,9 @@ def add_filer(name, values, role): "age": 40, "is_tax_unit_head": role == "head", "is_tax_unit_spouse": role == "spouse", - "employment_income": values["employment_income"], + # Benefits sit inside employment_income as a pre-tax payroll + # deduction, so box 1 wages are employment_income - benefits. + "employment_income": values["box_1_wages"] + values["benefits"], "dependent_care_employer_benefits": values["benefits"], } @@ -223,7 +230,7 @@ def _treated_as_unmarried(unit): ) -def _expected(units, year, caps, *, raised=False): +def _expected(units, year, caps, *, raised=False, wages_include_benefits=False): # Independent cap-arithmetic oracle; qualified-expense/person eligibility # under Form 2441 lines 16-25 is outside this property's domain. benefits, cap, earned = [], [], [] @@ -234,7 +241,13 @@ def _expected(units, year, caps, *, raised=False): "filing_status" ] == "SEPARATE" and not _treated_as_unmarried(unit) cap.append(caps[1] if ordinary_separate else caps[0]) - earned.append(min(f["employment_income"] for f in filers)) + earned.append( + min( + f["box_1_wages"] + + wages_include_benefits * (f["benefits"] + raised * f["increase"]) + for f in filers + ) + ) return np.minimum(np.array(benefits), np.minimum(cap, earned)) @@ -275,9 +288,9 @@ def _check(units, year): ) earned_income_limits = np.array( [ - min(unit["head"]["employment_income"], unit["spouse"]["employment_income"]) + min(unit["head"]["box_1_wages"], unit["spouse"]["box_1_wages"]) if unit["spouse"] - else unit["head"]["employment_income"] + else unit["head"]["box_1_wages"] for unit in units ] ) @@ -294,12 +307,23 @@ def _check(units, year): atol=TOLERANCE, ) - # 4. California's line 22 cap. + # 4. California's line 22 cap. California earned income is measured from + # California wages (ca_taxable_wages), which the model does not reduce by + # the line 12 benefits; California law on that point is not checked here. california = base["ca_dependent_care_assistance_exclusion"] np.testing.assert_allclose( - california, _expected(units, year, CALIFORNIA_CAP), atol=TOLERANCE + california, + _expected(units, year, CALIFORNIA_CAP, wages_include_benefits=True), + atol=TOLERANCE, + ) + # Where the federal earned income limit doesn't bind below California's + # cap, California's smaller cap keeps its exclusion at or below federal. + federal_limit_slack = earned_income_limits >= np.minimum( + benefits, CALIFORNIA_CAP[0] ) - assert (california <= exclusion + TOLERANCE).all() + assert ( + california[federal_limit_slack] <= exclusion[federal_limit_slack] + TOLERANCE + ).all() # 5. Section 21(c) dollar limit. persons = np.array([min(unit["children"], 2) for unit in units]) diff --git a/policyengine_us/tests/microsimulation/test_calculate_chunked.py b/policyengine_us/tests/microsimulation/test_calculate_chunked.py index 6ffc10a886b..1613b4d1a29 100644 --- a/policyengine_us/tests/microsimulation/test_calculate_chunked.py +++ b/policyengine_us/tests/microsimulation/test_calculate_chunked.py @@ -767,5 +767,5 @@ def test_guard_adds_no_frames_per_dependency(chunking_dataset): del simulation gc.collect() # The chain is long: each variable costs several frames. - assert depths[False] > 300 + assert depths[False] > 200 assert 0 <= depths[True] - depths[False] <= 3, depths diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/cdcc.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/cdcc.yaml index 5a99a4a7a09..144411162af 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/cdcc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/cdcc.yaml @@ -241,7 +241,9 @@ people: parent1: age: 40 - employment_income: 60_000 + # employment_income includes the $9,000 of dependent care benefits; + # box 1 wages are $60,000 ($69,000 - $9,000). + employment_income: 69_000 dependent_care_employer_benefits: 9_000 parent2: age: 38 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_earned_income.yaml index 864b3378901..c76a0475215 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_earned_income.yaml @@ -440,3 +440,89 @@ # No qualifying children: 7.65% x $8,260 = $632, and the $10,000 of AGI # is under the $10,330 phase-out threshold. eitc: 632 + +# Dependent care benefits above the IRC 129 exclusion (Form 2441 line 26, +# Form 1040 line 1e) are includible in gross income, so they are earned income +# under IRC 32(c)(2)(A)(i). +- name: Separate filer's taxable dependent care benefits count as earned income, 2024 + period: 2024 + input: + people: + person: + age: 35 + employment_income: 20_000 + dependent_care_employer_benefits: 5_000 + child: + age: 5 + tax_units: + tax_unit: + members: [person, child] + filing_status: SEPARATE + tax_unit_childcare_expenses: 5_000 + households: + household: + members: [person, child] + state_code: TX + output: + # Box 1 $20,000 - $5,000 = $15,000. Exclusion min($5,000, $2,500 MFS + # cap, $15,000, $5,000 of care expenses) = $2,500; line 1e $2,500. + # Earned income $17,500. + eitc_earned_income: 17_500 + +- name: Part-time worker's line 1e benefits count toward the EITC, 2024 + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + employment_income: 6_000 + dependent_care_employer_benefits: 5_000 + child: + age: 5 + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + tax_unit_childcare_expenses: 5_000 + households: + household: + members: [head, child] + state_code: TX + output: + # Box 1 $1,000; exclusion min($5,000, $5,000, $1,000, $5,000 of care + # expenses) = $1,000; line 1e $4,000. Earned income $5,000. + eitc_earned_income: 5_000 + # One child, 2024: 34% x $5,000 = $1,700 (below the $12,390 plateau). + eitc: 1_700 + +- name: Joint filers' line 1e benefits count toward the EITC phase-out, 2024 + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + employment_income: 25_000 + spouse: + age: 30 + employment_income: 6_000 + dependent_care_employer_benefits: 5_000 + child: + age: 5 + tax_units: + tax_unit: + members: [head, spouse, child] + filing_status: JOINT + tax_unit_childcare_expenses: 5_000 + households: + household: + members: [head, spouse, child] + state_code: TX + output: + # Box 1 $25,000 + $1,000; exclusion min($5,000, $5,000, $1,000, $5,000 of + # care expenses) = $1,000; line 1e $4,000. Earned income and AGI $30,000. + eitc_earned_income: 30_000 + adjusted_gross_income: 30_000 + # One child, 2024 joint: $4,213 - 15.98% x ($30,000 - $29,640) = $4,155.47. + eitc: 4_155.47 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/standard_deduction.yaml new file mode 100644 index 00000000000..5ea496a5df5 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/standard_deduction.yaml @@ -0,0 +1,79 @@ +# IRC 63(c)(6)(A): a married individual filing separately whose spouse +# itemizes has a standard deduction of zero. The standard deduction is the +# sum of the basic and additional amounts (63(c)(1)), so the additional +# amount for age or blindness (63(f)) is zeroed too. + +- name: Aged and blind separate filer whose spouse itemizes has no standard deduction, 2025 + period: 2025 + input: + people: + head: + age: 70 + is_blind: true + employment_income: 50_000 + tax_units: + tax_unit: + members: [head] + filing_status: SEPARATE + separate_filer_itemizes: true + output: + basic_standard_deduction: 0 + # The 63(f) amount itself is unchanged: two $1,600 MFS amounts. + additional_standard_deduction: 3_200 + standard_deduction: 0 + +- name: Aged separate filer whose spouse itemizes has no standard deduction, 2024 + period: 2024 + input: + people: + head: + age: 66 + employment_income: 40_000 + tax_units: + tax_unit: + members: [head] + filing_status: SEPARATE + separate_filer_itemizes: true + output: + additional_standard_deduction: 1_550 + standard_deduction: 0 + +- name: Aged and blind separate filer whose spouse does not itemize keeps both amounts, 2025 + period: 2025 + input: + people: + head: + age: 70 + is_blind: true + employment_income: 50_000 + tax_units: + tax_unit: + members: [head] + filing_status: SEPARATE + separate_filer_itemizes: false + output: + # $15,750 basic (2025, MFS) plus 2 x $1,600. + basic_standard_deduction: 15_750 + additional_standard_deduction: 3_200 + standard_deduction: 18_950 + +- name: Joint filers keep the whole deduction even if the separate filer flag is set, 2025 + period: 2025 + input: + people: + head: + age: 70 + employment_income: 50_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + separate_filer_itemizes: true + output: + # IRC 63(c)(6)(A) covers only a married individual filing a separate + # return. $31,500 basic (2025, joint) plus $1,600 for the aged head. + basic_standard_deduction: 31_500 + additional_standard_deduction: 1_600 + standard_deduction: 33_100 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_dependent_care_benefits.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_dependent_care_benefits.yaml new file mode 100644 index 00000000000..21e399e3bd0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_dependent_care_benefits.yaml @@ -0,0 +1,169 @@ +# Employer dependent care benefits (W-2 box 10, Form 2441 line 12) are left +# out of box 1 wages and of FICA and FUTA wages (IRC 129(a)(1), 3121(a)(18), +# 3306(b)(13)). The part above the filer's own IRC 129 exclusion is taxable +# (Form 2441 line 26, Form 1040 line 1e). + +- name: Benefits within the exclusion leave income tax and FICA wages, 2025 + period: 2025 + input: + people: + head: + age: 35 + employment_income: 60_000 + dependent_care_employer_benefits: 5_000 + child: + age: 3 + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + # Qualified expenses at least equal to the benefits (Form 2441 line 16). + tax_unit_childcare_expenses: 5_000 + households: + household: + members: [head, child] + state_code: TX + output: + # $60,000 - $5,000 = $55,000. + irs_employment_income: [55_000, 0] + payroll_tax_gross_wages: [55_000, 0] + dependent_care_assistance_exclusion: 5_000 + taxable_dependent_care_benefits: [0, 0] + adjusted_gross_income: 55_000 + # 6.2% and 1.45% of $55,000. + employee_social_security_tax: [3_410, 0] + employee_medicare_tax: [797.5, 0] + +- name: Separate filer's benefits above the $2,500 cap return to gross income, 2025 + period: 2025 + input: + people: + head: + age: 35 + employment_income: 60_000 + dependent_care_employer_benefits: 5_000 + child: + age: 3 + tax_units: + tax_unit: + members: [head, child] + filing_status: SEPARATE + tax_unit_childcare_expenses: 5_000 + households: + household: + members: [head, child] + state_code: TX + output: + irs_employment_income: [55_000, 0] + # FICA follows the employer's exclusion, not the filer's MFS cap. + payroll_tax_gross_wages: [55_000, 0] + # min($5,000, $2,500 MFS cap, earnings) = $2,500. + dependent_care_assistance_exclusion: 2_500 + taxable_dependent_care_benefits: [2_500, 0] + # $55,000 + $2,500. + adjusted_gross_income: 57_500 + +- name: Joint return splits the taxable part by each spouse's benefits, 2024 + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 80_000 + dependent_care_employer_benefits: 4_000 + spouse: + age: 40 + employment_income: 3_000 + dependent_care_employer_benefits: 2_000 + child: + age: 4 + tax_units: + tax_unit: + members: [head, spouse, child] + filing_status: JOINT + tax_unit_childcare_expenses: 6_000 + households: + household: + members: [head, spouse, child] + state_code: TX + output: + # Form 2441 lines 18-19 leave out line 12 benefits: the spouse's earned + # income is $3,000 - $2,000 = $1,000. Exclusion min($6,000, $5,000, + # $1,000) = $1,000; taxable $5,000, split 4:2. + dependent_care_assistance_exclusion: 1_000 + taxable_dependent_care_benefits: [3_333.33, 1_666.67, 0] + irs_employment_income: [76_000, 1_000, 0] + # $77,000 of box 1 wages plus $5,000 taxable benefits. + adjusted_gross_income: 82_000 + +- name: Part-time worker's exclusion is capped at wages net of benefits, 2024 + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + employment_income: 6_000 + dependent_care_employer_benefits: 5_000 + child: + age: 5 + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + tax_unit_childcare_expenses: 5_000 + households: + household: + members: [head, child] + state_code: TX + output: + # Form 2441 line 18 earned income leaves out line 12 benefits: + # $6,000 - $5,000 = $1,000. Exclusion min($5,000, $5,000, $1,000) = + # $1,000; taxable $4,000 (Form 1040 line 1e). + irs_employment_income: [1_000, 0] + dependent_care_assistance_exclusion: 1_000 + taxable_dependent_care_benefits: [4_000, 0] + # $1,000 of box 1 wages plus $4,000 taxable benefits. + adjusted_gross_income: 5_000 + +- name: Claimable filer's taxable dependent care benefits are earned income, 2024 + period: 2024 + input: + people: + head: + age: 22 + employment_income: 6_000 + dependent_care_employer_benefits: 5_000 + claimable_as_dependent_on_another_return: true + child: + age: 2 + tax_units: + tax_unit: + members: [head, child] + filing_status: SINGLE + tax_unit_childcare_expenses: 5_000 + households: + household: + members: [head, child] + state_code: TX + output: + # Box 1 $1,000; exclusion min($5,000, $5,000, $1,000) = $1,000; line 1e + # $4,000. Earned income is line 1z, $5,000; unearned income is $0. + standard_deduction_worksheet_earned_income_person: [5_000, 0] + # max($1,300, $5,000 + $450) = $5,450 (Rev. Proc. 2023-34). + basic_standard_deduction: 5_450 + # Taxable income $5,000 - $5,450 < 0. Refundable credits are left out: + # the model's EITC doesn't test whether the filer is claimable. + income_tax_before_refundable_credits: 0 + tax_unit_is_required_to_file: false + medicaid_person_is_required_to_file: [false, false] + +- name: No benefits leaves wages unchanged + period: 2025 + input: + employment_income: 30_000 + output: + irs_employment_income: 30_000 + payroll_tax_gross_wages: 30_000 + taxable_dependent_care_benefits: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/ira/dependent_traditional_ira_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/ira/dependent_traditional_ira_deduction.yaml index e1dd8ac9c91..76bd76be764 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/ira/dependent_traditional_ira_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/ira/dependent_traditional_ira_deduction.yaml @@ -158,3 +158,33 @@ state_code: TX output: dependent_traditional_ira_deduction: [0, 2_200, 0] + +- name: A dependent's IRA compensation includes disability pay, less the deductible half of SE tax, 2025 + period: 2025 + absolute_error_margin: 0.01 + 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 + self_employment_income: 3_000 + earned_disability_benefits: 1_000 + traditional_ira_contributions: 5_000 + tax_units: + tax_unit: + members: [parent, student] + households: + household: + members: [parent, student] + state_code: TX + output: + # Net earnings $3,000 x 0.9235 = $2,770.50; SE tax 15.3% = $423.89, half + # $211.94. Compensation $1,000 + $3,000 - $211.94 = $3,788.06, below the + # $5,000 of contributions and the $7,000 limit. + ira_compensation: [80_000, 3_788.06] + dependent_traditional_ira_deduction: [0, 3_788.06] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/ira/traditional_ira_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/ira/traditional_ira_deduction.yaml new file mode 100644 index 00000000000..0d738e0d91a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/ira/traditional_ira_deduction.yaml @@ -0,0 +1,29 @@ +- name: Taxable dependent care benefits are IRA compensation, 2024 + period: 2024 + input: + people: + head: + age: 30 + employment_income: 6_000 + dependent_care_employer_benefits: 5_000 + traditional_ira_contributions: 3_000 + child: + age: 5 + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + tax_unit_childcare_expenses: 5_000 + households: + household: + members: [head, child] + state_code: TX + output: + # Box 1 $1,000; exclusion min($5,000, $5,000, $1,000) = $1,000; line 1e + # $4,000. Treas. Reg. 1.219-1(c)(1) counts includible pay for services, + # so compensation is $1,000 + $4,000 = $5,000. + ira_compensation: [5_000, 0] + # min($3,000, $7,000 limit, $5,000 compensation) = $3,000. + traditional_ira_deduction: [3_000, 0] + # $5,000 - $3,000. + adjusted_gross_income: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml index c150201ee60..3794b919292 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml @@ -795,11 +795,12 @@ separate_filer_itemizes: true employment_income: 1_000 output: - # The model zeroes the basic standard deduction whenever this input is - # true, and the formula then withdraws the (A) exception: $1,000 >= $0. - # Without that the threshold would be $0 basic + $2,050 age addition. In - # law section 63(c)(6)(A) reaches only a married individual filing a - # separate return, and a head of household isn't married (sections 2(b)(1), - # 2(c), 63(g), 7703(b)), so this case pins the model's input rule. - basic_standard_deduction: 0 + # Section 63(c)(6)(A) reaches only a married individual filing a separate + # return, and a head of household isn't married (sections 2(b)(1), 2(c), + # 63(g), 7703(b)), so basic_standard_deduction keeps the 2026 head of + # household amount. The filing test still withdraws the (A) exception + # whenever this input is true, whatever the filing status: $1,000 >= $0. + # This case pins that input rule; in law the threshold would be $24,150 + # basic + $2,050 age addition, and no return would be required. + basic_standard_deduction: 24_150 tax_unit_is_required_to_file: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/al_federal_income_tax_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/al_federal_income_tax_deduction.yaml index 1d7847bfa54..c2f67aca5f1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/al_federal_income_tax_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/al_federal_income_tax_deduction.yaml @@ -260,7 +260,9 @@ people: head: age: 40 - employment_income: 60_000 + # employment_income includes the $9,000 of dependent care benefits; + # box 1 wages are $60,000 ($69,000 - $9,000). + employment_income: 69_000 dependent_care_employer_benefits: 9_000 spouse: age: 40 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_dependent_care_assistance_addition.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_dependent_care_assistance_addition.yaml new file mode 100644 index 00000000000..4d32b3e6452 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_dependent_care_assistance_addition.yaml @@ -0,0 +1,94 @@ +# Federal AGI leaves out the whole IRC 129 exclusion. California kept its +# $5,000 cap for 2021 and from 2026, so the federal exclusion above +# California's goes back into California AGI (Schedule CA (540), Part I, +# line 1, column C). + +- name: 2021 federal $10,500 exclusion adds back $5,500 in California + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 60_000 + dependent_care_employer_benefits: 10_500 + child: + age: 3 + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + tax_unit_childcare_expenses: 16_000 + households: + household: + members: [head, child] + state_code: CA + output: + # Federal min($10,500, $10,500, $49,500) = $10,500; federal AGI + # $60,000 - $10,500 = $49,500. + dependent_care_assistance_exclusion: 10_500 + adjusted_gross_income: 49_500 + # California min($10,500, $5,000, earned income) = $5,000. + ca_dependent_care_assistance_exclusion: 5_000 + # $10,500 - $5,000 = $5,500; California AGI $49,500 + $5,500 = $55,000. + ca_dependent_care_assistance_addition: 5_500 + ca_agi: 55_000 + ca_income_tax: 215.79 + +- name: 2026 federal $7,500 exclusion adds back $2,500 in California + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 60_000 + dependent_care_employer_benefits: 7_500 + child: + age: 3 + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + tax_unit_childcare_expenses: 16_000 + households: + household: + members: [head, child] + state_code: CA + output: + # Federal min($7,500, $7,500, $52,500) = $7,500; federal AGI $52,500. + dependent_care_assistance_exclusion: 7_500 + adjusted_gross_income: 52_500 + ca_dependent_care_assistance_exclusion: 5_000 + # $7,500 - $5,000 = $2,500; California AGI $52,500 + $2,500 = $55,000. + ca_dependent_care_assistance_addition: 2_500 + ca_agi: 55_000 + +- name: 2024 equal caps add nothing back for a part-time worker + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + employment_income: 6_000 + dependent_care_employer_benefits: 5_000 + child: + age: 5 + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + tax_unit_childcare_expenses: 5_000 + households: + household: + members: [head, child] + state_code: CA + output: + # Federal min($5,000, $5,000, $1,000) = $1,000; federal AGI $1,000 of + # box 1 wages plus $4,000 of line 1e = $5,000. The model's California + # earned income keeps the benefits, so California's exclusion is $5,000; + # the addition is max($1,000 - $5,000, 0) = 0. + dependent_care_assistance_exclusion: 1_000 + ca_dependent_care_assistance_addition: 0 + ca_agi: 5_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc_relevant_expenses.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc_relevant_expenses.yaml index 7b92615cd2a..4a566ade40c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc_relevant_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc_relevant_expenses.yaml @@ -156,7 +156,8 @@ ca_dependent_care_assistance_exclusion: 5_000 # min($12,000, $6,000 two-person 2020 limit - $5,000, $40,000) = $1,000. ca_cdcc_relevant_expenses: 1_000 - # AGI $95,000: the 2020 rate is 35% less 1 point per $2,000 over $15,000, + # AGI $86,000 ($95,000 of employment_income less the $9,000 of excluded + # benefits): the 2020 rate is 35% less 1 point per $2,000 over $15,000, # floored at 20%. $1,000 x 20% = $200. ca_federal_cdcc: 200 # California matches 34% for AGI over $70,000 and not over $100,000. @@ -186,7 +187,8 @@ members: [head, child1, child2] state_code: CA output: - # Treated as unmarried: federal min($6,000, $10,500, $50,000) = $6,000. + # Treated as unmarried: federal min($6,000, $10,500, $44,000) = $6,000, + # where $44,000 is the $50,000 of wages less the $6,000 of benefits. # California line 22 is $5,000 (FTB 3506 line 20 instructions), so the # state exclusion is $5,000 and its remaining limit is $1,000. # The federal $16,000 - $6,000 limit does not further reduce the base. @@ -268,7 +270,8 @@ # Using federal line 29 alone incorrectly gives a $1,000 California base. # https://www.ftb.ca.gov/forms/2021/2021-3506.pdf#page=2 ca_cdcc_relevant_expenses: 500 - # FTB 3506 lines 7-10: AGI $95,000 gives 20% and a 34% state match. + # FTB 3506 lines 7-10: AGI $89,000 ($95,000 of employment_income less the + # $6,000 of excluded benefits) gives 20% and a 34% state match. ca_federal_cdcc: 100 ca_federal_capped_cdcc: 100 ca_cdcc: 34 diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/agi/subtractions/md_dependent_care_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/agi/subtractions/md_dependent_care_subtraction.yaml index eef9d5e25be..742374d8106 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/agi/subtractions/md_dependent_care_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/agi/subtractions/md_dependent_care_subtraction.yaml @@ -43,7 +43,9 @@ person1: is_tax_unit_head: true age: 35 - employment_income: 60_000 + # employment_income includes the $10,500 of dependent care benefits; + # box 1 wages are $60,000 ($70,500 - $10,500). + employment_income: 70_500 dependent_care_employer_benefits: 10_500 person2: is_tax_unit_spouse: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_cdcc.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_cdcc.yaml index 9930ffcb5d9..78659f28c7e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_cdcc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_cdcc.yaml @@ -207,13 +207,16 @@ state_code: NY output: # Worksheet 1: line 5 = 5,000; line 6 = 8,000; line 7 = 5,000; - # lines 8 and 9 = 50,000; line 10 = 5,000; line 11 = 5,000; + # line 8 = 45,000 (person1's $50,000 less the $5,000 of benefits); + # line 9 = 50,000; line 10 = 5,000; line 11 = 5,000; # line 12 = 6,000 (two persons); line 13 = 5,000; # line 14 = 6,000 - 5,000 = 1,000; line 15 = 8,000 - 5,000 = 3,000; # line 16 = 1,000. - # Form IT-216: line 3b = 1,000; line 5 = 1,000; lines 6 and 7 = 50,000; - # line 8 = 1,000; line 10 (FAGI 100,000) = .20; line 11 = 200; - # line 13 (NYAGI 100,000) = .600; line 14 = 200 x .600 = 120. + # Form IT-216: line 3b = 1,000; line 5 = 1,000; line 6 = 45,000; + # line 7 = 50,000; + # line 8 = 1,000; line 10 (FAGI 95,000: $100,000 of employment_income + # less the $5,000 of excluded benefits) = .20; line 11 = 200; + # line 13 (NYAGI 95,000) = .600; line 14 = 200 x .600 = 120. ny_cdcc_max: 1_000 ny_cdcc: 120 @@ -248,12 +251,14 @@ state_code: NY output: # Worksheet 1: line 5 = 2,000; line 6 = 5,000; line 7 = 2,000; - # lines 8 and 9 = 50,000; line 10 = 2,000; line 11 = 5,000; + # line 8 = 48,000 (person1's $50,000 less the $2,000 of benefits); + # line 9 = 50,000; line 10 = 2,000; line 11 = 5,000; # line 12 = 6,000; line 13 = 2,000; line 14 = 6,000 - 2,000 = 4,000; # line 15 = 5,000 - 2,000 = 3,000; line 16 = 3,000. # Form IT-216: line 3b = 3,000; line 5 = 3,000; line 8 = 3,000; - # line 10 (FAGI 100,000) = .20; line 11 = 600; - # line 13 (NYAGI 100,000) = .600; line 14 = 600 x .600 = 360. + # line 10 (FAGI 98,000: $100,000 of employment_income less the $2,000 + # of excluded benefits) = .20; line 11 = 600; + # line 13 (NYAGI 98,000) = .600; line 14 = 600 x .600 = 360. ny_cdcc_max: 3_000 ny_cdcc: 360 diff --git a/policyengine_us/tests/test_ira_formula_differential.py b/policyengine_us/tests/test_ira_formula_differential.py index e09d888e4bd..12eccaabdc1 100644 --- a/policyengine_us/tests/test_ira_formula_differential.py +++ b/policyengine_us/tests/test_ira_formula_differential.py @@ -517,3 +517,44 @@ def test_generated_single_contributions_preserve_shares_within_compensation(): ] == 1 ) + + +def test_dependent_helper_repeats_the_formula_without_line_1e(): + # ira_compensation_without_line_1e repeats the formula's sum without + # Form 1040 line 1e, so with line 1e at zero the two agree on every term. + path = RETIREMENT / "ira_compensation.py" + tree = ast.parse(path.read_text(), filename=str(path)) + helper = next( + node + for node in tree.body + if isinstance(node, ast.FunctionDef) + and node.name == "ira_compensation_without_line_1e" + ) + namespace = { + "max_": np.maximum, + "add": lambda person, period, names: sum( + person(name, period) for name in names + ), + } + exec( + compile(ast.Module(body=[helper], type_ignores=[]), str(path), "exec"), + namespace, + ) + terms = [ + "irs_employment_income", + "earned_disability_benefits", + "taxable_alimony_income", + "self_employment_income", + "sstb_self_employment_income", + "farm_operations_income", + "partnership_self_employment_net_earnings", + "self_employment_tax_ald_person", + "self_employed_pension_contribution_ald_person", + ] + # Each row sets one term to 1,000, plus one row with every term set. + rows = [{term: 1_000} for term in terms] + [dict.fromkeys(terms, 1_000)] + people = People({term: [row.get(term, 0) for row in rows] for term in terms}) + expected = formula(path, "ira_compensation")(people, None, parameters()) + np.testing.assert_array_equal( + namespace["ira_compensation_without_line_1e"](people, None), expected + ) diff --git a/policyengine_us/tests/test_pinned_branch_credit_dependents.py b/policyengine_us/tests/test_pinned_branch_credit_dependents.py index 316de2bea77..8214dbc6c4a 100644 --- a/policyengine_us/tests/test_pinned_branch_credit_dependents.py +++ b/policyengine_us/tests/test_pinned_branch_credit_dependents.py @@ -67,7 +67,9 @@ def test_alabama_recomputes_dependent_care_exclusion_in_any_order(): "people": { "head": { "age": {YEAR: 40}, - "employment_income": {YEAR: 60_000}, + # employment_income includes the $9,000 of dependent care + # benefits; box 1 wages are $60,000 ($69,000 - $9,000). + "employment_income": {YEAR: 69_000}, "dependent_care_employer_benefits": {YEAR: 9_000}, }, "spouse": { diff --git a/policyengine_us/tests/test_separate_filer_and_dependent_care_wages.py b/policyengine_us/tests/test_separate_filer_and_dependent_care_wages.py new file mode 100644 index 00000000000..d7dca4289d4 --- /dev/null +++ b/policyengine_us/tests/test_separate_filer_and_dependent_care_wages.py @@ -0,0 +1,131 @@ +"""Property tests for two federal rules on a separate return and on wages. + +1. IRC 63(c)(6)(A): a married individual filing separately whose spouse + itemizes has a standard deduction of zero. The standard deduction is the + sum in 63(c)(1), so this holds whatever the filer's age or blindness. +2. IRC 129(a)(1), 3121(a)(18), 3306(b)(13): employer dependent care benefits + (W-2 box 10, Form 2441 line 12) are left out of income tax wages and of + FICA and FUTA wages. The part above the filer's IRC 129 exclusion returns + to gross income (Form 2441 line 26, Form 1040 line 1e). So for one earner: + income tax wages + taxable benefits + exclusion = employment income, + FICA wages = employment income - benefits, + exclusion <= employment income - benefits (IRC 129(b), Form 2441 + line 18 earned income leaves out line 12 benefits). + +Each example builds one simulation holding many tax units. +""" + +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +YEARS = [2018, 2021, 2024, 2025, 2026] +FILING_STATUSES = ["SINGLE", "SEPARATE", "HEAD_OF_HOUSEHOLD", "JOINT"] + +FILER = st.fixed_dictionaries( + { + "age": st.integers(min_value=18, max_value=90), + "blind": st.booleans(), + "wages": st.integers(min_value=0, max_value=200_000), + } +) + +EARNER = st.fixed_dictionaries( + { + "wages": st.integers(min_value=0, max_value=150_000), + "benefit_share": st.floats(min_value=0, max_value=1), + "benefit_cap": st.integers(min_value=0, max_value=10_500), + "status": st.sampled_from(FILING_STATUSES[:3]), + } +) + + +def _simulate(year, people, tax_units): + households = { + f"h_{name}": {"members": unit["members"], "state_code": {year: "TX"}} + for name, unit in tax_units.items() + } + return Simulation( + situation={ + "people": people, + "tax_units": tax_units, + "households": households, + } + ) + + +@settings( + max_examples=10, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) +@given( + year=st.sampled_from(YEARS), + filers=st.lists(FILER, min_size=1, max_size=12), +) +def test_separate_filer_whose_spouse_itemizes_has_no_standard_deduction(year, filers): + people, tax_units = {}, {} + for i, filer in enumerate(filers): + people[f"p{i}"] = { + "age": {year: filer["age"]}, + "is_blind": {year: filer["blind"]}, + "employment_income": {year: filer["wages"]}, + } + tax_units[f"t{i}"] = { + "members": [f"p{i}"], + "filing_status": {year: "SEPARATE"}, + "separate_filer_itemizes": {year: True}, + } + sim = _simulate(year, people, tax_units) + assert (sim.calculate("standard_deduction", year) == 0).all() + # The deduction a non-itemizer takes is therefore zero too. + itemizes = sim.calculate("tax_unit_itemizes", year) + deductions = sim.calculate("taxable_income_deductions", year) + assert (deductions[~itemizes] == 0).all() + + +@settings( + max_examples=10, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) +@given( + year=st.sampled_from(YEARS), + earners=st.lists(EARNER, min_size=1, max_size=12), +) +def test_dependent_care_benefits_leave_wages_once(year, earners): + people, tax_units, benefits = {}, {}, [] + for i, earner in enumerate(earners): + benefit = min( + round(earner["wages"] * earner["benefit_share"]), + earner["benefit_cap"], + ) + benefits.append(benefit) + people[f"p{i}"] = { + "age": {year: 35}, + "employment_income": {year: earner["wages"]}, + "dependent_care_employer_benefits": {year: benefit}, + } + tax_units[f"t{i}"] = { + "members": [f"p{i}"], + "filing_status": {year: earner["status"]}, + } + sim = _simulate(year, people, tax_units) + wages = sim.calculate("employment_income", year) + box_1 = sim.calculate("irs_employment_income", year) + taxable = sim.calculate("taxable_dependent_care_benefits", year) + exclusion = sim.calculate("dependent_care_assistance_exclusion", year) + fica_wages = sim.calculate("payroll_tax_gross_wages", year) + for i, benefit in enumerate(benefits): + assert box_1[i] == wages[i] - benefit + assert fica_wages[i] == wages[i] - benefit + assert 0 <= taxable[i] <= benefit + assert abs(box_1[i] + taxable[i] + exclusion[i] - wages[i]) < 0.01 + # IRC 129(b) and Form 2441 line 18: the exclusion cannot exceed + # earned income, which leaves out the line 12 benefits. The identity + # above holds for any exclusion, so this pins the limit itself. + assert exclusion[i] <= max(wages[i] - benefit, 0) + 0.01 + # The exclusion reaches the smallest cap drawn ($2,500 on a separate + # return) unless the benefits or net wages are smaller. + assert exclusion[i] >= min(benefit, 2_500, max(wages[i] - benefit, 0)) - 0.01 diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/eitc_earned_income.py b/policyengine_us/variables/gov/irs/credits/earned_income/eitc_earned_income.py index 0a7e33c5322..3216d3abea2 100644 --- a/policyengine_us/variables/gov/irs/credits/earned_income/eitc_earned_income.py +++ b/policyengine_us/variables/gov/irs/credits/earned_income/eitc_earned_income.py @@ -24,6 +24,10 @@ def formula(tax_unit, period, parameters): # deduction, so supplied W-2 wages are not reduced twice. earned_income_sources = [ "irs_employment_income", + # Dependent care benefits above the IRC 129 exclusion (Form 1040 + # line 1e) are includible in gross income, so they are earned + # income under IRC 32(c)(2)(A)(i). + "taxable_dependent_care_benefits", "self_employment_income", "sstb_self_employment_income", "farm_operations_income", diff --git a/policyengine_us/variables/gov/irs/income/adjusted_earnings.py b/policyengine_us/variables/gov/irs/income/adjusted_earnings.py index 0356ae5c8db..2f67fbad293 100644 --- a/policyengine_us/variables/gov/irs/income/adjusted_earnings.py +++ b/policyengine_us/variables/gov/irs/income/adjusted_earnings.py @@ -28,10 +28,12 @@ class adjusted_earnings(Variable): definition_period = YEAR label = "Personal earned income adjusted for self-employment tax" documentation = ( - "Earned income under IRC 32(c)(2): wages includible in gross income " - "(after pre-tax payroll deductions such as traditional elective " - "deferrals) plus self-employment earnings less the deductible part " - "of self-employment tax. It sets each spouse's earned income limit " + "Earned income for the IRC 129(b) limit (IRC 129(e)(2), Form 2441 " + "lines 18-19): W-2 box 1 wages, which leave out pre-tax payroll " + "deductions and all dependent care benefits, plus self-employment " + "earnings less the deductible part of self-employment tax. Taxable " + "dependent care benefits (Form 1040 line 1e) stay out, though they " + "count toward earned income under IRC 32(c)(2) and 21(d). It sets each spouse's earned income limit " "for the child and dependent care credit and the dependent care " "assistance exclusion." ) diff --git a/policyengine_us/variables/gov/irs/income/dependent_gross_income.py b/policyengine_us/variables/gov/irs/income/dependent_gross_income.py index 55070323316..f9e7c0859be 100644 --- a/policyengine_us/variables/gov/irs/income/dependent_gross_income.py +++ b/policyengine_us/variables/gov/irs/income/dependent_gross_income.py @@ -17,6 +17,11 @@ # Gross income includes gains but not losses (computed below). Modified # AGI nets them, subject to the IRC 1211(b) limit. "capital_gains": [], + # Dependent care benefits above the IRC 129 exclusion depend on the + # exclusion's filing-status cap, and filing status depends on this + # variable. Excluded benefits are already out of irs_employment_income; + # the taxable excess is left out here to break that cycle. + "taxable_dependent_care_benefits": [], } diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/dependent_traditional_ira_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/dependent_traditional_ira_deduction.py index 6a411b828bf..8b896ddd0e6 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/dependent_traditional_ira_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/dependent_traditional_ira_deduction.py @@ -5,6 +5,9 @@ from policyengine_us.variables.gov.irs.income.taxable_income.adjusted_gross_income.irs_gross_income.social_security.dependent_taxable_social_security import ( dependent_taxable_social_security_amount, ) +from policyengine_us.variables.household.expense.retirement.ira_compensation import ( + dependent_ira_compensation, +) from policyengine_us.variables.household.expense.retirement._ira_supplied_contributions import ( traditional_ira_contributions_without_spousal_rule, ) @@ -40,7 +43,8 @@ def formula(person, period, parameters): retirement = parameters(period).gov.irs.gross_income.retirement_contributions catch_up = person("age", period) >= retirement.catch_up.age_threshold dollar_limit = retirement.limit.ira + catch_up * retirement.catch_up.limit.ira - compensation = person("ira_compensation", period) + # A dependent's own line 1e would need the filer's filing status. + compensation = dependent_ira_compensation(person, period) # The spousal rule never reaches a dependent, so their contributions # are figured on their own limit. This avoids the filer's filing # status, which itself depends on who is a dependent. diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_dependent_care_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_dependent_care_benefits.py new file mode 100644 index 00000000000..a63de47a527 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_dependent_care_benefits.py @@ -0,0 +1,38 @@ +from policyengine_us.model_api import * + + +class taxable_dependent_care_benefits(Variable): + value_type = float + entity = Person + label = "Taxable dependent care benefits" + documentation = ( + "Employer dependent care benefits above the IRC 129 exclusion (Form " + "2441 line 26, reported on Form 1040 line 1e). Box 1 wages leave out " + "all of dependent_care_employer_benefits, so this adds back the part " + "the filer cannot exclude. The tax unit's amount is split among its " + "members in proportion to their benefits." + ) + unit = USD + definition_period = YEAR + reference = ( + # IRC 129(a)(1) (the exclusion) and 129(a)(2)(B) (the excess is + # gross income). + "https://www.law.cornell.edu/uscode/text/26/129#a", + # Form 2441 Part III, line 26: taxable benefits, to Form 1040 line 1e. + "https://www.irs.gov/pub/irs-prior/f2441--2025.pdf#page=2", + ) + + def formula(person, period, parameters): + tax_unit = person.tax_unit + benefits = person("dependent_care_employer_benefits", period) + # The tax_unit projector returns each member's unit-level value. + unit_benefits = tax_unit.sum(benefits) + exclusion = tax_unit("dependent_care_assistance_exclusion", period) + unit_taxable = max_(unit_benefits - exclusion, 0) + share = np.divide( + benefits, + unit_benefits, + out=np.zeros_like(benefits), + where=unit_benefits > 0, + ) + return unit_taxable * share diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py index 68386ca8890..9c20afe29b3 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py @@ -19,7 +19,11 @@ class basic_standard_deduction(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.irs.deductions.standard filing_status = tax_unit("filing_status", period) - separate_filer_itemizes = tax_unit("separate_filer_itemizes", period) + # IRC 63(c)(6)(A) applies only to a married individual filing a + # separate return. + separate_filer_itemizes = tax_unit("separate_filer_itemizes", period) & ( + filing_status == filing_status.possible_values.SEPARATE + ) # A joint return uses the dependent worksheet, with the couple's # earned income and the joint amount as the cap, when either spouse # can be claimed. diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/standard_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/standard_deduction.py index 70a0a50cd55..209e633e25a 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/standard_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/standard_deduction.py @@ -7,9 +7,32 @@ class standard_deduction(Variable): label = "Standard deduction" unit = USD definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/63#c" - adds = [ - "basic_standard_deduction", - "additional_standard_deduction", - "bonus_guaranteed_deduction", - ] + reference = ( + # IRC 63(c)(1) (the sum) and 63(c)(6)(A) (zero for a separate filer + # whose spouse itemizes). + "https://www.law.cornell.edu/uscode/text/26/63#c", + ) + + def formula(tax_unit, period, parameters): + total = add( + tax_unit, + period, + [ + "basic_standard_deduction", + "additional_standard_deduction", + "bonus_guaranteed_deduction", + ], + ) + # IRC 63(c)(6)(A): a married individual filing separately whose + # spouse itemizes has a standard deduction of zero. The standard + # deduction is the sum in 63(c)(1), so the additional amount for + # age or blindness (63(c)(3), (f)) is zeroed as well, not only the + # basic amount. + filing_status = tax_unit("filing_status", period) + separate_filer_itemizes = tax_unit("separate_filer_itemizes", period) + return where( + separate_filer_itemizes + & (filing_status == filing_status.possible_values.SEPARATE), + 0, + total, + ) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py index 8a0bee91b3e..64e3b87952d 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py @@ -19,4 +19,5 @@ class ca_additions(Variable): "ca_educator_expense_addition", "ca_other_additions", "charitable_contributions_ald", + "ca_dependent_care_assistance_addition", ] diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_dependent_care_assistance_addition.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_dependent_care_assistance_addition.py new file mode 100644 index 00000000000..79b309651de --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_dependent_care_assistance_addition.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class ca_dependent_care_assistance_addition(Variable): + value_type = float + entity = TaxUnit + label = "California addition for dependent care benefits excluded federally" + documentation = ( + "The federal IRC 129 exclusion above California's own exclusion. " + "Federal AGI leaves out the whole federal exclusion, but California " + "kept its $5,000 ($2,500) cap when the federal cap rose for 2021 and " + "from 2026, so the difference goes back into California AGI " + "(Schedule CA (540), Part I, line 1, column C)." + ) + unit = USD + definition_period = YEAR + reference = ( + "https://www.ftb.ca.gov/forms/2021/2021-540-ca-instructions.html", + "https://www.ftb.ca.gov/about-ftb/data-reports-plans/Summary-of-Federal-Income-Tax-Changes/index.html#PL-119-21-70404", + ) + defined_for = StateCode.CA + + def formula(tax_unit, period, parameters): + federal = tax_unit("dependent_care_assistance_exclusion", period) + california = tax_unit("ca_dependent_care_assistance_exclusion", period) + return max_(federal - california, 0) diff --git a/policyengine_us/variables/household/expense/retirement/ira_compensation.py b/policyengine_us/variables/household/expense/retirement/ira_compensation.py index 469328f4c56..69f36ce0e79 100644 --- a/policyengine_us/variables/household/expense/retirement/ira_compensation.py +++ b/policyengine_us/variables/household/expense/retirement/ira_compensation.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.household.expense.retirement._ira_supplied_contributions import ( + _is_supplied, +) class ira_compensation(Variable): @@ -7,23 +10,36 @@ class ira_compensation(Variable): label = "Compensation for IRA contribution limits" unit = USD documentation = ( - "Taxable wages, taxable alimony, and net self-employment earnings " + "Taxable wages, including taxable dependent care benefits (Form 1040 " + "line 1e), taxable alimony, and net self-employment earnings " "less deductible self-employment taxes and self-employed retirement " "plan contributions. A net self-employment loss does not reduce wages." ) definition_period = YEAR reference = ( "https://www.law.cornell.edu/uscode/text/26/219#f_1", + "https://www.ecfr.gov/current/title-26/section-1.219-1", "https://www.law.cornell.edu/uscode/text/26/401#c_2", "https://www.irs.gov/publications/p590a", "https://www.irs.gov/pub/irs-pdf/p4491.pdf#page=144", ) def formula(person, period, parameters): + # tests/test_ira_formula_differential.py runs this body on its own, + # so it repeats the sum in ira_compensation_without_line_1e and + # adds line 1e. # Disability pay treated as wages before minimum retirement age is - # compensation (Pub. 4491 (Rev. 10-2025), file page 144). + # compensation (Pub. 4491 (Rev. 10-2025), file page 144). Taxable + # dependent care benefits (Form 1040 line 1e) are includible pay for + # services (Treas. Reg. 1.219-1(c)(1)). wages = add( - person, period, ["irs_employment_income", "earned_disability_benefits"] + person, + period, + [ + "irs_employment_income", + "earned_disability_benefits", + "taxable_dependent_care_benefits", + ], ) alimony = person("taxable_alimony_income", period) self_employment_earnings = ( @@ -41,3 +57,40 @@ def formula(person, period, parameters): - person("self_employed_pension_contribution_ald_person", period) ) return max_(wages, 0) + max_(alimony, 0) + max_(self_employment_earnings, 0) + + +def ira_compensation_without_line_1e(person, period): + """IRC 219(f)(1) compensation without Form 1040 line 1e. + + The ira_compensation formula repeats this sum and adds line 1e. + """ + # Disability pay treated as wages before minimum retirement age is + # compensation (Pub. 4491 (Rev. 10-2025), file page 144). + wages = add(person, period, ["irs_employment_income", "earned_disability_benefits"]) + alimony = person("taxable_alimony_income", period) + self_employment_earnings = ( + add( + person, + period, + [ + "self_employment_income", + "sstb_self_employment_income", + "farm_operations_income", + "partnership_self_employment_net_earnings", + ], + ) + - person("self_employment_tax_ald_person", period) + - person("self_employed_pension_contribution_ald_person", period) + ) + return max_(wages, 0) + max_(alimony, 0) + max_(self_employment_earnings, 0) + + +def dependent_ira_compensation(person, period): + """IRA compensation for a tax unit dependent's own return. + + A supplied ira_compensation is used as given. Otherwise this leaves out + the dependent's own line 1e, which would need the filer's filing status. + """ + if _is_supplied(person.simulation, "ira_compensation", period): + return person("ira_compensation", period) + return ira_compensation_without_line_1e(person, period) diff --git a/policyengine_us/variables/household/income/person/dependent_care_employer_benefits.py b/policyengine_us/variables/household/income/person/dependent_care_employer_benefits.py index 3a98b8f08c9..5325606891c 100644 --- a/policyengine_us/variables/household/income/person/dependent_care_employer_benefits.py +++ b/policyengine_us/variables/household/income/person/dependent_care_employer_benefits.py @@ -5,6 +5,21 @@ class dependent_care_employer_benefits(Variable): value_type = float entity = Person label = "Dependent care benefits received from an employer" - # description = "Amounts you received as an employee should be shown in Box 10 of your federal Form(s) W-2. If you were self-employed or a partner, include amounts you received under a dependent care assistance program from your sole proprietorship or partnership" + documentation = ( + "Dependent care assistance received as an employee (W-2 box 10, Form " + "2441 line 12), not counting box 10 amounts the employer already " + "reported as box 1 wages. Treated as a pre-tax payroll deduction " + "included in employment_income, like a dependent care flexible " + "spending account salary reduction: it is subtracted from income tax " + "wages (irs_employment_income) and from FICA and FUTA wages " + "(payroll_tax_gross_wages), and any part above the filer's IRC 129 " + "exclusion returns as taxable_dependent_care_benefits." + ) unit = USD definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/129#a_1", + "https://www.law.cornell.edu/uscode/text/26/3121#a_18", + "https://www.law.cornell.edu/uscode/text/26/3306#b_13", + "https://www.irs.gov/pub/irs-prior/i2441--2025.pdf#page=5", + )