diff --git a/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md b/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md new file mode 100644 index 00000000000..1e78b8a6d1f --- /dev/null +++ b/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md @@ -0,0 +1 @@ +Count only wages includible in gross income (after traditional 401(k)/403(b) deferrals and other pre-tax payroll deductions) in `adjusted_earnings`, the earned income limit of the child and dependent care credit, the IRC 129 dependent care exclusion and the state credits that inherit them, per IRC 32(c)(2)(A)(i) and Treas. Reg. 1.21-2(b)(3); California's credit limit, exclusion and AMT child exemption use California wages, which keep the payroll HSA contributions California taxes, net of any business loss, and New York's 2026 credit keeps counting the deferrals under Tax Law 606(c-2)(2)(C), also net of any business loss. California's credit now applies its own FTB 3506 expense, employer benefit and earned income limits in every year, not only 2021, and its AMT child exemption uses California's own minimum ($9,750 for 2025) instead of the federal amount. diff --git a/policyengine_us/parameters/gov/irs/income/amt/exemption/child/in_effect.yaml b/policyengine_us/parameters/gov/irs/income/amt/exemption/child/in_effect.yaml index 233e131f57f..d802675748a 100644 --- a/policyengine_us/parameters/gov/irs/income/amt/exemption/child/in_effect.yaml +++ b/policyengine_us/parameters/gov/irs/income/amt/exemption/child/in_effect.yaml @@ -7,8 +7,8 @@ values: # 55(d)(4), so the limitation stays off. The IRS still publishes the # inflation-adjusted section 59(j) amount (child/amount.yaml), but Form 6251 # applies the normal exemption to every filer from 2018. - # ca_amt_exemption.py still reads child/amount.yaml; this federal switch - # does not change California's use of that amount. + # This federal switch does not change California: ca_amt_exemption.py reads + # California's own amount (gov.states.ca.tax.income.amt.exemption.child). 2018-01-01: false metadata: unit: bool diff --git a/policyengine_us/parameters/gov/states/ca/tax/income/amt/exemption/child/amount.yaml b/policyengine_us/parameters/gov/states/ca/tax/income/amt/exemption/child/amount.yaml new file mode 100644 index 00000000000..22a2b591746 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ca/tax/income/amt/exemption/child/amount.yaml @@ -0,0 +1,25 @@ +description: California limits the alternative minimum tax exemption of certain children under age 24 to their earned income plus this amount. +values: + 2021-01-01: 7_850 + 2022-01-01: 8_300 + 2023-01-01: 8_950 + 2024-01-01: 9_450 + 2025-01-01: 9_750 +metadata: + uprating: gov.states.ca.cpi + unit: currency-USD + period: year + label: California AMT minimum exemption amount for certain children + reference: + # California publishes its own amount, which differs from the federal + # IRC 59(j) amount (for example, $9,750 against $9,550 for 2025). + - title: 2021 Instructions for Schedule P (540), line 22, Exemption Worksheet line 7 + href: https://www.ftb.ca.gov/forms/2021/2021-540-p-instructions.html + - title: 2022 Instructions for Schedule P (540), line 22, Exemption Worksheet line 7 + href: https://www.ftb.ca.gov/forms/2022/2022-540-p-instructions.html + - title: 2023 Instructions for Schedule P (540), line 22, Exemption Worksheet line 7 + href: https://www.ftb.ca.gov/forms/2023/2023-540-p-instructions.html + - title: 2024 Instructions for Schedule P (540), line 22, Exemption Worksheet line 7 + href: https://www.ftb.ca.gov/forms/2024/2024-540-p-instructions.html + - title: 2025 Instructions for Schedule P (540), line 22, Exemption Worksheet line 7 + href: https://www.ftb.ca.gov/forms/2025/2025-540-p-instructions.html diff --git a/policyengine_us/parameters/gov/states/ca/tax/income/credits/child_dependent_care/input.yaml b/policyengine_us/parameters/gov/states/ca/tax/income/credits/child_dependent_care/input.yaml index 16e792eb2a8..47f544240c2 100644 --- a/policyengine_us/parameters/gov/states/ca/tax/income/credits/child_dependent_care/input.yaml +++ b/policyengine_us/parameters/gov/states/ca/tax/income/credits/child_dependent_care/input.yaml @@ -1,15 +1,19 @@ -description: California uses this input for the state child/dependent care credit, based on year. +description: California uses this input for the state child/dependent care credit, based on year. values: + # FTB 3506 applies the federal decimal (line 7) to California's own + # expense and earned income limits (lines 3-6), not to the federal credit. + # R&TC 17052.6 has done so since 2000, so the California computation + # applies in every year, including years before 2021. 2012-01-01: - - cdcc - 2021-01-01: - ca_federal_capped_cdcc - 2022-01-01: - - cdcc metadata: unit: list - period: year + period: year label: California child/dependent care credit input - reference: + reference: - title: Details of Public Law 117-2, section 9631 href: https://www.ftb.ca.gov/about-ftb/data-reports-plans/Summary-of-Federal-Income-Tax-Changes/index.html#PL-117-2-9631 + - title: 2025 FTB 3506, Part III, lines 3-10 + href: https://www.ftb.ca.gov/forms/2025/2025-3506.pdf#page=1 + - title: California Revenue and Taxation Code 17052.6(a)(1) + href: https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17052.6 diff --git a/policyengine_us/tests/core/test_eitc_elective_deferral_invariants.py b/policyengine_us/tests/core/test_eitc_elective_deferral_invariants.py index 2e560ecbd55..eb383fc7efc 100644 --- a/policyengine_us/tests/core/test_eitc_elective_deferral_invariants.py +++ b/policyengine_us/tests/core/test_eitc_elective_deferral_invariants.py @@ -1,10 +1,12 @@ """Differential coverage of payroll exclusions across vectorized tax units. Replacing gross wages and pre-tax deductions with the same W-2 box 1 wages -must leave federal EITC and ACTC earnings/credits unchanged. California must -retain taxable payroll HSA amounts, while gross earned income and the AMT -earnings cap must remain independent of payroll deductions. The paired runs -also remove dependents' wages to check that they cannot enter filer earnings. +must leave federal EITC and ACTC earnings/credits and the CDCC earned income +limit unchanged. California must retain taxable payroll HSA amounts, while +gross earned income must remain independent of payroll deductions. The paired +runs also remove dependents' wages to check that they cannot enter filer +earnings. (The AMT child earnings cap applies only before 2018, so +eitc_earned_income.yaml covers it.) Additional California pairs retain payroll HSA deductions when substituting California taxable wages, so YCTC must agree even with business losses. """ @@ -54,9 +56,6 @@ def _situations(): tax_units[f"unit_{i}"] = { "members": members, "takes_up_eitc": {YEAR: True}, - # Force the existing cap to isolate its gross earnings dependency. - "amt_kiddie_tax_applies": {YEAR: True}, - "amt_income": {YEAR: 200_000}, } households[f"household_{i}"] = { "members": members, @@ -101,8 +100,6 @@ def _situations(): tax_units[f"unit_{i}"] = { "members": members, "takes_up_eitc": {YEAR: True}, - "amt_kiddie_tax_applies": {YEAR: True}, - "amt_income": {YEAR: 200_000}, } households[f"household_{i}"] = { "members": members, @@ -156,8 +153,9 @@ def _run(situation): "ca_eitc_earned_income", "ca_yctc", "earned_income", - "adjusted_earnings", - "amt_exemption", + "head_earned", + "spouse_earned", + "min_head_spouse_earned", ) return {name: simulation.calculate(name, YEAR).copy() for name in outputs} @@ -175,6 +173,9 @@ def test_payroll_exclusions_match_equivalent_taxable_compensation(): "ctc_phase_in_relevant_earnings", "ctc_phase_in", "filer_adjusted_earnings", + "head_earned", + "spouse_earned", + "min_head_spouse_earned", ): np.testing.assert_allclose( with_deductions[name], @@ -214,7 +215,7 @@ def test_payroll_exclusions_match_equivalent_taxable_compensation(): ) assert np.all(with_deductions["ca_yctc"][yctc_pairs] > 0) - for name in ("earned_income", "adjusted_earnings", "amt_exemption"): + for name in ("earned_income",): np.testing.assert_allclose( with_deductions[name], no_deductions[name], diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/dependent_care_assistance_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/dependent_care_assistance_exclusion.yaml index 5d08cd87b7b..07b22fa61d1 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/dependent_care_assistance_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/dependent_care_assistance_exclusion.yaml @@ -404,3 +404,30 @@ cdcc_treated_as_unmarried: true # min($6,000 benefits, $5,000 unmarried cap, $38,000 earnings) = $5,000. dependent_care_assistance_exclusion: 5_000 + +# IRC 129(e)(2) gives earned income the IRC 32(c)(2) meaning, which counts +# wages only if includible in gross income. Form 2441 lines 18 and 19 figure +# it the same way as lines 4 and 5 (Form 1040 line 1z). +- name: Section 129(b) limit uses the lower earner's wages after a 401(k) deferral + period: 2025 + input: + people: + head: + age: 40 + employment_income: 50_000 + dependent_care_employer_benefits: 5_000 + spouse: + age: 38 + employment_income: 4_000 + traditional_401k_contributions_desired: 1_500 + child: + age: 4 + tax_units: + tax_unit: + members: [head, spouse, child] + filing_status: JOINT + output: + # Spouse: 4,000 wages - 1,500 deferral = 2,500. + min_head_spouse_earned: 2_500 + # min(5,000 benefits, 5,000 joint cap, 2,500 earned income) = 2,500. + dependent_care_assistance_exclusion: 2_500 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/integration.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/integration.yaml index ceea4d90a91..44013980306 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/integration.yaml @@ -256,3 +256,44 @@ count_cdcc_eligible: 2 cdcc_relevant_expenses: 6_000 cdcc: 1_200 + +# IRC 21(d)(1)(B) caps expenses at the lower-earning spouse's earned income, +# which counts wages only if includible in gross income (Treas. Reg. +# 1.21-2(b)(3); IRC 32(c)(2)(A)(i)). Form 2441 line 5 starts from Form 1040 +# line 1z, which leaves out the spouse's traditional 401(k) deferral. +- name: Lower-earning spouse's traditional 401(k) deferral lowers the earned income limit + absolute_error_margin: 0.01 + period: 2025 + input: + people: + head: + age: 40 + employment_income: 56_000 + spouse: + age: 38 + employment_income: 4_000 + traditional_401k_contributions_desired: 1_000 + child1: + age: 5 + child2: + age: 3 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + tax_unit_childcare_expenses: 9_103 + households: + household: + members: [head, spouse, child1, child2] + state_code: TX + output: + # Spouse: 4,000 wages - 1,000 deferral = 3,000 taxable wages. + spouse_earned: 3_000 + min_head_spouse_earned: 3_000 + # min(9,103 expenses, 6,000 two-child limit, 3,000 earned income). + cdcc_relevant_expenses: 3_000 + # AGI 56,000 + 3,000 = 59,000: 35% - 22 points + # (ceil((59,000 - 15,000) / 2,000)), floored at 20%. + # 3,000 * 20% = 600 (gross wages would give 4,000 * 20% = 800). + # Tax before credits: (59,000 - 31,500) taxable income + # -> 2,385 + 12% * 3,650 = 2,823, so the credit is not limited. + cdcc: 600 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 8a697202475..864b3378901 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 @@ -360,7 +360,7 @@ eitc_earned_income: 8500 filer_adjusted_earnings: 8500 -- name: AMT kiddie-tax exemption retains its pre-2018 gross earnings cap +- name: AMT kiddie-tax exemption caps at taxable earnings before 2018 period: 2017 absolute_error_margin: 0.01 input: @@ -384,11 +384,14 @@ output: filer_adjusted_earnings: 8000 # Ordinary exemption = 54,300 - 0.25 * (200,000 - 120,700) = 34,475. - # Historical cap uses gross earnings: 10,000 + 7,500 = 17,500. - # amt_exemption = min(34,475, 17,500) = 17,500. - amt_exemption: 17500 + # IRC 59(j) earned income (IRC 911(d)(2)) counts amounts received; the + # deferral is not made available to the child (IRC 402(e)(3)), and the + # 2017 Form 8615 instructions use Form 1040 line 7 (taxable) wages. + # Historical cap: (10,000 - 2,000) + 7,500 = 15,500. + # amt_exemption = min(34,475, 15,500) = 15,500. + amt_exemption: 15500 -- name: AMT kiddie-tax exemption has no gross earnings cap from 2018 +- name: AMT kiddie-tax exemption has no earnings cap from 2018 period: 2022 absolute_error_margin: 0.01 input: diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/adjusted_earnings.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/adjusted_earnings.yaml new file mode 100644 index 00000000000..3ab736942e1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/adjusted_earnings.yaml @@ -0,0 +1,55 @@ +# IRC 32(c)(2)(A)(i) counts employee compensation only if it is includible in +# gross income. Treas. Reg. 1.21-2(b)(3) (CDCC) and IRC 129(e)(2) (dependent +# care exclusion) use that definition, and Form 2441 lines 4 and 5 start from +# Form 1040 line 1z (W-2 box 1). + +- name: Traditional 401(k) deferral is left out of adjusted earnings + period: 2025 + input: + employment_income: 4_000 + traditional_401k_contributions_desired: 1_000 + output: + # Gross earned income keeps the deferral: 4,000. + earned_income: 4_000 + # W-2 box 1 wages: 4,000 - 1,000 = 3,000 (IRC 402(e)(3)). + irs_employment_income: 3_000 + # 4,000 - (4,000 - 3,000) excluded wages - 0 SE adjustment = 3,000. + adjusted_earnings: 3_000 + +- name: Roth deferral stays in adjusted earnings, other pre-tax payroll deductions do not + period: 2025 + input: + employment_income: 10_000 + roth_401k_contributions_desired: 1_000 + pre_tax_health_insurance_premiums: 500 + health_savings_account_payroll_contributions: 1_000 + output: + # Designated Roth contributions are includible in gross income + # (IRC 402A(a)(1)); the cafeteria-plan premium and HSA payroll + # contribution are not (IRC 106, 125). + # 10,000 - 500 - 1,000 = 8,500. + irs_employment_income: 8_500 + adjusted_earnings: 8_500 + +- name: Deferral and self-employment income together + absolute_error_margin: 0.01 + period: 2025 + input: + employment_income: 4_000 + traditional_401k_contributions_desired: 1_000 + self_employment_income: 10_000 + output: + # SE tax: 10,000 * 0.9235 * 0.153 = 1,412.96. + # Adjustment: 0.5 * 1,412.955 = 706.48. + # 4,000 + 10,000 - 1,000 excluded wages - 706.48 = 12,293.52. + adjusted_earnings: 12_293.52 + +- name: Self-employment loss larger than taxable wages floors adjusted earnings at zero + period: 2025 + input: + employment_income: 2_000 + traditional_401k_contributions_desired: 1_000 + self_employment_income: -3_000 + output: + # max(0, 2,000 - 3,000 - 1,000 excluded wages - 0) = 0. + adjusted_earnings: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/alternative_minimum_tax/ca_amt_exemption.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/alternative_minimum_tax/ca_amt_exemption.yaml index 27ea8a27dc2..6d2c72207c4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/alternative_minimum_tax/ca_amt_exemption.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/alternative_minimum_tax/ca_amt_exemption.yaml @@ -8,7 +8,7 @@ members: [person1] filing_status: SINGLE ca_amti: 320_000 - head_earned: 320_000 + ca_head_earned: 320_000 households: household: members: [person1] @@ -27,7 +27,7 @@ members: [person1, person2] filing_status: JOINT ca_amti: 900_000 - head_earned: 700_000 + ca_head_earned: 700_000 households: household: members: [person1, person2] @@ -45,7 +45,7 @@ members: [person1] filing_status: SURVIVING_SPOUSE ca_amti: 390_351 - head_earned: 320_000 + ca_head_earned: 320_000 households: household: members: [person1] @@ -63,7 +63,7 @@ members: [person1] filing_status: SINGLE ca_amti: 605_043 - head_earned: 320_000 + ca_head_earned: 320_000 households: household: members: [person1] @@ -87,7 +87,7 @@ members: [person1] filing_status: SINGLE ca_amti: 500_000 - head_earned: 500_000 + ca_head_earned: 500_000 households: household: members: [person1] @@ -110,7 +110,7 @@ members: [person1, person2] filing_status: JOINT ca_amti: 700_000 - head_earned: 700_000 + ca_head_earned: 700_000 households: household: members: [person1, person2] @@ -131,10 +131,66 @@ members: [person1] filing_status: SINGLE ca_amti: 500_000 - head_earned: 500_000 + ca_head_earned: 500_000 households: household: members: [person1] state_code: CA output: ca_amt_exemption: 54_701 + +- name: Child exemption counts California wages, which leave out a 401(k) deferral + period: 2025 + input: + people: + person1: + age: 17 + is_tax_unit_head: true + is_qualifying_child_dependent: true + employment_income: 10_000 + traditional_401k_contributions_desired: 1_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + ca_amti: 100_000 + households: + household: + members: [person1] + state_code: CA + output: + # Worksheet line 8 counts wages "received for personal services + # performed". California excludes the 401(k) deferral, as the federal + # rules do: 10,000 - 1,000 = 9,000. + head_earned: 9_000 + ca_head_earned: 9_000 + # Adult exemption: AMTI below the 347,808 threshold, so 92,749. + # Child (2025 Schedule P (540) instructions, line 22, Exemption + # Worksheet lines 7-10): line 7 is California's 9,750, not the federal + # 9,550, so min(9,750 + 9,000, 92,749) = 18,750. + ca_amt_exemption: 18_750 + +- name: Child exemption uses the California minimum, not the federal one + period: 2022 + input: + people: + person1: + age: 17 + is_tax_unit_head: true + is_qualifying_child_dependent: true + employment_income: 5_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + ca_amti: 50_000 + households: + household: + members: [person1] + state_code: CA + output: + # Adult exemption: AMTI below the threshold, so 84,550. + # Child (2022 Schedule P (540) instructions, line 22, Exemption + # Worksheet lines 7-10): min(8,300 + 5,000, 84,550) = 13,300. The + # federal IRC 59(j) amount of 8,200 would give 13,200. + ca_amt_exemption: 13_300 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_adjusted_earnings.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_adjusted_earnings.yaml new file mode 100644 index 00000000000..9e3c99fcf86 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_adjusted_earnings.yaml @@ -0,0 +1,40 @@ +# California earned income: California wages (W-2 box 16), which keep +# payroll HSA contributions (Schedule CA (540) line 1h), plus net earnings +# from self-employment (FTB 3506 line 4), floored at zero once. +- name: Payroll HSA contribution counts, a 401(k) deferral does not + period: 2025 + input: + employment_income: 10_000 + traditional_401k_contributions_desired: 1_000 + health_savings_account_payroll_contributions: 1_000 + state_code: CA + output: + # Federal: 10,000 - 1,000 - 1,000 = 8,000. + adjusted_earnings: 8_000 + # California: 10,000 - 1,000 = 9,000. + ca_adjusted_earnings: 9_000 + +- name: A self-employment loss offsets the California wages before the zero floor + period: 2025 + input: + employment_income: 4_000 + health_savings_account_payroll_contributions: 1_000 + self_employment_income: -3_500 + state_code: CA + output: + # Federal: max(3,000 - 3,500, 0) = 0. + adjusted_earnings: 0 + # California: max(4,000 - 3,500, 0) = 500, not 0 + 1,000. + ca_adjusted_earnings: 500 + +- name: A self-employment loss larger than the California wages leaves zero + period: 2025 + input: + employment_income: 4_000 + health_savings_account_payroll_contributions: 1_000 + self_employment_income: -5_000 + state_code: CA + output: + adjusted_earnings: 0 + # max(4,000 - 5,000, 0) = 0. + ca_adjusted_earnings: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc.yaml index 3cbbe67213b..252c587162c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc.yaml @@ -3,7 +3,6 @@ input: state_code: CA adjusted_gross_income: 100_001 - cdcc: 0 ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 0 @@ -13,7 +12,6 @@ input: state_code: CA adjusted_gross_income: 1_000 - cdcc: 0 ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 500 @@ -23,7 +21,6 @@ input: state_code: CA adjusted_gross_income: 50_000 - cdcc: 0 ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 430 @@ -33,7 +30,6 @@ input: state_code: CA adjusted_gross_income: -50_000 - cdcc: 0 ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 500 @@ -43,8 +39,7 @@ input: state_code: CA adjusted_gross_income: 100_001 - cdcc: 1_000 - ca_federal_capped_cdcc: 0 + ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 0 @@ -53,8 +48,7 @@ input: state_code: CA adjusted_gross_income: 1_000 - cdcc: 1_000 - ca_federal_capped_cdcc: 0 + ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 500 @@ -63,8 +57,7 @@ input: state_code: CA adjusted_gross_income: 50_000 - cdcc: 1_000 - ca_federal_capped_cdcc: 0 + ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 430 @@ -73,7 +66,158 @@ input: state_code: CA adjusted_gross_income: -50_000 - cdcc: 1_000 - ca_federal_capped_cdcc: 0 + ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 500 + +# FTB 3506 (2025), Part III: line 6 is the smallest of the expense limit +# (line 3) and each spouse's California earned income (lines 4 and 5), +# line 8 applies the federal decimal and line 10 the California decimal. +# California taxes the spouse's $1,000 payroll HSA contribution (Schedule +# CA (540) line 1h), so the spouse's California earned income is $4,000 +# while federal earned income is $3,000. +- name: 2025 final credit uses California earned income, not the federal credit + period: 2025 + input: + people: + head: + age: 40 + employment_income: 90_000 + spouse: + age: 38 + employment_income: 4_000 + health_savings_account_payroll_contributions: 1_000 + child1: + age: 5 + child2: + age: 3 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + tax_unit_childcare_expenses: 9_000 + households: + household: + members: [head, spouse, child1, child2] + state_code: CA + output: + # Federal: min(9,000, 6,000, 90,000, 3,000) x 20% = 600. + cdcc: 600 + # Federal AGI: 90,000 + 3,000 = 93,000, so line 7 is .20 and line 9 is .34. + # Line 6: min(9,000, 6,000, 90,000, 4,000) = 4,000. Line 8: 800. + ca_federal_capped_cdcc: 800 + # Line 10: 800 x .34 = 272. + ca_cdcc: 272 + # California AGI: 93,000 + 1,000 HSA addition = 94,000. Taxable income: + # 94,000 - 11,412 standard deduction = 82,588. Tax: 22,158 x 1% + # + 30,370 x 2% + 30,060 x 4% = 221.58 + 607.40 + 1,202.40 = 2,031.38. + # Exemption credits: 2 x 153 + 2 x 475 = 1,256. + # 2,031.38 - 1,256 - 272 = 503.38. + ca_income_tax: 503.38 + +# IRC 21(d), which R&TC 17052.6(d)(2) adopts, limits expenses to net earned +# income: wages plus net earnings from self-employment (Treas. Reg. +# 1.21-2(b)(3); FTB 3506 line 4). A business loss therefore offsets the +# spouse's California wages, including the payroll HSA contribution. +- name: 2021 self-employment loss that offsets all California wages leaves no credit + period: 2021 + input: + people: + head: + age: 40 + employment_income: 90_000 + spouse: + age: 38 + employment_income: 4_000 + health_savings_account_payroll_contributions: 1_000 + self_employment_income: -4_000 + child1: + age: 5 + child2: + age: 3 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + tax_unit_childcare_expenses: 9_000 + households: + household: + members: [head, spouse, child1, child2] + state_code: CA + output: + # max(4,000 California wages - 4,000 loss, 0) = 0. + ca_spouse_earned: 0 + ca_cdcc_min_head_spouse_earned: 0 + ca_cdcc_relevant_expenses: 0 + ca_cdcc: 0 + # California AGI: 90,000 + 3,000 - 4,000 + 1,000 HSA addition = 90,000. + # Taxable income: 90,000 - 9,606 = 80,394. Tax: 18,650 x 1% + # + 25,564 x 2% + 25,570 x 4% + 10,610 x 6% + # = 186.50 + 511.28 + 1,022.80 + 636.60 = 2,357.18. + # Exemption credits: 2 x 129 + 2 x 400 = 1,058. 2,357.18 - 1,058 = 1,299.18. + ca_income_tax: 1_299.18 + +- name: 2025 self-employment loss that offsets part of the California wages + period: 2025 + input: + people: + head: + age: 40 + employment_income: 90_000 + spouse: + age: 38 + employment_income: 4_000 + health_savings_account_payroll_contributions: 1_000 + self_employment_income: -3_500 + child1: + age: 5 + child2: + age: 3 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + tax_unit_childcare_expenses: 9_000 + households: + household: + members: [head, spouse, child1, child2] + state_code: CA + output: + # Federal: max(3,000 - 3,500, 0) = 0, so no federal credit. + spouse_earned: 0 + cdcc: 0 + # California: max(4,000 - 3,500, 0) = 500. + ca_spouse_earned: 500 + # Federal AGI: 90,000 + 3,000 - 3,500 = 89,500 (line 7 .20, line 9 .34). + # 500 x .20 x .34 = 34. + ca_cdcc: 34 + +# FTB 3506 Part IV: line 29 is the $3,000 limit less the $2,000 excluded +# employer benefits (1,000); line 30 is the qualified expenses less the +# excluded benefits (2,500 - 2,000 = 500); line 31 is the smaller, 500. +# Line 3 then takes 500 instead of the 2,500 expenses. +- name: 2025 employer dependent care benefits reduce the California base through FTB 3506 Part IV + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + dependent_care_employer_benefits: 2_000 + child: + age: 5 + tax_units: + tax_unit: + members: [head, child] + tax_unit_childcare_expenses: 2_500 + households: + household: + members: [head, child] + state_code: CA + output: + # min(2,000 benefits, 5,000 cap, 60,000 earned income) = 2,000. + dependent_care_assistance_exclusion: 2_000 + ca_dependent_care_assistance_exclusion: 2_000 + # min(2,500 expenses, line 31 of 500, 60,000 earned income) = 500. + ca_cdcc_relevant_expenses: 500 + # Federal AGI 60,000: line 7 is .20 and line 9 is .43. + # 500 x .20 = 100; 100 x .43 = 43. + ca_federal_capped_cdcc: 100 + ca_cdcc: 43 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc_rate.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc_rate.yaml index 50706f44577..00196a34e9a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc_rate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_cdcc_rate.yaml @@ -45,3 +45,16 @@ adjusted_gross_income: 15_000 output: ca_cdcc_rate: 0.35 + +# R&TC 17052.6(a)(1) uses IRC 21 "as applicable for federal income tax +# purposes for the taxable year", so 2026 takes the P.L. 119-21 schedule: +# first phase-out max(.50 - ceil((80,000 - 15,000) / 2,000) x .01, .35) = .35; +# second phase-out ceil((80,000 - 75,000) / 2,000) x .01 = .03; .35 - .03 = .32. +- name: 2026 head of household with AGI of $80,000 gets the federal rate after the second phase-out + period: 2026 + input: + state_code: CA + filing_status: HEAD_OF_HOUSEHOLD + adjusted_gross_income: 80_000 + output: + ca_cdcc_rate: 0.32 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 c53fa606220..7b92615cd2a 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 @@ -4,7 +4,7 @@ state_code: CA tax_unit_childcare_expenses: 10_000 count_cdcc_eligible: 1 - min_head_spouse_earned: 50_000 + ca_cdcc_min_head_spouse_earned: 50_000 output: ca_cdcc_relevant_expenses: 3_000 @@ -14,7 +14,7 @@ state_code: CA tax_unit_childcare_expenses: 10_000 count_cdcc_eligible: 1 - min_head_spouse_earned: 50_000 + ca_cdcc_min_head_spouse_earned: 50_000 output: ca_cdcc_relevant_expenses: 3_000 @@ -24,7 +24,7 @@ state_code: CA tax_unit_childcare_expenses: 10_000 count_cdcc_eligible: 1 - min_head_spouse_earned: 50_000 + ca_cdcc_min_head_spouse_earned: 50_000 output: ca_cdcc_relevant_expenses: 3_000 @@ -34,7 +34,7 @@ state_code: CA tax_unit_childcare_expenses: 20_000 count_cdcc_eligible: 3 - min_head_spouse_earned: 50_000 + ca_cdcc_min_head_spouse_earned: 50_000 output: ca_cdcc_relevant_expenses: 6_000 @@ -44,7 +44,7 @@ state_code: CA tax_unit_childcare_expenses: 35_000 count_cdcc_eligible: 4 - min_head_spouse_earned: 40_000 + ca_cdcc_min_head_spouse_earned: 40_000 output: ca_cdcc_relevant_expenses: 6_000 @@ -63,7 +63,7 @@ tax_unit: members: [person1, person2] tax_unit_childcare_expenses: 0 - min_head_spouse_earned: 50_000 + ca_cdcc_min_head_spouse_earned: 50_000 households: household: members: [person1, person2] @@ -83,8 +83,12 @@ tax_unit_childcare_expenses: 10_000 count_cdcc_eligible: 2 min_head_spouse_earned: 50_000 + ca_cdcc_min_head_spouse_earned: 50_000 dependent_care_assistance_exclusion: 5_000 output: + # California earned income equals federal earned income, so the + # California exclusion is the federal 5,000, within the $5,000 cap. + ca_dependent_care_assistance_exclusion: 5_000 # min(10_000 expenses, $6,000 two-child cap - $5,000 exclusion, 50_000) = 1_000. ca_cdcc_relevant_expenses: 1_000 @@ -329,3 +333,43 @@ state_code: CA output: ca_cdcc_relevant_expenses: 3000 + +# R&TC 17052.6(d)(2) limits earned income to "earned income subject to tax +# under this part", and FTB 3506 line 4 counts "other taxable employee +# compensation". California excludes a traditional 401(k) deferral but taxes +# payroll HSA contributions (Schedule CA (540) line 1h, column C). +- name: 2025 California earned income limit counts payroll HSA but not a 401(k) deferral + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + spouse: + age: 38 + employment_income: 6_000 + traditional_401k_contributions_desired: 1_000 + health_savings_account_payroll_contributions: 1_000 + child1: + age: 5 + child2: + age: 3 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + tax_unit_childcare_expenses: 9_000 + households: + household: + members: [head, spouse, child1, child2] + state_code: CA + output: + # Federal (W-2 box 1): 6,000 - 1,000 401(k) - 1,000 HSA = 4,000. + spouse_earned: 4_000 + min_head_spouse_earned: 4_000 + # California (W-2 box 16): 6,000 - 1,000 401(k) = 5,000. + # ca_adjusted_earnings measures earned income on these wages: 5,000. + ca_spouse_earned: 5_000 + ca_cdcc_min_head_spouse_earned: 5_000 + # min(9,000 expenses, 6,000 two-person limit, federal line 31 of 6,000, + # 5,000 California earned income) = 5,000. + ca_cdcc_relevant_expenses: 5_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_dependent_care_assistance_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_dependent_care_assistance_exclusion.yaml index a7a905b3e21..6057b1982fc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_dependent_care_assistance_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/child_dependent_care/ca_dependent_care_assistance_exclusion.yaml @@ -68,3 +68,64 @@ output: # min($7,000 federal exclusion under the $7,500 cap, $5,000) = $5,000. ca_dependent_care_assistance_exclusion: 5_000 + +- name: California earned income limit on the exclusion counts payroll HSA contributions + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + dependent_care_employer_benefits: 5_000 + spouse: + age: 38 + employment_income: 4_000 + health_savings_account_payroll_contributions: 1_000 + child: + age: 4 + tax_units: + tax_unit: + members: [head, spouse, child] + filing_status: JOINT + households: + household: + members: [head, spouse, child] + state_code: CA + output: + # Federal: spouse earns 4,000 - 1,000 HSA = 3,000, so the IRC 129(b) + # exclusion is min(5,000 benefits, 5,000 cap, 3,000) = 3,000. + dependent_care_assistance_exclusion: 3_000 + # California taxes the HSA contribution: spouse earns 4,000. + ca_cdcc_min_head_spouse_earned: 4_000 + # min(5,000 benefits, 5,000 California cap, 4,000) = 4,000. + ca_dependent_care_assistance_exclusion: 4_000 + +- name: A self-employment loss that offsets the California wages does not raise the exclusion + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + dependent_care_employer_benefits: 5_000 + spouse: + age: 38 + employment_income: 4_000 + health_savings_account_payroll_contributions: 1_000 + self_employment_income: -4_000 + child: + age: 4 + tax_units: + tax_unit: + members: [head, spouse, child] + filing_status: JOINT + households: + household: + members: [head, spouse, child] + state_code: CA + output: + # Federal: max(3,000 - 4,000, 0) = 0, so the IRC 129(b) exclusion is 0. + dependent_care_assistance_exclusion: 0 + # California: max(4,000 - 4,000, 0) = 0, so the exclusion stays at 0. + ca_cdcc_min_head_spouse_earned: 0 + ca_dependent_care_assistance_exclusion: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_cdcc_qualifying_expenses.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_cdcc_qualifying_expenses.yaml index 3a41d224047..e084fbf105d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_cdcc_qualifying_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_cdcc_qualifying_expenses.yaml @@ -8,7 +8,7 @@ state_code: NY count_cdcc_eligible: 1 tax_unit_childcare_expenses: 9_600 - head_earned: 100_195 + employment_income: 100_195 output: ny_cdcc_qualifying_expenses: 3_000 @@ -18,7 +18,7 @@ state_code: NY count_cdcc_eligible: 3 tax_unit_childcare_expenses: 12_000 - head_earned: 50_000 + employment_income: 50_000 output: ny_cdcc_qualifying_expenses: 7_500 @@ -28,7 +28,7 @@ state_code: NY count_cdcc_eligible: 4 tax_unit_childcare_expenses: 8_500 - head_earned: 100_000 + employment_income: 100_000 output: ny_cdcc_qualifying_expenses: 8_500 @@ -38,7 +38,7 @@ state_code: NY count_cdcc_eligible: 6 tax_unit_childcare_expenses: 20_000 - head_earned: 500_000 + employment_income: 500_000 output: ny_cdcc_qualifying_expenses: 9_000 @@ -48,7 +48,7 @@ state_code: NY count_cdcc_eligible: 1 tax_unit_childcare_expenses: 3_000 - head_earned: 2_000 + employment_income: 2_000 output: ny_cdcc_qualifying_expenses: 2_000 @@ -58,7 +58,7 @@ state_code: NY count_cdcc_eligible: 0 tax_unit_childcare_expenses: 5_000 - head_earned: 40_000 + employment_income: 40_000 output: ny_cdcc_qualifying_expenses: 0 @@ -132,3 +132,103 @@ output: # min(4,000 - 2,000, 3,000 cap, 50,000 earned) = 2,000 ny_cdcc_qualifying_expenses: 2_000 + +- name: Case 10, a 401(k) deferral stays in New York earned income from 2026. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 50_000 + person2: + age: 38 + employment_income: 4_000 + traditional_401k_contributions_desired: 1_500 + person3: + age: 5 + person4: + age: 3 + tax_units: + tax_unit: + members: [person1, person2, person3, person4] + tax_unit_childcare_expenses: 9_000 + households: + household: + members: [person1, person2, person3, person4] + state_code: NY + output: + # Federal earned income (IRC 32(c)(2)) leaves out the deferral: + # 4,000 - 1,500 = 2,500. + spouse_earned: 2_500 + # 606(c-2)(2)(C) counts "wages, salaries, tips and other employee + # compensation" with no gross-income inclusion test, so the spouse's + # earned income stays 4,000. + # min(9,000 expenses, 6,000 two-person cap, 4,000 earned) = 4,000 + ny_cdcc_qualifying_expenses: 4_000 + +# 606(c-2)(2)(C) counts wages "and those items of gross income which are +# includible in the computation of net earnings from self-employment", so a +# business loss offsets the gross wages before the zero floor. +- name: Case 11, a self-employment loss that offsets all wages leaves no New York earned income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 90_000 + person2: + age: 38 + employment_income: 4_000 + traditional_401k_contributions_desired: 1_000 + self_employment_income: -4_000 + person3: + age: 5 + person4: + age: 3 + tax_units: + tax_unit: + members: [person1, person2, person3, person4] + tax_unit_childcare_expenses: 9_000 + households: + household: + members: [person1, person2, person3, person4] + state_code: NY + output: + # Federal: max(4,000 - 1,000 deferral - 4,000 loss, 0) = 0. + spouse_earned: 0 + # New York: max(4,000 gross wages - 4,000 loss, 0) = 0. Adding the + # 1,000 deferral back after the federal floor would give 1,000. + ny_cdcc_qualifying_expenses: 0 + +- name: Case 12, a self-employment loss that offsets part of the gross wages. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 90_000 + person2: + age: 38 + employment_income: 4_000 + traditional_401k_contributions_desired: 1_000 + self_employment_income: -3_500 + person3: + age: 5 + person4: + age: 3 + tax_units: + tax_unit: + members: [person1, person2, person3, person4] + tax_unit_childcare_expenses: 9_000 + households: + household: + members: [person1, person2, person3, person4] + state_code: NY + output: + # Federal: max(4,000 - 1,000 deferral - 3,500 loss, 0) = 0. + spouse_earned: 0 + # New York: 4,000 gross wages - 3,500 loss = 500. + # min(9,000 expenses, 6,000 two-person cap, 500 earned) = 500 + ny_cdcc_qualifying_expenses: 500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_expense_deduction.yaml index 44aa2de1cfe..45b2c1eebec 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_expense_deduction.yaml @@ -116,3 +116,38 @@ dependent_care_assistance_exclusion: 5_000 va_child_dependent_care_deduction_cdcc_limit: 1_000 va_child_dependent_care_expense_deduction: 1_000 + +# Va. Code 58.1-322.03(3) deducts "the amount of employment-related expenses +# upon which the federal credit is based under § 21", so the federal earned +# income limit (taxable wages, Treas. Reg. 1.21-2(b)(3)) carries over. +- name: Lower-earning spouse's 401(k) deferral lowers the deduction + period: 2025 + input: + people: + head: + age: 40 + employment_income: 50_000 + spouse: + age: 38 + employment_income: 4_000 + traditional_401k_contributions_desired: 1_500 + child1: + age: 5 + child2: + age: 3 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + filing_status: JOINT + tax_unit_childcare_expenses: 9_000 + households: + household: + members: [head, spouse, child1, child2] + state_code: VA + output: + # Spouse: 4,000 wages - 1,500 deferral = 2,500. + min_head_spouse_earned: 2_500 + # 6,000 two-child federal limit, no employer benefits. + va_child_dependent_care_deduction_cdcc_limit: 6_000 + # min(9,000 expenses, 6,000 limit, 2,500 earned income) = 2,500. + va_child_dependent_care_expense_deduction: 2_500 diff --git a/policyengine_us/variables/gov/irs/credits/cdcc/min_head_spouse_earned.py b/policyengine_us/variables/gov/irs/credits/cdcc/min_head_spouse_earned.py index b2649caf4c3..23506586ffa 100644 --- a/policyengine_us/variables/gov/irs/credits/cdcc/min_head_spouse_earned.py +++ b/policyengine_us/variables/gov/irs/credits/cdcc/min_head_spouse_earned.py @@ -1,6 +1,50 @@ from policyengine_us.model_api import * +def lesser_head_spouse_earned( + tax_unit, period, parameters, head_earnings, spouse_earnings +): + """ + IRC section 21(d)(1)-(2) earned income limit from the head's and + spouse's earned income. States that measure earned income their own + way, such as California, pass their own head and spouse amounts. + """ + p = parameters(period).gov.irs.credits.cdcc + is_joint = tax_unit("tax_unit_is_joint", period) + person = tax_unit.members + is_head = person("is_tax_unit_head", period) + is_spouse = person("is_tax_unit_spouse", period) + floor_eligible = person("cdcc_income_floor_eligible", period) + + head_floor_eligible = tax_unit.sum(is_head * floor_eligible) > 0 + spouse_floor_eligible = tax_unit.sum(is_spouse * floor_eligible) > 0 + + # IRC section 21(d)(2): a spouse who is a student or incapable of + # self-care is deemed to earn at least this floor, but only one spouse + # may be deemed. Deem whichever eligible spouse yields the larger + # lesser-of-earnings, leaving the other spouse's actual earnings. + # When both spouses are floor-eligible and neither earns, the + # non-deemed spouse's earnings stay zero, so the lesser of earnings + # is zero (Treas. Reg. 1.21-2(b)(4)(iii); (c), Example 3). + qualifying_individuals = tax_unit("count_cdcc_eligible", period) + floor = p.deemed_earned_income.calc(qualifying_individuals) + no_deem = min_(head_earnings, spouse_earnings) + deem_head = where( + head_floor_eligible, + min_(max_(head_earnings, floor), spouse_earnings), + 0, + ) + deem_spouse = where( + spouse_floor_eligible, + min_(max_(spouse_earnings, floor), head_earnings), + 0, + ) + joint_earnings = max_(no_deem, max_(deem_head, deem_spouse)) + + # Source conflict us:dependent-care-exclusion-separate-filer-income + return where(is_joint, joint_earnings, head_earnings) + + class min_head_spouse_earned(Variable): value_type = float entity = TaxUnit @@ -10,39 +54,10 @@ class min_head_spouse_earned(Variable): reference = "https://www.law.cornell.edu/uscode/text/26/21#d" def formula(tax_unit, period, parameters): - p = parameters(period).gov.irs.credits.cdcc - is_joint = tax_unit("tax_unit_is_joint", period) - person = tax_unit.members - is_head = person("is_tax_unit_head", period) - is_spouse = person("is_tax_unit_spouse", period) - floor_eligible = person("cdcc_income_floor_eligible", period) - - head_earnings = tax_unit("head_earned", period) - spouse_earnings = tax_unit("spouse_earned", period) - head_floor_eligible = tax_unit.sum(is_head * floor_eligible) > 0 - spouse_floor_eligible = tax_unit.sum(is_spouse * floor_eligible) > 0 - - # IRC section 21(d)(2): a spouse who is a student or incapable of - # self-care is deemed to earn at least this floor, but only one spouse - # may be deemed. Deem whichever eligible spouse yields the larger - # lesser-of-earnings, leaving the other spouse's actual earnings. - # When both spouses are floor-eligible and neither earns, the - # non-deemed spouse's earnings stay zero, so the lesser of earnings - # is zero (Treas. Reg. 1.21-2(b)(4)(iii); (c), Example 3). - qualifying_individuals = tax_unit("count_cdcc_eligible", period) - floor = p.deemed_earned_income.calc(qualifying_individuals) - no_deem = min_(head_earnings, spouse_earnings) - deem_head = where( - head_floor_eligible, - min_(max_(head_earnings, floor), spouse_earnings), - 0, - ) - deem_spouse = where( - spouse_floor_eligible, - min_(max_(spouse_earnings, floor), head_earnings), - 0, + return lesser_head_spouse_earned( + tax_unit, + period, + parameters, + tax_unit("head_earned", period), + tax_unit("spouse_earned", period), ) - joint_earnings = max_(no_deem, max_(deem_head, deem_spouse)) - - # Source conflict us:dependent-care-exclusion-separate-filer-income - return where(is_joint, joint_earnings, head_earnings) diff --git a/policyengine_us/variables/gov/irs/income/adjusted_earnings.py b/policyengine_us/variables/gov/irs/income/adjusted_earnings.py index 946410b0c73..0356ae5c8db 100644 --- a/policyengine_us/variables/gov/irs/income/adjusted_earnings.py +++ b/policyengine_us/variables/gov/irs/income/adjusted_earnings.py @@ -1,21 +1,60 @@ from policyengine_us.model_api import * +def earnings_before_zero_floor(person, period, parameters, taxable_wages): + """ + IRC 32(c)(2) earned income before the zero floor: wages measured as + taxable_wages, plus self-employment earnings, less the deductible part + of self-employment tax. States that tax a different wage base, such as + California, pass their own taxable wages so that a business loss offsets + those wages before the floor. + """ + p = parameters(period).gov.irs.ald.misc + adjustment = ( + (1 - p.self_emp_tax_adj) + * p.employer_share + * person("self_employment_tax", period) + ) + excluded_wages = person("employment_income", period) - taxable_wages + # Disability pay reported as wages is earned income (Pub. 503 (2024), + # file page 4). + earnings = add(person, period, ["earned_income", "earned_disability_benefits"]) + return earnings - excluded_wages - adjustment + + class adjusted_earnings(Variable): value_type = float entity = Person 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 " + "for the child and dependent care credit and the dependent care " + "assistance exclusion." + ) unit = USD + reference = ( + "https://www.law.cornell.edu/uscode/text/26/32#c_2_A", + "https://www.law.cornell.edu/uscode/text/26/21#d_1", + "https://www.law.cornell.edu/cfr/text/26/1.21-2#b_3", + "https://www.law.cornell.edu/uscode/text/26/129#e_2", + "https://www.law.cornell.edu/uscode/text/26/402#e_3", + "https://www.irs.gov/pub/irs-prior/i2441--2025.pdf#page=4", + ) def formula(person, period, parameters): - p = parameters(period).gov.irs.ald.misc - adjustment = ( - (1 - p.self_emp_tax_adj) - * p.employer_share - * person("self_employment_tax", period) + # IRC 32(c)(2)(A)(i) counts employee compensation "only if such + # amounts are includible in gross income", and Treas. Reg. + # 1.21-2(b)(3) and IRC 129(e)(2) use that definition. Form 2441 + # (lines 4, 5, 18 and 19) starts from Form 1040 line 1z, which + # leaves out pre-tax payroll deductions such as traditional + # elective deferrals (IRC 402(e)(3)). Gross earned_income is kept + # for programs that count wages before these deductions. + taxable_wages = person("irs_employment_income", period) + return max_( + 0, + earnings_before_zero_floor(person, period, parameters, taxable_wages), ) - # Disability pay reported as wages is earned income (Pub. 503 (2024), - # file page 4). - earnings = add(person, period, ["earned_income", "earned_disability_benefits"]) - return max_(0, earnings - adjustment) diff --git a/policyengine_us/variables/gov/irs/income/filer_adjusted_earnings.py b/policyengine_us/variables/gov/irs/income/filer_adjusted_earnings.py index 151efa82ee1..e24d2baaab7 100644 --- a/policyengine_us/variables/gov/irs/income/filer_adjusted_earnings.py +++ b/policyengine_us/variables/gov/irs/income/filer_adjusted_earnings.py @@ -15,22 +15,8 @@ class filer_adjusted_earnings(Variable): ) def formula(tax_unit, period, parameters): - person = tax_unit.members - p = parameters(period).gov.irs.ald.misc - adjustment = ( - (1 - p.self_emp_tax_adj) - * p.employer_share - * person("self_employment_tax", period) - ) - # Federal-style state credits exclude wages that are not includible - # in federal gross income. Preserve the existing personal SE-tax - # adjustment and zero floor, without changing gross earned_income. - excluded_wages = person("employment_income", period) - person( - "irs_employment_income", period - ) - # Disability pensions reported as wages before minimum retirement - # age are earned income (Pub. 596). - earnings = add(person, period, ["earned_income", "earned_disability_benefits"]) - adjusted = max_(0, earnings - excluded_wages - adjustment) - is_dependent = person("is_tax_unit_dependent", period) - return tax_unit.sum(adjusted * ~is_dependent) + # adjusted_earnings already leaves out wages that are not includible + # in federal gross income (IRC 32(c)(2)(A)(i)) and keeps the + # personal SE-tax adjustment and zero floor. Dependents' earnings + # belong on their own returns. + return tax_unit_non_dep_sum("adjusted_earnings", tax_unit, period) diff --git a/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/exemption/amt_exemption.py b/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/exemption/amt_exemption.py index 978c523cfd3..f2451fe8354 100644 --- a/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/exemption/amt_exemption.py +++ b/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/exemption/amt_exemption.py @@ -16,6 +16,9 @@ class amt_exemption(Variable): "https://www.law.cornell.edu/uscode/text/26/55#d", # 26 U.S.C. § 55(d) "https://www.law.cornell.edu/uscode/text/26/59#j", "https://www.law.cornell.edu/uscode/text/26/911#d_2", + "https://www.law.cornell.edu/uscode/text/26/402#e_3", + "https://www.irs.gov/pub/irs-prior/i6251--2017.pdf#page=11", + "https://www.irs.gov/pub/irs-prior/i8615--2017.pdf#page=2", "https://www.irs.gov/instructions/i6251", "https://www.law.cornell.edu/uscode/text/26/55#d_4_A_iii", ] @@ -45,9 +48,13 @@ def formula(tax_unit, period, parameters): # A reduced exemption amount is applied to kiddie tax filers kiddie_tax_applies = tax_unit("amt_kiddie_tax_applies", period) - # IRC 59(j) uses earned income under IRC 911(d)(2), rather than - # the EITC gross-income inclusion rule. Preserve the existing gross - # earnings cap independently of EITC payroll exclusions. + # IRC 59(j) uses earned income under IRC 911(d)(2): "amounts + # received as compensation for personal services". Traditional + # elective deferrals are not treated as made available to the + # employee (IRC 402(e)(3)). The 2017 Form 8615 instructions, for the + # same children and the same 911(d)(2) definition, generally take + # earned income from Form 1040 line 7 (taxable wages), so the cap + # uses adjusted_earnings, which leaves out pre-tax payroll deductions. adj_earnings = tax_unit_non_dep_sum("adjusted_earnings", tax_unit, period) child_amount = p.exemption.child.amount diff --git a/policyengine_us/variables/gov/states/ca/tax/income/alternative_minimum_tax/exemption/ca_amt_exemption.py b/policyengine_us/variables/gov/states/ca/tax/income/alternative_minimum_tax/exemption/ca_amt_exemption.py index 84812a4bb9f..69a1c065487 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/alternative_minimum_tax/exemption/ca_amt_exemption.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/alternative_minimum_tax/exemption/ca_amt_exemption.py @@ -32,11 +32,12 @@ def formula(tax_unit, period, parameters): eligible_child = person("is_qualifying_child_dependent", period) is_head = person("is_tax_unit_head", period) head_is_eligible_child = tax_unit.any(eligible_child & is_head) - # Line 7 - p_irs = parameters(period).gov.irs.income.amt.exemption - exemption_amount_child = p_irs.child.amount - # Line 8 - earned_income = tax_unit("head_earned", period) + # Line 7: California's own amount, not the federal IRC 59(j) one. + exemption_amount_child = p.child.amount + # Line 8: "wages, tips, and other payments received for personal + # services performed", which in California include payroll HSA + # contributions. + earned_income = tax_unit("ca_head_earned", period) # Line 9 total_reduced_exemption_amount = exemption_amount_child + earned_income # Line 10 diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_adjusted_earnings.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_adjusted_earnings.py new file mode 100644 index 00000000000..936f7853fa0 --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_adjusted_earnings.py @@ -0,0 +1,43 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.income.adjusted_earnings import ( + earnings_before_zero_floor, +) + + +class ca_adjusted_earnings(Variable): + value_type = float + entity = Person + label = "California personal earned income adjusted for self-employment tax" + documentation = ( + "Earned income subject to California tax: California wages, which " + "count the payroll HSA contributions that federal W-2 box 1 wages " + "leave out, plus self-employment earnings, less the deductible part " + "of self-employment tax, floored at zero." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.CA + reference = ( + "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17052.6", # (d)(2) + "https://www.ftb.ca.gov/forms/2025/2025-3506-instructions.html", # Line 4 + "https://www.ftb.ca.gov/forms/2025/2025-540-ca-instructions.html", # Line 1h + "https://www.ftb.ca.gov/forms/2025/2025-1005-publication.pdf#page=10", + "https://www.law.cornell.edu/cfr/text/26/1.21-2#b_3", + ) + + def formula(person, period, parameters): + # R&TC 17052.6(d)(2) limits earned income to "earned income subject + # to tax under this part", and FTB 3506 line 4 counts "other taxable + # employee compensation". California does not exclude payroll HSA + # contributions (Schedule CA (540) line 1h, column C), so its wages + # (ca_taxable_wages, W-2 box 16) exceed federal taxable wages by them. + # ca_taxable_wages subtracts a subset of the federal pre-tax list, so + # it is never below irs_employment_income. + taxable_wages = person("ca_taxable_wages", period) + # FTB 3506 line 4 counts wages together with net earnings from + # self-employment, so a business loss offsets the California wages + # before the zero floor, as adjusted_earnings does for federal wages. + return max_( + 0, + earnings_before_zero_floor(person, period, parameters, taxable_wages), + ) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_head_earned.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_head_earned.py new file mode 100644 index 00000000000..dede11b0169 --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_head_earned.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class ca_head_earned(Variable): + value_type = float + entity = TaxUnit + label = "California head's adjusted earnings" + documentation = ( + "Head's earned income subject to California tax (ca_adjusted_earnings), " + "which counts the payroll HSA contributions that federal W-2 box 1 " + "wages leave out." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.CA + reference = ( + "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17052.6", # (d)(2) + "https://www.ftb.ca.gov/forms/2025/2025-3506-instructions.html", # Line 4 + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + ca_adjusted_earnings = person("ca_adjusted_earnings", period) + is_head = person("is_tax_unit_head", period) + return tax_unit.sum(is_head * ca_adjusted_earnings) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_spouse_earned.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_spouse_earned.py new file mode 100644 index 00000000000..1a0a526d801 --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_spouse_earned.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class ca_spouse_earned(Variable): + value_type = float + entity = TaxUnit + label = "California spouse's adjusted earnings" + documentation = ( + "Spouse's earned income subject to California tax " + "(ca_adjusted_earnings), which counts the payroll HSA contributions " + "that federal W-2 box 1 wages leave out." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.CA + reference = ( + "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17052.6", # (d)(2) + "https://www.ftb.ca.gov/forms/2025/2025-3506-instructions.html", # Line 4 + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + ca_adjusted_earnings = person("ca_adjusted_earnings", period) + is_spouse = person("is_tax_unit_spouse", period) + return tax_unit.sum(is_spouse * ca_adjusted_earnings) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_min_head_spouse_earned.py b/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_min_head_spouse_earned.py new file mode 100644 index 00000000000..5c6897511d7 --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_min_head_spouse_earned.py @@ -0,0 +1,29 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.credits.cdcc.min_head_spouse_earned import ( + lesser_head_spouse_earned, +) + + +class ca_cdcc_min_head_spouse_earned(Variable): + value_type = float + entity = TaxUnit + label = "Lesser of head and spouse's California CDCC earned income" + unit = USD + definition_period = YEAR + defined_for = StateCode.CA + reference = ( + "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17052.6", # (d)(2) + "https://www.ftb.ca.gov/forms/2025/2025-3506-instructions.html", # Lines 4 and 5 + ) + + def formula(tax_unit, period, parameters): + # FTB 3506 lines 4 and 5 use California earned income (which counts + # payroll HSA contributions) and deem a student or disabled spouse's + # earnings at $250 ($500) a month, as federal Form 2441 does. + return lesser_head_spouse_earned( + tax_unit, + period, + parameters, + tax_unit("ca_head_earned", period), + tax_unit("ca_spouse_earned", period), + ) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_rate.py b/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_rate.py index a862d3a0203..4de30050ff2 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_rate.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_rate.py @@ -7,19 +7,27 @@ class ca_cdcc_rate(Variable): label = "CDCC credit rate replicated to include California limitations" unit = USD definition_period = YEAR - reference = "https://www.ftb.ca.gov/about-ftb/data-reports-plans/Summary-of-Federal-Income-Tax-Changes/index.html#PL-117-2-9631" + reference = ( + "https://www.ftb.ca.gov/about-ftb/data-reports-plans/Summary-of-Federal-Income-Tax-Changes/index.html#PL-117-2-9631", + "https://www.ftb.ca.gov/forms/2025/2025-3506-instructions.html", # Line 7 + "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17052.6", # (a)(1) + ) defined_for = StateCode.CA def formula(tax_unit, period, parameters): - agi = tax_unit("adjusted_gross_income", period) - year = period.start.year - if year == 2021: - period_adjusted = f"{year - 1}-01-01" - else: - period_adjusted = f"{year}-01-01" - - p = parameters(period_adjusted).gov.irs.credits.cdcc + if year != 2021: + # R&TC 17052.6(a)(1) ties the credit to IRC 21 "as applicable for + # federal income tax purposes for the taxable year", which + # overrides the R&TC 17024.5 specified date. So outside 2021, + # FTB 3506 line 7 uses the federal decimal table for the same + # year, including the 2026 schedule of P.L. 119-21. + return tax_unit("cdcc_rate", period) + + # California did not adopt the 2021 American Rescue Plan Act rate, + # so 2021 uses the 2020 schedule. + agi = tax_unit("adjusted_gross_income", period) + p = parameters(f"{year - 1}-01-01").gov.irs.credits.cdcc # First phase-out excess_agi = max_(0, agi - p.phase_out.start) # start diff --git a/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_relevant_expenses.py b/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_relevant_expenses.py index 90fe572f530..d7691376b53 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_relevant_expenses.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_relevant_expenses.py @@ -49,8 +49,9 @@ def formula(tax_unit, period, parameters): eligible_capped_expenses = min_( expenses, min_(dollar_limit_after_exclusion, federal_line_31) ) - # Then, cap further to the lowest earnings between the taxpayer and spouse + # Then, cap further to the lowest earnings between the taxpayer and + # spouse, measured as California earned income (R&TC 17052.6(d)(2)). return min_( eligible_capped_expenses, - tax_unit("min_head_spouse_earned", period), + tax_unit("ca_cdcc_min_head_spouse_earned", period), ) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_dependent_care_assistance_exclusion.py b/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_dependent_care_assistance_exclusion.py index 0a2becc0c8b..f415060ac92 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_dependent_care_assistance_exclusion.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_dependent_care_assistance_exclusion.py @@ -42,4 +42,21 @@ def formula(tax_unit, period, parameters): # gives line 26 whenever the federal cap is at least California's, # as it is for every filer in 2021. federal_exclusion = tax_unit("dependent_care_assistance_exclusion", period) - return min_(federal_exclusion, dollar_cap) + # The federal IRC 129(b) earned-income limit leaves out payroll HSA + # contributions, which California taxes. Where California's earned + # income is higher, let the exclusion rise by the difference, up to + # the benefits. This equals the lesser of the benefits, the cap and + # California earned income, because the federal cap is at least + # California's. + increase = max_( + tax_unit("ca_cdcc_min_head_spouse_earned", period) + - tax_unit("min_head_spouse_earned", period), + 0, + ) + benefits = add(tax_unit, period, ["dependent_care_employer_benefits"]) + exclusion = where( + increase > 0, + min_(benefits, federal_exclusion + increase), + federal_exclusion, + ) + return min_(exclusion, dollar_cap) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/cdcc/ny_cdcc_qualifying_expenses.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/cdcc/ny_cdcc_qualifying_expenses.py index 46275ff6a20..90154e8dcae 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/cdcc/ny_cdcc_qualifying_expenses.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/cdcc/ny_cdcc_qualifying_expenses.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.income.adjusted_earnings import ( + earnings_before_zero_floor, +) class ny_cdcc_qualifying_expenses(Variable): @@ -7,7 +10,7 @@ class ny_cdcc_qualifying_expenses(Variable): label = "New York child and dependent care credit qualifying expenses" unit = USD definition_period = YEAR - reference = "https://www.nysenate.gov/legislation/laws/TAX/606" # (c-2)(2)(D) + reference = "https://www.nysenate.gov/legislation/laws/TAX/606" # (c-2)(2)(C), (D) defined_for = StateCode.NY def formula(tax_unit, period, parameters): @@ -25,10 +28,23 @@ def formula(tax_unit, period, parameters): # IRC 21(d)(2), 606(c-2) deems no earned income for a student or # incapacitated spouse. cap = p.expense_cap.calc(tax_unit("count_cdcc_eligible", period)) - head_earned = tax_unit("head_earned", period) + # (C) defines earned income as "the wages, salaries, tips and other + # employee compensation", without IRC 32(c)(2)(A)(i)'s "only if such + # amounts are includible in gross income", so measure it on gross + # wages, which keep the pre-tax payroll deductions. A business loss + # offsets these wages before the zero floor. + person = tax_unit.members + earned = max_( + 0, + earnings_before_zero_floor( + person, period, parameters, person("employment_income", period) + ), + ) + head_earned = tax_unit.sum(earned * person("is_tax_unit_head", period)) + spouse_earned = tax_unit.sum(earned * person("is_tax_unit_spouse", period)) earned = where( tax_unit("tax_unit_is_joint", period), - min_(head_earned, tax_unit("spouse_earned", period)), + min_(head_earned, spouse_earned), head_earned, ) return min_(expenses, min_(cap, earned))