From a31d290bbbb7258fea6b7b68981278de129699c3 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Tue, 27 Oct 2026 16:26:50 -0400 Subject: [PATCH 1/4] Count only taxable wages in the CDCC earned income limit adjusted_earnings, which feeds head_earned, spouse_earned and min_head_spouse_earned, started from gross employment_income, so a spouse's traditional 401(k)/403(b) deferrals, pre-tax health premiums and payroll HSA contributions raised the IRC 21(d)(1) expense limit and the IRC 129(b) exclusion limit. Treas. Reg. 1.21-2(b)(3) and IRC 129(e)(2) use the IRC 32(c)(2) definition, which counts employee pay only if includible in gross income, and Form 2441 starts from Form 1040 line 1z. - adjusted_earnings subtracts employment_income - irs_employment_income, as filer_adjusted_earnings did since #10030; earned_income stays gross. - filer_adjusted_earnings sums adjusted_earnings over non-dependents again (same values). - The pre-2018 AMT child cap (IRC 59(j), 911(d)(2)) follows: the 2017 Form 8615 instructions read the same definition as Form 1040 line 7 wages. The #10030 test that pinned the gross cap now expects 15,500. - New York's 2026 credit adds the excluded wages back, because Tax Law 606(c-2)(2)(C) has no gross-income inclusion test. Fixes #4007 Co-Authored-By: Claude Opus 5.5 --- ...4007-cdcc-earned-income-deferrals.fixed.md | 1 + .../test_eitc_elective_deferral_invariants.py | 22 +++++--- .../dependent_care_assistance_exclusion.yaml | 27 +++++++++ .../gov/irs/credits/cdcc/integration.yaml | 41 ++++++++++++++ .../earned_income/eitc_earned_income.yaml | 13 +++-- .../gov/irs/income/adjusted_earnings.yaml | 55 +++++++++++++++++++ .../credits/ny_cdcc_qualifying_expenses.yaml | 33 +++++++++++ ...hild_dependent_care_expense_deduction.yaml | 35 ++++++++++++ .../gov/irs/income/adjusted_earnings.py | 28 +++++++++- .../gov/irs/income/filer_adjusted_earnings.py | 24 ++------ .../exemption/amt_exemption.py | 13 ++++- .../cdcc/ny_cdcc_qualifying_expenses.py | 20 ++++++- 12 files changed, 274 insertions(+), 38 deletions(-) create mode 100644 changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/adjusted_earnings.yaml 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..9d5ae0bf333 --- /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); New York's 2026 credit keeps counting the deferrals under Tax Law 606(c-2)(2)(C). 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..7be457ef7c3 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,11 @@ """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, the CDCC earned income +limit and the AMT earnings cap 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. Additional California pairs retain payroll HSA deductions when substituting California taxable wages, so YCTC must agree even with business losses. """ @@ -54,7 +55,8 @@ def _situations(): tax_units[f"unit_{i}"] = { "members": members, "takes_up_eitc": {YEAR: True}, - # Force the existing cap to isolate its gross earnings dependency. + # Force the AMT child cap's kiddie tax flag (the cap itself only + # applies before 2018). "amt_kiddie_tax_applies": {YEAR: True}, "amt_income": {YEAR: 200_000}, } @@ -156,7 +158,9 @@ def _run(situation): "ca_eitc_earned_income", "ca_yctc", "earned_income", - "adjusted_earnings", + "head_earned", + "spouse_earned", + "min_head_spouse_earned", "amt_exemption", ) return {name: simulation.calculate(name, YEAR).copy() for name in outputs} @@ -175,6 +179,10 @@ 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", + "amt_exemption", ): np.testing.assert_allclose( with_deductions[name], @@ -214,7 +222,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/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..7a949086002 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 @@ -132,3 +132,36 @@ 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 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/income/adjusted_earnings.py b/policyengine_us/variables/gov/irs/income/adjusted_earnings.py index 946410b0c73..d6873964233 100644 --- a/policyengine_us/variables/gov/irs/income/adjusted_earnings.py +++ b/policyengine_us/variables/gov/irs/income/adjusted_earnings.py @@ -6,7 +6,23 @@ class adjusted_earnings(Variable): 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 @@ -15,7 +31,17 @@ def formula(person, period, parameters): * 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. + excluded_wages = person("employment_income", period) - person( + "irs_employment_income", period + ) # 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) + return max_(0, earnings - excluded_wages - 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/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..c98e6d2daef 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 @@ -7,7 +7,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 +25,24 @@ 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". head_earned and + # spouse_earned follow 32(c)(2), so add back the pre-tax payroll + # deductions they leave out. + person = tax_unit.members + excluded_wages = person("employment_income", period) - person( + "irs_employment_income", period + ) + head_earned = tax_unit("head_earned", period) + tax_unit.sum( + excluded_wages * person("is_tax_unit_head", period) + ) + spouse_earned = tax_unit("spouse_earned", period) + tax_unit.sum( + excluded_wages * 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)) From 0623f5606f61ffcce2b38cc8c393e99a0ce8a4cc Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Tue, 27 Oct 2026 16:41:31 -0400 Subject: [PATCH 2/4] Use California wages for the California CDCC earned income limit California taxes payroll HSA contributions (Schedule CA (540) line 1h, column C), so with taxable federal adjusted_earnings the California CDCC limit under R&TC 17052.6(d)(2) dropped them. - ca_head_earned / ca_spouse_earned add ca_taxable_wages minus irs_employment_income (the payroll HSA amount) to head_earned / spouse_earned. - ca_cdcc_min_head_spouse_earned applies the IRC 21(d)(2) deeming (FTB 3506 line 5) to them through lesser_head_spouse_earned, a helper split out of min_head_spouse_earned with no change to federal results. - ca_cdcc_relevant_expenses, ca_dependent_care_assistance_exclusion and the ca_amt_exemption child worksheet (Schedule P line 8) use the California amounts. Co-Authored-By: Claude Opus 5.5 --- ...4007-cdcc-earned-income-deferrals.fixed.md | 2 +- .../ca_amt_exemption.yaml | 30 +++++++ .../ca_cdcc_relevant_expenses.yaml | 54 ++++++++++-- ...a_dependent_care_assistance_exclusion.yaml | 31 +++++++ .../credits/cdcc/min_head_spouse_earned.py | 85 +++++++++++-------- .../exemption/ca_amt_exemption.py | 6 +- .../states/ca/tax/income/ca_head_earned.py | 37 ++++++++ .../states/ca/tax/income/ca_spouse_earned.py | 37 ++++++++ .../credits/ca_cdcc_min_head_spouse_earned.py | 29 +++++++ .../credits/ca_cdcc_relevant_expenses.py | 5 +- .../ca_dependent_care_assistance_exclusion.py | 19 ++++- 11 files changed, 287 insertions(+), 48 deletions(-) create mode 100644 policyengine_us/variables/gov/states/ca/tax/income/ca_head_earned.py create mode 100644 policyengine_us/variables/gov/states/ca/tax/income/ca_spouse_earned.py create mode 100644 policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_min_head_spouse_earned.py diff --git a/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md b/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md index 9d5ae0bf333..7f09ea7d23e 100644 --- a/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md +++ b/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md @@ -1 +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); New York's 2026 credit keeps counting the deferrals under Tax Law 606(c-2)(2)(C). +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, and New York's 2026 credit keeps counting the deferrals under Tax Law 606(c-2)(2)(C). 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 1738b1c2937..549d0dff286 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 @@ -70,3 +70,33 @@ state_code: CA output: ca_amt_exemption: 0 + +- name: Child exemption counts payroll HSA contributions but not 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 + health_savings_account_payroll_contributions: 1_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + ca_amti: 100_000 + households: + household: + members: [person1] + state_code: CA + output: + # Federal taxable wages: 10,000 - 1,000 - 1,000 = 8,000. + head_earned: 8_000 + # California wages add back the payroll HSA contribution: 9,000. + ca_head_earned: 9_000 + # Adult exemption: AMTI below the 347,808 threshold, so 92,749. + # Child: min(9,550 + 9,000, 92,749) = 18,550 (Schedule P worksheet + # lines 7-10). + ca_amt_exemption: 18_550 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..dd37d55d448 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] @@ -82,7 +82,7 @@ state_code: CA 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: # min(10_000 expenses, $6,000 two-child cap - $5,000 exclusion, 50_000) = 1_000. @@ -329,3 +329,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_spouse_earned = 4,000 + (5,000 - 4,000) = 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..5d41015ff9e 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,34 @@ 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 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/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 209f252897f..96164ec2f8b 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 @@ -35,8 +35,10 @@ def formula(tax_unit, period, parameters): head_is_eligible_child = tax_unit.any(eligible_child & is_head) # Line 7 exemption_amount_child = p_irs.child.amount - # Line 8 - earned_income = tax_unit("head_earned", period) + # 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_head_earned.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_head_earned.py new file mode 100644 index 00000000000..ade29df11fd --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_head_earned.py @@ -0,0 +1,37 @@ +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: head_earned plus " + "the payroll HSA contributions that California taxes but 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 + "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", + ) + + def formula(tax_unit, 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. + person = tax_unit.members + ca_only_wages = max_( + 0, + person("ca_taxable_wages", period) + - person("irs_employment_income", period), + ) + return tax_unit("head_earned", period) + tax_unit.sum( + ca_only_wages * person("is_tax_unit_head", period) + ) 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..fd31692299d --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_spouse_earned.py @@ -0,0 +1,37 @@ +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: spouse_earned plus " + "the payroll HSA contributions that California taxes but 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 + "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", + ) + + def formula(tax_unit, 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. + person = tax_unit.members + ca_only_wages = max_( + 0, + person("ca_taxable_wages", period) + - person("irs_employment_income", period), + ) + return tax_unit("spouse_earned", period) + tax_unit.sum( + ca_only_wages * person("is_tax_unit_spouse", period) + ) 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_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) From ec4438cb785a247bbf29351d166aa09af74a54cf Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Thu, 29 Oct 2026 15:51:17 -0400 Subject: [PATCH 3/4] Route California's CDCC through its own limits and net losses first - From 2021, ca_cdcc reads ca_federal_capped_cdcc (FTB 3506 lines 3-10) in every year, so California earned income reaches the final credit; ca_cdcc_rate is the federal rate outside 2021. - ca_adjusted_earnings nets a business loss against California wages before the zero floor; ca_head_earned and ca_spouse_earned sum it. - The California AMT child exemption uses California's own minimum (Schedule P (540) worksheet line 7; $9,750 for 2025). Co-Authored-By: Claude Opus 5.5 --- ...4007-cdcc-earned-income-deferrals.fixed.md | 2 +- .../income/amt/exemption/child/amount.yaml | 25 ++++ .../credits/child_dependent_care/input.yaml | 12 +- .../ca_amt_exemption.yaml | 32 ++++- .../ca/tax/income/ca_adjusted_earnings.yaml | 40 ++++++ .../credits/child_dependent_care/ca_cdcc.yaml | 135 ++++++++++++++++-- ...a_dependent_care_assistance_exclusion.yaml | 30 ++++ .../gov/irs/income/adjusted_earnings.py | 37 +++-- .../exemption/ca_amt_exemption.py | 4 +- .../ca/tax/income/ca_adjusted_earnings.py | 44 ++++++ .../states/ca/tax/income/ca_head_earned.py | 24 +--- .../states/ca/tax/income/ca_spouse_earned.py | 24 +--- .../ca/tax/income/credits/ca_cdcc_rate.py | 23 +-- 13 files changed, 356 insertions(+), 76 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/ca/tax/income/amt/exemption/child/amount.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_adjusted_earnings.yaml create mode 100644 policyengine_us/variables/gov/states/ca/tax/income/ca_adjusted_earnings.py diff --git a/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md b/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md index 7f09ea7d23e..8ba04db3bf0 100644 --- a/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md +++ b/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md @@ -1 +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, and New York's 2026 credit keeps counting the deferrals under Tax Law 606(c-2)(2)(C). +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). California's credit now applies its own FTB 3506 expense and earned income limits from 2022 as well as 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/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..cf792b72091 --- /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 child exemption additional income base + 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..e230ecb795c 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,17 @@ -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: 2012-01-01: - cdcc + # 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. 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 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 549d0dff286..460b16a96e6 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 @@ -97,6 +97,32 @@ # California wages add back the payroll HSA contribution: 9,000. ca_head_earned: 9_000 # Adult exemption: AMTI below the 347,808 threshold, so 92,749. - # Child: min(9,550 + 9,000, 92,749) = 18,550 (Schedule P worksheet - # lines 7-10). - ca_amt_exemption: 18_550 + # 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..00bcf368d86 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 @@ -43,8 +43,8 @@ input: state_code: CA adjusted_gross_income: 100_001 - cdcc: 1_000 - ca_federal_capped_cdcc: 0 + cdcc: 0 + ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 0 @@ -53,8 +53,8 @@ input: state_code: CA adjusted_gross_income: 1_000 - cdcc: 1_000 - ca_federal_capped_cdcc: 0 + cdcc: 0 + ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 500 @@ -63,8 +63,8 @@ input: state_code: CA adjusted_gross_income: 50_000 - cdcc: 1_000 - ca_federal_capped_cdcc: 0 + cdcc: 0 + ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 430 @@ -73,7 +73,126 @@ input: state_code: CA adjusted_gross_income: -50_000 - cdcc: 1_000 - ca_federal_capped_cdcc: 0 + 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 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 5d41015ff9e..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 @@ -99,3 +99,33 @@ 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/variables/gov/irs/income/adjusted_earnings.py b/policyengine_us/variables/gov/irs/income/adjusted_earnings.py index d6873964233..0356ae5c8db 100644 --- a/policyengine_us/variables/gov/irs/income/adjusted_earnings.py +++ b/policyengine_us/variables/gov/irs/income/adjusted_earnings.py @@ -1,6 +1,27 @@ 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 @@ -25,12 +46,6 @@ class adjusted_earnings(Variable): ) 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 @@ -38,10 +53,8 @@ def formula(person, period, parameters): # 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. - excluded_wages = person("employment_income", period) - person( - "irs_employment_income", period + 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 - excluded_wages - adjustment) 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 96164ec2f8b..e656c0d105e 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 @@ -33,8 +33,8 @@ 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 - exemption_amount_child = p_irs.child.amount + # 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. 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..0518f5f0095 --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_adjusted_earnings.py @@ -0,0 +1,44 @@ +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. + taxable_wages = max_( + person("ca_taxable_wages", period), + person("irs_employment_income", 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 index ade29df11fd..dede11b0169 100644 --- 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 @@ -6,9 +6,9 @@ class ca_head_earned(Variable): entity = TaxUnit label = "California head's adjusted earnings" documentation = ( - "Head's earned income subject to California tax: head_earned plus " - "the payroll HSA contributions that California taxes but federal " - "W-2 box 1 wages leave out." + "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 @@ -16,22 +16,10 @@ class ca_head_earned(Variable): 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", ) def formula(tax_unit, 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. person = tax_unit.members - ca_only_wages = max_( - 0, - person("ca_taxable_wages", period) - - person("irs_employment_income", period), - ) - return tax_unit("head_earned", period) + tax_unit.sum( - ca_only_wages * person("is_tax_unit_head", period) - ) + 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 index fd31692299d..1a0a526d801 100644 --- 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 @@ -6,9 +6,9 @@ class ca_spouse_earned(Variable): entity = TaxUnit label = "California spouse's adjusted earnings" documentation = ( - "Spouse's earned income subject to California tax: spouse_earned plus " - "the payroll HSA contributions that California taxes but federal " - "W-2 box 1 wages leave out." + "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 @@ -16,22 +16,10 @@ class ca_spouse_earned(Variable): 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", ) def formula(tax_unit, 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. person = tax_unit.members - ca_only_wages = max_( - 0, - person("ca_taxable_wages", period) - - person("irs_employment_income", period), - ) - return tax_unit("spouse_earned", period) + tax_unit.sum( - ca_only_wages * person("is_tax_unit_spouse", period) - ) + 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_rate.py b/policyengine_us/variables/gov/states/ca/tax/income/credits/ca_cdcc_rate.py index a862d3a0203..2a9f6dc1850 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,24 @@ 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 + ) 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: + # Outside 2021, FTB 3506 line 7 uses the federal decimal table + # (2025 instructions, line 7). The FTB has not published a 2026 + # table, so 2026 onward keeps the federal rate. + 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 From 9959a00e7eee72b1a2b41ac843ead5287218f5a6 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 30 Oct 2026 10:12:51 -0400 Subject: [PATCH 4/4] Address second review on CDCC earned income (#10520) New York 2026: measure each person's earned income on gross wages with earnings_before_zero_floor, so a business loss offsets the wages before the zero floor instead of the deferrals being added back after it. Add full and partial loss cases. California: route ca_cdcc through ca_federal_capped_cdcc in every year (R&TC 17052.6(a)(1)), cite (a)(1) for the 2026 rate and pin it, pin the FTB 3506 Part IV employer benefit path, and use ca_taxable_wages directly. Clean up test inputs, the CA AMT child case, the section 129 case, stale comments, the AMT parameter label and the vacuous AMT check in the invariant test. Co-Authored-By: Claude Opus 5.5 --- ...4007-cdcc-earned-income-deferrals.fixed.md | 2 +- .../income/amt/exemption/child/in_effect.yaml | 4 +- .../income/amt/exemption/child/amount.yaml | 2 +- .../credits/child_dependent_care/input.yaml | 8 +- .../test_eitc_elective_deferral_invariants.py | 19 ++--- .../ca_amt_exemption.yaml | 24 +++--- .../credits/child_dependent_care/ca_cdcc.yaml | 41 ++++++++-- .../child_dependent_care/ca_cdcc_rate.yaml | 13 +++ .../ca_cdcc_relevant_expenses.yaml | 6 +- .../credits/ny_cdcc_qualifying_expenses.yaml | 79 +++++++++++++++++-- .../ca/tax/income/ca_adjusted_earnings.py | 7 +- .../ca/tax/income/credits/ca_cdcc_rate.py | 9 ++- .../cdcc/ny_cdcc_qualifying_expenses.py | 24 +++--- 13 files changed, 173 insertions(+), 65 deletions(-) diff --git a/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md b/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md index 8ba04db3bf0..1e78b8a6d1f 100644 --- a/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md +++ b/changelog.d/fix-4007-cdcc-earned-income-deferrals.fixed.md @@ -1 +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). California's credit now applies its own FTB 3506 expense and earned income limits from 2022 as well as 2021, and its AMT child exemption uses California's own minimum ($9,750 for 2025) instead of the federal amount. +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 index cf792b72091..22a2b591746 100644 --- 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 @@ -9,7 +9,7 @@ metadata: uprating: gov.states.ca.cpi unit: currency-USD period: year - label: California AMT child exemption additional income base + 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). 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 e230ecb795c..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,10 +1,10 @@ description: California uses this input for the state child/dependent care credit, based on year. values: - 2012-01-01: - - cdcc # 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. - 2021-01-01: + # R&TC 17052.6 has done so since 2000, so the California computation + # applies in every year, including years before 2021. + 2012-01-01: - ca_federal_capped_cdcc metadata: unit: list @@ -15,3 +15,5 @@ metadata: 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 7be457ef7c3..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,11 +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, the CDCC earned income -limit and the AMT earnings cap 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. +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. """ @@ -55,10 +56,6 @@ def _situations(): tax_units[f"unit_{i}"] = { "members": members, "takes_up_eitc": {YEAR: True}, - # Force the AMT child cap's kiddie tax flag (the cap itself only - # applies before 2018). - "amt_kiddie_tax_applies": {YEAR: True}, - "amt_income": {YEAR: 200_000}, } households[f"household_{i}"] = { "members": members, @@ -103,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, @@ -161,7 +156,6 @@ def _run(situation): "head_earned", "spouse_earned", "min_head_spouse_earned", - "amt_exemption", ) return {name: simulation.calculate(name, YEAR).copy() for name in outputs} @@ -182,7 +176,6 @@ def test_payroll_exclusions_match_equivalent_taxable_compensation(): "head_earned", "spouse_earned", "min_head_spouse_earned", - "amt_exemption", ): np.testing.assert_allclose( with_deductions[name], 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 c8f9a9a7657..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,7 +131,7 @@ members: [person1] filing_status: SINGLE ca_amti: 500_000 - head_earned: 500_000 + ca_head_earned: 500_000 households: household: members: [person1] @@ -139,7 +139,7 @@ output: ca_amt_exemption: 54_701 -- name: Child exemption counts payroll HSA contributions but not a 401(k) deferral +- name: Child exemption counts California wages, which leave out a 401(k) deferral period: 2025 input: people: @@ -149,7 +149,6 @@ is_qualifying_child_dependent: true employment_income: 10_000 traditional_401k_contributions_desired: 1_000 - health_savings_account_payroll_contributions: 1_000 tax_units: tax_unit: members: [person1] @@ -160,9 +159,10 @@ members: [person1] state_code: CA output: - # Federal taxable wages: 10,000 - 1,000 - 1,000 = 8,000. - head_earned: 8_000 - # California wages add back the payroll HSA contribution: 9,000. + # 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 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 00bcf368d86..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,7 +39,6 @@ input: state_code: CA adjusted_gross_income: 100_001 - cdcc: 0 ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 0 @@ -53,7 +48,6 @@ input: state_code: CA adjusted_gross_income: 1_000 - cdcc: 0 ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 500 @@ -63,7 +57,6 @@ input: state_code: CA adjusted_gross_income: 50_000 - cdcc: 0 ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 430 @@ -73,7 +66,6 @@ input: state_code: CA adjusted_gross_income: -50_000 - cdcc: 0 ca_federal_capped_cdcc: 1_000 output: ca_cdcc: 500 @@ -196,3 +188,36 @@ # 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 dd37d55d448..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 @@ -82,9 +82,13 @@ state_code: CA 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 @@ -363,7 +367,7 @@ spouse_earned: 4_000 min_head_spouse_earned: 4_000 # California (W-2 box 16): 6,000 - 1,000 401(k) = 5,000. - # ca_spouse_earned = 4,000 + (5,000 - 4,000) = 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, 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 7a949086002..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 @@ -165,3 +165,70 @@ # 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/variables/gov/states/ca/tax/income/ca_adjusted_earnings.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_adjusted_earnings.py index 0518f5f0095..936f7853fa0 100644 --- 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 @@ -31,10 +31,9 @@ def formula(person, period, parameters): # 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. - taxable_wages = max_( - person("ca_taxable_wages", period), - person("irs_employment_income", period), - ) + # 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. 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 2a9f6dc1850..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 @@ -10,15 +10,18 @@ class ca_cdcc_rate(Variable): 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): year = period.start.year if year != 2021: - # Outside 2021, FTB 3506 line 7 uses the federal decimal table - # (2025 instructions, line 7). The FTB has not published a 2026 - # table, so 2026 onward keeps the federal rate. + # 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, 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 c98e6d2daef..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): @@ -27,19 +30,18 @@ def formula(tax_unit, period, parameters): cap = p.expense_cap.calc(tax_unit("count_cdcc_eligible", 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". head_earned and - # spouse_earned follow 32(c)(2), so add back the pre-tax payroll - # deductions they leave out. + # 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 - excluded_wages = person("employment_income", period) - person( - "irs_employment_income", period - ) - head_earned = tax_unit("head_earned", period) + tax_unit.sum( - excluded_wages * person("is_tax_unit_head", period) - ) - spouse_earned = tax_unit("spouse_earned", period) + tax_unit.sum( - excluded_wages * person("is_tax_unit_spouse", period) + 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, spouse_earned),