diff --git a/changelog.d/cdcc-coupling-accuracy.fixed.md b/changelog.d/cdcc-coupling-accuracy.fixed.md new file mode 100644 index 00000000000..6073bebc3fe --- /dev/null +++ b/changelog.d/cdcc-coupling-accuracy.fixed.md @@ -0,0 +1,4 @@ +- Honored the IRC § 21(e)(4) separated-taxpayer exception in the South Carolina child and dependent care credit. +- Stopped the Idaho and Georgia contributed child tax credit reforms from applying in years before their in_effect activation date. +- Computed the Kentucky, Maine, and Vermont child and dependent care credits from the pre-OBBBA federal IRC § 21 credit for 2026, matching each state's static conformity to the Internal Revenue Code as of December 31, 2024. +- Reduced the California, Idaho, and Virginia child and dependent care benefit bases by employer-provided dependent care benefits excluded under IRC § 129, matching each state's form treatment and the federal IRC § 21(c) reduction. diff --git a/policyengine_us/reforms/states/ga/ctc/ga_ctc_reform.py b/policyengine_us/reforms/states/ga/ctc/ga_ctc_reform.py index 74b942341c9..6ff5bf1eb8a 100644 --- a/policyengine_us/reforms/states/ga/ctc/ga_ctc_reform.py +++ b/policyengine_us/reforms/states/ga/ctc/ga_ctc_reform.py @@ -40,7 +40,12 @@ def formula(tax_unit, period, parameters): ga_child_age_eligible = age < baseline.age_threshold eligible_children = tax_unit.sum(ctc_eligible_child & ga_child_age_eligible) refund_limit = p.refundable.amount * eligible_children - return min_(unused_credit, refund_limit) + refundable_credit = min_(unused_credit, refund_limit) + # Only pay the refund in periods where the reform is in effect. The + # reform is installed for the whole simulation whenever it activates + # in any of the next five years (see create_ga_ctc_reform), so this + # per-period gate prevents a refund leaking into pre-activation years. + return where(p.refundable.in_effect, refundable_credit, 0) class ga_refundable_credits(Variable): value_type = float diff --git a/policyengine_us/reforms/states/id/ctc/id_ctc_reform.py b/policyengine_us/reforms/states/id/ctc/id_ctc_reform.py index 26dcd81c9bb..771d5c45848 100644 --- a/policyengine_us/reforms/states/id/ctc/id_ctc_reform.py +++ b/policyengine_us/reforms/states/id/ctc/id_ctc_reform.py @@ -6,6 +6,24 @@ ) +def _first_true_instant(nodes, default): + """Earliest instant at which any of the boolean parameters is true. + + Scans each parameter's ``values_list`` (which reflects user-supplied dated + overrides) for the earliest instant whose value is truthy, and returns the + minimum such instant across all nodes. Falls back to ``default`` when none + of the parameters is ever true. + """ + candidates = [] + for node in nodes: + for value_at_instant in node.values_list: + if value_at_instant.value: + candidates.append(instant(value_at_instant.instant_str)) + if not candidates: + return default + return min(candidates) + + def create_id_ctc() -> Reform: class id_refundable_ctc(Variable): value_type = float @@ -40,22 +58,38 @@ def formula(tax_unit, period, parameters): return where(p.refundable.in_effect, refundable_credit, 0) def modify_parameters(parameters): - # Revive id_ctc in the ordered nonrefundable list (it was dropped as of - # 2026), so the baseline credit applies against liability again. + # The baseline id_ctc drops out of the ordered nonrefundable list as of + # 2026-01-01. Revive it only from the instant the reform actually turns + # on, so a delayed activation does not leak the credit into + # pre-activation years. The activation instant is the earliest date at + # which either in_effect toggle is true; default to 2026-01-01 (the + # baseline drop date) when neither carries a dated override. + p_id = parameters.gov.contrib.states.id.ctc + baseline_drop = instant("2026-01-01") + activation = _first_true_instant( + [p_id.in_effect, p_id.refundable.in_effect], + default=baseline_drop, + ) + # Never revive before the baseline drop date: id_ctc is already in the + # list through 2025, so revival only matters from 2026 onward. + revival_start = max(activation, baseline_drop) non_refundable = parameters.gov.states.id.tax.income.credits.non_refundable - current_non_refundable = non_refundable(instant("2026-01-01")) + current_non_refundable = non_refundable(revival_start) if "id_ctc" not in current_non_refundable: non_refundable.update( - start=instant("2026-01-01"), + start=revival_start, stop=instant("2100-12-31"), value=list(current_non_refundable) + ["id_ctc"], ) - # Register the refundable top-up in the refundable list. + # Register the refundable top-up in the refundable list. The + # id_refundable_ctc variable self-gates on refundable.in_effect, so it + # pays nothing before activation; registering it from the same revival + # instant keeps the list free of a dead entry in pre-activation years. refundable = parameters.gov.states.id.tax.income.credits.refundable - current_refundable = refundable(instant("2026-01-01")) + current_refundable = refundable(revival_start) if "id_refundable_ctc" not in current_refundable: refundable.update( - start=instant("2026-01-01"), + start=revival_start, stop=instant("2100-12-31"), value=list(current_refundable) + ["id_refundable_ctc"], ) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/pre_obbba_cdcc.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/pre_obbba_cdcc.yaml new file mode 100644 index 00000000000..1806d204ca7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/pre_obbba_cdcc.yaml @@ -0,0 +1,67 @@ +# The pre_obbba_cdcc family recomputes the federal section 21 credit on the +# rate schedule as it stood before the One Big Beautiful Bill Act (OBBBA). For +# 2025 it must equal the live cdcc (identical schedule); for 2026 it must hold +# the pre-OBBBA rate (max 35%, min 20%, no amended second phase-out) while the +# live cdcc reflects the OBBBA increase (max 50%, min 35%). Read by states whose +# income tax statically conforms to a pre-2026 IRC (KY, ME, VT). + +# Low-income joint family, two children, $6,000 childcare, AGI below the first +# phase-out start so the applicable rate is at its maximum. +- name: 2025 pre-OBBBA CDCC equals the live CDCC (identical schedule) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 25_000 + spouse: + age: 35 + employment_income: 25_000 + child1: + age: 5 + child2: + age: 7 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + filing_status: JOINT + tax_unit_childcare_expenses: 6_000 + premium_tax_credit: 0 + output: + # $6,000 x 20% applicable rate at this AGI = $1,200 (same as live cdcc). + cdcc: 1_200 + pre_obbba_cdcc_potential: 1_200 + pre_obbba_cdcc: 1_200 + pre_obbba_capped_cdcc: 1_200 + +- name: 2026 pre-OBBBA CDCC holds the pre-OBBBA rate while live CDCC rises + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 25_000 + spouse: + age: 35 + employment_income: 25_000 + child1: + age: 5 + child2: + age: 7 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + filing_status: JOINT + tax_unit_childcare_expenses: 6_000 + premium_tax_credit: 0 + output: + # Live 2026 credit uses the OBBBA rate (35% here) = $2,100 potential, + # $1,780 after the section 26 cap for this family. + cdcc_potential: 2_100 + cdcc: 1_780 + # Pre-OBBBA credit holds the 20% pre-OBBBA rate = $1,200. + pre_obbba_cdcc_potential: 1_200 + pre_obbba_cdcc: 1_200 + pre_obbba_capped_cdcc: 1_200 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 582dd6df1fc..8f099dd7295 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 @@ -72,3 +72,18 @@ # One qualifying individual at the pre-ARPA $3,000 cap: # min(10_000 adult care, 3_000, 50_000) = 3_000. ca_cdcc_relevant_expenses: 3_000 + +# FTB 3506 Part IV reduces the $3,000 / $6,000 dollar limit by the IRC § 129 +# employer-provided dependent care benefits excluded from income, matching the +# federal § 21(c) reduction. +- name: Section 129 employer benefits reduce the California base + period: 2024 + input: + state_code: CA + tax_unit_childcare_expenses: 10_000 + count_cdcc_eligible: 2 + 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. + ca_cdcc_relevant_expenses: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/deductions/household_and_dependent_care/id_household_and_dependent_care_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/deductions/household_and_dependent_care/id_household_and_dependent_care_expense_deduction.yaml index 79457d953dd..f02284f9c08 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/deductions/household_and_dependent_care/id_household_and_dependent_care_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/deductions/household_and_dependent_care/id_household_and_dependent_care_expense_deduction.yaml @@ -109,3 +109,18 @@ state_code: ID output: id_household_and_dependent_care_expense_deduction: 3_000 + +# Idaho Form 39R Line 6 worksheet line 4 reduces the operative cap by the IRC +# § 129 employer-provided dependent care benefits ("excluded benefits from Part +# III of Form 2441"). Here the reduced $12,000 cap binds below the expenses. +- name: Section 129 employer benefits reduce the Idaho deduction cap + period: 2023 + input: + tax_unit_childcare_expenses: 10_000 + id_cdcc_limit: 6_000 + min_head_spouse_earned: 60_000 + dependent_care_assistance_exclusion: 5_000 + state_code: ID + output: + # min(10_000 expenses, max(6_000, 12_000) - 5_000 = 7_000, 60_000) = 7_000. + id_household_and_dependent_care_expense_deduction: 7_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/credits/dependent_care_credit/ky_cdcc.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/credits/dependent_care_credit/ky_cdcc.yaml index 203d68b36a8..e57c2abaa4b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/credits/dependent_care_credit/ky_cdcc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/credits/dependent_care_credit/ky_cdcc.yaml @@ -20,3 +20,39 @@ ky_tuition_tax_credit: 0 output: ky_cdcc: 0 + +# KRS 141.010(21) freezes Kentucky's IRC at December 31, 2024, so from 2026 the +# credit uses the pre-OBBBA federal section 21 credit (20% of $1,200 = $240), +# not the OBBBA-inflated live credit (which would give 20% of $1,780 = $356). +- name: 2026 uses the pre-OBBBA federal credit under static conformity + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 25_000 + spouse: + age: 35 + employment_income: 25_000 + child1: + age: 5 + child2: + age: 7 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + filing_status: JOINT + tax_unit_childcare_expenses: 6_000 + premium_tax_credit: 0 + ky_income_tax_before_non_refundable_credits_unit: 1_000_000 + ky_personal_tax_credits: 0 + ky_family_size_tax_credit: 0 + ky_tuition_tax_credit: 0 + households: + household: + members: [head, spouse, child1, child2] + state_code: KY + output: + # 20% of the pre-OBBBA $1,200 federal credit. + ky_cdcc: 240 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/child_care_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/child_care_credit.yaml index cac68bcba34..d3de3601bf5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/child_care_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/child_care_credit.yaml @@ -59,3 +59,21 @@ me_child_care_credit: 750 me_refundable_child_care_credit: 500 me_non_refundable_child_care_credit: 250 + +# 36 M.R.S. § 111(1-A) freezes Maine's "Code" at December 31, 2024, so from 2026 +# the credit is 25% of the pre-OBBBA federal credit (pre_obbba_cdcc), not the +# OBBBA-inflated live cdcc. Inject both to confirm the pin reads the frozen +# variable. +- name: 2026 uses the pre-OBBBA federal credit under static conformity + period: 2026 + absolute_error_margin: 0 + input: + state_code: ME + filing_status: SINGLE + cdcc: 4_000 + pre_obbba_cdcc: 1_000 + me_step_4_share_of_child_care_expenses: 0 + me_income_tax_before_credits: 1_000_000 + output: + # 25% of the frozen $1,000, ignoring the $4,000 live credit. + me_child_care_credit: 250 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/credits/sc_cdcc.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/credits/sc_cdcc.yaml index ece99500f3e..41efc4c120a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/credits/sc_cdcc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/credits/sc_cdcc.yaml @@ -79,3 +79,60 @@ sc_income_tax_before_non_refundable_credits: 1000000 output: sc_cdcc: 210 + +# S.C. Code § 12-6-3380 computes the credit "as provided in Internal Revenue +# Code Section 21," importing § 21(e)(2)'s joint-return rule together with the +# § 21(e)(4) separated-taxpayer exception. An ordinary married-separate filer +# remains ineligible; a separated filer who lives apart while maintaining a home +# for a qualifying individual qualifies, matching the federal treatment. +- name: Ordinary married-filing-separately filer gets no SC CDCC + period: 2024 + input: + people: + head: + age: 35 + is_tax_unit_head: true + employment_income: 40_000 + child: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [head, child] + filing_status: SEPARATE + cdcc_relevant_expenses: 3_000 + sc_income_tax_before_non_refundable_credits: 1_000_000 + households: + household: + members: [head, child] + state_code: SC + output: + sc_cdcc: 0 + +- name: Separated MFS filer maintaining a home for a qualifying child gets the SC CDCC + period: 2024 + input: + people: + head: + age: 35 + is_tax_unit_head: true + is_separated: true + employment_income: 40_000 + child: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [head, child] + filing_status: SEPARATE + cdcc_relevant_expenses: 3_000 + sc_income_tax_before_non_refundable_credits: 1_000_000 + households: + household: + members: [head, child] + state_code: SC + output: + # 7% of min($3,000 spent, $3,000 one-person 2024 cap) = $210. + sc_cdcc: 210 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_deduction_cdcc_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_deduction_cdcc_limit.yaml index d285bf0c4a1..f5a01e7f762 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_deduction_cdcc_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_deduction_cdcc_limit.yaml @@ -13,3 +13,26 @@ state_code: VA output: va_child_dependent_care_deduction_cdcc_limit: 3_000 + +# Va. Code § 58.1-322.03(3) deducts the amount on which the federal credit is +# based (Form 2441 line 31), which IRC § 21(c) reduces by § 129 employer +# benefits. The Virginia limit inherits that reduction. +- name: Section 129 employer benefits reduce the Virginia limit + period: 2024 + input: + capped_count_cdcc_eligible: 2 + dependent_care_assistance_exclusion: 5_000 + state_code: VA + output: + # $6,000 two-child limit less the $5,000 exclusion. + va_child_dependent_care_deduction_cdcc_limit: 1_000 + +- name: Section 129 benefits exceeding the limit floor it at zero + period: 2024 + input: + capped_count_cdcc_eligible: 1 + dependent_care_assistance_exclusion: 5_000 + state_code: VA + output: + # max($3,000 - $5,000, 0) = $0. + va_child_dependent_care_deduction_cdcc_limit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.yaml index 9a7a5c687df..8551b964dac 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.yaml @@ -41,3 +41,17 @@ state_code: VT output: vt_cdcc: 720 + +# 32 V.S.A. § 5824 freezes Vermont's IRC at December 31, 2024, so from 2026 the +# credit is 72% of the pre-OBBBA federal credit (pre_obbba_capped_cdcc), not the +# OBBBA-inflated live capped_cdcc. Inject both to confirm the pin reads the +# frozen variable. +- name: Vermont CDCC 2026 uses the pre-OBBBA federal credit under static conformity + period: 2026 + input: + capped_cdcc: 2_000 + pre_obbba_capped_cdcc: 1_000 + state_code: VT + output: + # 72% of the frozen $1,000, ignoring the $2,000 live credit. + vt_cdcc: 720 diff --git a/policyengine_us/tests/policy/contrib/states/test_id_ga_ctc_reform_activation.py b/policyengine_us/tests/policy/contrib/states/test_id_ga_ctc_reform_activation.py new file mode 100644 index 00000000000..91f4d035358 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/test_id_ga_ctc_reform_activation.py @@ -0,0 +1,132 @@ +"""Regression tests for the Idaho and Georgia CTC contributed reforms under a +DELAYED ``in_effect`` activation date (PolicyEngine/policyengine-us#8899). + +The YAML tests for these reforms can only set contrib parameters as scalars +(one value for every period), so they cannot express "``in_effect`` turns on in +2028" and never exercised the pre-activation years. Two leaks hid behind that +gap: + +* ``ga_refundable_ctc`` had no per-period ``in_effect`` gate, so once the reform + was installed (the factory installs it for the whole simulation whenever the + toggle is true in any of the next five years) it paid the refund in every + year, including pre-activation ones. +* the Idaho reform's ``modify_parameters`` revived ``id_ctc`` in the ordered + nonrefundable list from a hardcoded ``2026-01-01`` regardless of the actual + activation date, so the revived nonrefundable credit applied before the + reform turned on. + +These tests drive a multi-year simulation with the toggle set true only from +2028 and assert the reform is inert in the 2026 pre-activation year and active +in 2028. +""" + +from policyengine_core.periods import instant +from policyengine_core.reforms import Reform + +from policyengine_us import Simulation + +ACTIVATION_YEAR = "2028-01-01" + + +def _activate_from_2028(param_path): + """A user reform that sets the given boolean contrib parameter true from + 2028 onward (mirroring how the app applies a contrib toggle).""" + + class ActivateFrom2028(Reform): + def apply(self): + def modify(parameters): + node = parameters + for part in param_path.split("."): + node = getattr(node, part) + node.update( + start=instant(ACTIVATION_YEAR), + stop=instant("2100-12-31"), + value=True, + ) + return parameters + + self.modify_parameters(modify) + + return ActivateFrom2028 + + +GA_SITUATION = { + "people": { + "parent": {"age": {"2026": 30, "2028": 30}}, + "child": { + "age": {"2026": 2, "2028": 4}, + "ctc_qualifying_child": {"2026": True, "2028": True}, + }, + }, + "tax_units": { + "tax_unit": { + "members": ["parent", "child"], + "filing_status": {"2026": "SINGLE", "2028": "SINGLE"}, + "ga_income_tax_before_non_refundable_credits": {"2026": 0, "2028": 0}, + } + }, + "households": { + "household": { + "members": ["parent", "child"], + "state_code": {"2026": "GA", "2028": "GA"}, + } + }, +} + +ID_SITUATION = { + "people": { + "parent": {"age": {"2026": 30, "2028": 30}}, + "child1": { + "age": {"2026": 2, "2028": 4}, + "ctc_qualifying_child": {"2026": True, "2028": True}, + }, + "child2": { + "age": {"2026": 4, "2028": 6}, + "ctc_qualifying_child": {"2026": True, "2028": True}, + }, + }, + "tax_units": { + "tax_unit": { + "members": ["parent", "child1", "child2"], + "filing_status": {"2026": "SINGLE", "2028": "SINGLE"}, + "id_income_tax_before_non_refundable_credits": { + "2026": 1_000, + "2028": 1_000, + }, + } + }, + "households": { + "household": { + "members": ["parent", "child1", "child2"], + "state_code": {"2026": "ID", "2028": "ID"}, + } + }, +} + + +def _simulation(situation, param_path): + # start_instant must be the earliest simulated year so the structural-reform + # factory sees the full window (this is the documented start_instant patch). + return Simulation( + situation=situation, + reform=_activate_from_2028(param_path), + start_instant="2026-01-01", + ) + + +def test__ga_refundable_ctc_does_not_leak_before_activation(): + sim = _simulation(GA_SITUATION, "gov.contrib.states.ga.ctc.refundable.in_effect") + # Pre-activation (2026): no refund. + assert sim.calculate("ga_refundable_ctc", 2026)[0] == 0 + # Activation year (2028): full $250 per-child refund pays out. + assert sim.calculate("ga_refundable_ctc", 2028)[0] == 250 + + +def test__id_ctc_revival_does_not_leak_before_activation(): + sim = _simulation(ID_SITUATION, "gov.contrib.states.id.ctc.in_effect") + # id_ctc always computes the worksheet amount (2 * 205 = 410)... + assert sim.calculate("id_ctc", 2026)[0] == 410 + # ...but the revival must NOT apply it in the pre-activation year. + assert sim.calculate("id_non_refundable_credits", 2026)[0] == 0 + # In the activation year the revived nonrefundable credit applies. + assert sim.calculate("id_non_refundable_credits", 2028)[0] == 410 diff --git a/policyengine_us/variables/gov/irs/credits/cdcc/pre_obbba_cdcc.py b/policyengine_us/variables/gov/irs/credits/cdcc/pre_obbba_cdcc.py new file mode 100644 index 00000000000..90d52da4f6a --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/cdcc/pre_obbba_cdcc.py @@ -0,0 +1,86 @@ +from policyengine_us.model_api import * + + +class pre_obbba_cdcc_potential(Variable): + value_type = float + entity = TaxUnit + label = "Federal CDCC before the section 26 cap, pre-OBBBA section 21 rate" + documentation = ( + "The federal child and dependent care credit before the section 26 " + "liability cap, recomputed with the IRC section 21 rate schedule as it " + "stood before the One Big Beautiful Bill Act (OBBBA). States whose " + "income tax statically conforms to a pre-2026 IRC read this instead of " + "the live federal credit so they do not silently adopt the OBBBA " + "section 21 rate increase, effective for tax years beginning after " + "2025-12-31." + ) + unit = USD + definition_period = YEAR + reference = "https://www.law.cornell.edu/uscode/text/26/21#a_2" + + def formula(tax_unit, period, parameters): + # Only the section 21(a)(2) rate schedule changed under OBBBA + # (phase_out.max 0.35->0.50, phase_out.min 0.20->0.35, plus a new + # amended second phase-out), all dated 2026-01-01. The section 21(b)-(e) + # expense base is unchanged, so freeze only the rate by reading the cdcc + # parameter subtree at a pre-OBBBA instant. Any 2023-2025 instant yields + # the same schedule; use 2025-01-01. The pre-OBBBA schedule has no + # second phase-out after 2021 (second_start is infinite from 2022), so + # the first phase-out is the whole rate computation. + frozen = parameters("2025-01-01").gov.irs.credits.cdcc + agi = tax_unit("adjusted_gross_income", period) + excess_agi = max_(0, agi - frozen.phase_out.start) + increments = np.ceil(excess_agi / frozen.phase_out.increment) + percentage_reduction = increments * frozen.phase_out.rate + rate = max_( + frozen.phase_out.min, + frozen.phase_out.max - percentage_reduction, + ) + eligible = tax_unit("cdcc_filing_status_eligible", period) + expenses = tax_unit("cdcc_relevant_expenses", period) + return eligible * expenses * rate + + +class pre_obbba_cdcc(Variable): + value_type = float + entity = TaxUnit + label = "Federal CDCC (section 26 non-refundable cap), pre-OBBBA section 21 rate" + documentation = ( + "Mirror of the live cdcc variable but built on the pre-OBBBA section 21 " + "rate schedule, for states whose income tax statically conforms to a " + "pre-2026 IRC." + ) + unit = USD + definition_period = YEAR + reference = "https://www.law.cornell.edu/uscode/text/26/21" + + def formula(tax_unit, period, parameters): + # cdcc has been non-refundable since 2022; every state that reads this + # computes post-2022, so apply the section 26 liability cap directly + # (the refundable-2021 branch of the live cdcc is not reachable here). + potential = tax_unit("pre_obbba_cdcc_potential", period) + credit_limit = tax_unit("cdcc_credit_limit", period) + return min_(credit_limit, potential) + + +class pre_obbba_capped_cdcc(Variable): + value_type = float + entity = TaxUnit + label = "Capped federal CDCC (Credit Limit Worksheet), pre-OBBBA section 21 rate" + documentation = ( + "Mirror of the live capped_cdcc variable but built on the pre-OBBBA " + "section 21 rate schedule, for states whose income tax statically " + "conforms to a pre-2026 IRC." + ) + unit = USD + definition_period = YEAR + reference = "https://www.irs.gov/instructions/i2441" + + def formula(tax_unit, period, parameters): + cdcc = tax_unit("pre_obbba_cdcc", period) + # 2022 Form 2441 Credit Limit Worksheet: cap at tax before credits less + # the foreign tax credit (the section 26 cap is unchanged by OBBBA). + itaxbc = tax_unit("income_tax_before_credits", period) + offset = tax_unit("foreign_tax_credit", period) + cap = max_(itaxbc - offset, 0) + return min_(cdcc, cap) 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 498eec75dbd..25fe3bf1736 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 @@ -28,7 +28,15 @@ def formula(tax_unit, period, parameters): count_eligible = min_( cdcc.eligibility.max, tax_unit("count_cdcc_eligible", period) ) - eligible_capped_expenses = min_(expenses, cdcc.max * count_eligible) + # FTB 3506 Part IV (lines 26-33) reduces the $3,000 / $6,000 dollar + # limit by the IRC § 129 employer-provided dependent care benefits + # excluded from income (line 30 subtracts the excluded benefits, and + # line 32 caps at the already-net federal Form 2441 line 31). Mirror the + # § 21(c) reduction the federal credit's base applies. + dollar_limit = cdcc.max * count_eligible + exclusion = tax_unit("dependent_care_assistance_exclusion", period) + dollar_limit_after_exclusion = max_(dollar_limit - exclusion, 0) + eligible_capped_expenses = min_(expenses, dollar_limit_after_exclusion) # Then, cap further to the lowest earnings between the taxpayer and spouse return min_( eligible_capped_expenses, diff --git a/policyengine_us/variables/gov/states/id/tax/income/subtractions/household_and_dependent_care/id_cdcc_limit.py b/policyengine_us/variables/gov/states/id/tax/income/subtractions/household_and_dependent_care/id_cdcc_limit.py index f8b209e8dea..66d8b326980 100644 --- a/policyengine_us/variables/gov/states/id/tax/income/subtractions/household_and_dependent_care/id_cdcc_limit.py +++ b/policyengine_us/variables/gov/states/id/tax/income/subtractions/household_and_dependent_care/id_cdcc_limit.py @@ -17,4 +17,9 @@ def formula(tax_unit, period, parameters): instant_str = period p = parameters(instant_str).gov.irs.credits.cdcc capped_count_cdcc_eligible = tax_unit("capped_count_cdcc_eligible", period) + # This is the raw federal per-qualifying-individual dollar limit. The + # IRC § 129 employer-benefit reduction (Form 39R Line 6 worksheet line 4) + # is applied against the operative Idaho cap in + # id_household_and_dependent_care_expense_deduction, so it is not + # subtracted here. return p.max * capped_count_cdcc_eligible diff --git a/policyengine_us/variables/gov/states/id/tax/income/subtractions/household_and_dependent_care/id_household_and_dependent_care_expense_deduction.py b/policyengine_us/variables/gov/states/id/tax/income/subtractions/household_and_dependent_care/id_household_and_dependent_care_expense_deduction.py index ffaafd294be..bfe2b957235 100644 --- a/policyengine_us/variables/gov/states/id/tax/income/subtractions/household_and_dependent_care/id_household_and_dependent_care_expense_deduction.py +++ b/policyengine_us/variables/gov/states/id/tax/income/subtractions/household_and_dependent_care/id_household_and_dependent_care_expense_deduction.py @@ -21,6 +21,12 @@ def formula(tax_unit, period, parameters): expenses = childcare + adult_care # In 2023 Idaho implemented an increased cap independent of the number of children limit = max_(tax_unit("id_cdcc_limit", period), p.cap) + # Form 39R Line 6 worksheet line 4: the operative cap is reduced by the + # IRC § 129 employer-provided dependent care benefits ("excluded + # benefits from Part III of Form 2441"), mirroring the § 21(c) reduction + # the federal credit's base applies. + exclusion = tax_unit("dependent_care_assistance_exclusion", period) + limit = max_(limit - exclusion, 0) eligible_capped_expenses = min_(expenses, limit) # cap further to the lowest earnings between the taxpayer and spouse return min_( diff --git a/policyengine_us/variables/gov/states/ky/tax/income/credits/dependent_care_service/ky_cdcc_potential.py b/policyengine_us/variables/gov/states/ky/tax/income/credits/dependent_care_service/ky_cdcc_potential.py index 3876680f41f..7a7ea11a9d9 100644 --- a/policyengine_us/variables/gov/states/ky/tax/income/credits/dependent_care_service/ky_cdcc_potential.py +++ b/policyengine_us/variables/gov/states/ky/tax/income/credits/dependent_care_service/ky_cdcc_potential.py @@ -11,8 +11,16 @@ class ky_cdcc_potential(Variable): defined_for = StateCode.KY def formula(tax_unit, period, parameters): - # Kentucky matches the federal credit taken - dependent_care_credit = tax_unit("cdcc", period) + # Kentucky matches the federal credit taken. KRS 141.067 sets the credit + # at 20% of "the federal credit allowed under Section 21 of the Internal + # Revenue Code," and KRS 141.010(21) fixes Kentucky's IRC at the Code in + # effect on December 31, 2024. From 2026 that frozen Code excludes the + # OBBBA section 21 rate increase (effective for tax years beginning after + # 2025-12-31), so Kentucky uses the pre-OBBBA federal credit. + if period.start.year >= 2026: + dependent_care_credit = tax_unit("pre_obbba_cdcc", period) + else: + dependent_care_credit = tax_unit("cdcc", period) rate = parameters( period ).gov.states.ky.tax.income.credits.dependent_care_service.match diff --git a/policyengine_us/variables/gov/states/me/tax/income/credits/child_care_credit/me_child_care_credit.py b/policyengine_us/variables/gov/states/me/tax/income/credits/child_care_credit/me_child_care_credit.py index f9b79a4fe12..41dd4dfb512 100644 --- a/policyengine_us/variables/gov/states/me/tax/income/credits/child_care_credit/me_child_care_credit.py +++ b/policyengine_us/variables/gov/states/me/tax/income/credits/child_care_credit/me_child_care_credit.py @@ -21,8 +21,16 @@ def formula(tax_unit, period, parameters): "me_step_4_share_of_child_care_expenses", period ) # Line 2: Divide Federal CDCC according to share of regular vs. Step 4 expenses - # Maine matches the federal credit taken - cdcc = tax_unit("cdcc", period) + # Maine matches the federal credit taken. 36 M.R.S. § 5218 sets the + # credit at 25% of "the federal tax credit allowable for child and + # dependent care expenses," and 36 M.R.S. § 111(1-A) fixes Maine's + # "Code" at the IRC and amendments as of December 31, 2024. From 2026 + # that frozen Code excludes the OBBBA section 21 rate increase, so Maine + # uses the pre-OBBBA federal credit. + if period.start.year >= 2026: + cdcc = tax_unit("pre_obbba_cdcc", period) + else: + cdcc = tax_unit("cdcc", period) # Line 2a: Column A cdcc_regular_portion = cdcc * (1 - step_4_share_of_expenses) # Line 2a, Column B diff --git a/policyengine_us/variables/gov/states/sc/tax/income/credits/cdcc/sc_cdcc_potential.py b/policyengine_us/variables/gov/states/sc/tax/income/credits/cdcc/sc_cdcc_potential.py index 3b913d3107b..beece662287 100644 --- a/policyengine_us/variables/gov/states/sc/tax/income/credits/cdcc/sc_cdcc_potential.py +++ b/policyengine_us/variables/gov/states/sc/tax/income/credits/cdcc/sc_cdcc_potential.py @@ -8,7 +8,10 @@ class sc_cdcc_potential(Variable): documentation = "South Carolina Child and Dependent Care Credit" unit = USD definition_period = YEAR - reference = "https://dor.sc.gov/forms-site/Forms/IITPacket_2022.pdf#page=22" + reference = ( + "https://www.scstatehouse.gov/code/t12c006.php", # S.C. Code § 12-6-3380 + "https://dor.sc.gov/forms-site/Forms/IITPacket_2022.pdf#page=22", + ) defined_for = StateCode.SC def formula(tax_unit, period, parameters): @@ -24,9 +27,16 @@ def formula(tax_unit, period, parameters): # Get child care expenses. childcare_expenses = tax_unit("cdcc_relevant_expenses", period) - # Married filing separate are ineligible. - filing_status = tax_unit("filing_status", period) - eligible = filing_status != filing_status.possible_values.SEPARATE + # S.C. Code Ann. § 12-6-3380 computes the credit "as provided in + # Internal Revenue Code Section 21," importing § 21 wholesale with only + # the applicable-percentage (7%) and SC-source-income modifications. It + # adds no filing-status language of its own, so it carries the + # § 21(e)(2) joint-return requirement together with the § 21(e)(4) + # separated-taxpayer exception. A married-separate filer who lives apart + # while maintaining a home for a qualifying individual is therefore + # eligible, matching the federal treatment; an ordinary MFS filer + # remains ineligible. + eligible = tax_unit("cdcc_filing_status_eligible", period) # Number of qualifying people count_cdcc_eligible = min_( diff --git a/policyengine_us/variables/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_deduction_cdcc_limit.py b/policyengine_us/variables/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_deduction_cdcc_limit.py index 249f824ece5..0b9f8324eb9 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_deduction_cdcc_limit.py +++ b/policyengine_us/variables/gov/states/va/tax/income/deductions/cdcc_expense/va_child_dependent_care_deduction_cdcc_limit.py @@ -17,4 +17,12 @@ def formula(tax_unit, period, parameters): instant_str = period p = parameters(instant_str).gov.irs.credits.cdcc capped_count_cdcc_eligible = tax_unit("capped_count_cdcc_eligible", period) - return p.max * capped_count_cdcc_eligible + dollar_limit = p.max * capped_count_cdcc_eligible + # Va. Code § 58.1-322.03(3) deducts "the amount of employment-related + # expenses upon which the federal credit is based under § 21," and the + # Form 760 code 101 instruction points to the Form 2441 amount that is + # multiplied by the decimal (line 31) — already reduced by the IRC § 129 + # employer-provided dependent care benefits under § 21(c). Mirror that + # reduction so the deduction base matches the federal credit's base. + exclusion = tax_unit("dependent_care_assistance_exclusion", period) + return max_(dollar_limit - exclusion, 0) diff --git a/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.py b/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.py index aa0e966e853..6b613164326 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.py @@ -17,8 +17,16 @@ class vt_cdcc(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.vt.tax.income.credits.cdcc - # The form refers to 2022 Form 2441 line 11, which caps the credit at tax liability. - federal_cdcc = tax_unit("capped_cdcc", period) + # The form refers to 2022 Form 2441 line 11, which caps the credit at tax + # liability. 32 V.S.A. § 5828c sets the credit at 72% of "the federal + # child and dependent care credit allowed to the taxpayer," and § 5824 + # adopts the federal income tax laws as in effect on December 31, 2024. + # From 2026 that frozen Code excludes the OBBBA section 21 rate increase, + # so Vermont uses the pre-OBBBA federal credit. + if period.start.year >= 2026: + federal_cdcc = tax_unit("pre_obbba_capped_cdcc", period) + else: + federal_cdcc = tax_unit("capped_cdcc", period) # 2022 Act 138 raised the 5828c match to 72 percent but retained the # low-income adjusted gross income cap and the certified-facility # requirement for 2022; 2023 Act 72 removed them effective 2023. We diff --git a/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc.py b/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc.py index c02a3365d83..2ee5fc89db3 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc.py @@ -15,5 +15,11 @@ class vt_low_income_cdcc(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.vt.tax.income.credits.cdcc.low_income - federal_cdcc = tax_unit("capped_cdcc", period) + # 32 V.S.A. § 5824 adopts the federal income tax laws as in effect on + # December 31, 2024, so from 2026 Vermont uses the pre-OBBBA federal + # credit (excluding the OBBBA section 21 rate increase). + if period.start.year >= 2026: + federal_cdcc = tax_unit("pre_obbba_capped_cdcc", period) + else: + federal_cdcc = tax_unit("capped_cdcc", period) return p.rate * federal_cdcc