From cfe49cd6e7576ba15ceac2cffe84878b9150d879 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 1 Oct 2026 15:46:10 -0400 Subject: [PATCH 1/5] Order the residential clean energy credit after the CTC (Schedule 8812 Credit Limit Worksheets A and B) The CTC's tax-liability limit subtracted every other non-refundable credit, and the residential clean energy credit (26 U.S.C. 25D) was ordered before the CTC and before the 25C, elderly and clean vehicle credits. Section 25D(c) orders it last. Follow the IRS worksheets: - Credit Limit Worksheet A line 2 is a parameter (gov.irs.credits.ctc_tax_liability_limit.preceding_credits); line 3 is the new ctc_tax_liability_after_preceding_credits. - Worksheet A line 4 / Worksheet B line 15 (subsequent_credits) reduces the limit only when Worksheet B applies (ctc_credit_limit_worksheet_b_applies). - Worksheet B line 14 is ctc_non_refundable_minimum, the CTC that precedes the residential clean energy credit on the Form 5695 line 14 worksheet. - The residential clean energy credit's limit subtracts every other credit; the 25C, elderly and clean vehicle lists no longer subtract it. - Colorado's 2022-2023 replica reads Worksheet A line 3 (DR 0104CN 4-6). - The Alabama 2020-IRC pin, CRFB and AFA reforms update the new lists. Adds YAML tests (hand-worked 2025 households) and a statutory differential plus Hypothesis property tests; adds hypothesis to the dev extra. Co-Authored-By: Claude Opus 5.5 --- .../ctc-limit-worksheet-a-credits.fixed.md | 1 + .../clean_vehicle/new/preceding_credits.yaml | 8 +- .../clean_vehicle/used/preceding_credits.yaml | 8 +- .../credits/ctc_tax_liability_limit/README.md | 1 + .../preceding_credits.yaml | 49 ++++ .../subsequent_credits.yaml | 21 ++ .../preceding_credits.yaml | 8 +- .../preceding_credits.yaml | 8 +- .../preceding_credits.yaml | 31 ++- .../afa/afa_other_dependent_credit.py | 39 ++- .../reforms/crfb/non_refundable_ss_credit.py | 22 ++ .../credit_limit_worksheets_integration.yaml | 185 +++++++++++++ .../ctc_credit_limit_worksheet_b_applies.yaml | 35 +++ .../ctc_limiting_tax_liability.yaml | 28 ++ .../ctc_non_refundable_minimum.yaml | 26 ++ ...tax_liability_after_preceding_credits.yaml | 52 ++++ ...tial_clean_energy_credit_credit_limit.yaml | 58 +++++ .../al_federal_income_tax_deduction.yaml | 5 +- .../federal_ctc/co_non_refundable_ctc.yaml | 18 +- .../tests/test_ctc_credit_limit_worksheets.py | 243 ++++++++++++++++++ .../tests/test_ctc_itemizing_branch_cycle.py | 5 +- policyengine_us/tools/pinned_tbs.py | 29 ++- .../ctc_credit_limit_worksheet_b_applies.py | 34 +++ .../refundable/ctc_limiting_tax_liability.py | 54 ++-- .../refundable/ctc_non_refundable_minimum.py | 26 ++ ...c_tax_liability_after_preceding_credits.py | 46 ++++ ...ential_clean_energy_credit_credit_limit.py | 36 ++- .../ctc/federal_ctc/co_non_refundable_ctc.py | 4 +- pyproject.toml | 1 + uv.lock | 90 +++++++ 30 files changed, 1100 insertions(+), 71 deletions(-) create mode 100644 changelog.d/ctc-limit-worksheet-a-credits.fixed.md create mode 100644 policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/README.md create mode 100644 policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml create mode 100644 policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/credit_limit_worksheets_integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_tax_liability_after_preceding_credits.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/residential_clean_energy/residential_clean_energy_credit_credit_limit.yaml create mode 100644 policyengine_us/tests/test_ctc_credit_limit_worksheets.py create mode 100644 policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py create mode 100644 policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.py create mode 100644 policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_tax_liability_after_preceding_credits.py diff --git a/changelog.d/ctc-limit-worksheet-a-credits.fixed.md b/changelog.d/ctc-limit-worksheet-a-credits.fixed.md new file mode 100644 index 00000000000..673a96559c5 --- /dev/null +++ b/changelog.d/ctc-limit-worksheet-a-credits.fixed.md @@ -0,0 +1 @@ +Order the residential clean energy credit after the Child Tax Credit, as Schedule 8812 Credit Limit Worksheets A and B and Form 5695 do: the CTC's tax-liability limit subtracts that credit only when Credit Limit Worksheet B applies, the credit's own limit subtracts every other non-refundable credit (including the CTC that cannot be refunded), and it no longer precedes the energy efficient home improvement, elderly or disabled, and clean vehicle credits. diff --git a/policyengine_us/parameters/gov/irs/credits/clean_vehicle/new/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/clean_vehicle/new/preceding_credits.yaml index bf7b30abe53..91bee8a4966 100644 --- a/policyengine_us/parameters/gov/irs/credits/clean_vehicle/new/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/clean_vehicle/new/preceding_credits.yaml @@ -1,12 +1,12 @@ description: Non-refundable tax credits which precede the New Clean Vehicle Credit. values: + # Section 25D(c) orders the residential clean energy credit after these. 2013-01-01: - - foreign_tax_credit + - foreign_tax_credit - cdcc - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - - residential_clean_energy_credit - energy_efficient_home_improvement_credit - elderly_disabled_credit @@ -17,3 +17,7 @@ metadata: reference: - title: IRS Schedule 3, Additional Credits and Payments href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c + - title: 2025 Instructions for Form 8936, line 11 + href: https://www.irs.gov/pub/irs-pdf/i8936.pdf#page=6 diff --git a/policyengine_us/parameters/gov/irs/credits/clean_vehicle/used/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/clean_vehicle/used/preceding_credits.yaml index 262014a84e2..12e0f8b7348 100644 --- a/policyengine_us/parameters/gov/irs/credits/clean_vehicle/used/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/clean_vehicle/used/preceding_credits.yaml @@ -1,12 +1,12 @@ description: Non-refundable tax credits which precede the Used Clean Vehicle Credit. values: + # Section 25D(c) orders the residential clean energy credit after these. 2013-01-01: - - foreign_tax_credit + - foreign_tax_credit - cdcc - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - - residential_clean_energy_credit - energy_efficient_home_improvement_credit - elderly_disabled_credit - new_clean_vehicle_credit @@ -18,3 +18,7 @@ metadata: reference: - title: IRS Schedule 3, Additional Credits and Payments href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c + - title: 2025 Instructions for Form 8936, line 16 + href: https://www.irs.gov/pub/irs-pdf/i8936.pdf#page=6 diff --git a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/README.md b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/README.md new file mode 100644 index 00000000000..c14cd8f355d --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/README.md @@ -0,0 +1 @@ +# Child Tax Credit tax liability limit diff --git a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml new file mode 100644 index 00000000000..18180b6e2c9 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml @@ -0,0 +1,49 @@ +description: The IRS subtracts these non-refundable credits from income tax before credits to find the tax liability that limits the non-refundable Child Tax Credit. +values: + # The foreign tax credit also precedes the CTC before 2022, but the model + # applies it against regular tax only from 2022 (see non_refundable.yaml). + 2013-01-01: + - cdcc + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + - savers_credit + - energy_efficient_home_improvement_credit + - elderly_disabled_credit + # The Child and Dependent Care Credit was refundable for 2021 only. + 2021-01-01: + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + - savers_credit + - energy_efficient_home_improvement_credit + - elderly_disabled_credit + - new_clean_vehicle_credit + 2022-01-01: + - foreign_tax_credit # Schedule 3, line 1 + - cdcc # Schedule 3, line 2 + - non_refundable_american_opportunity_credit # Schedule 3, line 3 + - lifetime_learning_credit # Schedule 3, line 3 + - savers_credit # Schedule 3, line 4 + - energy_efficient_home_improvement_credit # Schedule 3, line 5b + - elderly_disabled_credit # Schedule 3, line 6d + - new_clean_vehicle_credit # Schedule 3, line 6f + - used_clean_vehicle_credit # Schedule 3, line 6m + # The residential clean energy credit (Schedule 3, line 5a) is not listed: + # section 25D(c) orders it after every other credit in subpart A, including + # the Child Tax Credit. See subsequent_credits.yaml for the case in which it + # still reduces this limit. + +metadata: + unit: list + period: year + label: Non-refundable credits preceding the Child Tax Credit + reference: + - title: 2025 Instructions for Schedule 8812 (Form 1040), Credit Limit Worksheet A, line 2 + href: https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4 + - title: 2022 Instructions for Schedule 8812 (Form 1040), Credit Limit Worksheet A, line 2 + href: https://www.irs.gov/pub/irs-prior/i1040s8--2022.pdf#page=4 + - title: 2020 Publication 972, Child Tax Credit and Credit for Other Dependents Worksheet, line 10 + href: https://www.irs.gov/pub/irs-prior/p972--2020.pdf#page=7 + - title: 26 U.S. Code § 26(a) - Limitation based on amount of tax + href: https://www.law.cornell.edu/uscode/text/26/26#a + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c diff --git a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml new file mode 100644 index 00000000000..8eeb1ab231c --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml @@ -0,0 +1,21 @@ +description: The IRS subtracts these non-refundable credits, which are ordered after the Child Tax Credit, from the tax liability that limits the non-refundable Child Tax Credit when the filer completes Credit Limit Worksheet B. +values: + # The adoption credit (Schedule 3, line 6c), mortgage interest credit + # (line 6g) and District of Columbia first-time homebuyer credit (line 6h) + # also belong here but are not modeled. + 2013-01-01: + - residential_clean_energy_credit # Schedule 3, line 5a + +metadata: + unit: list + period: year + label: Non-refundable credits following the Child Tax Credit + reference: + - title: 2025 Instructions for Schedule 8812 (Form 1040), Credit Limit Worksheet A, line 4, and Credit Limit Worksheet B, line 15 + href: https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=6 + - title: 2020 Publication 972, Line 11 Worksheet, line 13 + href: https://www.irs.gov/pub/irs-prior/p972--2020.pdf#page=9 + - title: 26 U.S. Code § 24(d)(1)(B) - Portion of credit refundable + href: https://www.law.cornell.edu/uscode/text/26/24#d_1_B + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c diff --git a/policyengine_us/parameters/gov/irs/credits/elderly_or_disabled/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/elderly_or_disabled/preceding_credits.yaml index 7f99fcce934..4bf6a376585 100644 --- a/policyengine_us/parameters/gov/irs/credits/elderly_or_disabled/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/elderly_or_disabled/preceding_credits.yaml @@ -1,12 +1,12 @@ description: Non-refundable tax credits which precede the Elderly or Disabled Credit. values: + # Section 25D(c) orders the residential clean energy credit after these. 2013-01-01: - - foreign_tax_credit + - foreign_tax_credit - cdcc - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - - residential_clean_energy_credit - energy_efficient_home_improvement_credit metadata: @@ -16,3 +16,7 @@ metadata: reference: - title: IRS Schedule 3, Additional Credits and Payments href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c + - title: 2025 Instructions for Schedule R (Form 1040), Credit Limit Worksheet, line 2 + href: https://www.irs.gov/pub/irs-pdf/i1040sr.pdf#page=4 diff --git a/policyengine_us/parameters/gov/irs/credits/energy_efficient_home_improvement/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/energy_efficient_home_improvement/preceding_credits.yaml index 5bc4cab8012..5cd5596097c 100644 --- a/policyengine_us/parameters/gov/irs/credits/energy_efficient_home_improvement/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/energy_efficient_home_improvement/preceding_credits.yaml @@ -1,12 +1,12 @@ description: Non-refundable tax credits which precede the Energy Efficient Home Improvement Credit. values: + # Section 25D(c) orders the residential clean energy credit after these. 2013-01-01: - - foreign_tax_credit + - foreign_tax_credit - cdcc - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - - residential_clean_energy_credit metadata: unit: list @@ -15,3 +15,7 @@ metadata: reference: - title: IRS Schedule 3, Additional Credits and Payments href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c + - title: 2025 Instructions for Form 5695, Energy Efficient Home Improvement Credit Limit Worksheet, line 2 + href: https://www.irs.gov/pub/irs-pdf/i5695.pdf#page=7 diff --git a/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml index 3a8035d05e5..992ac98b979 100644 --- a/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml @@ -1,16 +1,39 @@ -description: Non-refundable tax credits which precede the Residential Clean Energy Credit. +description: Non-refundable tax credits which precede the Residential Clean Energy Credit. Section 25D(c) orders this credit after every other credit in subpart A; the Child Tax Credit's share is handled in residential_clean_energy_credit_credit_limit. values: 2013-01-01: - - foreign_tax_credit + - foreign_tax_credit - cdcc - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit + - energy_efficient_home_improvement_credit + - elderly_disabled_credit + # The Child and Dependent Care Credit was refundable for 2021 only. + 2021-01-01: + - foreign_tax_credit + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + - savers_credit + - energy_efficient_home_improvement_credit + - elderly_disabled_credit + - new_clean_vehicle_credit + 2022-01-01: + - foreign_tax_credit # Schedule 3, line 1 + - cdcc # Schedule 3, line 2 + - non_refundable_american_opportunity_credit # Schedule 3, line 3 + - lifetime_learning_credit # Schedule 3, line 3 + - savers_credit # Schedule 3, line 4 + - energy_efficient_home_improvement_credit # Form 5695, line 32 + - elderly_disabled_credit # Schedule 3, line 6d + - new_clean_vehicle_credit # Schedule 3, line 6f + - used_clean_vehicle_credit # Schedule 3, line 6m metadata: unit: list period: year label: Non-refundable tax credits preceding the Residential Clean Energy Credit reference: - - title: IRS Schedule 3, Additional Credits and Payments - href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c + - title: 2025 Instructions for Form 5695, Residential Clean Energy Credit Limit Worksheet, line 2 + href: https://www.irs.gov/pub/irs-pdf/i5695.pdf#page=5 diff --git a/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py b/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py index 225697a4d0d..b858c81db6a 100644 --- a/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py +++ b/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py @@ -199,22 +199,35 @@ def formula(person, period, parameters): return pre_baby_bonus_amount + baby_bonus_amount def modify_parameters(parameters): - parameters.gov.irs.credits.non_refundable.update( + credits = parameters.gov.irs.credits + credits_before_residential_clean_energy = [ + "cdcc", + "elderly_disabled_credit", + "non_refundable_american_opportunity_credit", + "lifetime_learning_credit", + "savers_credit", + "energy_efficient_home_improvement_credit", + "new_clean_vehicle_credit", + "used_clean_vehicle_credit", + "other_dependent_credit", + ] + credits.non_refundable.update( start=instant("2025-01-01"), stop=instant("2039-12-31"), - value=[ - "cdcc", - "elderly_disabled_credit", - "non_refundable_american_opportunity_credit", - "lifetime_learning_credit", - "savers_credit", - "residential_clean_energy_credit", - "energy_efficient_home_improvement_credit", - "new_clean_vehicle_credit", - "used_clean_vehicle_credit", - "other_dependent_credit", - ], + value=credits_before_residential_clean_energy + + ["residential_clean_energy_credit"], ) + # 26 U.S.C. 25D(c) orders the residential clean energy credit after + # every other credit, including the separate other dependent credit. + for preceding_credits in ( + credits.ctc_tax_liability_limit.preceding_credits, + credits.residential_clean_energy.preceding_credits, + ): + preceding_credits.update( + start=instant("2025-01-01"), + stop=instant("2039-12-31"), + value=credits_before_residential_clean_energy, + ) parameters.gov.irs.credits.ctc.refundable.fully_refundable.update( start=instant("2025-01-01"), stop=instant("2039-12-31"), diff --git a/policyengine_us/reforms/crfb/non_refundable_ss_credit.py b/policyengine_us/reforms/crfb/non_refundable_ss_credit.py index 66753340c08..959b2108285 100644 --- a/policyengine_us/reforms/crfb/non_refundable_ss_credit.py +++ b/policyengine_us/reforms/crfb/non_refundable_ss_credit.py @@ -101,6 +101,28 @@ def modify_parameters(parameters): "ss_credit", ], ) + # The credit precedes the Child Tax Credit and, like every credit, + # the residential clean energy credit (26 U.S.C. 25D(c)). + credits = parameters.gov.irs.credits + for preceding_credits in ( + credits.ctc_tax_liability_limit.preceding_credits, + credits.residential_clean_energy.preceding_credits, + ): + preceding_credits.update( + start=instant("2026-01-01"), + value=[ + "foreign_tax_credit", + "cdcc", + "non_refundable_american_opportunity_credit", + "lifetime_learning_credit", + "savers_credit", + "energy_efficient_home_improvement_credit", + "elderly_disabled_credit", + "new_clean_vehicle_credit", + "used_clean_vehicle_credit", + "ss_credit", + ], + ) return parameters class reform(Reform): diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/credit_limit_worksheets_integration.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/credit_limit_worksheets_integration.yaml new file mode 100644 index 00000000000..f107dc98300 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/credit_limit_worksheets_integration.yaml @@ -0,0 +1,185 @@ +# Schedule 8812 Credit Limit Worksheets A and B with the residential clean +# energy credit (Form 5695, Part I), following the 2025 instructions: +# https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4 (Schedule 8812) +# https://www.irs.gov/pub/irs-pdf/i5695.pdf#page=5 (Form 5695, line 14) +# +# Every case is a married couple in Texas with $60,000 of wages in 2025: +# Form 1040, line 18: taxable income 60,000 - 31,500 = 28,500; +# tax = 10% x 23,850 + 12% x 4,650 = 2,943. +# Each qualifying child carries a $2,200 CTC, of which up to $1,700 can be +# refunded; the earned income phase-in is 15% x (60,000 - 2,500) = 8,625. + +- name: Two children; the residential clean energy credit is limited and displaces the refundable CTC. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + spouse: + age: 40 + child1: + age: 5 + child2: + age: 8 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + solar_electric_property_expenditures: 10_000 + households: + household: + members: [head, spouse, child1, child2] + state_code: TX + output: + income_tax_before_credits: 2_943 + # Credit Limit Worksheet A, lines 1-3: no line 2 credits. + ctc_tax_liability_after_preceding_credits: 2_943 + # Credit Limit Worksheet B: line 1 = 4,400; line 2 = 3,400; line 12 = 8,625; + # line 13 = min(3,400, 8,625) = 3,400; line 14 = 4,400 - 3,400 = 1,000. + ctc_credit_limit_worksheet_b_applies: true + ctc_non_refundable_minimum: 1_000 + # Form 5695, line 14 worksheet: 2,943 - 1,000 = 1,943. The credit before + # the limit is 30% x 10,000 = 3,000, so 1,943 is allowed and 1,057 carries + # forward. + residential_clean_energy_credit_potential: 3_000 + residential_clean_energy_credit_credit_limit: 1_943 + residential_clean_energy_credit: 1_943 + # Worksheet B, line 15 = 1,943 = Worksheet A, line 4; line 5 = 2,943 - 1,943. + ctc_limiting_tax_liability: 1_000 + # Schedule 8812, line 14 = min(4,400, 1,000); line 27 = min(4,400 - 1,000, + # 3,400, 8,625). + non_refundable_ctc: 1_000 + refundable_ctc: 3_400 + income_tax_capped_non_refundable_credits: 2_943 + income_tax_before_refundable_credits: 0 + +- name: Two children; the residential clean energy credit is not limited but still reduces the CTC limit. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + spouse: + age: 40 + child1: + age: 5 + child2: + age: 8 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + solar_electric_property_expenditures: 2_000 + households: + household: + members: [head, spouse, child1, child2] + state_code: TX + output: + # Form 5695: the credit of 30% x 2,000 = 600 fits under the 1,943 limit. + residential_clean_energy_credit_credit_limit: 1_943 + residential_clean_energy_credit: 600 + # Worksheet A, line 5 = 2,943 - 600. + ctc_limiting_tax_liability: 2_343 + # Schedule 8812, line 14 = 2,343; line 27 = min(4,400 - 2,343, 3,400, 8,625). + # Without the residential clean energy credit, line 27 would be 1,457. + non_refundable_ctc: 2_343 + refundable_ctc: 2_057 + income_tax_capped_non_refundable_credits: 2_943 + +- name: Two children and no residential clean energy credit. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + spouse: + age: 40 + child1: + age: 5 + child2: + age: 8 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + households: + household: + members: [head, spouse, child1, child2] + state_code: TX + output: + ctc_limiting_tax_liability: 2_943 + # Schedule 8812, line 27 = min(4,400 - 2,943, 3,400, 8,625). + refundable_ctc: 1_457 + non_refundable_ctc: 2_943 + +- name: Credit for other dependents only; Worksheet B does not apply, so the CTC precedes the residential clean energy credit in full. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + spouse: + age: 40 + dependent: + age: 17 # Too old for the CTC; qualifies for the $500 credit for other dependents. + tax_units: + tax_unit: + members: [head, spouse, dependent] + solar_electric_property_expenditures: 10_000 + households: + household: + members: [head, spouse, dependent] + state_code: TX + output: + income_tax_before_credits: 2_943 + ctc: 500 + ctc_credit_limit_worksheet_b_applies: false + # Worksheet A, line 4 is zero: the limit is line 3. + ctc_limiting_tax_liability: 2_943 + non_refundable_ctc: 500 + refundable_ctc: 0 + # Form 5695, line 14 worksheet: 2,943 - 500 (Form 1040, line 19) = 2,443. + residential_clean_energy_credit_credit_limit: 2_443 + residential_clean_energy_credit: 2_443 + income_tax_capped_non_refundable_credits: 2_943 + +- name: The energy efficient home improvement credit precedes both the CTC and the residential clean energy credit. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + spouse: + age: 40 + child1: + age: 5 + child2: + age: 8 + tax_units: + tax_unit: + members: [head, spouse, child1, child2] + solar_electric_property_expenditures: 10_000 + energy_efficient_home_improvement_credit_potential: 300 + households: + household: + members: [head, spouse, child1, child2] + state_code: TX + output: + # Form 5695, line 31 worksheet: nothing on Schedule 3, lines 1-4, 6d or 6l. + energy_efficient_home_improvement_credit: 300 + # Credit Limit Worksheet A, line 3 = 2,943 - 300 (Schedule 3, line 5b). + ctc_tax_liability_after_preceding_credits: 2_643 + # Form 5695, line 14 worksheet: 2,943 - 300 - 1,000 (Worksheet B, line 14). + residential_clean_energy_credit: 1_643 + # Worksheet A, line 5 = 2,643 - 1,643. + ctc_limiting_tax_liability: 1_000 + refundable_ctc: 3_400 + # 300 + 1,643 + 1,000 = 2,943. + income_tax_capped_non_refundable_credits: 2_943 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml new file mode 100644 index 00000000000..f3556ddd214 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml @@ -0,0 +1,35 @@ +- name: Applies with a qualifying child. + period: 2025 + input: + ctc_qualifying_children: 1 + output: + ctc_credit_limit_worksheet_b_applies: true + +- name: Does not apply without a qualifying child (Schedule 8812, line 4 is zero). + period: 2025 + input: + ctc_qualifying_children: 0 + output: + ctc_credit_limit_worksheet_b_applies: false + +- name: Does not apply to filers of Form 2555. + period: 2025 + input: + ctc_qualifying_children: 2 + foreign_earned_income_exclusion: 10_000 + output: + ctc_credit_limit_worksheet_b_applies: false + +- name: Does not apply in 2021, when the CTC was fully refundable. + period: 2021 + input: + ctc_qualifying_children: 2 + output: + ctc_credit_limit_worksheet_b_applies: false + +- name: Applies before 2021 too (Publication 972, Line 11 Worksheet). + period: 2020 + input: + ctc_qualifying_children: 1 + output: + ctc_credit_limit_worksheet_b_applies: true diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_limiting_tax_liability.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_limiting_tax_liability.yaml index 0478b6d0c42..ee45af5c291 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_limiting_tax_liability.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_limiting_tax_liability.yaml @@ -6,3 +6,31 @@ non_refundable_ctc: 10 output: ctc_limiting_tax_liability: 80 + +- name: Credit Limit Worksheet B applies, so the residential clean energy credit reduces the limit. + period: 2025 + input: + ctc_tax_liability_after_preceding_credits: 2_943 # Worksheet A, line 3 + ctc_credit_limit_worksheet_b_applies: true + residential_clean_energy_credit: 1_943 # Worksheet B, line 15 + output: + ctc_limiting_tax_liability: 1_000 # Worksheet A, line 5 + +- name: Without Credit Limit Worksheet B, the residential clean energy credit does not reduce the limit. + period: 2025 + input: + ctc_tax_liability_after_preceding_credits: 2_943 + ctc_credit_limit_worksheet_b_applies: false + residential_clean_energy_credit: 1_943 + output: + ctc_limiting_tax_liability: 2_943 + +- name: A reform that empties the list of subsequent credits leaves the limit at line 3. + period: 2025 + input: + gov.irs.credits.ctc_tax_liability_limit.subsequent_credits: [] + ctc_tax_liability_after_preceding_credits: 2_943 + ctc_credit_limit_worksheet_b_applies: true + residential_clean_energy_credit: 1_943 + output: + ctc_limiting_tax_liability: 2_943 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.yaml new file mode 100644 index 00000000000..cab306c78a8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.yaml @@ -0,0 +1,26 @@ +- name: The refundable maximum binds. + period: 2025 + input: + ctc: 4_400 # Credit Limit Worksheet B, line 1 + ctc_refundable_maximum: 3_400 # Line 2: two children at $1,700 + ctc_phase_in: 8_625 # Line 12: 15% x ($60,000 - $2,500) + output: + ctc_non_refundable_minimum: 1_000 # Line 14: 4,400 - min(3,400, 8,625) + +- name: The earned income phase-in binds. + period: 2025 + input: + ctc: 4_400 + ctc_refundable_maximum: 3_400 + ctc_phase_in: 1_000 + output: + ctc_non_refundable_minimum: 3_400 # 4,400 - min(3,400, 1,000) + +- name: Zero when the whole credit could be refunded. + period: 2025 + input: + ctc: 1_000 + ctc_refundable_maximum: 1_700 + ctc_phase_in: 5_000 + output: + ctc_non_refundable_minimum: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_tax_liability_after_preceding_credits.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_tax_liability_after_preceding_credits.yaml new file mode 100644 index 00000000000..2304ab2ae24 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_tax_liability_after_preceding_credits.yaml @@ -0,0 +1,52 @@ +- name: Subtracts the Credit Limit Worksheet A line 2 credits, but not the residential clean energy credit. + period: 2025 + input: + income_tax_before_credits: 5_000 + foreign_tax_credit: 100 # Schedule 3, line 1 + cdcc: 200 # Schedule 3, line 2 + non_refundable_american_opportunity_credit: 300 # Schedule 3, line 3 + savers_credit: 400 # Schedule 3, line 4 + energy_efficient_home_improvement_credit: 500 # Schedule 3, line 5b + elderly_disabled_credit: 600 # Schedule 3, line 6d + new_clean_vehicle_credit: 700 # Schedule 3, line 6f + used_clean_vehicle_credit: 800 # Schedule 3, line 6m + residential_clean_energy_credit: 900 # Schedule 3, line 5a: not subtracted. + output: + # 5,000 - (100 + 200 + 300 + 400 + 500 + 600 + 700 + 800) = 1,400 + ctc_tax_liability_after_preceding_credits: 1_400 + +- name: Does not fall below zero. + period: 2025 + input: + income_tax_before_credits: 1_000 + cdcc: 2_000 + output: + ctc_tax_liability_after_preceding_credits: 0 + +- name: The Child and Dependent Care Credit does not precede the CTC in 2021, when it was refundable. + period: 2021 + input: + income_tax_before_credits: 3_000 + cdcc: 1_000 + elderly_disabled_credit: 500 + output: + ctc_tax_liability_after_preceding_credits: 2_500 + +- name: The Child and Dependent Care Credit precedes the CTC in 2020. + period: 2020 + input: + income_tax_before_credits: 3_000 + cdcc: 500 + savers_credit: 200 + residential_clean_energy_credit: 1_000 + output: + ctc_tax_liability_after_preceding_credits: 2_300 + +- name: A reform that empties the list of preceding credits leaves income tax before credits. + period: 2025 + input: + gov.irs.credits.ctc_tax_liability_limit.preceding_credits: [] + income_tax_before_credits: 3_000 + cdcc: 500 + output: + ctc_tax_liability_after_preceding_credits: 3_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/residential_clean_energy/residential_clean_energy_credit_credit_limit.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/residential_clean_energy/residential_clean_energy_credit_credit_limit.yaml new file mode 100644 index 00000000000..22309321d1f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/residential_clean_energy/residential_clean_energy_credit_credit_limit.yaml @@ -0,0 +1,58 @@ +# Form 5695, Residential Clean Energy Credit Limit Worksheet (2025 instructions): +# https://www.irs.gov/pub/irs-pdf/i5695.pdf#page=5 + +- name: With Credit Limit Worksheet B, only the CTC that cannot be refunded precedes the credit. + period: 2025 + input: + income_tax_before_credits: 2_943 + ctc: 4_400 + ctc_credit_limit_worksheet_b_applies: true + ctc_non_refundable_minimum: 1_000 # Credit Limit Worksheet B, line 14 + output: + residential_clean_energy_credit_credit_limit: 1_943 + +- name: Without Credit Limit Worksheet B, the non-refundable CTC precedes the credit (Form 1040, line 19). + period: 2025 + input: + income_tax_before_credits: 2_943 + ctc: 500 + ctc_credit_limit_worksheet_b_applies: false + output: + residential_clean_energy_credit_credit_limit: 2_443 + +- name: Without Credit Limit Worksheet B, a CTC above the liability leaves nothing. + period: 2025 + input: + income_tax_before_credits: 2_943 + ctc: 4_000 + ctc_credit_limit_worksheet_b_applies: false + output: + residential_clean_energy_credit_credit_limit: 0 + +- name: Every other non-refundable credit precedes the credit. + period: 2025 + input: + income_tax_before_credits: 5_000 + foreign_tax_credit: 100 + cdcc: 200 + non_refundable_american_opportunity_credit: 300 + savers_credit: 400 + energy_efficient_home_improvement_credit: 500 + elderly_disabled_credit: 600 + new_clean_vehicle_credit: 700 + used_clean_vehicle_credit: 800 + ctc: 0 + output: + # 5,000 - (100 + 200 + 300 + 400 + 500 + 600 + 700 + 800) + residential_clean_energy_credit_credit_limit: 1_400 + +- name: In 2021, only the CTC that is not refunded precedes the credit, and the refundable CDCC does not. + period: 2021 + input: + income_tax_before_credits: 3_000 + cdcc: 1_000 + ctc: 6_500 + refundable_ctc: 6_000 + output: + # 3,000 - (6,500 - 6,000) + residential_clean_energy_credit_credit_limit: 2_500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/al_federal_income_tax_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/al_federal_income_tax_deduction.yaml index 4ccce0a8e92..c3e17edd41b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/al_federal_income_tax_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/al_federal_income_tax_deduction.yaml @@ -172,8 +172,9 @@ # (Pub. 972 p.7) uses the CDCC (Form 2441) BEFORE the CTC when computing the # CTC's tax-liability limit, and Schedule 8812 then routes the unused CTC to # the ACTC. The recompute branch pins the CDCC out of the refundable list but - # must ALSO add it to the non-refundable list read by ctc_limiting_tax_liability - # so the recomputed CDCC reduces that limit. This is the one case that makes the + # must ALSO add it to the list of credits preceding the CTC + # (gov.irs.credits.ctc_tax_liability_limit.preceding_credits) so the + # recomputed CDCC reduces that limit. This is the one case that makes the # ordering bind: line_6 floors at 0, so the extra ACTC (not offset by line_6) # flows straight into Part II and cancels the NIIT. # diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.yaml index c410b67d76f..86a2eb43f66 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.yaml @@ -3,7 +3,7 @@ input: state_code: CO co_federal_ctc_maximum: 1_000 - ctc_limiting_tax_liability: 999 + ctc_tax_liability_after_preceding_credits: 999 output: co_non_refundable_ctc: 999 @@ -12,6 +12,20 @@ input: state_code: CO co_federal_ctc_maximum: 998 - ctc_limiting_tax_liability: 1_000 + ctc_tax_liability_after_preceding_credits: 1_000 output: co_non_refundable_ctc: 998 + +- name: The Colorado replica does not subtract the residential clean energy credit. + # 2022 DR 0104CN lines 4 - 6 reproduce Schedule 8812 Credit Limit Worksheet A, + # lines 1 - 3, without Credit Limit Worksheet B. + period: 2022 + input: + state_code: CO + co_federal_ctc_maximum: 2_000 + ctc_tax_liability_after_preceding_credits: 1_500 + ctc_credit_limit_worksheet_b_applies: true + residential_clean_energy_credit: 1_000 + output: + ctc_limiting_tax_liability: 500 + co_non_refundable_ctc: 1_500 diff --git a/policyengine_us/tests/test_ctc_credit_limit_worksheets.py b/policyengine_us/tests/test_ctc_credit_limit_worksheets.py new file mode 100644 index 00000000000..6b60291f4ac --- /dev/null +++ b/policyengine_us/tests/test_ctc_credit_limit_worksheets.py @@ -0,0 +1,243 @@ +"""Property and differential tests for the CTC's tax-liability limit. + +The model follows Schedule 8812 Credit Limit Worksheets A and B and the +Form 5695 Residential Clean Energy Credit Limit Worksheet. These tests check +it against the statute applied directly, which never mentions the worksheets: + +- 26 U.S.C. 26(a) limits the aggregate of the subpart A credits to the tax. +- 26 U.S.C. 24(d)(1) refunds the lesser of the CTC (capped at $1,700 per + qualifying child by 24(h)(5)) and the amount by which the aggregate subpart + A credits would increase if the 26(a) limit rose by the earned income + phase-in amount; the refunded amount reduces the non-refundable CTC. +- 26 U.S.C. 25D(c) orders the residential clean energy credit after every + other subpart A credit. + +Households are married or single filers in Texas who take the standard +deduction, so the CTC limit's no-SALT liability equals actual liability. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +YEAR = 2025 + +OUTPUTS = [ + "income_tax_before_credits", + "tax_unit_itemizes", + "ctc", + "ctc_refundable_maximum", + "ctc_phase_in", + "energy_efficient_home_improvement_credit_potential", + "energy_efficient_home_improvement_credit", + "residential_clean_energy_credit_potential", + "residential_clean_energy_credit_credit_limit", + "residential_clean_energy_credit", + "ctc_tax_liability_after_preceding_credits", + "ctc_credit_limit_worksheet_b_applies", + "ctc_non_refundable_minimum", + "ctc_limiting_tax_liability", + "refundable_ctc", + "non_refundable_ctc", + "income_tax_capped_non_refundable_credits", + "income_tax_before_refundable_credits", +] + + +def build_situation(households): + people, tax_units, units = {}, {}, {} + groups = {"households": {}, "spm_units": {}, "families": {}} + for i, h in enumerate(households): + head = f"head_{i}" + people[head] = { + "age": {YEAR: 40}, + "employment_income": {YEAR: h["wages"]}, + "taxable_interest_income": {YEAR: h["interest"]}, + } + members = [head] + units[f"marital_unit_{i}"] = {"members": [head]} + if h["married"]: + spouse = f"spouse_{i}" + people[spouse] = {"age": {YEAR: 40}} + members.append(spouse) + units[f"marital_unit_{i}"]["members"].append(spouse) + for j, age in enumerate(h["dependent_ages"]): + dependent = f"dependent_{i}_{j}" + people[dependent] = {"age": {YEAR: age}} + members.append(dependent) + units[f"marital_unit_{i}_{j}"] = {"members": [dependent]} + tax_units[f"tax_unit_{i}"] = { + "members": members, + "solar_electric_property_expenditures": {YEAR: h["solar"]}, + "energy_efficient_home_improvement_credit_potential": { + YEAR: h["home_improvement_credit"] + }, + } + groups["households"][f"household_{i}"] = { + "members": members, + "state_code": {YEAR: "TX"}, + } + groups["spm_units"][f"spm_unit_{i}"] = {"members": members} + groups["families"][f"family_{i}"] = {"members": members} + return { + "people": people, + "tax_units": tax_units, + "marital_units": units, + **groups, + } + + +def calculate(households): + simulation = Simulation(situation=build_situation(households)) + return { + variable: np.asarray(simulation.calculate(variable, YEAR)) + for variable in OUTPUTS + } + + +def statutory_reference(r): + """Apply 26 U.S.C. 24(d)(1), 25D(c) and 26(a) directly. + + The inputs this change does not touch come from the model: income tax + before credits, each credit before the 26(a) limit, the per-child refund + cap and the earned income (or Social Security tax) increase. + """ + liability = r["income_tax_before_credits"] + # The only other credit in these households is the energy efficient home + # improvement credit, which precedes both (Form 5695, line 31). + prior_potential = r["energy_efficient_home_improvement_credit_potential"] + prior = r["energy_efficient_home_improvement_credit"] + ctc = r["ctc"] + potential = r["residential_clean_energy_credit_potential"] + + def aggregate(limit): + # Subpart A credits allowed under a 26(a) limit, before 24(d). + return np.minimum(np.maximum(limit, 0), prior_potential + ctc + potential) + + increase = aggregate(liability + r["ctc_phase_in"]) - aggregate(liability) + refundable = np.minimum(np.minimum(r["ctc_refundable_maximum"], ctc), increase) + non_refundable_ctc = ctc - refundable + liability_after_prior = np.maximum(liability - prior, 0) + ctc_used = np.minimum(non_refundable_ctc, liability_after_prior) + residential_clean_energy = np.minimum(potential, liability_after_prior - ctc_used) + return { + "refundable_ctc": refundable, + "residential_clean_energy_credit": residential_clean_energy, + "income_tax_capped_non_refundable_credits": ( + prior + ctc_used + residential_clean_energy + ), + } + + +def assert_matches_statute(r): + assert not r["tax_unit_itemizes"].any() + expected = statutory_reference(r) + for variable, values in expected.items(): + np.testing.assert_allclose(r[variable], values, atol=0.01, err_msg=variable) + + +def assert_invariants(r): + tol = 0.01 + line_3 = r["ctc_tax_liability_after_preceding_credits"] + line_5 = r["ctc_limiting_tax_liability"] + rce = r["residential_clean_energy_credit"] + worksheet_b = r["ctc_credit_limit_worksheet_b_applies"] + # Worksheet A: 0 <= line 5 <= line 3 <= income tax before credits. + assert (line_5 >= 0).all() + assert (line_5 <= line_3 + tol).all() + assert (line_3 <= np.maximum(r["income_tax_before_credits"], 0) + tol).all() + # With Worksheet B, line 5 = line 3 - the residential clean energy credit; + # without it, line 5 = line 3. + np.testing.assert_allclose( + line_5, np.where(worksheet_b, line_3 - rce, line_3), atol=tol + ) + # The residential clean energy credit never exceeds its potential or limit. + assert (rce <= r["residential_clean_energy_credit_potential"] + tol).all() + assert (rce <= r["residential_clean_energy_credit_credit_limit"] + tol).all() + # Section 24(d)(1): the refund never exceeds the per-child cap, the credit + # or the phase-in amount. + cap = np.minimum( + np.minimum(r["ctc_refundable_maximum"], r["ctc"]), + np.maximum(r["ctc_phase_in"], 0), + ) + assert (r["refundable_ctc"] <= cap + tol).all() + # Section 26(a): non-refundable credits never exceed the tax. + assert ( + r["income_tax_capped_non_refundable_credits"] + <= r["income_tax_before_credits"] + tol + ).all() + + +GRID = [ + { + "married": married, + "dependent_ages": ages, + "wages": wages, + "interest": interest, + "solar": solar, + "home_improvement_credit": home_improvement_credit, + } + for married in (True, False) + for ages in ([], [5], [5, 8], [3, 6, 9], [17], [5, 17]) + for wages in (0, 12_000, 30_000, 60_000, 120_000) + for interest in (0, 40_000) + for solar in (0, 2_000, 10_000) + for home_improvement_credit in (0, 300) +] + + +def test_grid_matches_statute_and_invariants(): + r = calculate(GRID) + assert_matches_statute(r) + assert_invariants(r) + # The grid exercises every branch of Worksheet B. + assert r["ctc_credit_limit_worksheet_b_applies"].any() + assert (~r["ctc_credit_limit_worksheet_b_applies"] & (r["ctc"] > 0)).any() + limited = r["residential_clean_energy_credit"] < ( + r["residential_clean_energy_credit_potential"] - 1 + ) + assert (limited & r["ctc_credit_limit_worksheet_b_applies"]).any() + + +household_strategy = st.fixed_dictionaries( + { + "married": st.booleans(), + "dependent_ages": st.lists(st.integers(0, 18), max_size=4), + "wages": st.integers(0, 200_000), + "interest": st.integers(0, 100_000), + "solar": st.integers(0, 40_000), + "home_improvement_credit": st.integers(0, 1_200), + } +) + + +@settings( + max_examples=10, + deadline=None, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.lists(household_strategy, min_size=1, max_size=25)) +def test_random_households_match_statute_and_invariants(households): + r = calculate(households) + assert_matches_statute(r) + assert_invariants(r) + + +@settings( + max_examples=8, + deadline=None, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given( + st.lists(household_strategy, min_size=1, max_size=15), + st.integers(1, 20_000), +) +def test_more_residential_clean_energy_spending_never_raises_tax(households, extra): + # Each household appears twice: with its spending and with more. + more = [{**h, "solar": h["solar"] + extra} for h in households] + r = calculate(households + more) + tax = r["income_tax_before_refundable_credits"] - r["refundable_ctc"] + n = len(households) + assert (tax[n:] <= tax[:n] + 0.01).all() diff --git a/policyengine_us/tests/test_ctc_itemizing_branch_cycle.py b/policyengine_us/tests/test_ctc_itemizing_branch_cycle.py index 9d797ce7b43..9ba59171672 100644 --- a/policyengine_us/tests/test_ctc_itemizing_branch_cycle.py +++ b/policyengine_us/tests/test_ctc_itemizing_branch_cycle.py @@ -3,7 +3,8 @@ Calling `refundable_ctc` on an `itemizing` reform branch used to form a dependency cycle - refundable_ctc -> ctc_limiting_tax_liability -> (no_salt branch) + refundable_ctc -> ctc_limiting_tax_liability + -> ctc_tax_liability_after_preceding_credits -> (no_salt branch) -> income_tax_before_credits -> taxable_income -> tax_unit_itemizes -> tax_liability_if_itemizing -> (itemizing branch) -> income_tax -> income_tax_before_refundable_credits @@ -15,7 +16,7 @@ consumers (e.g. policyengine.py's household-impact integration tests) on `policyengine-core >= 3.24`. -The fix in `ctc_limiting_tax_liability.py` propagates the parent's +The fix in `ctc_tax_liability_after_preceding_credits.py` propagates the parent's `tax_unit_itemizes` value to the no_salt child branch so the `tax_unit_itemizes` formula is never re-entered there. """ diff --git a/policyengine_us/tools/pinned_tbs.py b/policyengine_us/tools/pinned_tbs.py index 0a32321b14c..04f2d91f509 100644 --- a/policyengine_us/tools/pinned_tbs.py +++ b/policyengine_us/tools/pinned_tbs.py @@ -99,17 +99,24 @@ def _pin_2020_irc(tbs): pass # The 2020 CTC & ODC Worksheet (Pub. 972, p.7) uses the CDCC before the CTC # when computing the CTC's tax-liability limit, so the recomputed CDCC must - # reduce that limit inside `ctc_limiting_tax_liability`, which reads the - # non-refundable-credits list. The 2021 list omits `cdcc` (it was refundable - # under ARPA). Add `cdcc` to the current 2021 membership rather than pinning - # the whole 2020 list, which would drop `new_clean_vehicle_credit` (2021+). - try: - non_refundable_2021 = list(credits.non_refundable(start)) - if "cdcc" not in non_refundable_2021: - non_refundable_2021 = ["cdcc"] + non_refundable_2021 - credits.non_refundable.update(start=start, stop=stop, value=non_refundable_2021) - except Exception: - pass + # reduce that limit (`ctc_tax_liability_after_preceding_credits`) and the + # residential clean energy credit's limit, and count among the + # non-refundable credits. The 2021 lists omit `cdcc` (it was refundable + # under ARPA). Add `cdcc` to each current 2021 membership rather than + # pinning the whole 2020 lists, which would drop `new_clean_vehicle_credit` + # (2021+). + for credit_list in ( + credits.non_refundable, + credits.ctc_tax_liability_limit.preceding_credits, + credits.residential_clean_energy.preceding_credits, + ): + try: + members_2021 = list(credit_list(start)) + if "cdcc" not in members_2021: + members_2021 = ["cdcc"] + members_2021 + credit_list.update(start=start, stop=stop, value=members_2021) + except Exception: + pass def get_pre_arpa_eitc_tbs(base_tbs): diff --git a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py new file mode 100644 index 00000000000..f4d9153954c --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * + + +class ctc_credit_limit_worksheet_b_applies(Variable): + value_type = bool + entity = TaxUnit + label = "Schedule 8812 Credit Limit Worksheet B applies" + documentation = ( + "Whether the filer completes Schedule 8812 Credit Limit Worksheet B, " + "which lets the residential clean energy credit (and the adoption, " + "mortgage interest and DC first-time homebuyer credits) displace the " + "part of the Child Tax Credit that can be refunded. The IRS also " + "requires the filer to claim one of those credits; that condition is " + "omitted because Worksheet B changes nothing when those credits are " + "zero." + ) + definition_period = YEAR + reference = ( + # 2025 Instructions for Schedule 8812, Credit Limit Worksheet A, line 3. + "https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4", + "https://www.law.cornell.edu/uscode/text/26/24#d_1_B", + # No refundable CTC for filers excluding foreign earned income. + "https://www.law.cornell.edu/uscode/text/26/24#d_5", + ) + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.irs.credits.ctc + if p.refundable.fully_refundable: + # In 2021, Worksheet B applied only to filers completing Part I-C, + # whose CTC was not fully refundable. + return np.zeros(tax_unit.count, dtype=bool) + has_qualifying_child = tax_unit("ctc_qualifying_children", period) > 0 + files_form_2555 = tax_unit("foreign_earned_income_exclusion", period) > 0 + return has_qualifying_child & ~files_form_2555 diff --git a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_limiting_tax_liability.py b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_limiting_tax_liability.py index 796ce992c1d..85d6ccccbdb 100644 --- a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_limiting_tax_liability.py +++ b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_limiting_tax_liability.py @@ -6,32 +6,36 @@ class ctc_limiting_tax_liability(Variable): entity = TaxUnit label = "CTC-limiting tax liability" unit = USD - documentation = "The tax liability used to determine the maximum amount of the non-refundable CTC. Excludes SALT from all calculations (this is an inaccuracy required to avoid circular dependencies)." + documentation = ( + "The tax liability used to determine the maximum amount of the " + "non-refundable CTC (Schedule 8812 Credit Limit Worksheet A, line 5): " + "income tax before credits less the credits that precede the CTC, " + "less the residential clean energy credit when Credit Limit Worksheet " + "B applies. Excludes SALT from income tax before credits (this is an " + "inaccuracy required to avoid circular dependencies)." + ) definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/26#a", + "https://www.law.cornell.edu/uscode/text/26/24#d_1_B", + # 2025 Instructions for Schedule 8812, Credit Limit Worksheets A and B. + "https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4", + "https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=6", + ) def formula(tax_unit, period, parameters): - simulation = tax_unit.simulation - no_salt_branch = simulation.get_branch("no_salt") - no_salt_branch.set_input("salt_deduction", period, np.zeros(tax_unit.count)) - # Propagate the parent's itemization determination so the - # no_salt branch doesn't re-enter - # `tax_unit_itemizes` -> `tax_liability_if_itemizing` -> - # `income_tax` -> `refundable_ctc`, which forms a cycle - # (issue #8059). The parent's value has already been computed - # by the time we get here: either set as input on the - # itemizing / not_itemizing branch, or computed and cached on - # the top-level sim before `refundable_ctc` was reached (the - # `income_tax_before_credits` branch of - # `income_tax_before_refundable_credits` runs first). - itemizes = tax_unit("tax_unit_itemizes", period) - no_salt_branch.set_input("tax_unit_itemizes", period, itemizes) - tax_liability_before_credits = no_salt_branch.calculate( - "income_tax_before_credits", period + liability_after_preceding_credits = tax_unit( + "ctc_tax_liability_after_preceding_credits", period + ) # Line 3 + # Section 25D(c) orders the residential clean energy credit after the + # CTC, but section 24(d)(1)(B) refunds the CTC by the increase in all + # subpart A credits. Worksheet B therefore lets that credit use the + # liability first, moving the displaced CTC into the refundable part. + p = parameters(period).gov.irs.credits.ctc_tax_liability_limit + # add() returns None for an empty list, which a reform may set. + subsequent_credits = ( + add(tax_unit, period, p.subsequent_credits) if p.subsequent_credits else 0 ) - non_refundable_credits = parameters(period).gov.irs.credits.non_refundable - non_refundable_credits_ex_ctc = [ - x for x in non_refundable_credits if x != "non_refundable_ctc" - ] - total_credits = add(tax_unit, period, non_refundable_credits_ex_ctc) - - return max_(0, tax_liability_before_credits - total_credits) + worksheet_b_applies = tax_unit("ctc_credit_limit_worksheet_b_applies", period) + worksheet_b_amount = where(worksheet_b_applies, subsequent_credits, 0) # Line 4 + return max_(0, liability_after_preceding_credits - worksheet_b_amount) # Line 5 diff --git a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.py b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.py new file mode 100644 index 00000000000..981af7ccad6 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class ctc_non_refundable_minimum(Variable): + value_type = float + entity = TaxUnit + label = "CTC that cannot be refunded" + unit = USD + documentation = ( + "The part of the Child Tax Credit (including the credit for other " + "dependents) that exceeds the most that could be refunded, whatever " + "the tax liability (Schedule 8812 Credit Limit Worksheet B, line 14)." + ) + definition_period = YEAR + reference = ( + # 2025 Instructions for Schedule 8812, Credit Limit Worksheet B. + "https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=5", + "https://www.law.cornell.edu/uscode/text/26/24#d_1", + ) + + def formula(tax_unit, period, parameters): + ctc = tax_unit("ctc", period) # Line 1 + refundable_maximum = tax_unit("ctc_refundable_maximum", period) # Line 2 + phase_in = tax_unit("ctc_phase_in", period) # Line 12 + most_refundable = min_(refundable_maximum, phase_in) # Line 13 + return max_(0, ctc - most_refundable) # Line 14 diff --git a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_tax_liability_after_preceding_credits.py b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_tax_liability_after_preceding_credits.py new file mode 100644 index 00000000000..83ac15117be --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_tax_liability_after_preceding_credits.py @@ -0,0 +1,46 @@ +from policyengine_us.model_api import * + + +class ctc_tax_liability_after_preceding_credits(Variable): + value_type = float + entity = TaxUnit + label = "Tax liability after credits preceding the CTC" + unit = USD + documentation = ( + "Income tax before credits less the non-refundable credits that " + "precede the Child Tax Credit (Schedule 8812 Credit Limit Worksheet " + "A, line 3). Excludes SALT from income tax before credits (this is an " + "inaccuracy required to avoid circular dependencies)." + ) + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/26#a", + # 2025 Instructions for Schedule 8812, Credit Limit Worksheet A. + "https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4", + ) + + def formula(tax_unit, period, parameters): + simulation = tax_unit.simulation + no_salt_branch = simulation.get_branch("no_salt") + no_salt_branch.set_input("salt_deduction", period, np.zeros(tax_unit.count)) + # Propagate the parent's itemization determination so the + # no_salt branch doesn't re-enter + # `tax_unit_itemizes` -> `tax_liability_if_itemizing` -> + # `income_tax` -> `refundable_ctc`, which forms a cycle + # (issue #8059). The parent's value has already been computed + # by the time we get here: either set as input on the + # itemizing / not_itemizing branch, or computed and cached on + # the top-level sim before `refundable_ctc` was reached (the + # `income_tax_before_credits` branch of + # `income_tax_before_refundable_credits` runs first). + itemizes = tax_unit("tax_unit_itemizes", period) + no_salt_branch.set_input("tax_unit_itemizes", period, itemizes) + tax_liability_before_credits = no_salt_branch.calculate( + "income_tax_before_credits", period + ) + p = parameters(period).gov.irs.credits.ctc_tax_liability_limit + # add() returns None for an empty list, which a reform may set. + preceding_credits = ( + add(tax_unit, period, p.preceding_credits) if p.preceding_credits else 0 + ) + return max_(0, tax_liability_before_credits - preceding_credits) diff --git a/policyengine_us/variables/gov/irs/credits/residential_clean_energy/residential_clean_energy_credit_credit_limit.py b/policyengine_us/variables/gov/irs/credits/residential_clean_energy/residential_clean_energy_credit_credit_limit.py index 87ce2750b71..b16a5df70b1 100644 --- a/policyengine_us/variables/gov/irs/credits/residential_clean_energy/residential_clean_energy_credit_credit_limit.py +++ b/policyengine_us/variables/gov/irs/credits/residential_clean_energy/residential_clean_energy_credit_credit_limit.py @@ -6,12 +6,42 @@ class residential_clean_energy_credit_credit_limit(Variable): entity = TaxUnit label = "Residential clean energy credit credit limit" definition_period = YEAR - documentation = "Residential clean energy tax credit" + documentation = ( + "Income tax before credits less every other non-refundable credit, " + "including the non-refundable Child Tax Credit (Form 5695 Residential " + "Clean Energy Credit Limit Worksheet)." + ) unit = USD - reference = "https://www.law.cornell.edu/uscode/text/26/25D" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/25D#c", + # 2025 Instructions for Form 5695, Residential Clean Energy Credit + # Limit Worksheet. + "https://www.irs.gov/pub/irs-pdf/i5695.pdf#page=5", + ) def formula(tax_unit, period, parameters): income_tax_before_credits = tax_unit("income_tax_before_credits", period) p = parameters(period).gov.irs.credits.residential_clean_energy preceding_credits = add(tax_unit, period, p.preceding_credits) - return max_(income_tax_before_credits - preceding_credits, 0) + liability_after_preceding_credits = max_( + income_tax_before_credits - preceding_credits, 0 + ) + # The Child Tax Credit and credit for other dependents also precede + # this credit (Form 1040, line 19). When Schedule 8812 Credit Limit + # Worksheet B applies, only the part of the CTC that cannot be refunded + # counts (Worksheet B, line 14): this credit uses the liability first + # and the CTC it displaces is refunded instead. + ctc = tax_unit("ctc", period) + if parameters(period).gov.irs.credits.ctc.refundable.fully_refundable: + # The refundable CTC does not depend on tax liability in these + # years, so it can be read here without a circular dependency. + ctc_not_refunded = ctc - tax_unit("refundable_ctc", period) + else: + ctc_not_refunded = ctc + non_refundable_ctc = min_(ctc_not_refunded, liability_after_preceding_credits) + preceding_ctc = where( + tax_unit("ctc_credit_limit_worksheet_b_applies", period), + tax_unit("ctc_non_refundable_minimum", period), + non_refundable_ctc, + ) + return max_(liability_after_preceding_credits - preceding_ctc, 0) diff --git a/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.py b/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.py index 9e99daaac73..30bf80cbfbc 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.py +++ b/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.py @@ -23,7 +23,9 @@ class co_non_refundable_ctc(Variable): def formula(tax_unit, period, parameters): # follow 2022 DR 0104CN form and its instructions (in Book cited above): maximum = tax_unit("co_federal_ctc_maximum", period) # Line 3 + # Lines 4 - 6 follow Schedule 8812 Credit Limit Worksheet A, lines 1 - 3, + # without Worksheet B: the form subtracts no residential energy credit. limiting_tax_liability = tax_unit( - "ctc_limiting_tax_liability", period + "ctc_tax_liability_after_preceding_credits", period ) # Line 4 - 6 return min_(maximum, limiting_tax_liability) # Line 7 diff --git a/pyproject.toml b/pyproject.toml index 30021bc970d..79776aa1864 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ dev = [ "setuptools>=80.9.0", "build>=1.2.2.post1", "towncrier>=24.8.0", + "hypothesis>=6.100.0", ] [tool.hatch.build.targets.wheel] diff --git a/uv.lock b/uv.lock index 8b09da51874..f835d61e810 100644 --- a/uv.lock +++ b/uv.lock @@ -766,6 +766,94 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/07/3d0c34c345043c6a398a5882e196b2220dc5861adfa18322448b90908f26/huggingface_hub-1.3.4-py3-none-any.whl", hash = "sha256:a0c526e76eb316e96a91e8a1a7a93cf66b0dd210be1a17bd5fc5ae53cba76bfd", size = 536611, upload-time = "2026-01-26T14:05:08.549Z" }, ] +[[package]] +name = "hypothesis" +version = "6.168.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/b7/13118bbc45d6d8b9d04e2de779e2a4ff23145ea39efa692b994fb874ca72/hypothesis-6.168.3.tar.gz", hash = "sha256:a43388f9067678fef6e13bdff325b6cfa6961a590498bb37f7ff31589c83bc75", size = 511022, upload-time = "2026-09-28T05:20:58.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/cd/746a2fc1e5e5ba34f07ea6ee1ad07525a3dde5ff4836db3a7980af49a891/hypothesis-6.168.3-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d20972ca134e652e9928ecd200966a8a2857adc2812c1d74b12a872e5cd503eb", size = 791536, upload-time = "2026-09-28T05:18:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/c3/0f/7a158e377b69556c8e12c25c8fd811d0103e54c24a12dab1f3b9819f202e/hypothesis-6.168.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:eafbec09d3e87d13d8242411d1f5868b5e879f1bd6d95e233e9ff80c527bea1c", size = 787313, upload-time = "2026-09-28T05:20:18.122Z" }, + { url = "https://files.pythonhosted.org/packages/45/c6/7df9104ee359e8fcd77791781c47e70794964ca69673665e0de2a6590f4c/hypothesis-6.168.3-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73c5627497968cc62d140e9fde1ea21e12cac7b64dc419fea8286cd34ff1ad4e", size = 1124036, upload-time = "2026-09-28T05:20:04.372Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/8a61715404a73b9a82cb1f976803428d603cdea976a9c30266c72ad6a7ce/hypothesis-6.168.3-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dc56e6dc6eeb0aeb08ac463f847279bde2336c4786faf7ee0af4b93cd031a7", size = 1147875, upload-time = "2026-09-28T05:19:15.495Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3f/2b16e95cc3b9a069afa23830a012aefe377ce4a457a227e5a5026eb827ff/hypothesis-6.168.3-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:753bb501f8560d2e321ed3b62596b4496c56f15668b0a0669231ccc3e6c4e80d", size = 1149489, upload-time = "2026-09-28T05:19:18.886Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e7/d7cf6dc068bb02732b2a6e38a35a0dcb7f2137608198fc79d740f69d197c/hypothesis-6.168.3-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71ab606c472449cb872ef2a7acaec679ee4f651b7f0a477a04ebc85d8933ef8c", size = 1191992, upload-time = "2026-09-28T05:19:21.83Z" }, + { url = "https://files.pythonhosted.org/packages/5f/26/f1e5b25dec15998e8375d722825ec3dae1075cbe7cb1f74221b2312a2e33/hypothesis-6.168.3-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26928956c54748e4dfa587333741ab750246123b93484955646ff316cca27eef", size = 1169911, upload-time = "2026-09-28T05:19:27.996Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/901234a49147e6bf8a5b9e54b775706a223c2b52befea1106ea2aaf15d48/hypothesis-6.168.3-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:0bdfc53c041b61c854fa3761735736b991bc2bddafee45a361cfe4d43027c1ef", size = 1129406, upload-time = "2026-09-28T05:18:42.632Z" }, + { url = "https://files.pythonhosted.org/packages/55/c1/5fc9ff91ec9fba6bb527833386c66812524b5cc75fe7d4a4f9122ee1c420/hypothesis-6.168.3-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:650528e2b1e2a45e95c2df624d4b9364b8ac5a027ba84e1eea2bc5398dd01dcd", size = 1160399, upload-time = "2026-09-28T05:20:23.911Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d2/49ad1ef5c55547c8abfd0fc571b3493806dda530fd5cb3d8263dbad86a72/hypothesis-6.168.3-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:209dc54cdb1b4d6d7020ad8d09e44c49756361b7183adacea4d6f5705085b595", size = 1299906, upload-time = "2026-09-28T05:18:58.417Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5f/f98a26094c4f462c4215807bed0f9a6b5508b27a3c329bcba1bc8adc7ac7/hypothesis-6.168.3-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:af8ca98cd11dc7f9427bb90483abcd9688d4df2e8e63893a30a2423b027ebb11", size = 1425538, upload-time = "2026-09-28T05:20:49.074Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b8/c5b4406e3a6591e51a42f85469351d1d824e0a54b84167aec9b2353759fe/hypothesis-6.168.3-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:f8122cfdd0bc0ba843063effa22ac310bdebdb3a1319bf1e63f14baa119c72ab", size = 1377084, upload-time = "2026-09-28T05:20:06.618Z" }, + { url = "https://files.pythonhosted.org/packages/e0/01/0d84a8ea469d024c15f602799c0b8410fbe03d99fdb0370449b33c3c916f/hypothesis-6.168.3-cp310-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4bedbb379eab34f792af7ee9a05aae04e9c08bbb52e0f90f5a2110d4fe4b2fbd", size = 1281162, upload-time = "2026-09-28T05:19:42.768Z" }, + { url = "https://files.pythonhosted.org/packages/22/b5/ea6435038de1a795f005cb603531e8ccc053febc50180f3ab55583c9c60e/hypothesis-6.168.3-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:5a953115b9f5c95133ab2d04efffeec96e5658c3207923dca7285f7db3e6bbef", size = 1300433, upload-time = "2026-09-28T05:18:55.634Z" }, + { url = "https://files.pythonhosted.org/packages/6c/78/fa6c77d9f64b6d592e69e582d83cbce7d7e56897faa4762885a2123b0166/hypothesis-6.168.3-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6173558e676ad25ed1e20507fa4024a0d816dd90f715f77006e5a28f19109026", size = 1336273, upload-time = "2026-09-28T05:19:08.273Z" }, + { url = "https://files.pythonhosted.org/packages/f1/56/4acd0d778bab818c9ea336fcba768bf43bc70db7f84cc373579298df4641/hypothesis-6.168.3-cp310-abi3-win32.whl", hash = "sha256:dc66390fb12d80585aa9222bf538ce8b7aa22cf5d118250647355a1c9e8f62f4", size = 678211, upload-time = "2026-09-28T05:19:23.355Z" }, + { url = "https://files.pythonhosted.org/packages/b0/db/0a02b146ad1c30f9716362f2e1a379dfd6b0f7be03ac1e22c9da24c9e2a9/hypothesis-6.168.3-cp310-abi3-win_amd64.whl", hash = "sha256:92325b276360fe86c5bf71a568c0d53a6d140b0de36dcf029f9164a17803bb24", size = 684906, upload-time = "2026-09-28T05:18:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c0/958deaf726848f96f52250740bf39f13f476b068e22f73e2b358eaa07532/hypothesis-6.168.3-cp310-abi3-win_arm64.whl", hash = "sha256:3cf6f1eeaf41cd8d60cf1f88fde905ca1dd77c906929a507d6ac7f66f2ccba2a", size = 683292, upload-time = "2026-09-28T05:19:57.404Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ab/443208c2988a24eaafbfbd6791b897ad9a8a1dd7f870d5b455d7d15a28ad/hypothesis-6.168.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9029775dc2e25e3e8e596c4306926f0079158353631c7c52c285bbc5c8073c2b", size = 792239, upload-time = "2026-09-28T05:19:59.144Z" }, + { url = "https://files.pythonhosted.org/packages/cf/7b/619c1a76c52de2d3cfd68a2f9876c09464d63097db5bf86a28b62dc37bb1/hypothesis-6.168.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e1d91530cefdb9e0b6467c203eb509c61a11d70480a37db59cb53a2f9913102", size = 788113, upload-time = "2026-09-28T05:19:17.145Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/33841e8833105f850214a486d83740ba52041892ca94e687a05829c0ce83/hypothesis-6.168.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0df00cbe8133aa220308d11fa28e3add996cf5102a39b279c1d711015fd9114b", size = 1124241, upload-time = "2026-09-28T05:18:37.589Z" }, + { url = "https://files.pythonhosted.org/packages/4b/43/c84a7900feced6a420e824ab61d316e4f59a6887d991bc143ecc294a09ed/hypothesis-6.168.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd7f75a2e23288ee82ee965a952473d9c5447c2cbc1afc94be09d2402201774e", size = 1170406, upload-time = "2026-09-28T05:19:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/47/5d/6e2a32b9c3020af7a06b00fe446a2c5155ae76ac468c0b68d7188e524002/hypothesis-6.168.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc2b1c37631f2231dd0d077225aa5908fc2bd34a4c3faf475053b0bc2eb24c95", size = 1300339, upload-time = "2026-09-28T05:20:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f9/78/19f3c9eb9a5c215b9fbde57be18ef7c1c78afa509b460d4e8f86026d741a/hypothesis-6.168.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:73960a58f6efc8cbc57d8ad0647955f7c5b25f3d562e3eaa57afa1c69894f7aa", size = 1336484, upload-time = "2026-09-28T05:19:02.841Z" }, + { url = "https://files.pythonhosted.org/packages/88/bb/d46c4519e989e022667705d8a56e83a09ad4ea50eae07ca4bb0f4708dd23/hypothesis-6.168.3-cp311-cp311-win_amd64.whl", hash = "sha256:3f1122223759acc0fe5301c1ae505d762ee61af91db7b191a791ccdbcd965462", size = 684646, upload-time = "2026-09-28T05:20:00.842Z" }, + { url = "https://files.pythonhosted.org/packages/7a/56/44a17266bcf1add5fba7c3df5c3920396a5e924d917fb0534a6f9e2b9b9a/hypothesis-6.168.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bff12e036b67abc5ded682f724f4d7afaff5223190270a0763695f218c079068", size = 793313, upload-time = "2026-09-28T05:20:22.045Z" }, + { url = "https://files.pythonhosted.org/packages/d8/18/1a25b11e9133cb54c02ee6cf99a61296ad461aabc7aae3399e1d0ff1f095/hypothesis-6.168.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c73c6188056e6dc110260e39d141c69c3d02f23e3ba1d8610c7a8b6f2510ec96", size = 784855, upload-time = "2026-09-28T05:19:34.496Z" }, + { url = "https://files.pythonhosted.org/packages/8b/da/728d3210f0b0089337f37206a100d31ef546d1f99839e709f0a9ca443f8e/hypothesis-6.168.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb1063cb794765097b8c0add598bd34a904f44dc15db81275dd1f0ed1ac567ed", size = 1123043, upload-time = "2026-09-28T05:19:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/53/86/bfb9ec344a8b3ef2fa6f59bb3d30c5b3a7d86d594107670539c34ee53d1b/hypothesis-6.168.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b2d47f5d9060b049039bef0b267e88480f6468f95ee394e514e84a81ccdc5f5", size = 1169121, upload-time = "2026-09-28T05:20:40.512Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e0/5a76dbafbcaf8fff075f09377cddefa17289ef05dbf51878dde88385b966/hypothesis-6.168.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8367f623a98cbf90f33fd2a43b100671b152b5975bc343a578290945f52f7018", size = 1298749, upload-time = "2026-09-28T05:19:32.883Z" }, + { url = "https://files.pythonhosted.org/packages/28/15/0e65af96b353f2c4bdc283c53ed0980e5ac5009afe97ef3b23c77b364664/hypothesis-6.168.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd81241f4cd76fb18d481e87b9688b30a0cb5c30841730513323ba1c7aba1837", size = 1335271, upload-time = "2026-09-28T05:20:54.005Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c5/37eab2155664aa544df321f6d3940b89c6702d79a72fdc98d7b5a2286f8c/hypothesis-6.168.3-cp312-cp312-win_amd64.whl", hash = "sha256:377438de53afb94347d9845b7d90db5905c6d2a8d612108e9938e0f80503bb90", size = 682211, upload-time = "2026-09-28T05:18:51.206Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a2/6787da846d929e52fc3344d803299c45782dbeac287528af08380e984bf8/hypothesis-6.168.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1b230a850de63334c16654a34a2d547e0179d36b9071d4439b3e7237f6d077e7", size = 793254, upload-time = "2026-09-28T05:19:36.294Z" }, + { url = "https://files.pythonhosted.org/packages/89/ba/2893f5ca42501f4d562cba3229c8994c3a3e5fac66ef60c1e0e0b5220a5d/hypothesis-6.168.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d63b0226cd3e0d8bdd97c3384b22a21934ed4d53246c1c93575dada616672499", size = 784699, upload-time = "2026-09-28T05:18:54.194Z" }, + { url = "https://files.pythonhosted.org/packages/b7/aa/7d7349daf75b71f6f35876f8de115e974c5c04d600e86df1b2779b0883ab/hypothesis-6.168.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0369f5df055f96e117ab12e5f249668ff144731ab5280bc7a205fdf81f990b89", size = 1123016, upload-time = "2026-09-28T05:20:36.372Z" }, + { url = "https://files.pythonhosted.org/packages/00/f0/7774e1ea072708ea46cb25c4aeee5f9978b6c271764809069e8a6789858e/hypothesis-6.168.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f076bcd0f77fdcdb7797826c879099573d03a02e228ebe649ea917081b962ac0", size = 1168989, upload-time = "2026-09-28T05:20:08.44Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7d/113992abed9efbd7944e3a496a382da58152dce126fddf6419ff31a0a57d/hypothesis-6.168.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b823ba1fcec8da730f29316d010b06d3f7e0c3828dcf91020e24c55e7d24652a", size = 1298626, upload-time = "2026-09-28T05:19:40.986Z" }, + { url = "https://files.pythonhosted.org/packages/d5/65/3659fa5e733027e5b37a27486e57f4053535e40853d23501c422bb275043/hypothesis-6.168.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dd2849c269d674e4618590f3b48d443bd4c06b5aef3d8d869086c2e6d213d248", size = 1335184, upload-time = "2026-09-28T05:20:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/b4/6c/35aab2221b5ea65125340f236e1a765a9e9f3b28d89a389ea0033fed29f7/hypothesis-6.168.3-cp313-cp313-win_amd64.whl", hash = "sha256:3ef7d26f5789e691401d5f87eafed9bd2763f0dbe47af6d6b66012a509404766", size = 682173, upload-time = "2026-09-28T05:19:12.762Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/27b0cb56ff5d2bc92458bf6fcebdb1f0dc01f57f043a178e9f9d74498169/hypothesis-6.168.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e2d4c68729a13df9af4998d2652cfb5d541c5609b88c306880a5dfb284938ec2", size = 793287, upload-time = "2026-09-28T05:20:38.522Z" }, + { url = "https://files.pythonhosted.org/packages/e8/bd/5342f95c3bc36586777ef8cb8eba87ac7acf0184210327fb0e6d8654e295/hypothesis-6.168.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b345f818083ec99966a43ca4f7b38feb62c6920ce28572bc2bde4948a8b7eaba", size = 784857, upload-time = "2026-09-28T05:20:42.609Z" }, + { url = "https://files.pythonhosted.org/packages/4a/5f/fc774241518a0588d362680a3084275058aab86d6bc02a2c61e1073142d0/hypothesis-6.168.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0608c610fc002978fc5de0f471770d8817e9455cb8649a47983c9f8bccfc1897", size = 1123330, upload-time = "2026-09-28T05:19:29.735Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/131f16775a3dca5f4fe27f0d6ad9a9851600098a54a872d163f6ebf0b664/hypothesis-6.168.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dcf6448b1ecc37f2b23f2d1b3ddfc9ff6b6910614c15a642dc82f419b96037b", size = 1169178, upload-time = "2026-09-28T05:19:53.436Z" }, + { url = "https://files.pythonhosted.org/packages/06/61/c9f5bff8b73321c9fd12f2d69666c6e4861b97b60f5f24fdd6ac293007fd/hypothesis-6.168.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ae4f9f094041dcce5119ebd7bab71062743ab02b6e654d056b370beda78c19e2", size = 1299127, upload-time = "2026-09-28T05:20:14.486Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6d/d4618f8ab12dd76c4d58ea052252759aa2bf0c4e71f4b502ba5d577e0b9f/hypothesis-6.168.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d479985fe73af97badfb72dc6d20c6a353e486a36f6d065f600026e0cf954a86", size = 1335390, upload-time = "2026-09-28T05:20:10.326Z" }, + { url = "https://files.pythonhosted.org/packages/95/b6/727161e17cc297df1aeea945de05c532033b66751625bec192bc5b5ff707/hypothesis-6.168.3-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:785e2c45f8c08e274b4bf1ccae97f1a4e09407e9a68e80790cfab1d17a4a45fa", size = 624291, upload-time = "2026-09-28T05:18:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/77/23/2f9b506392a0b8712440bcb781abc5713be9ffac1303b6d10f3669050882/hypothesis-6.168.3-cp314-cp314-win_amd64.whl", hash = "sha256:320920b1e3dae8611eee8a03d063cf2187446f2a17c38cfb8a7fc1466f71eee2", size = 682064, upload-time = "2026-09-28T05:20:16.336Z" }, + { url = "https://files.pythonhosted.org/packages/50/93/efabfd95eb2b69c0c9fa1e1c83c8290aa2715d46baeb5a7764faf09c133a/hypothesis-6.168.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:35380baa981108a7f60c4eab71e46acd8d8f58440520346a1a6aba06dca7e074", size = 791875, upload-time = "2026-09-28T05:19:37.789Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fc/2a0ada1623a9a33048bbf9f00a7c92513398c6b8865cba5efb854262ce30/hypothesis-6.168.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f0aaaed00438fa6673d856aaee12a4a8afbde82a9f3c5ff487cacfb064239afe", size = 783412, upload-time = "2026-09-28T05:18:48.266Z" }, + { url = "https://files.pythonhosted.org/packages/41/31/73c615d37eeb12da0209555612c32ced628d860267c23cfd2691c6207aea/hypothesis-6.168.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27e6df1576bf838e7f484d4ae5cba92114497ca30afb917056bf1ea33e95675", size = 1121662, upload-time = "2026-09-28T05:19:26.225Z" }, + { url = "https://files.pythonhosted.org/packages/92/54/1893344a3b8bbf83fa7c9bb7e96f6836580213854f46671cb749f7b4706c/hypothesis-6.168.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3bc85014577982ec6d2e266edc5cd6e7a7d3674c648791ca983c67a52f89a0c4", size = 1167761, upload-time = "2026-09-28T05:18:49.85Z" }, + { url = "https://files.pythonhosted.org/packages/26/97/a61d82968febd25a0f08ffbbfabfaeead66f68fc5fa73f3c9ece1c36fc8f/hypothesis-6.168.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ccfc29505aa1cdcc254cb9cd701fd0811fef5480addd97d4127a00df023410f7", size = 1297309, upload-time = "2026-09-28T05:19:51.711Z" }, + { url = "https://files.pythonhosted.org/packages/bc/01/fe8e4cf6d39d02f4efadaa351427fa13722086cfc54a2da3579741c5177a/hypothesis-6.168.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32d0699566aaa93f9e97a44705d7164386f1e91de78d277f53bada35e78bcc96", size = 1334260, upload-time = "2026-09-28T05:19:00.121Z" }, + { url = "https://files.pythonhosted.org/packages/39/6a/09177ebc62f4778f94379ecade6a51299d3d8c4f4b75ba636bf3a0202364/hypothesis-6.168.3-cp314-cp314t-win_amd64.whl", hash = "sha256:28d88fa174ecbd4ecbd7bb290f06d0db084a3971c3f511ae2830b65b5e25500f", size = 681992, upload-time = "2026-09-28T05:19:48.043Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f6/890bf33d608cd63348d3146a3ca83362fbf09e589c5e5230a00c403070f4/hypothesis-6.168.3-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:61f5782d807b1e6aa5c9beef1054cb2037e7d1add48a64972cd6ee447778781f", size = 791231, upload-time = "2026-09-28T05:18:43.998Z" }, + { url = "https://files.pythonhosted.org/packages/03/dd/fc36b204f8aa7437c676576d9437f0422de46aa8a729e6cd5bbb0224e759/hypothesis-6.168.3-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:7aacf3cf40c7ce8f9e4924d5b57bc0b068beafdfed2347bcf160a28b4cfbba7b", size = 783171, upload-time = "2026-09-28T05:19:20.353Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d2/3cd087d577db67c404991d7723e64d7cd75570cdc82a0f7329aed6f2086c/hypothesis-6.168.3-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01768a03a30dc54df7fe457c0b34016c84d598fa00d5965ededab93ba4eb3408", size = 1121152, upload-time = "2026-09-28T05:18:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/42/6f/a05a68cc66e7a22701c50bf94fb9ddeb36f4f5d0de3cc130066709eda621/hypothesis-6.168.3-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1a8a4ffc6c6e6e577f2bfbcfebf7cffbb310283ba2532c729570a0a752cebaaa", size = 1144069, upload-time = "2026-09-28T05:20:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f4/fdd7a093fb2fb3a0ce24256c92ad5bf67936f4669bd06d5ceae4298150ec/hypothesis-6.168.3-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b987d73eba95183a7e59cca6d1925c588aa4307922d9852cc2b4d282a2ae4128", size = 1146692, upload-time = "2026-09-28T05:20:25.83Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/6dbd4377e935505ae4fc8ee4c7b18c69994c4bbaca015447c470218bdbee/hypothesis-6.168.3-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4569c39bd97d9573e946429ed676f3b55a7c8ed80920addd67d384a47d59b38", size = 1189480, upload-time = "2026-09-28T05:20:46.734Z" }, + { url = "https://files.pythonhosted.org/packages/ba/bf/ff288b496b690000d2686dcfa7f67855e4c5a6dddb464ed8e4880be83bb2/hypothesis-6.168.3-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6042b8707a4b25bbbfe20b110258fb7549a68951e5525608a8ce06091039e9fc", size = 1167109, upload-time = "2026-09-28T05:19:24.758Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d1/3fee2bc29fc747fabbb27e174515592e390809ca465d3bbbabbcfa3235a8/hypothesis-6.168.3-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:f413629de94d38a7a2ad259697a6752526143cb49c2e2d7bdba19381c80693f6", size = 1126823, upload-time = "2026-09-28T05:18:52.636Z" }, + { url = "https://files.pythonhosted.org/packages/58/6a/585294392fa6a6d9719446a042a777ba98ecca265d9e48cbedc10c88df0f/hypothesis-6.168.3-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f071737e4e775bebba07e319eb645a880d1e0186b4d24bad23255e849d86d483", size = 1155773, upload-time = "2026-09-28T05:20:51.268Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4d/fa283ff79996debf1ff08593e01f8b773eb8211b19b3d6d5d491317a65bf/hypothesis-6.168.3-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:f59a3912858d0609c26054aa1c474847c797937f5e0e1c77f0d3f08032e50f09", size = 1296671, upload-time = "2026-09-28T05:18:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/42/05/98f9c2f628da5afabb6c3e8df5d2d299d2d50bf306b14a6e26d3954c4951/hypothesis-6.168.3-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:f09c05a23a8025dd5cad07c2ed299a46778e72d49ff0dd5bf353bcc0f7dc1580", size = 1422001, upload-time = "2026-09-28T05:19:04.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c9/f2109ade29a7ec284d55bca328d784743e8b13e321576fd34ee7e65f99af/hypothesis-6.168.3-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:44ace770bda3a0301739fc5a413c764de790c739df1f1a7218dd049f8594d9f5", size = 1374182, upload-time = "2026-09-28T05:20:27.764Z" }, + { url = "https://files.pythonhosted.org/packages/ce/6d/e05d5f72441564a3bebc71fa155deafd0fd3b015d6014ec8a00edf6e42bb/hypothesis-6.168.3-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:03b131043608f94a2578a2a896a1a72092acb7079815eef5b8513b08447e0e62", size = 1278402, upload-time = "2026-09-28T05:20:56.332Z" }, + { url = "https://files.pythonhosted.org/packages/96/d7/6988a7f1f69c5c530687dce03a32c157e2d878e3a2e32af9269ce016b78a/hypothesis-6.168.3-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:e9784aca26eddfe99b03a0292320db742cc8a74200ef864fdce949f526f973cd", size = 1297770, upload-time = "2026-09-28T05:19:11.387Z" }, + { url = "https://files.pythonhosted.org/packages/a2/28/5bb82b60b836bd2329e1fe01ad94efc2b14dfa806f8e50ed14b795d78770/hypothesis-6.168.3-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:2b52ac363096232bebc2add117e9178d91f4248f4cdb919fd1026b5f86a4bb16", size = 1333977, upload-time = "2026-09-28T05:20:02.483Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7d/d419841b8f65481ea1a50c4ba36f2670d48b8c7a51e4e8586089564cf7f6/hypothesis-6.168.3-cp315-abi3.abi3t-win32.whl", hash = "sha256:d28e3a6b511a74ce37df5274b51f36c2b274fea7365e00b16f0c81e22acd5957", size = 675394, upload-time = "2026-09-28T05:19:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/1de6a2ad100e44817f2e9a8e8ba3eeaf4769621f4f87f2d4966c4971fcc4/hypothesis-6.168.3-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:7b9638789548361a57d984f56619ac694a914c328181d911271618409261ff4a", size = 681699, upload-time = "2026-09-28T05:19:09.957Z" }, + { url = "https://files.pythonhosted.org/packages/c0/79/be3370fd02734d6b1d950183ae9224580d19fa8d356b95348d60f1e3eeb7/hypothesis-6.168.3-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:65d78e4357ec48ed2c67825f06740ee3599be4cfe770a6092bed07108d679ac5", size = 679849, upload-time = "2026-09-28T05:19:06.884Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0e/27960db1d45a94497e1681fb9d7b9140b44da4e655ba20f0d82f95d10230/hypothesis-6.168.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:101531b4ccf7fa12965a6b295d10b64c2f8a8fab61e1f9b18c55335d8fb02575", size = 793135, upload-time = "2026-09-28T05:19:14.056Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cb/be08379286c1925fcd651b92ed835f470a83b859b5c484b3431da9d851f5/hypothesis-6.168.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:41e0120814de5c3a6b58d8cb27cb11b8873ab33fba130cd09855a27f5a12a1ca", size = 788995, upload-time = "2026-09-28T05:20:34.164Z" }, + { url = "https://files.pythonhosted.org/packages/38/7b/6d885d139b2ed76e2f4908cf10eae56acc73bf05cbef286357c4559c9a04/hypothesis-6.168.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d120009d145697909f2f0c532f2d568558ddd6990707f8cba6576bfb9770696", size = 1124974, upload-time = "2026-09-28T05:19:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/05/cc/b302ae809b48254830cae57801d0af0b66783df515b5dd56b9f21c8dfa86/hypothesis-6.168.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:818b3d09bba60ef90463e47a4bc87275dba1e8aeb184d3102888440ca7c7837d", size = 1171967, upload-time = "2026-09-28T05:20:12.295Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ab/51031080a6600f5b07adf5f9dfa117345c2adda07a5c24bda523e70bd1e9/hypothesis-6.168.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9e3c36eba80e089ecfa28ec08049723223cb41d3cfe4d9988169cf98440e402b", size = 685641, upload-time = "2026-09-28T05:19:44.609Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -1746,6 +1834,7 @@ dev = [ { name = "build" }, { name = "coverage" }, { name = "furo" }, + { name = "hypothesis" }, { name = "jupyter-book" }, { name = "ruff" }, { name = "setuptools" }, @@ -1757,6 +1846,7 @@ requires-dist = [ { name = "build", marker = "extra == 'dev'", specifier = ">=1.2.2.post1" }, { name = "coverage", marker = "extra == 'dev'", specifier = ">=7.9.2" }, { name = "furo", marker = "extra == 'dev'", specifier = ">=2024.8.6" }, + { name = "hypothesis", marker = "extra == 'dev'", specifier = ">=6.100.0" }, { name = "jupyter-book", marker = "extra == 'dev'", specifier = ">=1.0.4.post1" }, { name = "microdf-python", specifier = ">=1.0.0" }, { name = "pandas", specifier = ">=3.0" }, From d09612f0ae17611a0b232d44b65f43dc8de56588 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 1 Oct 2026 16:24:59 -0400 Subject: [PATCH 2/5] Cite 24(d)(3), not the pre-2018 (d)(5), for the Form 2555 bar on Worksheet B The section 911 exclusion bar was added as 24(d)(5) and redesignated (d)(3) by Pub. L. 115-141; Cornell LII has no #d_5 anchor. Co-Authored-By: Claude Opus 5.5 --- .../ctc/refundable/ctc_credit_limit_worksheet_b_applies.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py index f4d9153954c..334f385948d 100644 --- a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py +++ b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py @@ -19,8 +19,9 @@ class ctc_credit_limit_worksheet_b_applies(Variable): # 2025 Instructions for Schedule 8812, Credit Limit Worksheet A, line 3. "https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4", "https://www.law.cornell.edu/uscode/text/26/24#d_1_B", - # No refundable CTC for filers excluding foreign earned income. - "https://www.law.cornell.edu/uscode/text/26/24#d_5", + # No refundable CTC for filers excluding foreign earned income under + # section 911 (added as 24(d)(5), redesignated (d)(3) in 2018). + "https://www.law.cornell.edu/uscode/text/26/24#d_3", ) def formula(tax_unit, period, parameters): From 3b43fa58823b5f5770a3484da853f8ec5b6957cc Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 10:34:03 -0400 Subject: [PATCH 3/5] Cite the 2020 Publication 972 worksheet lines, not the 2013 numbering The 2020 CTC and ODC Worksheet lists the preceding credits on line 12, and the credits ordered after the CTC are line 15 of its Line 14 Worksheet. Co-Authored-By: Claude Opus 5.5 --- .../irs/credits/ctc_tax_liability_limit/preceding_credits.yaml | 2 +- .../irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml | 2 +- .../ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml index 18180b6e2c9..e5af9bc7651 100644 --- a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml @@ -41,7 +41,7 @@ metadata: href: https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4 - title: 2022 Instructions for Schedule 8812 (Form 1040), Credit Limit Worksheet A, line 2 href: https://www.irs.gov/pub/irs-prior/i1040s8--2022.pdf#page=4 - - title: 2020 Publication 972, Child Tax Credit and Credit for Other Dependents Worksheet, line 10 + - title: 2020 Publication 972, Child Tax Credit and Credit for Other Dependents Worksheet, line 12 href: https://www.irs.gov/pub/irs-prior/p972--2020.pdf#page=7 - title: 26 U.S. Code § 26(a) - Limitation based on amount of tax href: https://www.law.cornell.edu/uscode/text/26/26#a diff --git a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml index 8eeb1ab231c..b019328b7ce 100644 --- a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml @@ -13,7 +13,7 @@ metadata: reference: - title: 2025 Instructions for Schedule 8812 (Form 1040), Credit Limit Worksheet A, line 4, and Credit Limit Worksheet B, line 15 href: https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=6 - - title: 2020 Publication 972, Line 11 Worksheet, line 13 + - title: 2020 Publication 972, Line 14 Worksheet, line 15 href: https://www.irs.gov/pub/irs-prior/p972--2020.pdf#page=9 - title: 26 U.S. Code § 24(d)(1)(B) - Portion of credit refundable href: https://www.law.cornell.edu/uscode/text/26/24#d_1_B diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml index f3556ddd214..2e2a7ad089f 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml @@ -27,7 +27,7 @@ output: ctc_credit_limit_worksheet_b_applies: false -- name: Applies before 2021 too (Publication 972, Line 11 Worksheet). +- name: Applies before 2021 too (2020 Publication 972, Line 14 Worksheet). period: 2020 input: ctc_qualifying_children: 1 From acef51175f2f9a0b6f65236ee157d4ef1681c33e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 07:31:04 -0400 Subject: [PATCH 4/5] Apply review findings: Form 2555 gate from 2015, Colorado 2023 line 13, citations - Worksheet B excludes Form 2555 filers only from 2015, when 24(d)(3) (then (d)(5)) first barred their refundable CTC. Reuses #9750's foreign_earned_income_exclusion_bar_applies parameter, byte-identical. - Colorado's 2023 DR 0104CN line 4 is Schedule 8812 line 13 (Worksheet A line 5, after Worksheet B); 2022 lines 4-6 stay Worksheet A lines 1-3. - Comments name the 2022 lines (Form 5695 line 30; no 6m) and the credits not modeled (Form 8978, Form 8910); split the Worksheet A line 4 and Worksheet B line 15 anchors; Worksheet B line 14 is on page 6. - The 24(h)(5) cap is $1,400 indexed ($1,700 for 2025). Co-Authored-By: Claude Opus 5.5 --- .../ctc-limit-worksheet-a-credits.fixed.md | 2 +- ...n_earned_income_exclusion_bar_applies.yaml | 18 +++++++++++++++++ .../preceding_credits.yaml | 5 +++++ .../subsequent_credits.yaml | 6 ++++-- .../preceding_credits.yaml | 2 +- ...l_credit_limitation_after_worksheet_b.yaml | 15 ++++++++++++++ .../ctc_credit_limit_worksheet_b_applies.yaml | 8 ++++++++ .../federal_ctc/co_non_refundable_ctc.yaml | 14 +++++++++++++ .../tests/test_ctc_credit_limit_worksheets.py | 9 +++++---- .../ctc_credit_limit_worksheet_b_applies.py | 7 ++++++- .../refundable/ctc_non_refundable_minimum.py | 4 +++- .../ctc/federal_ctc/co_non_refundable_ctc.py | 20 +++++++++++++------ 12 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 policyengine_us/parameters/gov/irs/credits/ctc/refundable/foreign_earned_income_exclusion_bar_applies.yaml create mode 100644 policyengine_us/parameters/gov/states/co/tax/income/credits/ctc/federal_credit_limitation_after_worksheet_b.yaml diff --git a/changelog.d/ctc-limit-worksheet-a-credits.fixed.md b/changelog.d/ctc-limit-worksheet-a-credits.fixed.md index 673a96559c5..b7ea75c757a 100644 --- a/changelog.d/ctc-limit-worksheet-a-credits.fixed.md +++ b/changelog.d/ctc-limit-worksheet-a-credits.fixed.md @@ -1 +1 @@ -Order the residential clean energy credit after the Child Tax Credit, as Schedule 8812 Credit Limit Worksheets A and B and Form 5695 do: the CTC's tax-liability limit subtracts that credit only when Credit Limit Worksheet B applies, the credit's own limit subtracts every other non-refundable credit (including the CTC that cannot be refunded), and it no longer precedes the energy efficient home improvement, elderly or disabled, and clean vehicle credits. +Order the residential clean energy credit after the Child Tax Credit, as Schedule 8812 Credit Limit Worksheets A and B and Form 5695 do: the CTC's tax-liability limit subtracts that credit only when Credit Limit Worksheet B applies, the credit's own limit subtracts every other non-refundable credit (including the CTC that cannot be refunded), and it no longer precedes the energy efficient home improvement, elderly or disabled, and clean vehicle credits. Colorado's federal CTC replica uses Schedule 8812 line 13 from 2023, as the 2023 DR 0104CN does. diff --git a/policyengine_us/parameters/gov/irs/credits/ctc/refundable/foreign_earned_income_exclusion_bar_applies.yaml b/policyengine_us/parameters/gov/irs/credits/ctc/refundable/foreign_earned_income_exclusion_bar_applies.yaml new file mode 100644 index 00000000000..b538937049e --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/ctc/refundable/foreign_earned_income_exclusion_bar_applies.yaml @@ -0,0 +1,18 @@ +description: The IRS denies the refundable Child Tax Credit to filers who elect to exclude any amount from gross income under section 911 (the foreign earned income and housing exclusions) if this is true. It has no effect when the credit is fully refundable (2021). +values: + 2013-01-01: false + 2015-01-01: true +metadata: + unit: bool + period: year + label: Refundable Child Tax Credit denied to section 911 exclusion filers + reference: + - title: 26 U.S. Code § 24(d)(3) + href: https://www.law.cornell.edu/uscode/text/26/24#d_3 + # Section 807 of the Trade Preferences Extension Act of 2015 added this + # rule as section 24(d)(5) for taxable years beginning after 2014; Pub. L. + # 115-141 redesignated it as paragraph (3) in 2018. + - title: Public Law 114-27, § 807 + href: https://www.govinfo.gov/content/pkg/PLAW-114publ27/pdf/PLAW-114publ27.pdf#page=58 + - title: 2025 Instructions for Schedule 8812, Part II-A + href: https://www.irs.gov/pub/irs-prior/i1040s8--2025.pdf#page=3 diff --git a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml index e5af9bc7651..7c1d642f8cb 100644 --- a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml @@ -17,6 +17,11 @@ values: - energy_efficient_home_improvement_credit - elderly_disabled_credit - new_clean_vehicle_credit + # Line numbers are those of the 2023-2025 Schedule 3. In 2022 the energy + # efficient home improvement credit came from Form 5695, line 30, and the + # used clean vehicle credit did not yet exist (it is zero before 2023). + # Not modeled: Schedule 3, line 6l (Form 8978) in every year, and the + # alternative motor vehicle credit (Form 8910) through 2022. 2022-01-01: - foreign_tax_credit # Schedule 3, line 1 - cdcc # Schedule 3, line 2 diff --git a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml index b019328b7ce..5f54f0cb60a 100644 --- a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/subsequent_credits.yaml @@ -4,14 +4,16 @@ values: # (line 6g) and District of Columbia first-time homebuyer credit (line 6h) # also belong here but are not modeled. 2013-01-01: - - residential_clean_energy_credit # Schedule 3, line 5a + - residential_clean_energy_credit # Form 5695, line 15 (Schedule 3, line 5a from 2023) metadata: unit: list period: year label: Non-refundable credits following the Child Tax Credit reference: - - title: 2025 Instructions for Schedule 8812 (Form 1040), Credit Limit Worksheet A, line 4, and Credit Limit Worksheet B, line 15 + - title: 2025 Instructions for Schedule 8812 (Form 1040), Credit Limit Worksheet A, line 4 + href: https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4 + - title: 2025 Instructions for Schedule 8812 (Form 1040), Credit Limit Worksheet B, line 15 href: https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=6 - title: 2020 Publication 972, Line 14 Worksheet, line 15 href: https://www.irs.gov/pub/irs-prior/p972--2020.pdf#page=9 diff --git a/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml index 992ac98b979..89841582ab1 100644 --- a/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml @@ -23,7 +23,7 @@ values: - non_refundable_american_opportunity_credit # Schedule 3, line 3 - lifetime_learning_credit # Schedule 3, line 3 - savers_credit # Schedule 3, line 4 - - energy_efficient_home_improvement_credit # Form 5695, line 32 + - energy_efficient_home_improvement_credit # Form 5695, line 32 (line 30 in 2022) - elderly_disabled_credit # Schedule 3, line 6d - new_clean_vehicle_credit # Schedule 3, line 6f - used_clean_vehicle_credit # Schedule 3, line 6m diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/ctc/federal_credit_limitation_after_worksheet_b.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/ctc/federal_credit_limitation_after_worksheet_b.yaml new file mode 100644 index 00000000000..1df02124352 --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/ctc/federal_credit_limitation_after_worksheet_b.yaml @@ -0,0 +1,15 @@ +description: Colorado limits its replica of the federal Child Tax Credit by Schedule 8812 line 13, which reflects Credit Limit Worksheet B, if this is true; otherwise Colorado subtracts its own list of federal credits from federal income tax. +values: + # DR 0104CN lines 4-6 subtract listed credits from Form 1040 line 18. + 2022-01-01: false + # DR 0104CN line 4 is Schedule 8812 line 13 (Credit Limit Worksheet A, line 5). + 2023-01-01: true +metadata: + unit: bool + period: year + label: Colorado federal CTC limitation after Credit Limit Worksheet B + reference: + - title: 2022 Colorado Child Tax Credit (DR 0104CN), lines 4-6 + href: https://tax.colorado.gov/sites/tax/files/documents/DR_0104CN_2022.pdf#page=1 + - title: 2023 Colorado Child Tax Credit (DR 0104CN), line 4 instructions + href: https://tax.colorado.gov/sites/tax/files/documents/DR0104CN_2023.pdf#page=2 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml index 2e2a7ad089f..4b7b2ec6e97 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml @@ -33,3 +33,11 @@ ctc_qualifying_children: 1 output: ctc_credit_limit_worksheet_b_applies: true + +- name: Before 2015 Worksheet B still applies to Form 2555 filers (the section 911 bar starts in 2015). + period: 2014 + input: + ctc_qualifying_children: 2 + foreign_earned_income_exclusion: 10_000 + output: + ctc_credit_limit_worksheet_b_applies: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.yaml index 86a2eb43f66..79de1385189 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.yaml @@ -29,3 +29,17 @@ output: ctc_limiting_tax_liability: 500 co_non_refundable_ctc: 1_500 + +- name: From 2023 the Colorado limit is Schedule 8812 line 13, after Credit Limit Worksheet B. + # 2023 DR 0104CN line 4: "If you completed Federal Schedule 8812, enter the + # amount from line 13 of that form." + period: 2023 + input: + state_code: CO + co_federal_ctc_maximum: 2_000 + ctc_tax_liability_after_preceding_credits: 1_500 + ctc_credit_limit_worksheet_b_applies: true + residential_clean_energy_credit: 1_000 + output: + ctc_limiting_tax_liability: 500 + co_non_refundable_ctc: 500 diff --git a/policyengine_us/tests/test_ctc_credit_limit_worksheets.py b/policyengine_us/tests/test_ctc_credit_limit_worksheets.py index 6b60291f4ac..5285ed1a2e2 100644 --- a/policyengine_us/tests/test_ctc_credit_limit_worksheets.py +++ b/policyengine_us/tests/test_ctc_credit_limit_worksheets.py @@ -5,10 +5,11 @@ it against the statute applied directly, which never mentions the worksheets: - 26 U.S.C. 26(a) limits the aggregate of the subpart A credits to the tax. -- 26 U.S.C. 24(d)(1) refunds the lesser of the CTC (capped at $1,700 per - qualifying child by 24(h)(5)) and the amount by which the aggregate subpart - A credits would increase if the 26(a) limit rose by the earned income - phase-in amount; the refunded amount reduces the non-refundable CTC. +- 26 U.S.C. 24(d)(1) refunds the lesser of the CTC (capped per qualifying + child by 24(h)(5): $1,400, indexed for inflation, or $1,700 for 2025) and + the amount by which the aggregate subpart A credits would increase if the + 26(a) limit rose by the earned income phase-in amount; the refunded amount + reduces the non-refundable CTC. - 26 U.S.C. 25D(c) orders the residential clean energy credit after every other subpart A credit. diff --git a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py index 334f385948d..07af7bbaf36 100644 --- a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py +++ b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.py @@ -31,5 +31,10 @@ def formula(tax_unit, period, parameters): # whose CTC was not fully refundable. return np.zeros(tax_unit.count, dtype=bool) has_qualifying_child = tax_unit("ctc_qualifying_children", period) > 0 + # Form 2555 filers skip Worksheet B from 2015, when the refundable CTC + # was first denied to them. files_form_2555 = tax_unit("foreign_earned_income_exclusion", period) > 0 - return has_qualifying_child & ~files_form_2555 + excluded = ( + files_form_2555 & p.refundable.foreign_earned_income_exclusion_bar_applies + ) + return has_qualifying_child & ~excluded diff --git a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.py b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.py index 981af7ccad6..46810fd965e 100644 --- a/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.py +++ b/policyengine_us/variables/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.py @@ -13,8 +13,10 @@ class ctc_non_refundable_minimum(Variable): ) definition_period = YEAR reference = ( - # 2025 Instructions for Schedule 8812, Credit Limit Worksheet B. + # 2025 Instructions for Schedule 8812, Credit Limit Worksheet B (lines + # 1-9 on page 5, lines 10-15 on page 6). "https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=5", + "https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=6", "https://www.law.cornell.edu/uscode/text/26/24#d_1", ) diff --git a/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.py b/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.py index 30bf80cbfbc..00b3fc9fbf6 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.py +++ b/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_non_refundable_ctc.py @@ -17,15 +17,23 @@ class co_non_refundable_ctc(Variable): "https://tax.colorado.gov/sites/tax/files/documents/DR_0104CN_2022.pdf#page=1", # Colorado Individual Income Tax Filing Guide - Instructions for Select Credits from the DR 0104CR - Line 1 Child Tax Credit "https://tax.colorado.gov/sites/tax/files/documents/DR_104_Book_2022.pdf#page=16", + # 2023 Colorado Child Tax Credit, line 4 instructions + "https://tax.colorado.gov/sites/tax/files/documents/DR0104CN_2023.pdf#page=2", ) defined_for = StateCode.CO def formula(tax_unit, period, parameters): # follow 2022 DR 0104CN form and its instructions (in Book cited above): maximum = tax_unit("co_federal_ctc_maximum", period) # Line 3 - # Lines 4 - 6 follow Schedule 8812 Credit Limit Worksheet A, lines 1 - 3, - # without Worksheet B: the form subtracts no residential energy credit. - limiting_tax_liability = tax_unit( - "ctc_tax_liability_after_preceding_credits", period - ) # Line 4 - 6 - return min_(maximum, limiting_tax_liability) # Line 7 + p = parameters(period).gov.states.co.tax.income.credits.ctc + if p.federal_credit_limitation_after_worksheet_b: + # 2023 line 4: Schedule 8812 line 13 (Credit Limit Worksheet A, + # line 5), after Credit Limit Worksheet B. + limiting_tax_liability = tax_unit("ctc_limiting_tax_liability", period) + else: + # 2022 lines 4 - 6: Form 1040 line 18 less listed credits, as on + # Credit Limit Worksheet A lines 1 - 3, with no Worksheet B. + limiting_tax_liability = tax_unit( + "ctc_tax_liability_after_preceding_credits", period + ) + return min_(maximum, limiting_tax_liability) # Line 7 (2022), line 5 (2023) From 0b0c51f9f9a00ecfe245f6986dc8bd4f2fabd8d0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 10:24:17 -0400 Subject: [PATCH 5/5] Order federal credit limits as the IRS credit limit worksheets do Each non-refundable credit's limit subtracts a preceding_credits list. Several lists followed the Schedule 3 line order instead of the credit's own credit limit worksheet. Read together, the 2013-2025 worksheets give one order: foreign tax, child and dependent care, elderly or disabled, education, retirement savings, energy efficient home improvement, previously owned clean vehicle, new clean vehicle, Child Tax Credit, residential clean energy. - Schedule R subtracts only Schedule 3 lines 1, 2 and 6l, so the elderly or disabled credit precedes the education, saver's and 25C credits (Forms 8863, 8880 and 5695 subtract line 6d). - Form 8936 line 11 subtracts line 6m, so the previously owned clean vehicle credit precedes the new one; the used credit now applies its own limit (the unused part is lost, line 18). - The foreign tax credit applies against tax before 2022 too (26 U.S.C. 26(a)(1); Pub. 972 and every worksheet subtract it). - The 2021 lists drop the CDCC, a subpart C credit that year (21(g)(1)); the Alabama 2020-IRC pin restores it in every split list. - gov.irs.credits.non_refundable is in that order. Oklahoma's federal CTC applies the credits Credit Limit Worksheet A subtracts (line 2, plus line 4 under Worksheet B), not those listed before the CTC. - CRFB and AFA reform lists follow the order; AFA now includes the foreign tax credit. Tests: hand-worked YAML cases for each list, Oklahoma cases, a parameter chain test (baseline, Alabama pin, both reforms) and a sequential 26(a) differential with Hypothesis households. Co-Authored-By: Claude Opus 5.5 --- .../irs-credit-limit-worksheet-order.fixed.md | 1 + .../irs/credits/cdcc/preceding_credits.yaml | 10 +- .../clean_vehicle/new/preceding_credits.yaml | 36 +- .../clean_vehicle/used/preceding_credits.yaml | 32 +- .../preceding_credits.yaml | 16 +- .../preceding_credits.yaml | 24 +- .../preceding_credits.yaml | 27 +- .../preceding_credits.yaml | 25 +- .../preceding_credits.yaml | 34 +- .../gov/irs/credits/non_refundable.yaml | 54 +-- .../preceding_credits.yaml | 12 +- .../retirement_saving/preceding_credits.yaml | 28 +- .../afa/afa_other_dependent_credit.py | 5 +- .../reforms/crfb/non_refundable_ss_credit.py | 51 ++- ...new_clean_vehicle_credit_credit_limit.yaml | 14 +- .../used/used_clean_vehicle_credit.yaml | 38 +- .../used_clean_vehicle_credit_potential.yaml | 33 ++ .../gov/irs/credits/credit_limit_order.yaml | 129 +++++++ .../ok/tax/income/credits/ok_federal_ctc.yaml | 113 ++++++ .../tests/test_federal_credit_limit_order.py | 336 ++++++++++++++++++ policyengine_us/tools/pinned_tbs.py | 35 +- .../used/used_clean_vehicle_credit.py | 10 +- .../ok/tax/income/credits/ok_federal_ctc.py | 24 +- 23 files changed, 936 insertions(+), 151 deletions(-) create mode 100644 changelog.d/irs-credit-limit-worksheet-order.fixed.md create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit_potential.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/credit_limit_order.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ok/tax/income/credits/ok_federal_ctc.yaml create mode 100644 policyengine_us/tests/test_federal_credit_limit_order.py diff --git a/changelog.d/irs-credit-limit-worksheet-order.fixed.md b/changelog.d/irs-credit-limit-worksheet-order.fixed.md new file mode 100644 index 00000000000..6b5d8f27f6f --- /dev/null +++ b/changelog.d/irs-credit-limit-worksheet-order.fixed.md @@ -0,0 +1 @@ +Order the federal non-refundable credits as their IRS credit limit worksheets do: the elderly or disabled credit is limited only by the foreign tax and child and dependent care credits (Schedule R) and precedes the education, retirement savings and energy efficient home improvement credits (Forms 8863, 8880 and 5695); the previously owned clean vehicle credit precedes the new clean vehicle credit (Form 8936) and is now limited by tax; the foreign tax credit applies against tax before 2022; the refundable 2021 child and dependent care credit no longer reduces other credits' limits; and Oklahoma's federal Child Tax Credit applies the credits that Schedule 8812 Credit Limit Worksheet A subtracts before the CTC, not those listed before it. diff --git a/policyengine_us/parameters/gov/irs/credits/cdcc/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/cdcc/preceding_credits.yaml index 9e1420d5878..6e81ead28a7 100644 --- a/policyengine_us/parameters/gov/irs/credits/cdcc/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/cdcc/preceding_credits.yaml @@ -1,12 +1,16 @@ description: Non-refundable tax credits which precede the Child and Dependent Care Credit. values: + # Form 2441's Credit Limit Worksheet subtracts Schedule 3 lines 1 (foreign + # tax credit) and 6l (Form 8978, not modeled). 2013-01-01: - - foreign_tax_credit + - foreign_tax_credit metadata: unit: list period: year label: Non-refundable tax credits preceding the CDCC reference: - - title: IRS Schedule 3, Additional Credits and Payments - href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 2025 Instructions for Form 2441, Credit Limit Worksheet, line 2 + href: https://www.irs.gov/pub/irs-pdf/i2441.pdf#page=5 + - title: 2013 Instructions for Form 2441, Credit Limit Worksheet, line 2 + href: https://www.irs.gov/pub/irs-prior/i2441--2013.pdf#page=4 diff --git a/policyengine_us/parameters/gov/irs/credits/clean_vehicle/new/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/clean_vehicle/new/preceding_credits.yaml index 91bee8a4966..f7cf065c79e 100644 --- a/policyengine_us/parameters/gov/irs/credits/clean_vehicle/new/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/clean_vehicle/new/preceding_credits.yaml @@ -1,23 +1,49 @@ description: Non-refundable tax credits which precede the New Clean Vehicle Credit. values: - # Section 25D(c) orders the residential clean energy credit after these. + # Form 8936 line 11 (line 21 before 2023) subtracts the foreign tax, child + # and dependent care, education, retirement savings, energy efficient home + # improvement and elderly or disabled credits, Schedule 3 line 6l (Form + # 8978, not modeled) and, from 2023, the previously owned clean vehicle + # credit (Schedule 3, line 6m). Section 25D(c) orders the residential clean + # energy credit after this credit. 2013-01-01: - foreign_tax_credit - cdcc + - elderly_disabled_credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - energy_efficient_home_improvement_credit + # The Child and Dependent Care Credit was refundable for 2021 only. + 2021-01-01: + - foreign_tax_credit + - elderly_disabled_credit + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + - savers_credit + - energy_efficient_home_improvement_credit + 2022-01-01: + - foreign_tax_credit + - cdcc - elderly_disabled_credit + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + - savers_credit + - energy_efficient_home_improvement_credit + - used_clean_vehicle_credit metadata: unit: list period: year label: Non-refundable tax credits preceding the New Clean Vehicle Credit reference: - - title: IRS Schedule 3, Additional Credits and Payments - href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf - - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit - href: https://www.law.cornell.edu/uscode/text/26/25D#c - title: 2025 Instructions for Form 8936, line 11 href: https://www.irs.gov/pub/irs-pdf/i8936.pdf#page=6 + - title: 2023 Instructions for Form 8936, line 11 + href: https://www.irs.gov/pub/irs-prior/i8936--2023.pdf#page=5 + - title: 2022 Instructions for Form 8936, line 21 + href: https://www.irs.gov/pub/irs-prior/i8936--2022.pdf#page=3 + - title: 2017 Instructions for Form 8936, line 21 + href: https://www.irs.gov/pub/irs-prior/i8936--2017.pdf#page=2 + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c diff --git a/policyengine_us/parameters/gov/irs/credits/clean_vehicle/used/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/clean_vehicle/used/preceding_credits.yaml index 12e0f8b7348..7749d9d3279 100644 --- a/policyengine_us/parameters/gov/irs/credits/clean_vehicle/used/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/clean_vehicle/used/preceding_credits.yaml @@ -1,24 +1,44 @@ description: Non-refundable tax credits which precede the Used Clean Vehicle Credit. values: - # Section 25D(c) orders the residential clean energy credit after these. + # Form 8936 line 16 subtracts Schedule 3 lines 1 through 4 (foreign tax, + # child and dependent care, education and retirement savings credits), 5b + # (energy efficient home improvement credit), 6d (elderly or disabled + # credit) and 6l (Form 8978, not modeled). It does not subtract line 6f, so + # this credit precedes the new clean vehicle credit. Section 25D(c) orders + # the residential clean energy credit after this credit. 2013-01-01: - foreign_tax_credit - cdcc + - elderly_disabled_credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - energy_efficient_home_improvement_credit + # The Child and Dependent Care Credit was refundable for 2021 only. + 2021-01-01: + - foreign_tax_credit + - elderly_disabled_credit + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + - savers_credit + - energy_efficient_home_improvement_credit + 2022-01-01: + - foreign_tax_credit + - cdcc - elderly_disabled_credit - - new_clean_vehicle_credit + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + - savers_credit + - energy_efficient_home_improvement_credit metadata: unit: list period: year label: Non-refundable tax credits preceding the Used Clean Vehicle Credit reference: - - title: IRS Schedule 3, Additional Credits and Payments - href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf - - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit - href: https://www.law.cornell.edu/uscode/text/26/25D#c - title: 2025 Instructions for Form 8936, line 16 href: https://www.irs.gov/pub/irs-pdf/i8936.pdf#page=6 + - title: 2023 Instructions for Form 8936, line 16 + href: https://www.irs.gov/pub/irs-prior/i8936--2023.pdf#page=5 + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c diff --git a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml index 7c1d642f8cb..58a051467ff 100644 --- a/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/ctc_tax_liability_limit/preceding_credits.yaml @@ -1,21 +1,23 @@ description: The IRS subtracts these non-refundable credits from income tax before credits to find the tax liability that limits the non-refundable Child Tax Credit. values: - # The foreign tax credit also precedes the CTC before 2022, but the model - # applies it against regular tax only from 2022 (see non_refundable.yaml). + # Before 2021 the new clean vehicle credit is not modeled; Publication 972 + # also subtracted it (Form 8936, line 23). 2013-01-01: + - foreign_tax_credit - cdcc + - elderly_disabled_credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - energy_efficient_home_improvement_credit - - elderly_disabled_credit # The Child and Dependent Care Credit was refundable for 2021 only. 2021-01-01: + - foreign_tax_credit + - elderly_disabled_credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - energy_efficient_home_improvement_credit - - elderly_disabled_credit - new_clean_vehicle_credit # Line numbers are those of the 2023-2025 Schedule 3. In 2022 the energy # efficient home improvement credit came from Form 5695, line 30, and the @@ -25,13 +27,13 @@ values: 2022-01-01: - foreign_tax_credit # Schedule 3, line 1 - cdcc # Schedule 3, line 2 + - elderly_disabled_credit # Schedule 3, line 6d - non_refundable_american_opportunity_credit # Schedule 3, line 3 - lifetime_learning_credit # Schedule 3, line 3 - savers_credit # Schedule 3, line 4 - energy_efficient_home_improvement_credit # Schedule 3, line 5b - - elderly_disabled_credit # Schedule 3, line 6d - - new_clean_vehicle_credit # Schedule 3, line 6f - used_clean_vehicle_credit # Schedule 3, line 6m + - new_clean_vehicle_credit # Schedule 3, line 6f # The residential clean energy credit (Schedule 3, line 5a) is not listed: # section 25D(c) orders it after every other credit in subpart A, including # the Child Tax Credit. See subsequent_credits.yaml for the case in which it @@ -48,6 +50,8 @@ metadata: href: https://www.irs.gov/pub/irs-prior/i1040s8--2022.pdf#page=4 - title: 2020 Publication 972, Child Tax Credit and Credit for Other Dependents Worksheet, line 12 href: https://www.irs.gov/pub/irs-prior/p972--2020.pdf#page=7 + - title: 2013 Publication 972, Child Tax Credit Worksheet, line 10 + href: https://www.irs.gov/pub/irs-prior/p972--2013.pdf#page=5 - title: 26 U.S. Code § 26(a) - Limitation based on amount of tax href: https://www.law.cornell.edu/uscode/text/26/26#a - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit diff --git a/policyengine_us/parameters/gov/irs/credits/education/american_opportunity_credit/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/education/american_opportunity_credit/preceding_credits.yaml index d61f853230a..498255eddb1 100644 --- a/policyengine_us/parameters/gov/irs/credits/education/american_opportunity_credit/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/education/american_opportunity_credit/preceding_credits.yaml @@ -1,13 +1,31 @@ description: Non-refundable tax credits which precede the American Opportunity Credit. values: + # Form 8863's Credit Limit Worksheet subtracts Schedule 3 lines 1 (foreign + # tax credit), 2 (child and dependent care credit), 6d (elderly or disabled + # credit) and 6l (Form 8978, not modeled). 2013-01-01: - - foreign_tax_credit + - foreign_tax_credit - cdcc + - elderly_disabled_credit + # The Child and Dependent Care Credit was refundable for 2021 only. + 2021-01-01: + - foreign_tax_credit + - elderly_disabled_credit + 2022-01-01: + - foreign_tax_credit + - cdcc + - elderly_disabled_credit metadata: unit: list period: year label: Non-refundable tax credits preceding the American Opportunity Credit reference: - - title: IRS Schedule 3, Additional Credits and Payments - href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 2025 Instructions for Form 8863, Credit Limit Worksheet, line 5 + href: https://www.irs.gov/pub/irs-pdf/i8863.pdf#page=8 + - title: 2021 Instructions for Form 8863, Credit Limit Worksheet, line 5 + href: https://www.irs.gov/pub/irs-prior/i8863--2021.pdf#page=7 + - title: 2013 Instructions for Form 8863, Credit Limit Worksheet, line 5 + href: https://www.irs.gov/pub/irs-prior/i8863--2013.pdf#page=6 + - title: 26 U.S. Code § 21(g) - Special rules for 2021 + href: https://www.law.cornell.edu/uscode/text/26/21#g diff --git a/policyengine_us/parameters/gov/irs/credits/education/lifetime_learning_credit/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/education/lifetime_learning_credit/preceding_credits.yaml index b7c1387950e..190394b35bb 100644 --- a/policyengine_us/parameters/gov/irs/credits/education/lifetime_learning_credit/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/education/lifetime_learning_credit/preceding_credits.yaml @@ -1,8 +1,23 @@ description: Non-refundable tax credits which precede the Lifetime Learning Credit. values: + # Form 8863's Credit Limit Worksheet limits the non-refundable American + # opportunity and lifetime learning credits together (lines 1 to 3) to tax + # less Schedule 3 lines 1, 2, 6d and 6l. Applying the American opportunity + # credit first gives the same total. 2013-01-01: - - foreign_tax_credit + - foreign_tax_credit - cdcc + - elderly_disabled_credit + - non_refundable_american_opportunity_credit + # The Child and Dependent Care Credit was refundable for 2021 only. + 2021-01-01: + - foreign_tax_credit + - elderly_disabled_credit + - non_refundable_american_opportunity_credit + 2022-01-01: + - foreign_tax_credit + - cdcc + - elderly_disabled_credit - non_refundable_american_opportunity_credit metadata: @@ -10,5 +25,11 @@ metadata: period: year label: Non-refundable tax credits preceding the Lifetime Learning Credit reference: - - title: IRS Schedule 3, Additional Credits and Payments - href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 2025 Instructions for Form 8863, Credit Limit Worksheet, lines 1 to 7 + href: https://www.irs.gov/pub/irs-pdf/i8863.pdf#page=8 + - title: 2021 Instructions for Form 8863, Credit Limit Worksheet, lines 1 to 7 + href: https://www.irs.gov/pub/irs-prior/i8863--2021.pdf#page=7 + - title: 2013 Instructions for Form 8863, Credit Limit Worksheet, lines 1 to 7 + href: https://www.irs.gov/pub/irs-prior/i8863--2013.pdf#page=6 + - title: 26 U.S. Code § 21(g) - Special rules for 2021 + href: https://www.law.cornell.edu/uscode/text/26/21#g diff --git a/policyengine_us/parameters/gov/irs/credits/elderly_or_disabled/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/elderly_or_disabled/preceding_credits.yaml index 4bf6a376585..92765d6a2a2 100644 --- a/policyengine_us/parameters/gov/irs/credits/elderly_or_disabled/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/elderly_or_disabled/preceding_credits.yaml @@ -1,22 +1,29 @@ description: Non-refundable tax credits which precede the Elderly or Disabled Credit. values: - # Section 25D(c) orders the residential clean energy credit after these. + # Schedule R's Credit Limit Worksheet subtracts only Schedule 3 lines 1 + # (foreign tax credit), 2 (child and dependent care credit) and 6l (Form + # 8978, not modeled). The education, retirement savings, energy and clean + # vehicle credits come after this credit. 2013-01-01: - foreign_tax_credit - cdcc - - non_refundable_american_opportunity_credit - - lifetime_learning_credit - - savers_credit - - energy_efficient_home_improvement_credit + # The Child and Dependent Care Credit was refundable for 2021 only. + 2021-01-01: + - foreign_tax_credit + 2022-01-01: + - foreign_tax_credit + - cdcc metadata: unit: list period: year label: Non-refundable tax credits preceding the Elderly or Disabled Credit reference: - - title: IRS Schedule 3, Additional Credits and Payments - href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf - - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit - href: https://www.law.cornell.edu/uscode/text/26/25D#c - title: 2025 Instructions for Schedule R (Form 1040), Credit Limit Worksheet, line 2 href: https://www.irs.gov/pub/irs-pdf/i1040sr.pdf#page=4 + - title: 2021 Instructions for Schedule R (Form 1040), Credit Limit Worksheet—Line 21, line 2 + href: https://www.irs.gov/pub/irs-prior/i1040sr--2021.pdf#page=3 + - title: 2013 Instructions for Schedule R (Form 1040A or 1040), Credit Limit Worksheet—Line 21, line 2 + href: https://www.irs.gov/pub/irs-prior/i1040sr--2013.pdf#page=3 + - title: 26 U.S. Code § 21(g) - Special rules for 2021 + href: https://www.law.cornell.edu/uscode/text/26/21#g diff --git a/policyengine_us/parameters/gov/irs/credits/energy_efficient_home_improvement/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/energy_efficient_home_improvement/preceding_credits.yaml index 5cd5596097c..ef7987f71e6 100644 --- a/policyengine_us/parameters/gov/irs/credits/energy_efficient_home_improvement/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/energy_efficient_home_improvement/preceding_credits.yaml @@ -1,9 +1,29 @@ description: Non-refundable tax credits which precede the Energy Efficient Home Improvement Credit. values: - # Section 25D(c) orders the residential clean energy credit after these. + # Form 5695's Energy Efficient Home Improvement Credit Limit Worksheet (the + # Nonbusiness Energy Property Credit Limit Worksheet before 2022) subtracts + # the foreign tax, child and dependent care, elderly or disabled, education + # and retirement savings credits, and Schedule 3 line 6l (Form 8978, not + # modeled). Section 25D(c) orders the residential clean energy credit after + # this credit. 2013-01-01: - foreign_tax_credit - cdcc + - elderly_disabled_credit + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + - savers_credit + # The Child and Dependent Care Credit was refundable for 2021 only. + 2021-01-01: + - foreign_tax_credit + - elderly_disabled_credit + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + - savers_credit + 2022-01-01: + - foreign_tax_credit + - cdcc + - elderly_disabled_credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit @@ -13,9 +33,13 @@ metadata: period: year label: Non-refundable tax credits preceding the Energy Efficient Home Improvement Credit reference: - - title: IRS Schedule 3, Additional Credits and Payments - href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 2025 Instructions for Form 5695, Energy Efficient Home Improvement Credit Limit Worksheet—Line 31, line 2 + href: https://www.irs.gov/pub/irs-pdf/i5695.pdf#page=7 + - title: 2022 Instructions for Form 5695, Energy Efficient Home Improvement Credit Limit Worksheet—Line 29, line 2 + href: https://www.irs.gov/pub/irs-prior/i5695--2022.pdf#page=7 + - title: 2013 Form 5695, Nonbusiness Energy Property Credit Limit Worksheet—Line 29, line 2 + href: https://www.irs.gov/pub/irs-prior/f5695--2013.pdf#page=6 - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit href: https://www.law.cornell.edu/uscode/text/26/25D#c - - title: 2025 Instructions for Form 5695, Energy Efficient Home Improvement Credit Limit Worksheet, line 2 - href: https://www.irs.gov/pub/irs-pdf/i5695.pdf#page=7 + - title: 26 U.S. Code § 21(g) - Special rules for 2021 + href: https://www.law.cornell.edu/uscode/text/26/21#g diff --git a/policyengine_us/parameters/gov/irs/credits/non_refundable.yaml b/policyengine_us/parameters/gov/irs/credits/non_refundable.yaml index 1e103d12d6b..e1b804c2c00 100644 --- a/policyengine_us/parameters/gov/irs/credits/non_refundable.yaml +++ b/policyengine_us/parameters/gov/irs/credits/non_refundable.yaml @@ -1,47 +1,53 @@ -description: Non-refundable tax credits. +description: Non-refundable tax credits, in the order the IRS credit limit worksheets apply them against income tax. values: + # Each credit's limit subtracts the credits listed before it (see each + # credit's preceding_credits.yaml). The order follows the credit limit + # worksheets, not the Schedule 3 line order: the elderly or disabled credit + # precedes the education credits, the previously owned clean vehicle credit + # precedes the new clean vehicle credit, and the residential clean energy + # credit comes last (26 U.S.C. 25D(c)). 2013-01-01: + - foreign_tax_credit - cdcc - elderly_disabled_credit # Adoption expense credit - - non_refundable_ctc - # Home mortgage credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - - residential_clean_energy_credit - energy_efficient_home_improvement_credit + - non_refundable_ctc + # Home mortgage credit + - residential_clean_energy_credit # CDCC became refundable for 2021 only. # And we only define the EV credit for 2021 onward because the law changed # then. 2021-01-01: + - foreign_tax_credit - elderly_disabled_credit # Adoption expense credit - - non_refundable_ctc - # Home mortgage credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - - residential_clean_energy_credit - energy_efficient_home_improvement_credit - new_clean_vehicle_credit + - non_refundable_ctc + # Home mortgage credit + - residential_clean_energy_credit # CDCC became non-refundable again in 2022. 2022-01-01: - # Sequencing the credits based on Schedule 3 (Form 1040) - - foreign_tax_credit # 1 - - cdcc # 2 - - non_refundable_american_opportunity_credit # 3 - - lifetime_learning_credit # 3 - - savers_credit # 4 - - residential_clean_energy_credit # 5a - - energy_efficient_home_improvement_credit # 5b - - elderly_disabled_credit # 6d + - foreign_tax_credit # Schedule 3, line 1 + - cdcc # Schedule 3, line 2 + - elderly_disabled_credit # Schedule 3, line 6d + - non_refundable_american_opportunity_credit # Schedule 3, line 3 + - lifetime_learning_credit # Schedule 3, line 3 + - savers_credit # Schedule 3, line 4 + - energy_efficient_home_improvement_credit # Schedule 3, line 5b # Inflation Reduction Act introduces a used EV credit. - - new_clean_vehicle_credit # 6f - - used_clean_vehicle_credit # 6f - # Adoption expense credit - - non_refundable_ctc - # Home mortgage credit + - used_clean_vehicle_credit # Schedule 3, line 6m + - new_clean_vehicle_credit # Schedule 3, line 6f + - non_refundable_ctc # Form 1040, line 19 + # Adoption expense credit, home mortgage credit + - residential_clean_energy_credit # Schedule 3, line 5a metadata: unit: list @@ -51,5 +57,11 @@ metadata: href: https://www.law.cornell.edu/uscode/text/26/subtitle-A/chapter-1/subchapter-A/part-IV/subpart-A - title: 26 U.S. Code § 21 - Expenses for household and dependent care services necessary for gainful employment (g)(1) href: https://www.law.cornell.edu/uscode/text/26/21#g_1 + - title: 26 U.S. Code § 25D(c) - Carryforward of unused credit + href: https://www.law.cornell.edu/uscode/text/26/25D#c - title: 26 U.S. Code § 30D - New qualified plug-in electric drive motor vehicles (c)(2) href: https://www.law.cornell.edu/uscode/text/26/30D#c_2 + - title: 2025 Schedule 3 (Form 1040), Additional Credits and Payments + href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 2020 Publication 972, Child Tax Credit and Credit for Other Dependents Worksheet, line 11 + href: https://www.irs.gov/pub/irs-prior/p972--2020.pdf#page=7 diff --git a/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml index 89841582ab1..9b4ee84f1e5 100644 --- a/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/residential_clean_energy/preceding_credits.yaml @@ -3,30 +3,30 @@ values: 2013-01-01: - foreign_tax_credit - cdcc + - elderly_disabled_credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - energy_efficient_home_improvement_credit - - elderly_disabled_credit # The Child and Dependent Care Credit was refundable for 2021 only. 2021-01-01: - foreign_tax_credit + - elderly_disabled_credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - savers_credit - energy_efficient_home_improvement_credit - - elderly_disabled_credit - new_clean_vehicle_credit 2022-01-01: - foreign_tax_credit # Schedule 3, line 1 - cdcc # Schedule 3, line 2 + - elderly_disabled_credit # Schedule 3, line 6d - non_refundable_american_opportunity_credit # Schedule 3, line 3 - lifetime_learning_credit # Schedule 3, line 3 - savers_credit # Schedule 3, line 4 - energy_efficient_home_improvement_credit # Form 5695, line 32 (line 30 in 2022) - - elderly_disabled_credit # Schedule 3, line 6d - - new_clean_vehicle_credit # Schedule 3, line 6f - used_clean_vehicle_credit # Schedule 3, line 6m + - new_clean_vehicle_credit # Schedule 3, line 6f metadata: unit: list @@ -37,3 +37,7 @@ metadata: href: https://www.law.cornell.edu/uscode/text/26/25D#c - title: 2025 Instructions for Form 5695, Residential Clean Energy Credit Limit Worksheet, line 2 href: https://www.irs.gov/pub/irs-pdf/i5695.pdf#page=5 + - title: 2022 Instructions for Form 5695, Residential Clean Energy Credit Limit Worksheet—Line 14, line 2 + href: https://www.irs.gov/pub/irs-prior/i5695--2022.pdf#page=4 + - title: 2013 Form 5695, Residential Energy Efficient Property Credit Limit Worksheet—Line 14, lines 2 to 9 + href: https://www.irs.gov/pub/irs-prior/f5695--2013.pdf#page=5 diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/preceding_credits.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/preceding_credits.yaml index c8acae364b7..34d76e18611 100644 --- a/policyengine_us/parameters/gov/irs/credits/retirement_saving/preceding_credits.yaml +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/preceding_credits.yaml @@ -1,8 +1,24 @@ description: Non-refundable tax credits which precede the Retirement Savings Credit. values: + # Form 8880's Credit Limit Worksheet subtracts Schedule 3 lines 1 through 3 + # (foreign tax, child and dependent care, and education credits), 6d + # (elderly or disabled credit) and 6l (Form 8978, not modeled). 2013-01-01: - - foreign_tax_credit + - foreign_tax_credit - cdcc + - elderly_disabled_credit + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + # The Child and Dependent Care Credit was refundable for 2021 only. + 2021-01-01: + - foreign_tax_credit + - elderly_disabled_credit + - non_refundable_american_opportunity_credit + - lifetime_learning_credit + 2022-01-01: + - foreign_tax_credit + - cdcc + - elderly_disabled_credit - non_refundable_american_opportunity_credit - lifetime_learning_credit @@ -11,5 +27,11 @@ metadata: period: year label: Non-refundable tax credits preceding the Retirement Savings Credit reference: - - title: IRS Schedule 3, Additional Credits and Payments - href: https://www.irs.gov/pub/irs-pdf/f1040s3.pdf + - title: 2025 Form 8880, Credit Limit Worksheet, line 2 + href: https://www.irs.gov/pub/irs-pdf/f8880.pdf#page=2 + - title: 2021 Form 8880, Credit Limit Worksheet, line 2 + href: https://www.irs.gov/pub/irs-prior/f8880--2021.pdf#page=2 + - title: 2013 Form 8880, Credit Limit Worksheet, line 2 + href: https://www.irs.gov/pub/irs-prior/f8880--2013.pdf#page=2 + - title: 26 U.S. Code § 21(g) - Special rules for 2021 + href: https://www.law.cornell.edu/uscode/text/26/21#g diff --git a/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py b/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py index b858c81db6a..ee1bf39d602 100644 --- a/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py +++ b/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py @@ -200,15 +200,18 @@ def formula(person, period, parameters): def modify_parameters(parameters): credits = parameters.gov.irs.credits + # The baseline order of gov.irs.credits.non_refundable, with the + # separate other dependent credit in the Child Tax Credit's place. credits_before_residential_clean_energy = [ + "foreign_tax_credit", "cdcc", "elderly_disabled_credit", "non_refundable_american_opportunity_credit", "lifetime_learning_credit", "savers_credit", "energy_efficient_home_improvement_credit", - "new_clean_vehicle_credit", "used_clean_vehicle_credit", + "new_clean_vehicle_credit", "other_dependent_credit", ] credits.non_refundable.update( diff --git a/policyengine_us/reforms/crfb/non_refundable_ss_credit.py b/policyengine_us/reforms/crfb/non_refundable_ss_credit.py index 959b2108285..1687476e68d 100644 --- a/policyengine_us/reforms/crfb/non_refundable_ss_credit.py +++ b/policyengine_us/reforms/crfb/non_refundable_ss_credit.py @@ -84,44 +84,35 @@ def formula(tax_unit, period, parameters): return highest_rate def modify_parameters(parameters): - parameters.gov.irs.credits.non_refundable.update( + # The baseline order of gov.irs.credits.non_refundable, with the + # Social Security credit after the other credits that precede the + # Child Tax Credit. It precedes the CTC and, like every credit, the + # residential clean energy credit (26 U.S.C. 25D(c)). + credits_before_ctc = [ + "foreign_tax_credit", + "cdcc", + "elderly_disabled_credit", + "non_refundable_american_opportunity_credit", + "lifetime_learning_credit", + "savers_credit", + "energy_efficient_home_improvement_credit", + "used_clean_vehicle_credit", + "new_clean_vehicle_credit", + "ss_credit", + ] + credits = parameters.gov.irs.credits + credits.non_refundable.update( start=instant("2026-01-01"), - value=[ - "foreign_tax_credit", - "cdcc", - "non_refundable_american_opportunity_credit", - "lifetime_learning_credit", - "savers_credit", - "residential_clean_energy_credit", - "energy_efficient_home_improvement_credit", - "elderly_disabled_credit", - "new_clean_vehicle_credit", - "used_clean_vehicle_credit", - "non_refundable_ctc", - "ss_credit", - ], + value=credits_before_ctc + + ["non_refundable_ctc", "residential_clean_energy_credit"], ) - # The credit precedes the Child Tax Credit and, like every credit, - # the residential clean energy credit (26 U.S.C. 25D(c)). - credits = parameters.gov.irs.credits for preceding_credits in ( credits.ctc_tax_liability_limit.preceding_credits, credits.residential_clean_energy.preceding_credits, ): preceding_credits.update( start=instant("2026-01-01"), - value=[ - "foreign_tax_credit", - "cdcc", - "non_refundable_american_opportunity_credit", - "lifetime_learning_credit", - "savers_credit", - "energy_efficient_home_improvement_credit", - "elderly_disabled_credit", - "new_clean_vehicle_credit", - "used_clean_vehicle_credit", - "ss_credit", - ], + value=credits_before_ctc, ) return parameters diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/new/new_clean_vehicle_credit_credit_limit.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/new/new_clean_vehicle_credit_credit_limit.yaml index 7429c6c5aa2..7f43d203806 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/new/new_clean_vehicle_credit_credit_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/new/new_clean_vehicle_credit_credit_limit.yaml @@ -1,4 +1,4 @@ -- name: Income tax reduced by previous credits +- name: Income tax reduced by previous credits; the CDCC was refundable in 2021 and does not precede. period: 2021 input: new_clean_vehicle_credit_eligible: true @@ -6,4 +6,14 @@ cdcc: 1_000 savers_credit: 300 output: - new_clean_vehicle_credit_credit_limit: 1_700 + new_clean_vehicle_credit_credit_limit: 2_700 + +- name: The previously owned clean vehicle credit precedes the new clean vehicle credit (Form 8936, line 11). + period: 2025 + input: + new_clean_vehicle_credit_eligible: true + income_tax_before_credits: 3_000 + cdcc: 1_000 + used_clean_vehicle_credit: 800 + output: + new_clean_vehicle_credit_credit_limit: 1_200 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit.yaml index d57645b226e..e208d95bb2d 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit.yaml @@ -1,33 +1,19 @@ -- name: No credit if no sale price. - period: 2022 +- name: Limited by tax after preceding credits; the unused credit is lost (Form 8936, lines 16-18). + period: 2025 input: used_clean_vehicle_credit_eligible: true - used_clean_vehicle_sale_price: 0 + used_clean_vehicle_credit_potential: 4_000 + income_tax_before_credits: 3_000 + elderly_disabled_credit: 500 output: - used_clean_vehicle_credit: 0 + used_clean_vehicle_credit_credit_limit: 2_500 + used_clean_vehicle_credit: 2_500 -- name: 30% of sale price up to $4,000. - period: 2023 - absolute_error_margin: 0.01 +- name: Potential below the limit. + period: 2025 input: used_clean_vehicle_credit_eligible: true - used_clean_vehicle_sale_price: 100 + used_clean_vehicle_credit_potential: 1_000 + income_tax_before_credits: 3_000 output: - used_clean_vehicle_credit: 30 - -- name: 30% up to just below the cap. - period: 2023 - absolute_error_margin: 0.01 - input: - used_clean_vehicle_credit_eligible: true - used_clean_vehicle_sale_price: 13_333 - output: - used_clean_vehicle_credit: 3_999.9 - -- name: Capped at $4,000. - period: 2023 - input: - used_clean_vehicle_credit_eligible: true - used_clean_vehicle_sale_price: 13_334 - output: - used_clean_vehicle_credit: 4_000 + used_clean_vehicle_credit: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit_potential.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit_potential.yaml new file mode 100644 index 00000000000..615bb1db58d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit_potential.yaml @@ -0,0 +1,33 @@ +- name: No credit if no sale price. + period: 2022 + input: + used_clean_vehicle_credit_eligible: true + used_clean_vehicle_sale_price: 0 + output: + used_clean_vehicle_credit_potential: 0 + +- name: 30% of sale price up to $4,000. + period: 2023 + absolute_error_margin: 0.01 + input: + used_clean_vehicle_credit_eligible: true + used_clean_vehicle_sale_price: 100 + output: + used_clean_vehicle_credit_potential: 30 + +- name: 30% up to just below the cap. + period: 2023 + absolute_error_margin: 0.01 + input: + used_clean_vehicle_credit_eligible: true + used_clean_vehicle_sale_price: 13_333 + output: + used_clean_vehicle_credit_potential: 3_999.9 + +- name: Capped at $4,000. + period: 2023 + input: + used_clean_vehicle_credit_eligible: true + used_clean_vehicle_sale_price: 13_334 + output: + used_clean_vehicle_credit_potential: 4_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/credit_limit_order.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/credit_limit_order.yaml new file mode 100644 index 00000000000..957dce46a41 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/credit_limit_order.yaml @@ -0,0 +1,129 @@ +# Each non-refundable credit's limit is income tax before credits less the +# credits its own credit limit worksheet subtracts. The worksheets order the +# credits: foreign tax, child and dependent care, elderly or disabled, +# education, retirement savings, energy efficient home improvement, previously +# owned clean vehicle, new clean vehicle, Child Tax Credit, residential clean +# energy. Expected values are worked by hand from the worksheet lines cited. + +- name: Schedule R subtracts only the foreign tax and child and dependent care credits. + period: 2025 + input: + income_tax_before_credits: 3_000 + foreign_tax_credit: 200 + cdcc: 300 + non_refundable_american_opportunity_credit: 1_000 + savers_credit: 500 + energy_efficient_home_improvement_credit: 400 + output: + # 2025 Schedule R instructions, Credit Limit Worksheet: line 1 = 3,000; + # line 2 = Schedule 3 lines 1, 2 and 6l = 200 + 300; line 3 = 2,500. + elderly_disabled_credit_credit_limit: 2_500 + +- name: Form 8863 subtracts the elderly or disabled credit. + period: 2025 + input: + income_tax_before_credits: 3_000 + foreign_tax_credit: 200 + cdcc: 300 + elderly_disabled_credit: 400 + savers_credit: 500 + output: + # 2025 Form 8863 instructions, Credit Limit Worksheet: line 5 = Schedule 3 + # lines 1, 2, 6d and 6l = 200 + 300 + 400; line 6 = 3,000 - 900 = 2,100. + non_refundable_american_opportunity_credit_credit_limit: 2_100 + +- name: Form 8863 limits the lifetime learning credit together with the American opportunity credit. + period: 2025 + input: + income_tax_before_credits: 3_000 + foreign_tax_credit: 200 + cdcc: 300 + elderly_disabled_credit: 400 + non_refundable_american_opportunity_credit: 1_000 + output: + # Credit Limit Worksheet line 6 = 2,100, of which the American + # opportunity credit (line 2) uses 1,000. + lifetime_learning_credit_credit_limit: 1_100 + +- name: Form 8880 subtracts the elderly or disabled credit. + period: 2025 + input: + income_tax_before_credits: 3_000 + foreign_tax_credit: 200 + cdcc: 300 + elderly_disabled_credit: 400 + non_refundable_american_opportunity_credit: 1_000 + lifetime_learning_credit: 100 + energy_efficient_home_improvement_credit: 600 + output: + # 2025 Form 8880, Credit Limit Worksheet: line 2 = Schedule 3 lines 1 + # through 3, 6d and 6l = 200 + 300 + 1,100 + 400; line 3 = 1,000. + savers_credit_credit_limit: 1_000 + +- name: Form 5695 Part II subtracts the elderly or disabled credit. + period: 2025 + input: + income_tax_before_credits: 3_000 + foreign_tax_credit: 200 + cdcc: 300 + elderly_disabled_credit: 400 + non_refundable_american_opportunity_credit: 1_000 + savers_credit: 300 + used_clean_vehicle_credit: 700 + output: + # 2025 Form 5695 instructions, line 31 worksheet: line 2 = 6l + foreign + # tax + child and dependent care + elderly + education + retirement + # savings = 200 + 300 + 400 + 1,000 + 300; line 3 = 800. + energy_efficient_home_improvement_credit_credit_limit: 800 + +- name: The elderly or disabled credit takes priority over the retirement savings credit. + period: 2025 + input: + income_tax_before_credits: 1_000 + elderly_disabled_credit_potential: 600 + savers_credit_potential: 800 + output: + # Schedule R: 1,000 - 0 = 1,000, so the full 600. Form 8880: 1,000 - 600 + # = 400, so 400 of the 800. Before, the retirement savings credit took + # 800 first and left 200 for the elderly or disabled credit. + elderly_disabled_credit: 600 + savers_credit: 400 + income_tax_capped_non_refundable_credits: 1_000 + +- name: In 2021 the refundable child and dependent care credit does not reduce later limits. + period: 2021 + input: + income_tax_before_credits: 3_000 + foreign_tax_credit: 200 + cdcc: 1_000 + elderly_disabled_credit: 400 + output: + # The 2021 credit was refundable (26 U.S.C. 21(g)) and claimed on + # Schedule 3, line 13g, not line 2. + elderly_disabled_credit_credit_limit: 2_800 + non_refundable_american_opportunity_credit_credit_limit: 2_400 + +- name: The foreign tax credit reduces income tax before 2022. + period: 2020 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 50_000 + tax_units: + tax_unit: + members: [head] + foreign_tax_credit_potential: 500 + households: + household: + members: [head] + state_code: TX + output: + # 2020 single: taxable income 50,000 - 12,400 = 37,600; tax = 10% x + # 9,875 + 12% x 27,725 = 4,314.50 (Form 1040, line 16). The 500 foreign + # tax credit (Schedule 3, line 1) is within that limit. + income_tax_before_credits: 4_314.5 + foreign_tax_credit: 500 + income_tax_capped_non_refundable_credits: 500 + income_tax_before_refundable_credits: 3_814.5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ok/tax/income/credits/ok_federal_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/ok/tax/income/credits/ok_federal_ctc.yaml new file mode 100644 index 00000000000..4f8d7ea0d38 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ok/tax/income/credits/ok_federal_ctc.yaml @@ -0,0 +1,113 @@ +# Federal Child Tax Credit allowed for Oklahoma's Child Care/Child Tax Credit: +# the refundable CTC plus the non-refundable CTC actually applied against +# federal income tax. non_refundable_ctc is the CTC that is not refunded, +# before the tax limit; when the non-refundable credits exceed income tax, the +# credits that Schedule 8812 Credit Limit Worksheet A subtracts are applied +# before the CTC (line 2, and on line 4 the residential clean energy credit +# when Credit Limit Worksheet B applies). Otherwise the residential clean +# energy credit follows the CTC (26 U.S.C. 25D(c)): +# https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4 + +- name: The residential clean energy credit does not displace the CTC when credits exceed tax. + period: 2025 + input: + state_code: OK + income_tax_before_credits: 1_000 + non_refundable_ctc: 500 + refundable_ctc: 0 + residential_clean_energy_credit: 1_000 + output: + # Credits 500 + 1,000 = 1,500 exceed tax of 1,000, so 1,000 is applied. + # No credit precedes the CTC, so all 500 of it is applied. + income_tax_capped_non_refundable_credits: 1_000 + ok_federal_ctc: 500 + +- name: A credit on Credit Limit Worksheet A, line 2, is applied before the CTC when credits exceed tax. + period: 2025 + input: + state_code: OK + income_tax_before_credits: 1_000 + elderly_disabled_credit: 800 + non_refundable_ctc: 500 + refundable_ctc: 0 + output: + # Credits 800 + 500 = 1,300 exceed tax of 1,000; the elderly or disabled + # credit takes 800 first, leaving 200 for the CTC. + income_tax_capped_non_refundable_credits: 1_000 + ok_federal_ctc: 200 + +- name: With Credit Limit Worksheet B, the residential clean energy credit is applied before the CTC. + period: 2025 + input: + state_code: OK + income_tax_before_credits: 1_000 + ctc_credit_limit_worksheet_b_applies: true + non_refundable_ctc: 500 + refundable_ctc: 0 + residential_clean_energy_credit: 800 + output: + # Worksheet A: line 3 = 1,000; line 4 = 800 (Worksheet B, line 15); + # line 5 = 200. + income_tax_capped_non_refundable_credits: 1_000 + ok_federal_ctc: 200 + +- name: Credit for other dependents larger than tax, computed in full. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + employment_income: 26_625 + child: + age: 17 + tax_units: + tax_unit: + members: [parent, child] + solar_electric_property_expenditures: 10_000 + households: + household: + members: [parent, child] + state_code: OK + output: + # Head of household: taxable income 26,625 - 23,625 = 3,000; tax = 300. + income_tax_before_credits: 300 + # The 500 credit for other dependents is limited to 300 (Worksheet A, + # line 5), which leaves nothing for the residential clean energy credit. + non_refundable_ctc: 500 + refundable_ctc: 0 + residential_clean_energy_credit: 0 + income_tax_capped_non_refundable_credits: 300 + ok_federal_ctc: 300 + +- name: Credit for other dependents and a residential clean energy credit, computed in full. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + employment_income: 33_625 + child: + age: 17 + tax_units: + tax_unit: + members: [parent, child] + solar_electric_property_expenditures: 10_000 + households: + household: + members: [parent, child] + state_code: OK + output: + # Head of household: taxable income 33,625 - 23,625 = 10,000; tax 10% = + # 1,000 (Form 1040, line 18). + income_tax_before_credits: 1_000 + # A 17-year-old dependent gets the 500 credit for other dependents, none of + # which is refundable; nothing on Worksheet A, line 2. + non_refundable_ctc: 500 + refundable_ctc: 0 + # Form 5695, line 14 worksheet: 1,000 - 500 = 500; the credit before the + # limit is 30% x 10,000 = 3,000. + residential_clean_energy_credit: 500 + income_tax_capped_non_refundable_credits: 1_000 + ok_federal_ctc: 500 diff --git a/policyengine_us/tests/test_federal_credit_limit_order.py b/policyengine_us/tests/test_federal_credit_limit_order.py new file mode 100644 index 00000000000..bb05a1a844c --- /dev/null +++ b/policyengine_us/tests/test_federal_credit_limit_order.py @@ -0,0 +1,336 @@ +"""The federal non-refundable credits are applied in one order. + +Each credit's own credit limit worksheet subtracts the credits that precede it +from income tax before credits. Read together, the 2013 to 2025 worksheets +give a single order (LEGAL_ORDER below, written from the worksheets and not +from the model's parameters): + +- Form 2441, Credit Limit Worksheet: the foreign tax credit. +- Schedule R, Credit Limit Worksheet: Schedule 3 lines 1 and 2. +- Form 8863, Credit Limit Worksheet: Schedule 3 lines 1, 2 and 6d. +- Form 8880, Credit Limit Worksheet: Schedule 3 lines 1 through 3 and 6d. +- Form 5695, line 31 worksheet: those and Schedule 3 line 4. +- Form 8936, line 16: Schedule 3 lines 1 through 4, 5b and 6d. +- Form 8936, line 11: the same and line 6m. +- Schedule 8812, Credit Limit Worksheet A: lines 1 through 4, 5b, 6d, 6f, 6m. +- Form 5695, line 14 worksheet: every other credit, including the CTC. + +These tests check that the parameters encode that order, and that the model's +credits match a direct sequential application of 26 U.S.C. 26(a) in that +order. The households take the standard deduction, so the CTC limit's no-SALT +liability equals actual liability. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import CountryTaxBenefitSystem, Simulation +from policyengine_us.reforms.congress.afa.afa_other_dependent_credit import ( + create_afa_other_dependent_credit, +) +from policyengine_us.reforms.crfb.non_refundable_ss_credit import ( + non_refundable_ss_credit_reform, +) +from policyengine_us.tools.pinned_tbs import get_2020_irc_tbs + +LEGAL_ORDER = [ + "foreign_tax_credit", + "cdcc", + "elderly_disabled_credit", + "non_refundable_american_opportunity_credit", + "lifetime_learning_credit", + "savers_credit", + "energy_efficient_home_improvement_credit", + "used_clean_vehicle_credit", + "new_clean_vehicle_credit", + "non_refundable_ctc", + "residential_clean_energy_credit", +] + +# The parameter listing the credits that precede each credit. The foreign tax +# credit has none: its limit is the whole tax. +PRECEDING_CREDITS = { + "cdcc": "cdcc.preceding_credits", + "elderly_disabled_credit": "elderly_or_disabled.preceding_credits", + "non_refundable_american_opportunity_credit": ( + "education.american_opportunity_credit.preceding_credits" + ), + "lifetime_learning_credit": "education.lifetime_learning_credit.preceding_credits", + "savers_credit": "retirement_saving.preceding_credits", + "energy_efficient_home_improvement_credit": ( + "energy_efficient_home_improvement.preceding_credits" + ), + "used_clean_vehicle_credit": "clean_vehicle.used.preceding_credits", + "new_clean_vehicle_credit": "clean_vehicle.new.preceding_credits", + "non_refundable_ctc": "ctc_tax_liability_limit.preceding_credits", + "residential_clean_energy_credit": "residential_clean_energy.preceding_credits", +} + +DATES = [ + "2013-01-01", + "2017-06-01", + "2020-06-01", + "2021-06-01", + "2022-06-01", + "2023-06-01", + "2025-06-01", + "2026-06-01", + "2035-06-01", +] + + +def credit_list(credits, path, date): + node = credits + for part in path.split("."): + node = getattr(node, part) + return list(node(date)) + + +def expected_preceding(non_refundable, credit): + before = non_refundable[: non_refundable.index(credit)] + if credit == "residential_clean_energy_credit": + # Its limit subtracts the CTC in the formula (Form 1040, line 19, or + # Credit Limit Worksheet B, line 14), not through the list. + before = [c for c in before if c != "non_refundable_ctc"] + return before + + +@pytest.fixture(scope="module") +def baseline_system(): + return CountryTaxBenefitSystem() + + +@pytest.mark.parametrize("date", DATES) +def test_lists_follow_the_worksheet_order(baseline_system, date): + credits = baseline_system.parameters.gov.irs.credits + non_refundable = list(credits.non_refundable(date)) + assert non_refundable == [c for c in LEGAL_ORDER if c in non_refundable] + assert non_refundable[0] == "foreign_tax_credit" + for credit in non_refundable[1:]: + assert credit in PRECEDING_CREDITS, f"{credit} has no preceding list" + assert credit_list( + credits, PRECEDING_CREDITS[credit], date + ) == expected_preceding(non_refundable, credit), (credit, date) + + +def test_2020_irc_pin_keeps_the_order_in_2021(baseline_system): + credits = get_2020_irc_tbs(baseline_system).parameters.gov.irs.credits + date = "2021-06-01" + non_refundable = list(credits.non_refundable(date)) + assert "cdcc" in non_refundable + assert non_refundable == [c for c in LEGAL_ORDER if c in non_refundable] + for credit in non_refundable[1:]: + assert credit_list( + credits, PRECEDING_CREDITS[credit], date + ) == expected_preceding(non_refundable, credit), credit + + +@pytest.mark.parametrize( + "reform, credit_before_25d", + [ + (create_afa_other_dependent_credit(), "other_dependent_credit"), + (non_refundable_ss_credit_reform(), "non_refundable_ctc"), + ], +) +def test_reforms_order_the_ctc_and_25d_lists(reform, credit_before_25d): + credits = reform(CountryTaxBenefitSystem()).parameters.gov.irs.credits + date = "2026-06-01" + non_refundable = list(credits.non_refundable(date)) + assert non_refundable[0] == "foreign_tax_credit" + assert non_refundable[-2:] == [credit_before_25d, "residential_clean_energy_credit"] + before_ctc = non_refundable[: non_refundable.index(credit_before_25d)] + if credit_before_25d == "other_dependent_credit": + # The separate credit for other dependents takes the CTC's place. + before_ctc = before_ctc + ["other_dependent_credit"] + for path in ( + "ctc_tax_liability_limit.preceding_credits", + "residential_clean_energy.preceding_credits", + ): + assert credit_list(credits, path, date) == before_ctc, path + + +# Households with every modeled non-refundable credit at once. The potentials +# (each credit before its tax-liability limit) are inputs. +POTENTIALS = { + "foreign_tax_credit": "foreign_tax_credit_potential", + "cdcc": "cdcc_potential", + "elderly_disabled_credit": "elderly_disabled_credit_potential", + "non_refundable_american_opportunity_credit": ( + "non_refundable_american_opportunity_credit_potential" + ), + "lifetime_learning_credit": "lifetime_learning_credit_potential", + "savers_credit": "savers_credit_potential", + "energy_efficient_home_improvement_credit": ( + "energy_efficient_home_improvement_credit_potential" + ), + "used_clean_vehicle_credit": "used_clean_vehicle_credit_potential", + "new_clean_vehicle_credit": "new_clean_vehicle_credit_potential", +} +ELIGIBILITY = { + "used_clean_vehicle_credit": "used_clean_vehicle_credit_eligible", + "new_clean_vehicle_credit": "new_clean_vehicle_credit_eligible", +} + + +def build_situation(households, year): + people, tax_units, marital_units = {}, {}, {} + groups = {"households": {}, "spm_units": {}, "families": {}} + for i, h in enumerate(households): + head = f"head_{i}" + people[head] = { + "age": {year: h["age"]}, + "employment_income": {year: h["wages"]}, + "taxable_interest_income": {year: h["interest"]}, + } + members = [head] + marital_units[f"marital_unit_{i}"] = {"members": [head]} + if h["married"]: + spouse = f"spouse_{i}" + people[spouse] = {"age": {year: h["age"]}} + members.append(spouse) + marital_units[f"marital_unit_{i}"]["members"].append(spouse) + for j, age in enumerate(h["dependent_ages"]): + dependent = f"dependent_{i}_{j}" + people[dependent] = {"age": {year: age}} + members.append(dependent) + marital_units[f"marital_unit_{i}_{j}"] = {"members": [dependent]} + tax_unit = { + "members": members, + "solar_electric_property_expenditures": {year: h["solar"]}, + } + for credit, potential in POTENTIALS.items(): + tax_unit[potential] = {year: h[credit]} + for credit, eligible in ELIGIBILITY.items(): + tax_unit[eligible] = {year: h[credit] > 0} + tax_units[f"tax_unit_{i}"] = tax_unit + groups["households"][f"household_{i}"] = { + "members": members, + "state_code": {year: "TX"}, + } + groups["spm_units"][f"spm_unit_{i}"] = {"members": members} + groups["families"][f"family_{i}"] = {"members": members} + return { + "people": people, + "tax_units": tax_units, + "marital_units": marital_units, + **groups, + } + + +def check_households(households, year): + simulation = Simulation(situation=build_situation(households, year)) + + def calc(variable): + return np.asarray(simulation.calculate(variable, year), dtype=float) + + credits = simulation.tax_benefit_system.parameters(year).gov.irs.credits + non_refundable = list(credits.non_refundable) + tax = calc("income_tax_before_credits") + tol = 0.01 + + # Apply 26(a) in the worksheet order: each credit is the smaller of its + # potential and the tax the earlier credits leave. + remaining = np.maximum(tax, 0) + limited = {} + for credit in LEGAL_ORDER: + if credit not in non_refundable or credit not in POTENTIALS: + continue + potential = calc(POTENTIALS[credit]) + expected = np.minimum(potential, remaining) + np.testing.assert_allclose(calc(credit), expected, atol=tol, err_msg=credit) + limited[credit] = (expected > 0) & (expected < potential - tol) + remaining = remaining - expected + if "non_refundable_ctc" in non_refundable: + # Schedule 8812 Credit Limit Worksheet A, line 3. + np.testing.assert_allclose( + calc("ctc_tax_liability_after_preceding_credits"), remaining, atol=tol + ) + + # The per-credit amounts add up to the capped total: no credit is allowed + # tax that an earlier credit already used. non_refundable_ctc is the CTC + # that is not refunded, before its tax limit (intended), so its applied + # amount is the smaller of it and Credit Limit Worksheet A, line 5. + applied = {credit: calc(credit) for credit in non_refundable} + unused_ctc = 0 + if "non_refundable_ctc" in non_refundable: + not_refunded = applied["non_refundable_ctc"] + applied["non_refundable_ctc"] = np.minimum( + not_refunded, calc("ctc_limiting_tax_liability") + ) + unused_ctc = not_refunded - applied["non_refundable_ctc"] + capped = calc("income_tax_capped_non_refundable_credits") + np.testing.assert_allclose(sum(applied.values()), capped, atol=tol) + assert (capped <= np.maximum(tax, 0) + tol).all() + # The aggregate 26(a) cap removes only the unused CTC. + np.testing.assert_allclose( + calc("income_tax_unavailable_non_refundable_credits"), unused_ctc, atol=tol + ) + return limited + + +credit_amount = st.one_of(st.just(0), st.integers(1, 5_000)) +household_strategy = st.fixed_dictionaries( + { + "age": st.integers(25, 80), + "married": st.booleans(), + "dependent_ages": st.lists(st.integers(0, 18), max_size=3), + "wages": st.integers(0, 150_000), + "interest": st.integers(0, 60_000), + "solar": st.one_of(st.just(0), st.integers(1, 30_000)), + **{credit: credit_amount for credit in POTENTIALS}, + } +) + +GRID = [ + { + "age": 70, + "married": married, + "dependent_ages": ages, + "wages": wages, + "interest": 0, + "solar": solar, + **{credit: amount for credit in POTENTIALS}, + } + for married in (True, False) + for ages in ([], [5, 8], [17]) + for wages in (0, 25_000, 45_000, 80_000, 150_000) + for solar in (0, 10_000) + for amount in (0, 300, 2_000) +] + [ + # A wage ladder with 300 of every credit: tax rises by about 50 a step, so + # it stops inside each credit's 300 slot in turn. + { + "age": 40, + "married": False, + "dependent_ages": [], + "wages": wages, + "interest": 0, + "solar": 0, + **{credit: 300 for credit in POTENTIALS}, + } + for wages in range(12_000, 45_000, 500) +] + + +@pytest.mark.parametrize("year", [2018, 2021, 2025]) +def test_grid_applies_credits_in_worksheet_order(year): + limited = check_households(GRID, year) + # The grid partly limits every credit, so each list binds. + assert len(limited) >= 6 + for credit, partly_limited in limited.items(): + assert partly_limited.any(), credit + + +@settings( + max_examples=10, + deadline=None, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given( + st.lists(household_strategy, min_size=1, max_size=20), + st.sampled_from([2018, 2021, 2023, 2025, 2026]), +) +def test_random_households_apply_credits_in_worksheet_order(households, year): + check_households(households, year) diff --git a/policyengine_us/tools/pinned_tbs.py b/policyengine_us/tools/pinned_tbs.py index 04f2d91f509..f0fb5379dbb 100644 --- a/policyengine_us/tools/pinned_tbs.py +++ b/policyengine_us/tools/pinned_tbs.py @@ -97,23 +97,28 @@ def _pin_2020_irc(tbs): ) except Exception: pass - # The 2020 CTC & ODC Worksheet (Pub. 972, p.7) uses the CDCC before the CTC - # when computing the CTC's tax-liability limit, so the recomputed CDCC must - # reduce that limit (`ctc_tax_liability_after_preceding_credits`) and the - # residential clean energy credit's limit, and count among the - # non-refundable credits. The 2021 lists omit `cdcc` (it was refundable - # under ARPA). Add `cdcc` to each current 2021 membership rather than - # pinning the whole 2020 lists, which would drop `new_clean_vehicle_credit` - # (2021+). - for credit_list in ( - credits.non_refundable, - credits.ctc_tax_liability_limit.preceding_credits, - credits.residential_clean_energy.preceding_credits, - ): + # The 2020 credit limit worksheets (e.g. the CTC & ODC Worksheet, Pub. 972, + # p.7) subtract the CDCC from tax before every credit but the foreign tax + # credit, so the recomputed CDCC must reduce each later credit's limit and + # count among the non-refundable credits. The 2021 lists omit `cdcc` (it + # was refundable under ARPA). Add `cdcc` after the foreign tax credit in + # each current 2021 list whose 2020 version had it, rather than pinning + # the whole 2020 lists, which would drop `new_clean_vehicle_credit` (2021+). + credit_lists = [credits.non_refundable] + [ + param + for param in credits.get_descendants() + if isinstance(param, Parameter) and param.name.endswith(".preceding_credits") + ] + for credit_list in credit_lists: try: members_2021 = list(credit_list(start)) - if "cdcc" not in members_2021: - members_2021 = ["cdcc"] + members_2021 + if "cdcc" in credit_list(pin_date) and "cdcc" not in members_2021: + position = ( + members_2021.index("foreign_tax_credit") + 1 + if "foreign_tax_credit" in members_2021 + else 0 + ) + members_2021.insert(position, "cdcc") credit_list.update(start=start, stop=stop, value=members_2021) except Exception: pass diff --git a/policyengine_us/variables/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit.py b/policyengine_us/variables/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit.py index 802f79169b6..31a48bb01fb 100644 --- a/policyengine_us/variables/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit.py +++ b/policyengine_us/variables/gov/irs/credits/clean_vehicle/used/used_clean_vehicle_credit.py @@ -14,8 +14,8 @@ class used_clean_vehicle_credit(Variable): defined_for = "used_clean_vehicle_credit_eligible" def formula(tax_unit, period, parameters): - p = parameters(period).gov.irs.credits.clean_vehicle.used - sale_price = tax_unit("used_clean_vehicle_sale_price", period) - # Amount is lesser of $4,000 and 30% of sale price. - uncapped_amount = sale_price * p.amount.percent_of_sale_price - return min_(uncapped_amount, p.amount.max) + # Form 8936, line 18: the smaller of the credit and the tax liability + # limit; the unused credit is lost. + credit_limit = tax_unit("used_clean_vehicle_credit_credit_limit", period) + potential = tax_unit("used_clean_vehicle_credit_potential", period) + return min_(credit_limit, potential) diff --git a/policyengine_us/variables/gov/states/ok/tax/income/credits/ok_federal_ctc.py b/policyengine_us/variables/gov/states/ok/tax/income/credits/ok_federal_ctc.py index 4b5858a96ab..36d9c1f0ded 100644 --- a/policyengine_us/variables/gov/states/ok/tax/income/credits/ok_federal_ctc.py +++ b/policyengine_us/variables/gov/states/ok/tax/income/credits/ok_federal_ctc.py @@ -13,6 +13,9 @@ class ok_federal_ctc(Variable): "https://www.oscn.net/applications/oscn/DeliverDocument.asp?CiteID=92568", # 2025 Form 511 packet, pages 11 and 25. "https://oklahoma.gov/content/dam/ok/en/tax/documents/forms/individuals/current/511-Pkt.pdf#page=11", + # 2025 Instructions for Schedule 8812, Credit Limit Worksheet A. + "https://www.irs.gov/pub/irs-pdf/i1040s8.pdf#page=4", + "https://www.law.cornell.edu/uscode/text/26/25D#c", ) documentation = ( "Federal Child Tax Credit allowed for Oklahoma's Child Care/Child " @@ -21,7 +24,8 @@ class ok_federal_ctc(Variable): ) def formula(tax_unit, period, parameters): - non_refundable_credits = parameters(period).gov.irs.credits.non_refundable + credits = parameters(period).gov.irs.credits + non_refundable_credits = credits.non_refundable refundable_ctc = tax_unit("refundable_ctc", period) # NOTE: Fully-refundable-CTC reforms (e.g. the American Family Act # contrib) drop non_refundable_ctc from the federal list, so there is @@ -32,9 +36,21 @@ def formula(tax_unit, period, parameters): # choice); the baseline keeps the ODC inside ctc, so it is unaffected. if "non_refundable_ctc" not in non_refundable_credits: return refundable_ctc - ctc_index = non_refundable_credits.index("non_refundable_ctc") - credits_before_ctc = non_refundable_credits[:ctc_index] - preceding_credits = add(tax_unit, period, credits_before_ctc) + # The credits applied before the CTC are those Schedule 8812 Credit + # Limit Worksheet A subtracts, not those listed before it in the + # non-refundable list: line 2, and on line 4 the residential clean + # energy credit only when Credit Limit Worksheet B applies. Otherwise + # that credit follows the CTC (26 U.S.C. 25D(c)). + limit = credits.ctc_tax_liability_limit + + def applied(credit_list): + credit_list = [c for c in credit_list if c in non_refundable_credits] + return add(tax_unit, period, credit_list) if credit_list else 0 + + worksheet_b_applies = tax_unit("ctc_credit_limit_worksheet_b_applies", period) + preceding_credits = applied(limit.preceding_credits) + where( + worksheet_b_applies, applied(limit.subsequent_credits), 0 + ) non_refundable_ctc = tax_unit("non_refundable_ctc", period) total_non_refundable_credits = tax_unit(