diff --git a/changelog.d/ct-child-rebate-2021.fixed.md b/changelog.d/ct-child-rebate-2021.fixed.md new file mode 100644 index 00000000000..a07077b9c65 --- /dev/null +++ b/changelog.d/ct-child-rebate-2021.fixed.md @@ -0,0 +1 @@ +Move the Connecticut child tax rebate to the 2021 eligibility year as a refundable credit, matching how the analogous Rhode Island and New Mexico prior-year rebates are booked, so it no longer reduces 2022 Connecticut income tax. diff --git a/policyengine_us/parameters/gov/states/ct/tax/income/credits/non_refundable.yaml b/policyengine_us/parameters/gov/states/ct/tax/income/credits/non_refundable.yaml index 09d4e8ecbd1..7786570685a 100644 --- a/policyengine_us/parameters/gov/states/ct/tax/income/credits/non_refundable.yaml +++ b/policyengine_us/parameters/gov/states/ct/tax/income/credits/non_refundable.yaml @@ -9,7 +9,6 @@ values: - ct_property_tax_credit 2022-01-01: - ct_property_tax_credit - - ct_child_tax_rebate - ct_stillborn_credit 2023-01-01: - ct_property_tax_credit diff --git a/policyengine_us/parameters/gov/states/ct/tax/income/credits/refundable.yaml b/policyengine_us/parameters/gov/states/ct/tax/income/credits/refundable.yaml index 91783feb1c7..12c92357bea 100644 --- a/policyengine_us/parameters/gov/states/ct/tax/income/credits/refundable.yaml +++ b/policyengine_us/parameters/gov/states/ct/tax/income/credits/refundable.yaml @@ -3,7 +3,20 @@ metadata: unit: list period: year label: Connecticut refundable credits - + reference: + - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411(b)(1) + href: https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=548 + - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411(b)(4) + href: https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=549 values: + # The child tax rebate (PA 22-118 Sec. 411) is a refundable rebate keyed to the + # 2021 return, booked in the eligibility year like other prior-year rebates + # (cf. ri_child_tax_rebate, nm_2021_income_rebate). TSSB 2022-5 Q5 states the + # rebate "is not a credit"; booking it as a refundable credit follows the RI/NM + # convention and is the correct cash treatment (it raises household_net_income + # and does not touch SALT or ct_withheld_income_tax). 2021-01-01: - ct_eitc + - ct_child_tax_rebate + 2022-01-01: + - ct_eitc diff --git a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/age_limit.yaml b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/age_limit.yaml new file mode 100644 index 00000000000..c1788f34ad1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/age_limit.yaml @@ -0,0 +1,15 @@ +description: Connecticut limits its child tax rebate to dependent children this age or younger under the Child Tax Rebate program. +metadata: + unit: year + period: year + label: Connecticut child tax rebate age limit + reference: + - title: 2022 Child Tax Rebate + href: https://portal.ct.gov/drs/credit-programs/child-tax-rebate/overview + - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411(a)(1) + href: https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=548 + # The sentinel asymmetry with amount.yaml / child_cap.yaml is deliberate: a + # leaked/backdated age_limit is inert while the amount is 0 (rebate = + # capped_children * amount = 0), so age_limit need not carry a 2015/2022 sentinel. +values: + 2021-01-01: 18 diff --git a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/amount.yaml b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/amount.yaml index 74bcc8fe41b..809fda31ca6 100644 --- a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/amount.yaml +++ b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/amount.yaml @@ -1,4 +1,4 @@ -description: Connecticut provides a child tax rebate of this amount, for each eligible child. +description: Connecticut provides a child tax rebate of this amount for each eligible child under the Child Tax Rebate program. metadata: period: year unit: currency-USD @@ -6,10 +6,20 @@ metadata: reference: - title: 2022 Child Tax Rebate href: https://portal.ct.gov/drs/credit-programs/child-tax-rebate/overview - - title: TSSB 2022-5 - Frequently Asked Questions Concerning the Child Tax Rebate (4) + - title: TSSB 2022-5 - Frequently Asked Questions Concerning the Child Tax Rebate Q4 href: https://portal.ct.gov/-/media/drs/publications/tssb/2022/tssb-2022-5.pdf#page=1 - - title: House Bill No. 5506 Public Act No. 22-118 Sec. 410 (xxvi) + - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411(b)(2) + href: https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=549 + - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411(b)(1) href: https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=548 - # The legal code section has been repealed + # PA 22-118 Sec. 411 is uncodified session law, keyed to TY2021, never added to + # the General Statutes; it sunsets to 0 in 2022. The $250 amount is set in the + # opening clause of Sec. 411(b)(2) (p. 549); the one-time/TY2021 character is + # stated in Sec. 411(b)(1) (p. 548). TSSB 2022-5 cites this as H.B. 5506 + # Sec. 415; the enrolled act renumbers it Sec. 411 (same provision). values: - 2022-01-01: 250 + # No rebate before the 2021 tax year; the explicit 0 blocks backdating from + # phantoming the 2021 value into prior years at system build. + 2015-01-01: 0 + 2021-01-01: 250 + 2022-01-01: 0 diff --git a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/child_cap.yaml b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/child_cap.yaml index 901d9bbbea4..3f05ca262b7 100644 --- a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/child_cap.yaml +++ b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/child_cap.yaml @@ -1,15 +1,20 @@ -description: Connecticut caps the child for tax rebate at this number. +description: Connecticut caps its child tax rebate at this number of children under the Child Tax Rebate program. metadata: period: year - unit: currency-USD - label: Connecticut tax rebate child cap + unit: person + label: Connecticut child tax rebate child cap reference: - title: 2022 Child Tax Rebate href: https://portal.ct.gov/drs/credit-programs/child-tax-rebate/overview - - title: TSSB 2022-5 - Frequently Asked Questions Concerning the Child Tax Rebate (4) + - title: TSSB 2022-5 - Frequently Asked Questions Concerning the Child Tax Rebate Q4 href: https://portal.ct.gov/-/media/drs/publications/tssb/2022/tssb-2022-5.pdf#page=1 - - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411 (b) (1) + - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411(b)(1) href: https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=548 - # The legal code section has been repealed + # PA 22-118 Sec. 411 is uncodified session law, keyed to TY2021, never added to + # the General Statutes; it sunsets to 0 in 2022. values: - 2022-01-01: 3 + # No rebate before the 2021 tax year; the explicit 0 blocks backdating from + # phantoming the 2021 value into prior years at system build. + 2015-01-01: 0 + 2021-01-01: 3 + 2022-01-01: 0 diff --git a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/increment.yaml b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/increment.yaml index 04f2413998c..f6a117c9250 100644 --- a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/increment.yaml +++ b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/increment.yaml @@ -1,11 +1,16 @@ -description: Connecticut reduces the child tax rebate amount for each of these increments of state adjusted gross income exceeding the threshold. +description: Connecticut reduces the child tax rebate amount for each of these increments of federal adjusted gross income exceeding the threshold under the Child Tax Rebate program. metadata: period: year unit: currency-USD label: Connecticut child tax rebate reduction increment reference: - - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411 (b)(2) + - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411(b)(2) href: https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=549 - # The legal code section has been repealed + # PA 22-118 Sec. 411 is uncodified session law, keyed to TY2021, never added to + # the General Statutes. This file does NOT carry the 2022 sunset: that is carried + # by amount.yaml / child_cap.yaml (rebate = capped_children * amount, both 0 + # outside 2021), so this reduction increment is inert outside 2021. `increment` + # is deliberately NEVER zeroed because a 0 here divides by zero in + # ct_child_tax_rebate.py (np.ceil(excess / increment)). values: - 2022-01-01: 1_000 + 2021-01-01: 1_000 diff --git a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/rate.yaml b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/rate.yaml index 4a369e19e49..b2274720a55 100644 --- a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/rate.yaml +++ b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/rate.yaml @@ -1,11 +1,14 @@ -description: Connecticut reduces the personal exemption amount by this rate for each increment of state adjusted gross income exceeding the threshold. +description: Connecticut reduces the child tax rebate by this share for each increment of federal adjusted gross income exceeding the threshold under the Child Tax Rebate program. metadata: period: year unit: /1 label: Connecticut child tax rebate reduction rate reference: - - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411 (b)(2) + - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411(b)(2) href: https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=549 - # The legal code section has been repealed + # PA 22-118 Sec. 411 is uncodified session law, keyed to TY2021, never added to + # the General Statutes. This file does NOT carry the 2022 sunset: that is carried + # by amount.yaml / child_cap.yaml (rebate = capped_children * amount, both 0 + # outside 2021), so this reduction rate is inert outside 2021. values: - 2022-01-01: .1 + 2021-01-01: 0.1 diff --git a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/start.yaml b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/start.yaml index 718a2e17398..0b2773e8b12 100644 --- a/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/start.yaml +++ b/policyengine_us/parameters/gov/states/ct/tax/income/rebate/reduction/start.yaml @@ -1,4 +1,4 @@ -description: Connecticut reduces the child tax rebate for filers with state adjusted gross income above this amount, based on filing status. +description: Connecticut reduces the child tax rebate for filers with federal adjusted gross income above this amount, based on filing status, under the Child Tax Rebate program. metadata: period: year unit: currency-USD @@ -6,18 +6,21 @@ metadata: breakdown: - filing_status reference: - - title: TSSB 2022-5 - Frequently Asked Questions Concerning the Child Tax Rebate (11) + - title: TSSB 2022-5 - Frequently Asked Questions Concerning the Child Tax Rebate Q11 href: https://portal.ct.gov/-/media/drs/publications/tssb/2022/tssb-2022-5.pdf#page=2 - - title: House Bill No. 5506 Public Act No. 22-118 Sec. 410 (xxvi) + - title: House Bill No. 5506 Public Act No. 22-118 Sec. 411(b)(2) href: https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=549 - # The legal code section has been repealed + # PA 22-118 Sec. 411 is uncodified session law, keyed to TY2021, never added to + # the General Statutes. This file does NOT carry the 2022 sunset: that is carried + # by amount.yaml / child_cap.yaml (rebate = capped_children * amount, both 0 + # outside 2021), so this reduction start is inert outside 2021. SINGLE: - 2022-01-01: 100_000 -HEAD_OF_HOUSEHOLD: - 2022-01-01: 160_000 + 2021-01-01: 100_000 JOINT: - 2022-01-01: 200_000 -SURVIVING_SPOUSE: - 2022-01-01: 200_000 + 2021-01-01: 200_000 SEPARATE: - 2022-01-01: 100_000 + 2021-01-01: 100_000 +HEAD_OF_HOUSEHOLD: + 2021-01-01: 160_000 +SURVIVING_SPOUSE: + 2021-01-01: 200_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ct/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ct/tax/income/integration.yaml index 2c51c277e41..7b53b9c598d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ct/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ct/tax/income/integration.yaml @@ -15,6 +15,7 @@ tax_unit: members: [person1, person2] aca_ptc: 0 # not in TAXSIM35 + ct_child_tax_rebate: 0 # not in TAXSIM35 spm_units: spm_unit: members: [person1, person2] @@ -44,6 +45,7 @@ tax_unit: members: [person1, person2, person3, person4] aca_ptc: 0 # not in TAXSIM35 + ct_child_tax_rebate: 0 # not in TAXSIM35 spm_units: spm_unit: members: [person1, person2, person3, person4] @@ -132,3 +134,48 @@ ct_social_security_benefit_adjustment: 12_246 ct_agi: 100_559 ct_income_tax: 4_671 + +- name: CT 2021 single parent, one child - child tax rebate flows through ct_income_tax + period: 2021 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_tax_unit_dependent: false + child: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [parent, child] + state_code: CT + output: + # --- Rebate (booked in the 2021 eligibility year; the chain this PR fixes) --- + # 1 eligible child (age 8 <= 18, dependent); AGI 50,000 < SINGLE threshold + # 100,000 -> no phase-out -> 1 * 250 = 250. + ct_child_tax_rebate: 250 + # --- Refundable credits = ct_eitc + ct_child_tax_rebate --- + # No earned income -> federal EITC 0 -> ct_eitc 0; so aggregate = rebate = 250. + ct_refundable_credits: 250 + # --- Baseline ct_income_tax, hand-derived from 2021 CT parameters --- + # ct_agi = adjusted_gross_income 50,000 (no CT additions/subtractions). + # Personal exemption (SINGLE): max 15,000; reduction start 30,000; + # excess 20,000 -> ceil(20,000/1,000)=20 increments * 1,000 = 20,000 reduction + # -> max(15,000 - 20,000, 0) = 0 (fully phased out). + # ct_taxable_income = max(50,000 - 0, 0) = 50,000. + # Tax (SINGLE 2021 brackets): 10,000 @ 3% = 300; 40,000 @ 5% = 2,000 + # (50,000 is the start of the 5.5% bracket, 0 there) -> 2,300. + # Phase-out add-back: SINGLE start 56,500 > 50,000 -> 0. + # Recapture: low bracket not in effect in 2021; middle start 200,000 -> 0. + # Personal credit rate at ct_agi 50,000 (SINGLE agi table, bracket + # [33,300, 60,000)) = 0.10 -> credit 0.10 * 2,300 = 230. + # ct_income_tax_after_personal_credits = max(0, 2,300 - 230) = 2,070. + # ct_amt 0; non-refundable credits 0 (no property tax) -> before-refundable 2,070. + # ct_income_tax = 2,070 - ct_refundable_credits 250 = 1,820. + ct_income_tax: 1_820 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ct/tax/income/rebate/ct_child_tax_rebate.yaml b/policyengine_us/tests/policy/baseline/gov/states/ct/tax/income/rebate/ct_child_tax_rebate.yaml index 87c522ea73c..048f7b445ec 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ct/tax/income/rebate/ct_child_tax_rebate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ct/tax/income/rebate/ct_child_tax_rebate.yaml @@ -1,29 +1,816 @@ -- name: Single with $101,000 AGI and 2 dependents - period: 2022 +- name: Single with $101,000 federal AGI and 2 dependent children + period: 2021 input: - state_code: CT - ct_agi: 101_000 - filing_status: SINGLE - tax_unit_count_dependents: 2 + people: + parent: + age: 40 + is_tax_unit_dependent: false + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + adjusted_gross_income: 101_000 + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_code: CT output: - ct_child_tax_rebate: 400 + # 2 * 250 = 500; excess 1,000 over 100,000 -> ceil(1,000/1,000) = 1 increment + # -> reduction share min(1 * 0.1, 1) = 0.1; 500 * (1 - 0.1) = 450. + ct_child_tax_rebate: 450 -- name: Joint with $180,000 AGI and 1 dependent - period: 2022 +- name: Joint with $180,000 federal AGI and 1 dependent child + period: 2021 input: - state_code: CT - ct_agi: 180_000 - filing_status: JOINT - tax_unit_count_dependents: 1 + people: + parent1: + age: 40 + is_tax_unit_dependent: false + parent2: + age: 38 + is_tax_unit_dependent: false + child1: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent1, parent2, child1] + adjusted_gross_income: 180_000 + filing_status: JOINT + households: + household: + members: [parent1, parent2, child1] + state_code: CT output: + # 1 * 250 = 250; 180,000 below joint threshold of 200,000 -> no reduction. ct_child_tax_rebate: 250 -- name: Head of household with $170,000 AGI and 5 dependents +- name: Head of household with $165,000 federal AGI and 5 dependent children + period: 2021 + input: + people: + parent: + age: 45 + is_tax_unit_dependent: false + child1: + age: 15 + is_tax_unit_dependent: true + child2: + age: 13 + is_tax_unit_dependent: true + child3: + age: 11 + is_tax_unit_dependent: true + child4: + age: 9 + is_tax_unit_dependent: true + child5: + age: 7 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2, child3, child4, child5] + adjusted_gross_income: 165_000 + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [parent, child1, child2, child3, child4, child5] + state_code: CT + output: + # 5 children capped at 3 -> 750; excess 5,000 over 160,000 HoH threshold + # -> ceil(5,000/1,000) = 5 increments -> reduction share min(5 * 0.1, 1) = 0.5; + # 750 * (1 - 0.5) = 375. + ct_child_tax_rebate: 375 + +- name: Only dependent is age 21 - no eligible child (age gate) + period: 2021 + input: + people: + parent: + age: 50 + is_tax_unit_dependent: false + dependent: + age: 21 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, dependent] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [parent, dependent] + state_code: CT + output: + # Sole dependent exceeds the age 18 limit -> 0 eligible children -> rebate 0. + ct_child_tax_rebate: 0 + +- name: Mixed minor and adult dependents - only the minor counts + period: 2021 + input: + people: + parent: + age: 50 + is_tax_unit_dependent: false + minor: + age: 10 + is_tax_unit_dependent: true + adult_dependent: + age: 21 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, minor, adult_dependent] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [parent, minor, adult_dependent] + state_code: CT + output: + # Only the age 10 dependent qualifies (age <= 18) -> 1 * 250 = 250, not 500. + ct_child_tax_rebate: 250 + +- name: Federal AGI above threshold while CT AGI below it - federal phase-out applies + period: 2021 + input: + people: + parent: + age: 40 + is_tax_unit_dependent: false + child1: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + adjusted_gross_income: 130_000 + ct_agi: 90_000 + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_code: CT + output: + # Phase-out reads federal AGI (130,000), not ct_agi (90,000). 1 * 250 = 250; + # excess 30,000 over 100,000 -> ceil(30,000/1,000) = 30 increments + # -> reduction share min(30 * 0.1, 1) capped at 1.0; 250 * (1 - 1) = 0. + ct_child_tax_rebate: 0 + +- name: Sunset - 2023 tax unit with a qualifying child receives no rebate + period: 2023 + input: + people: + parent: + age: 40 + is_tax_unit_dependent: false + child1: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_code: CT + output: + # amount and child_cap sunset to 0 at 2022-01-01 -> one-time 2021 rebate only. + ct_child_tax_rebate: 0 + +- name: Single at threshold plus exactly $10,000 - fully phased out + period: 2021 + input: + people: + parent: + age: 40 + is_tax_unit_dependent: false + child1: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + adjusted_gross_income: 110_000 + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_code: CT + output: + # 1 * 250 = 250; excess 10,000 over 100,000 -> ceil(10,000/1,000) = 10 increments + # -> reduction share min(10 * 0.1, 1) = 1.0; 250 * (1 - 1) = 0. + ct_child_tax_rebate: 0 + +- name: Single in the partial phase-out band at threshold plus $9,000 + period: 2021 + input: + people: + parent: + age: 40 + is_tax_unit_dependent: false + child1: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + adjusted_gross_income: 109_000 + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_code: CT + output: + # 1 * 250 = 250; excess 9,000 over 100,000 -> ceil(9,000/1,000) = 9 increments + # -> reduction share min(9 * 0.1, 1) = 0.9; 250 * (1 - 0.9) = 25. + ct_child_tax_rebate: 25 + +- name: 2021 rebate flows through the refundable credits aggregate + period: 2021 + input: + people: + parent: + age: 40 + is_tax_unit_dependent: false + child1: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_code: CT + output: + # 1 * 250 = 250, AGI 50,000 below 100,000 threshold -> no reduction. + ct_child_tax_rebate: 250 + # No earned income -> ct_eitc 0, so the refundable aggregate equals the rebate. + ct_eitc: 0 + ct_refundable_credits: 250 + +- name: 2022 tax unit with a qualifying child receives no rebate (sunset) period: 2022 input: - state_code: CT - ct_agi: 165_000 - filing_status: HEAD_OF_HOUSEHOLD - tax_unit_count_dependents: 5 + people: + parent: + age: 40 + is_tax_unit_dependent: false + child1: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_code: CT + output: + # amount and child_cap sunset to 0 at 2022-01-01 -> rebate 0. The list-drop at + # 2022-01-01 is not observable here (the rebate is already 0 from the sunset); + # ct_refundable_credits below equals ct_eitc alone, with the rebate contributing 0. + ct_child_tax_rebate: 0 + ct_eitc: 0 + ct_refundable_credits: 0 + +# --------------------------------------------------------------------------- +# A2 - phase-out boundary (locks np.ceil "or fraction thereof") +# --------------------------------------------------------------------------- + +- name: Case 13, SINGLE $1 over threshold takes one full increment. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 100_001 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: CT + output: + # 1 * 250 = 250; excess 1 over 100,000 -> ceil(1/1,000) = 1 increment + # -> reduction share min(1 * 0.1, 1) = 0.1; 250 * (1 - 0.1) = 225. + ct_child_tax_rebate: 225 + absolute_error_margin: 0.01 + +- name: Case 14, SINGLE exactly at threshold takes no reduction. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 100_000 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: CT + output: + # 1 * 250 = 250; excess max(100,000 - 100,000, 0) = 0 -> no increments; + # reduction only applies to AGI OVER the threshold. 250 * (1 - 0) = 250. + ct_child_tax_rebate: 250 + absolute_error_margin: 0.01 + +- name: Case 15, SINGLE mid-fraction rounds up to one increment. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 10 + is_tax_unit_dependent: true + person3: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + adjusted_gross_income: 100_500 + filing_status: SINGLE + households: + household: + members: [person1, person2, person3] + state_code: CT + output: + # 2 * 250 = 500; excess 500 over 100,000 -> ceil(500/1,000) = 1 increment + # -> reduction share min(1 * 0.1, 1) = 0.1; 500 * (1 - 0.1) = 450. + ct_child_tax_rebate: 450 + absolute_error_margin: 0.01 + +# --------------------------------------------------------------------------- +# A3 - age + dependency boundary +# --------------------------------------------------------------------------- + +- name: Case 16, dependent aged exactly 18 counts. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 18 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: CT output: + # age 18 <= age_limit 18 and dependent -> 1 eligible child; AGI below + # threshold -> 1 * 250 = 250. ct_child_tax_rebate: 250 + absolute_error_margin: 0.01 + +- name: Case 17, dependent aged 19 does not count. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 19 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: CT + output: + # age 19 > age_limit 18 -> 0 eligible children -> rebate 0. + ct_child_tax_rebate: 0 + absolute_error_margin: 0.01 + +- name: Case 18, non-dependent minor does not count (& dependent conjunction). + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 17 + is_tax_unit_dependent: false + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: CT + output: + # age 17 <= 18 but is_tax_unit_dependent false -> fails the & dependent + # conjunction -> 0 eligible children -> rebate 0. + ct_child_tax_rebate: 0 + absolute_error_margin: 0.01 + +# --------------------------------------------------------------------------- +# A4 - filing status thresholds + refundable aggregate +# --------------------------------------------------------------------------- + +- name: Case 19, SEPARATE $1 under 100,000 threshold takes no reduction. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 99_999 + filing_status: SEPARATE + households: + household: + members: [person1, person2] + state_code: CT + output: + # SEPARATE threshold 100,000; 99,999 below -> no reduction -> 1 * 250 = 250. + ct_child_tax_rebate: 250 + absolute_error_margin: 0.01 + +- name: Case 20, SEPARATE $1 over 100,000 threshold takes one increment. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 100_001 + filing_status: SEPARATE + households: + household: + members: [person1, person2] + state_code: CT + output: + # SEPARATE threshold 100,000; excess 1 -> ceil(1/1,000) = 1 increment + # -> 250 * (1 - 0.1) = 225. Pins the SEPARATE threshold at 100,000. + ct_child_tax_rebate: 225 + absolute_error_margin: 0.01 + +- name: Case 21, SURVIVING_SPOUSE $1 under 200,000 threshold takes no reduction. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 199_999 + filing_status: SURVIVING_SPOUSE + households: + household: + members: [person1, person2] + state_code: CT + output: + # SURVIVING_SPOUSE threshold 200,000; 199,999 below -> no reduction + # -> 1 * 250 = 250. + ct_child_tax_rebate: 250 + absolute_error_margin: 0.01 + +- name: Case 22, SURVIVING_SPOUSE $1 over 200,000 threshold takes one increment. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 200_001 + filing_status: SURVIVING_SPOUSE + households: + household: + members: [person1, person2] + state_code: CT + output: + # SURVIVING_SPOUSE threshold 200,000; excess 1 -> ceil(1/1,000) = 1 increment + # -> 250 * (1 - 0.1) = 225. Pins the SURVIVING_SPOUSE threshold at 200,000. + ct_child_tax_rebate: 225 + absolute_error_margin: 0.01 + +- name: Case 23, JOINT $1 over 200,000 threshold pins the JOINT threshold. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 38 + is_tax_unit_dependent: false + person3: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + adjusted_gross_income: 200_001 + filing_status: JOINT + households: + household: + members: [person1, person2, person3] + state_code: CT + output: + # JOINT threshold 200,000; excess 1 -> ceil(1/1,000) = 1 increment + # -> 250 * (1 - 0.1) = 225. Would change if the JOINT threshold moved. + ct_child_tax_rebate: 225 + absolute_error_margin: 0.01 + +- name: Case 24, HEAD_OF_HOUSEHOLD headline maximum rebate of 750. + period: 2021 + input: + people: + person1: + age: 45 + is_tax_unit_dependent: false + person2: + age: 15 + is_tax_unit_dependent: true + person3: + age: 13 + is_tax_unit_dependent: true + person4: + age: 11 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3, person4] + adjusted_gross_income: 160_000 + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [person1, person2, person3, person4] + state_code: CT + output: + # 3 children capped at 3 -> 750; AGI exactly at HoH threshold 160,000 + # -> excess 0 -> no reduction -> 750. Headline statutory maximum. + ct_child_tax_rebate: 750 + absolute_error_margin: 0.01 + +- name: Case 25, positive EITC and rebate flow through the refundable aggregate. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 50_000 + eitc: 2_000 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: CT + output: + # Rebate: 1 * 250 = 250, AGI 50,000 below 100,000 -> no reduction. + ct_child_tax_rebate: 250 + # ct_eitc = federal eitc 2,000 * 2021 match 0.305 = 610 (nonzero, unlike the + # zero-earned-income rebate-only case above where ct_eitc is 0). + ct_eitc: 610 + # Refundable aggregate = ct_eitc 610 + ct_child_tax_rebate 250 = 860. + ct_refundable_credits: 860 + absolute_error_margin: 0.01 + +# --------------------------------------------------------------------------- +# S8 - polish: newborn, negative AGI, non-CT, 2020 phantom-rebate guard +# --------------------------------------------------------------------------- + +- name: Case 26, newborn age 0 dependent counts. + period: 2021 + input: + people: + person1: + age: 30 + is_tax_unit_dependent: false + person2: + age: 0 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 40_000 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: CT + output: + # age 0 <= 18 and dependent -> 1 eligible child; AGI below threshold + # -> 1 * 250 = 250. + ct_child_tax_rebate: 250 + absolute_error_margin: 0.01 + +- name: Case 27, negative AGI floors the excess to zero (full rebate). + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: -20_000 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: CT + output: + # excess = max(-20,000 - 100,000, 0) = 0 -> no reduction -> 1 * 250 = 250. + ct_child_tax_rebate: 250 + absolute_error_margin: 0.01 + +- name: Case 28, non-CT household receives no rebate (defined_for CT). + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: NY + output: + # defined_for StateCode.CT -> non-CT household gets 0 regardless of children. + ct_child_tax_rebate: 0 + absolute_error_margin: 0.01 + +- name: Case 29, 2020 phantom-rebate regression guard (C1 backdating fix). + period: 2020 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 50_000 + filing_status: SINGLE + households: + household: + members: [person1, person2] + state_code: CT + output: + # Explicit 2015-01-01: 0 in amount.yaml/child_cap.yaml blocks backdating from + # phantoming the 2021 value into 2020 -> rebate 0 despite a qualifying child. + ct_child_tax_rebate: 0 + absolute_error_margin: 0.01 + +# --------------------------------------------------------------------------- +# S5 - JOINT exactly at threshold takes no reduction (full rebate) +# --------------------------------------------------------------------------- + +- name: Case 30, JOINT exactly at 200,000 threshold takes no reduction. + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 38 + is_tax_unit_dependent: false + person3: + age: 10 + is_tax_unit_dependent: true + person4: + age: 8 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3, person4] + adjusted_gross_income: 200_000 + filing_status: JOINT + households: + household: + members: [person1, person2, person3, person4] + state_code: CT + output: + # JOINT threshold 200,000; excess max(200,000 - 200,000, 0) = 0 -> no increments; + # reduction only applies to AGI OVER the threshold. 2 * 250 = 500 * (1 - 0) = 500. + # Mirrors the SINGLE at-threshold precedent (Case 14) for the JOINT threshold. + ct_child_tax_rebate: 500 + absolute_error_margin: 0.01 + +# --------------------------------------------------------------------------- +# S6 - child-cap boundary at exactly 4 children (first count over the cap) +# --------------------------------------------------------------------------- + +- name: Case 31, exactly 4 children caps to 3 (750, no phase-out). + period: 2021 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: false + person2: + age: 12 + is_tax_unit_dependent: true + person3: + age: 10 + is_tax_unit_dependent: true + person4: + age: 8 + is_tax_unit_dependent: true + person5: + age: 6 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3, person4, person5] + adjusted_gross_income: 50_000 + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: CT + output: + # 4 eligible children capped at child_cap 3 -> 3 * 250 = 750; AGI 50,000 below + # HoH threshold 160,000 -> excess 0 -> no reduction -> 750. Pins the child cap. + ct_child_tax_rebate: 750 + absolute_error_margin: 0.01 diff --git a/policyengine_us/variables/gov/states/ct/tax/income/rebate/ct_child_tax_rebate.py b/policyengine_us/variables/gov/states/ct/tax/income/rebate/ct_child_tax_rebate.py index e9d043d1d6d..b9c8ceefdcf 100644 --- a/policyengine_us/variables/gov/states/ct/tax/income/rebate/ct_child_tax_rebate.py +++ b/policyengine_us/variables/gov/states/ct/tax/income/rebate/ct_child_tax_rebate.py @@ -7,23 +7,44 @@ class ct_child_tax_rebate(Variable): label = "Connecticut child tax rebate" unit = USD definition_period = YEAR + # Assumes 100% take-up: PA 22-118 Sec. 411(b)(3) required an electronic + # application to DRS by 2022-07-31 (TSSB 2022-5 Q6), which PolicyEngine does + # not model, so the 2021 aggregate is an upper bound. DRS's offset of the + # rebate against outstanding tax debts (Q19) is likewise unmodeled. defined_for = StateCode.CT + reference = ( + "https://cga.ct.gov/2022/ACT/PA/PDF/2022PA-00118-R00HB-05506-PA.PDF#page=548", + "https://portal.ct.gov/-/media/drs/publications/tssb/2022/tssb-2022-5.pdf#page=1", + ) def formula(tax_unit, period, parameters): - income = tax_unit("ct_agi", period) + # Statutory domicile test is 2022 domicile at application time + # (Sec. 411(a)(2); TSSB 2022-5 Q10/Q18), approximated here by 2021 CT + # residence via defined_for, matching the RI analogue. + income = tax_unit("adjusted_gross_income", period) filing_status = tax_unit("filing_status", period) p = parameters(period).gov.states.ct.tax.income.rebate reduction_start = p.reduction.start[filing_status] - increment = p.reduction.increment - reduction_per_increment = p.reduction.rate * increment - excess = max_(income - reduction_start, 0) - increments = np.ceil(excess / increment) - total_reduction_amount = increments * reduction_per_increment - - count_dependents = tax_unit("tax_unit_count_dependents", period) - capped_children = min_(count_dependents, p.child_cap) + person = tax_unit.members + # Age is measured at the period, not "as of December 31, 2021" + # (Sec. 411(a)(1)); PE's age has no within-year date (PE-wide limitation). + age = person("age", period) + # is_tax_unit_dependent (structurally ~head & ~spouse) proxies + # Sec. 411(b)(1)'s requirement that the child be validly claimed as a + # dependent on the 2021 federal return. + dependent = person("is_tax_unit_dependent", period) + eligible_child = (age <= p.age_limit) & dependent + count_children = tax_unit.sum(eligible_child) + capped_children = min_(count_children, p.child_cap) total_rebate = capped_children * p.amount - return max_(total_rebate - total_reduction_amount, 0) + excess = max_(income - reduction_start, 0) + increments = np.ceil(excess / p.reduction.increment) + # At exactly AGI $110,000 a single filer hits 10 increments x 10% = 100% + # reduction = $0, which Sec. 411(b)(2) controls; TSSB 2022-5 Q12's "less + # than or equal to $110,000" wording reads loosely but is not a discrepancy. + reduction_share = min_(increments * p.reduction.rate, 1) + + return total_rebate * (1 - reduction_share)