diff --git a/.gitignore b/.gitignore index 0474dab3b96..0dd53ed79dc 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ venv/ # Python caches .pytest_cache +.hypothesis **/__pycache__ # Data diff --git a/changelog.d/ctc-refundable-foreign-earned-income-exclusion.fixed.md b/changelog.d/ctc-refundable-foreign-earned-income-exclusion.fixed.md new file mode 100644 index 00000000000..f2a2d790c94 --- /dev/null +++ b/changelog.d/ctc-refundable-foreign-earned-income-exclusion.fixed.md @@ -0,0 +1 @@ +Deny the refundable Child Tax Credit to filers who elect to exclude foreign earned income or housing amounts under section 911 (26 U.S.C. 24(d)(3)) for tax years from 2015 except fully refundable 2021, including the Puerto Rico refundable Child Tax Credit, Colorado's replica of the refundable credit, the Wyden-Smith CTC expansion reform and the American Family Act reform before 2025; deny the EITC to filers who claim the benefits of section 911 (26 U.S.C. 32(c)(1)(C)), including in New Jersey's childless EITC age exception and the Colorado, DC, Illinois, Indiana and Washington credits that recompute the federal EITC; count the section 911 exclusion in gross income for the filing requirement (26 U.S.C. 6012(c)); and treat a section 911 claimant below the filing threshold as a filer when the tax unit would file if eligible for a refundable credit (would_file_if_eligible_for_refundable_credit). Under the American Family Act, waive the shared section 911 refundable CTC bar from 2025 onward, including the Puerto Rico refund after 2039. 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 ee1bf39d602..3b4e3719db4 100644 --- a/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py +++ b/policyengine_us/reforms/congress/afa/afa_other_dependent_credit.py @@ -5,6 +5,21 @@ def create_afa_other_dependent_credit() -> Reform: + class refundable_ctc_barred_by_section_911_exclusion(Variable): + reference = "https://www.bennet.senate.gov/wp-content/uploads/2025/04/American-Family-Act-2025.pdf" + documentation = ( + "The American Family Act replaces section 24 from 2025 with " + "section 24A, which does not bar refundability for section 911 " + "claimants, including in Puerto Rico. Earlier years retain " + "the current-law bar." + ) + + def formula_2025(tax_unit, period, parameters): + # Section 2(c) ends old section 24 after 2024; sections 24A(d) + # and 2(f)(1) provide refundability from 2025 without a sunset. + # Keep this rule after the fully_refundable parameter expires. + return False + class other_dependent_credit(Variable): value_type = float entity = TaxUnit @@ -152,7 +167,9 @@ def formula(tax_unit, period, parameters): ctc_capped_by_tax = min_(total_ctc, limiting_tax) ctc_capped_by_increased_tax = min_(total_ctc, limiting_tax + phase_in) amount_ctc_would_increase = ctc_capped_by_increased_tax - ctc_capped_by_tax - return min_(maximum_refundable_ctc, amount_ctc_would_increase) + refundable_amount = min_(maximum_refundable_ctc, amount_ctc_would_increase) + barred = tax_unit("refundable_ctc_barred_by_section_911_exclusion", period) + return where(barred, 0, refundable_amount) class ctc(Variable): value_type = float @@ -256,6 +273,7 @@ def apply(self): self.update_variable(ctc_lower_phase_out) self.update_variable(ctc_higher_phase_out) self.update_variable(ctc) + self.update_variable(refundable_ctc_barred_by_section_911_exclusion) self.update_variable(refundable_ctc) self.update_variable(ctc_child_individual_maximum_arpa) self.modify_parameters(modify_parameters) diff --git a/policyengine_us/reforms/congress/wyden_smith/ctc_expansion.py b/policyengine_us/reforms/congress/wyden_smith/ctc_expansion.py index 8d39284ee7e..59953499494 100644 --- a/policyengine_us/reforms/congress/wyden_smith/ctc_expansion.py +++ b/policyengine_us/reforms/congress/wyden_smith/ctc_expansion.py @@ -74,8 +74,13 @@ def formula(tax_unit, period, parameters): ctc_capped_by_tax = min_(total_ctc, limiting_tax) ctc_capped_by_increased_tax = min_(total_ctc, limiting_tax + tax_increase) amount_ctc_would_increase = ctc_capped_by_increased_tax - ctc_capped_by_tax + refundable_amount = min_(maximum_refundable_ctc, amount_ctc_would_increase) - return min_(maximum_refundable_ctc, amount_ctc_would_increase) + # The bill's CTC changes amend section 24(h) and leave section + # 24(d)(3) in place: no refundable CTC for filers electing a + # section 911 exclusion. + barred = tax_unit("refundable_ctc_barred_by_section_911_exclusion", period) + return where(barred, 0, refundable_amount) class reform(Reform): def apply(self): diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/integration.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/integration.yaml index bb79e5e6b30..0d6f23ecd1e 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/integration.yaml @@ -531,3 +531,92 @@ output: ctc: 3_500 # 3000 for 10-year-old + 500 for 18-year-old (ODC) ctc_adult_individual_maximum: [0, 0, 500] # ODC for 18-year-old under ARPA + +# Both cases below have zero taxable income and $0 of tax before credits. For +# the Form 2555 filer, the Foreign Earned Income Tax Worksheet (Form 1040 line +# 16), which applies section 911(f), is skipped when taxable income is zero; +# the first case in this file has a Form 2555 filer with positive taxable +# income. +# non_refundable_ctc is the credit before the section 26(a) limit; the limited +# amount is income_tax_capped_non_refundable_credits, which equals Schedule +# 8812 line 14 here because the CTC is the only non-refundable credit ($0). +- name: Head of household with two children and a section 911 exclusion gets no additional child tax credit in 2025. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + # Wages included in gross income; the excluded foreign wages are + # reported separately below. + employment_income: 8_000 + # Over the $11,950 investment income limit, so no EITC either way. + taxable_interest_income: 12_500 + child1: + age: 5 + child2: + age: 8 + tax_units: + tax_unit: + members: [parent, child1, child2] + # Form 2555 exclusion. + foreign_earned_income_exclusion: 40_000 + households: + household: + members: [parent, child1, child2] + state_code: TX + output: + filing_status: HEAD_OF_HOUSEHOLD + adjusted_gross_income: 20_500 + # $20,500 is below the $23,625 head of household standard deduction. + taxable_income: 0 + income_tax_before_credits: 0 + eitc: 0 + # Schedule 8812 line 12: 2 x $2,200. + ctc: 4_400 + # Line 13 (Credit Limit Worksheet A): no tax to offset. + ctc_limiting_tax_liability: 0 + refundable_ctc_barred_by_section_911_exclusion: true + # Part II-A: "If you file Form 2555, you cannot claim the additional + # child tax credit." A household with the same included income and no + # foreign earnings gets $825 (next case). + refundable_ctc: 0 + non_refundable_ctc: 4_400 + income_tax_capped_non_refundable_credits: 0 + income_tax_before_refundable_credits: 0 + income_tax: 0 + +- name: A household with the same included income and no foreign earnings gets an $825 additional child tax credit in 2025. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + employment_income: 8_000 + taxable_interest_income: 12_500 + child1: + age: 5 + child2: + age: 8 + tax_units: + tax_unit: + members: [parent, child1, child2] + foreign_earned_income_exclusion: 0 + households: + household: + members: [parent, child1, child2] + state_code: TX + output: + income_tax_before_credits: 0 + eitc: 0 + ctc: 4_400 + refundable_ctc_barred_by_section_911_exclusion: false + # Part II-A: line 16a = $4,400 - $0 = $4,400; line 16b = 2 x $1,700 + # = $3,400; line 17 = $3,400; line 20 = 15% x ($8,000 - $2,500) = $825; + # line 27 = smaller of lines 17 and 20 = $825. + refundable_ctc: 825 + non_refundable_ctc: 3_575 + income_tax_capped_non_refundable_credits: 0 + income_tax_before_refundable_credits: 0 + income_tax: -825 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 4b7b2ec6e97..aac331e1b63 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 @@ -41,3 +41,23 @@ foreign_earned_income_exclusion: 10_000 output: ctc_credit_limit_worksheet_b_applies: true + +# Worksheet B reads the section 24(d)(3) bar rather than repeating its test, so +# a bar set as an input decides both the refund and the worksheet. +- name: Does not apply when the section 24(d)(3) bar is set as an input, even without an exclusion. + period: 2025 + input: + ctc_qualifying_children: 2 + foreign_earned_income_exclusion: 0 + refundable_ctc_barred_by_section_911_exclusion: true + output: + ctc_credit_limit_worksheet_b_applies: false + +- name: Applies to a Form 2555 filer when the section 24(d)(3) bar is set to false as an input. + period: 2025 + input: + ctc_qualifying_children: 2 + foreign_earned_income_exclusion: 10_000 + refundable_ctc_barred_by_section_911_exclusion: false + output: + ctc_credit_limit_worksheet_b_applies: true diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/refundable_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/refundable_ctc.yaml index 268f999c09d..efeec12296e 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/refundable_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/refundable_ctc.yaml @@ -81,3 +81,111 @@ ctc_limiting_tax_liability: 4_000 output: refundable_ctc: 0 + +# Section 24(d)(3): no refundable CTC for a filer who elects to exclude any +# amount from gross income under section 911 (2025 Schedule 8812 +# instructions, Part II-A: "If you file Form 2555, you cannot claim the +# additional child tax credit."). +- name: Section 911 exclusion denies the refundable credit + period: 2025 + input: + ctc_refundable_maximum: 3_400 + ctc: 4_400 + ctc_phase_out: 0 + ctc_phase_in: 10_000 + ctc_limiting_tax_liability: 0 + foreign_earned_income_exclusion: 1 + output: + # Without the exclusion: min(3,400, min(4,400, 0 + 10,000) - 0) = 3,400. + refundable_ctc: 0 + +- name: Section 911 exclusion denies the refundable credit from social security taxes + period: 2025 + input: + # Three children and no earnings over the threshold: the phase-in amount + # is social security taxes less the EITC (section 24(d)(1)(B)(ii)), which + # section 24(d)(3) also switches off. + ctc_qualifying_children: 3 + ctc_phase_in_relevant_earnings: 0 + ctc_social_security_tax: 4_000 + eitc: 0 + ctc_refundable_maximum: 5_100 + ctc: 6_600 + ctc_phase_out: 0 + ctc_limiting_tax_liability: 1_000 + foreign_earned_income_exclusion: 30_000 + output: + ctc_phase_in: 4_000 + refundable_ctc: 0 + +- name: No section 911 exclusion keeps the refundable credit from social security taxes + period: 2025 + input: + ctc_qualifying_children: 3 + ctc_phase_in_relevant_earnings: 0 + ctc_social_security_tax: 4_000 + eitc: 0 + ctc_refundable_maximum: 5_100 + ctc: 6_600 + ctc_phase_out: 0 + ctc_limiting_tax_liability: 1_000 + foreign_earned_income_exclusion: 0 + output: + ctc_phase_in: 4_000 + # min(5,100, min(6,600, 1,000 + 4,000) - min(6,600, 1,000)) = 4,000. + refundable_ctc: 4_000 + +- name: Section 911 exclusion bar can be switched off + period: 2025 + input: + gov.irs.credits.ctc.refundable.foreign_earned_income_exclusion_bar_applies: false + ctc_refundable_maximum: 5_100 + ctc: 6_600 + ctc_phase_out: 0 + ctc_phase_in: 4_000 + ctc_limiting_tax_liability: 1_000 + foreign_earned_income_exclusion: 30_000 + output: + refundable_ctc: 4_000 + +- name: Section 911 exclusion did not deny the refundable credit before 2015 + period: 2014 + input: + # Public Law 114-27, section 807, applies to taxable years beginning + # after December 31, 2014. + ctc_refundable_maximum: 2_000 + ctc: 2_000 + ctc_phase_out: 0 + ctc_phase_in: 1_500 + ctc_limiting_tax_liability: 0 + foreign_earned_income_exclusion: 30_000 + output: + refundable_ctc: 1_500 + +- name: Section 911 exclusion denies the refundable credit from 2015 + period: 2015 + input: + ctc_refundable_maximum: 2_000 + ctc: 2_000 + ctc_phase_out: 0 + ctc_phase_in: 1_500 + ctc_limiting_tax_liability: 0 + foreign_earned_income_exclusion: 30_000 + output: + refundable_ctc: 0 + +- name: In 2021, the fully refundable credit ignores the section 911 exclusion + period: 2021 + input: + # Section 24(i)(1)(A): subsection (d), including the section 911 bar, + # does not apply to a filer whose principal place of abode was in the + # United States for more than half of 2021 (Schedule 8812 Part I-B, + # which has no Form 2555 condition). The model assumes that test is met. + ctc_refundable_maximum: 6_000 + ctc: 6_000 + ctc_phase_out: 0 + ctc_phase_in: 0 + ctc_limiting_tax_liability: 0 + foreign_earned_income_exclusion: 30_000 + output: + refundable_ctc: 6_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/refundable_ctc_barred_by_section_911_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/refundable_ctc_barred_by_section_911_exclusion.yaml new file mode 100644 index 00000000000..3bb22883ff1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/refundable_ctc_barred_by_section_911_exclusion.yaml @@ -0,0 +1,49 @@ +# Section 24(d)(3): "Paragraph (1) shall not apply to any taxpayer for any +# taxable year if such taxpayer elects to exclude any amount from gross +# income under section 911 for such taxable year." +- name: Any section 911 exclusion bars the refundable credit + period: 2025 + input: + foreign_earned_income_exclusion: 1 + output: + refundable_ctc_barred_by_section_911_exclusion: true + +- name: No section 911 exclusion, no bar + period: 2025 + input: + foreign_earned_income_exclusion: 0 + output: + refundable_ctc_barred_by_section_911_exclusion: false + +- name: The bar did not exist before 2015 + period: 2014 + input: + # Public Law 114-27, section 807, applies to taxable years beginning + # after December 31, 2014. + foreign_earned_income_exclusion: 30_000 + output: + refundable_ctc_barred_by_section_911_exclusion: false + +- name: The bar applies from 2015 + period: 2015 + input: + foreign_earned_income_exclusion: 30_000 + output: + refundable_ctc_barred_by_section_911_exclusion: true + +- name: No bar in the fully refundable year + period: 2021 + input: + # Section 24(i)(1)(A): subsection (d) does not apply to filers living in + # the United States or Puerto Rico, which the model assumes. + foreign_earned_income_exclusion: 30_000 + output: + refundable_ctc_barred_by_section_911_exclusion: false + +- name: The bar can be switched off + period: 2025 + input: + gov.irs.credits.ctc.refundable.foreign_earned_income_exclusion_bar_applies: false + foreign_earned_income_exclusion: 30_000 + output: + refundable_ctc_barred_by_section_911_exclusion: false diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_eligible.yaml index fc13da2b3ba..79a909e9c37 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_eligible.yaml @@ -37,3 +37,40 @@ filing_status: JOINT output: eitc_eligible: true + +# 26 U.S.C. 32(c)(1)(C): "The term 'eligible individual' does not include any +# individual who claims the benefits of section 911 (relating to citizens or +# residents living abroad) for the taxable year." +- name: Section 911 claimant is not eligible. + period: 2025 + input: + eitc_investment_income_eligible: true + eitc_demographic_eligible: true + filer_meets_eitc_identification_requirements: true + filing_status: JOINT + foreign_earned_income_exclusion: 1 + output: + eitc_eligible: false + +- name: Without a section 911 exclusion the same filer is eligible. + period: 2025 + input: + eitc_investment_income_eligible: true + eitc_demographic_eligible: true + filer_meets_eitc_identification_requirements: true + filing_status: JOINT + foreign_earned_income_exclusion: 0 + output: + eitc_eligible: true + +- name: Section 911 claimant is not eligible in 2021 either. + period: 2021 + input: + # Unlike the CTC bar, section 32(c)(1)(C) has no 2021 exception. + eitc_investment_income_eligible: true + eitc_demographic_eligible: true + filer_meets_eitc_identification_requirements: true + filing_status: SINGLE + foreign_earned_income_exclusion: 30_000 + output: + eitc_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit/tax_unit_is_filer.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit/tax_unit_is_filer.yaml index 7eb87041afb..47d5c22d186 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit/tax_unit_is_filer.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit/tax_unit_is_filer.yaml @@ -57,3 +57,131 @@ employment_income: 0 output: tax_unit_is_filer: false + +# 26 U.S.C. 6012(c): for the filing requirement, gross income is computed +# without regard to the section 911 exclusion. +- name: Excluded foreign earnings count toward the filing threshold + period: 2025 + input: + filing_status: SINGLE + age_head: 40 + employment_income: 5_000 + foreign_earned_income_exclusion: 30_000 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + output: + # $35,000 of gross income under section 6012(c), over the $15,750 + # single standard deduction. + tax_unit_is_required_to_file: true + tax_unit_is_filer: true + # Filing does not make a section 911 claimant eligible for the EITC + # (section 32(c)(1)(C)). + eitc: 0 + +- name: The same filer without excluded foreign earnings is under the threshold + period: 2025 + input: + filing_status: SINGLE + age_head: 40 + employment_income: 5_000 + foreign_earned_income_exclusion: 0 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + output: + tax_unit_is_required_to_file: false + tax_unit_is_filer: false + +- name: Excluded foreign earnings do not count as unearned income + period: 2025 + input: + filing_status: SINGLE + age_head: 40 + foreign_earned_income_exclusion: 10_000 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + output: + # $10,000 is under the $15,750 threshold, and none of it is unearned. + tax_unit_is_required_to_file: false + +# Treas. Reg. 1.911-7(a)(1): the section 911 election is filed with the return, +# and Form 2555 is attached to Form 1040 when filed, so a tax unit with an +# exclusion files even below the filing threshold and with no credit to claim. +- name: Section 911 claimant below the filing threshold files to claim the exclusion + period: 2025 + input: + filing_status: SINGLE + age_head: 40 + employment_income: 5_000 + foreign_earned_income_exclusion: 1_000 + output: + # $6,000 of gross income under section 6012(c), under $15,750. Section + # 32(c)(1)(C) denies the EITC, so the exclusion is the reason to file. + tax_unit_is_required_to_file: false + eitc: 0 + tax_unit_is_filer: true + +- name: Section 911 claimant below the filing threshold who would not file does not + period: 2025 + input: + filing_status: SINGLE + age_head: 40 + employment_income: 5_000 + foreign_earned_income_exclusion: 1_000 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + output: + # A continuing election needs no return below the filing threshold + # (Treas. Reg. 1.911-7(a)(1)). + tax_unit_is_required_to_file: false + tax_unit_is_filer: false + +- name: Section 911 parents under the threshold keep filing without the refundable credits + period: 2025 + input: + people: + head: + age: 40 + employment_income: 28_000 + spouse: + age: 40 + child: + age: 5 + tax_units: + tax_unit: + members: [head, spouse, child] + foreign_earned_income_exclusion: 1_000 + households: + household: + members: [head, spouse, child] + state_code: TX + output: + # $29,000 of gross income under section 6012(c), under $31,500. + tax_unit_is_required_to_file: false + refundable_ctc: 0 + eitc: 0 + tax_unit_is_filer: true + +- name: Section 911 parents under the threshold who would not file do not + period: 2025 + input: + people: + head: + age: 40 + employment_income: 28_000 + spouse: + age: 40 + child: + age: 5 + tax_units: + tax_unit: + members: [head, spouse, child] + foreign_earned_income_exclusion: 1_000 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + households: + household: + members: [head, spouse, child] + state_code: TX + output: + tax_unit_is_required_to_file: false + tax_unit_is_filer: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_refundable_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_refundable_ctc.yaml index 3aa9f41d41c..e64b6ddba6f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_refundable_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/ctc/federal_ctc/co_refundable_ctc.yaml @@ -127,3 +127,36 @@ eitc: 1_000 output: co_refundable_ctc: 9_500 + +# 1 CCR 201-2, Rule 39-22-129(1): the Colorado credit is a percentage of the +# allowed federal credit, so the section 24(d)(3) bar on section 911 filers +# carries over to the refundable part Colorado replicates. +- name: Section 911 filer gets no refundable part from social security taxes + period: 2022 + input: + state_code: CO + co_non_refundable_ctc: 0 + co_federal_ctc_maximum: 6_000 + co_ctc_eligible_children_count: 3 + tax_unit_earned_income: 1_000 + ctc_social_security_tax: 76.5 + eitc: 0 + foreign_earned_income_exclusion: 30_000 + output: + co_refundable_ctc: 0 + +- name: The same filer without a section 911 exclusion gets the social security tax excess + period: 2022 + input: + state_code: CO + co_non_refundable_ctc: 0 + co_federal_ctc_maximum: 6_000 + co_ctc_eligible_children_count: 3 + tax_unit_earned_income: 1_000 + ctc_social_security_tax: 76.5 + eitc: 0 + foreign_earned_income_exclusion: 0 + output: + # Earnings are under the $2,500 threshold, so line 19 is the $76.50 of + # social security taxes less the EITC; line 20 = min($4,500, $76.50). + co_refundable_ctc: 76.5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.yaml index 5759df32b3a..a94a5ac6133 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.yaml @@ -110,3 +110,26 @@ nj_eitc_income_eligible: true output: nj_childless_eitc_age_eligible: true + +# P.L. 2021, c.130, section 1a(4): the filer "shall meet all qualifications, +# except for the minimum or maximum age, for the federal earned income tax +# credit", including 26 U.S.C. 32(c)(1)(C) (no credit for an individual who +# claims the benefits of section 911). +- name: Section 911 claimant fails a non-age federal qualification. + period: 2025 + absolute_error_margin: 0 + input: + people: + person1: + age: 20 + households: + household: + members: [person1] + state_code: NJ + tax_units: + tax_unit: + members: [person1] + nj_eitc_income_eligible: true + foreign_earned_income_exclusion: 30_000 + output: + nj_childless_eitc_age_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_eitc.yaml index a72b9eed822..be6996b945b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_eitc.yaml @@ -75,3 +75,66 @@ nj_childless_eitc_age_eligible: true output: nj_eitc: 240 + +# Section 911 claimants: federal EITC eligibility fails under 26 U.S.C. +# 32(c)(1)(C), and the age exception in P.L. 2021, c.130, section 1a(4) +# requires every other federal qualification, so New Jersey pays nothing. +- name: Section 911 claimant within the federal age range gets no New Jersey EITC. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 5_000 + tax_units: + tax_unit: + members: [person1] + foreign_earned_income_exclusion: 30_000 + households: + household: + members: [person1] + state_code: NJ + output: + eitc: 0 + nj_eitc: 0 + +- name: The same filer without a section 911 exclusion gets 40% of the federal EITC. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 5_000 + tax_units: + tax_unit: + members: [person1] + foreign_earned_income_exclusion: 0 + households: + household: + members: [person1] + state_code: NJ + output: + # 7.65% of $5,000, below the $649 maximum and the phase-out. + eitc: 382.50 + nj_eitc: 153 + +- name: Section 911 claimant below the federal minimum age gets no New Jersey EITC. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 20 + employment_income: 5_000 + tax_units: + tax_unit: + members: [person1] + foreign_earned_income_exclusion: 30_000 + households: + household: + members: [person1] + state_code: NJ + output: + nj_eitc: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/section_911_state_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/section_911_state_eitc.yaml new file mode 100644 index 00000000000..4bec3bf257c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/section_911_state_eitc.yaml @@ -0,0 +1,126 @@ +# 26 U.S.C. 32(c)(1)(C): no EITC for an individual who claims the benefits of +# section 911. These state credits are the federal credit with named rules +# waived (age, SSN, separate filing), so the section 911 bar still applies: +# Colorado (1 CCR 201-2, Rule 39-22-123.5(2)), DC (D.C. Code 47-1806.04(f)), +# Illinois (35 ILCS 5/212(a), (b-5), (b-10), (b-15)), Indiana (IRC conformity, +# IC 6-3-1-11) and Washington (WAC 458-20-285). +# +# Each household: single, 40, no children, $5,000 of included wages, a $30,000 +# section 911 exclusion, valid SSN, and no propensity to file otherwise. The +# exclusion makes filing required (26 U.S.C. 6012(c)), which must not open +# the state credit. + +- name: Section 911 claimant in CO gets no state EITC through the federal-style recomputation. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 5_000 + tax_units: + tax_unit: + members: [person1] + foreign_earned_income_exclusion: 30_000 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + households: + household: + members: [person1] + state_code: CO + output: + tax_unit_is_required_to_file: true + eitc: 0 + co_eitc: 0 + +- name: Section 911 claimant in DC gets no state EITC through the federal-style recomputation. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 5_000 + tax_units: + tax_unit: + members: [person1] + foreign_earned_income_exclusion: 30_000 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + households: + household: + members: [person1] + state_code: DC + output: + tax_unit_is_required_to_file: true + eitc: 0 + dc_eitc_without_qualifying_child: 0 + +- name: Section 911 claimant in IL gets no state EITC through the federal-style recomputation. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 5_000 + tax_units: + tax_unit: + members: [person1] + foreign_earned_income_exclusion: 30_000 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + households: + household: + members: [person1] + state_code: IL + output: + tax_unit_is_required_to_file: true + eitc: 0 + il_eitc: 0 + +- name: Section 911 claimant in WA gets no state EITC through the federal-style recomputation. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 5_000 + tax_units: + tax_unit: + members: [person1] + foreign_earned_income_exclusion: 30_000 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + households: + household: + members: [person1] + state_code: WA + output: + tax_unit_is_required_to_file: true + eitc: 0 + wa_working_families_tax_credit: 0 + +- name: Section 911 claimant in IN gets no state EITC through the federal-style recomputation. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 5_000 + tax_units: + tax_unit: + members: [person1] + foreign_earned_income_exclusion: 30_000 + would_file_if_eligible_for_refundable_credit: false + would_file_taxes_voluntarily: false + households: + household: + members: [person1] + state_code: IN + output: + tax_unit_is_required_to_file: true + eitc: 0 + in_eitc_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/refundable_ctc/pr_refundable_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/refundable_ctc/pr_refundable_ctc.yaml index de0ada72255..9525cc2a5d0 100644 --- a/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/refundable_ctc/pr_refundable_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/refundable_ctc/pr_refundable_ctc.yaml @@ -19,3 +19,66 @@ ctc: 1_000 output: pr_refundable_ctc: 1_000 + +# Section 24(k)(2)(B)(ii) drops only the three-children condition for residents +# of Puerto Rico; section 24(d)(3) still denies the refundable credit to a +# filer electing a section 911 exclusion. +- name: Section 911 exclusion denies the credit despite social security taxes + period: 2024 + input: + eitc: 0 + uncapped_ssi: 0 + pr_refundable_ctc_social_security_tax: 1_530 + refundable_ctc: 0 + ctc: 2_000 + foreign_earned_income_exclusion: 1 + output: + pr_refundable_ctc: 0 + +- name: The same filer without a section 911 exclusion gets the social security tax amount + period: 2024 + input: + eitc: 0 + uncapped_ssi: 0 + pr_refundable_ctc_social_security_tax: 1_530 + refundable_ctc: 0 + ctc: 2_000 + foreign_earned_income_exclusion: 0 + output: + pr_refundable_ctc: 1_530 + +- name: In 2021, a section 911 exclusion does not deny the credit + period: 2021 + input: + # Section 24(i)(1) switched off subsection (d) for bona fide residents + # of Puerto Rico, so the whole credit was refundable. + eitc: 0 + uncapped_ssi: 0 + pr_refundable_ctc_social_security_tax: 1_530 + refundable_ctc: 2_000 + ctc: 2_000 + foreign_earned_income_exclusion: 30_000 + output: + pr_refundable_ctc: 2_000 + +- name: Puerto Rico section 911 filer remains barred in 2040 under current law + period: 2040 + input: + people: + head: + age: 40 + employment_income: 8_000 + child1: + age: 5 + tax_units: + tax_unit: + members: [head, child1] + foreign_earned_income_exclusion: 1_000 + households: + household: + members: [head, child1] + state_code: PR + output: + refundable_ctc_barred_by_section_911_exclusion: true + refundable_ctc: 0 + pr_refundable_ctc: 0 diff --git a/policyengine_us/tests/policy/contrib/congress/afa/afa_other_dependent_credit.yaml b/policyengine_us/tests/policy/contrib/congress/afa/afa_other_dependent_credit.yaml index c7070aa6666..32f9bb5f625 100644 --- a/policyengine_us/tests/policy/contrib/congress/afa/afa_other_dependent_credit.yaml +++ b/policyengine_us/tests/policy/contrib/congress/afa/afa_other_dependent_credit.yaml @@ -292,3 +292,90 @@ ctc_lower_phase_out: 0 ctc_refundable_maximum: 9_960 ctc: 9_960 + +# Before the bill's fully refundable credit starts (2025), current law applies, +# including the section 24(d)(3) bar on section 911 filers. Without the bar, the +# zero EITC of a section 911 claimant (26 U.S.C. 32(c)(1)(C)) would leave $76.50 +# of social security taxes to refund. +- name: Section 911 filer gets no refundable CTC in 2024 with the reform active + period: 2024 + reforms: policyengine_us.reforms.congress.afa.afa_other_dependent_credit.afa_other_dependent_credit + input: + gov.contrib.congress.afa.in_effect: true + people: + head: + age: 40 + employment_income: 1_000 + child1: + age: 6 + child2: + age: 7 + child3: + age: 8 + tax_units: + tax_unit: + members: [head, child1, child2, child3] + foreign_earned_income_exclusion: 30_000 + households: + household: + members: [head, child1, child2, child3] + state_code: TX + output: + eitc: 0 + refundable_ctc_barred_by_section_911_exclusion: true + refundable_ctc: 0 + +# The bill has no section 911 bar and no sunset, so the bar applies only to +# years before 2025. The reform's parameter changes stop in 2039 (an existing +# limitation), which leaves 2040 on the partial-refund branch: $825 here, +# where the bill would pay the full credit. The bar must not zero it. +- name: Section 911 filer keeps the reform's refundable CTC after 2039 + period: 2040 + absolute_error_margin: 0.01 + reforms: policyengine_us.reforms.congress.afa.afa_other_dependent_credit.afa_other_dependent_credit + input: + gov.contrib.congress.afa.in_effect: true + people: + head: + age: 40 + employment_income: 8_000 + child1: + age: 5 + tax_units: + tax_unit: + members: [head, child1] + foreign_earned_income_exclusion: 1_000 + households: + household: + members: [head, child1] + state_code: TX + output: + refundable_ctc_barred_by_section_911_exclusion: false + refundable_ctc: 825 + +# Section 24A(d) includes Puerto Rico. Its refund must survive even after +# the reform's fully_refundable parameter expires, just like the federal one. +- name: Puerto Rico section 911 filer keeps the AFA refund after 2039 + period: 2040 + absolute_error_margin: 0.01 + reforms: policyengine_us.reforms.congress.afa.afa_other_dependent_credit.afa_other_dependent_credit + input: + gov.contrib.congress.afa.in_effect: true + people: + head: + age: 40 + employment_income: 8_000 + child1: + age: 5 + tax_units: + tax_unit: + members: [head, child1] + foreign_earned_income_exclusion: 1_000 + households: + household: + members: [head, child1] + state_code: PR + output: + refundable_ctc_barred_by_section_911_exclusion: false + refundable_ctc: 825 + pr_refundable_ctc: 825 diff --git a/policyengine_us/tests/policy/contrib/congress/afa/test_afa_section_911.py b/policyengine_us/tests/policy/contrib/congress/afa/test_afa_section_911.py new file mode 100644 index 00000000000..f15abc8c569 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/congress/afa/test_afa_section_911.py @@ -0,0 +1,66 @@ +"""Exercise section 911 through the production AFA activation path. + +The neighboring YAML policy tests apply structural reforms directly. This +integration test uses Reform.from_dict to verify that enabling AFA updates +the shared bar, retains its pre-2025 formula, and reaches the Puerto Rico +credit consumer after AFA's fully-refundable parameter expires in 2039. +""" + +import pytest +from policyengine_core.reforms import Reform + +from policyengine_us import Simulation + + +YEARS = (2024, 2025, 2039, 2040) +BAR = "refundable_ctc_barred_by_section_911_exclusion" +CREDITS = ("refundable_ctc", "pr_refundable_ctc") + + +def test_afa_activation_preserves_section_911_rules_across_years(): + members = ["parent", "child"] + situation = { + "people": { + "parent": { + "age": {str(year): 40 for year in YEARS}, + "employment_income": {str(year): 8_000 for year in YEARS}, + }, + "child": {"age": {str(year): 5 for year in YEARS}}, + }, + "tax_units": { + "tax_unit": { + "members": members, + "foreign_earned_income_exclusion": {str(year): 1_000 for year in YEARS}, + } + }, + "spm_units": {"spm_unit": {"members": members}}, + "households": { + "household": { + "members": members, + "state_code": {str(year): "PR" for year in YEARS}, + } + }, + } + # Two independent simulations cover every year without repeated model + # construction or mutable caches shared between current law and AFA. + baseline = Simulation(situation=situation) + for year in YEARS: + assert baseline.calculate(BAR, year)[0] + for credit in CREDITS: + assert baseline.calculate(credit, year)[0] == 0 + + reform = Reform.from_dict( + {"gov.contrib.congress.afa.in_effect": {"2025-01-01.2100-12-31": True}}, + country_id="us", + ) + afa = Simulation(situation=situation, reform=reform) + # AFA applies to taxable years beginning after December 31, 2024. + assert afa.calculate(BAR, 2024)[0] + for year in (2025, 2039, 2040): + assert not afa.calculate(BAR, year)[0] + # Section 32(c)(1)(C) continues to deny EITC under AFA. + assert afa.calculate("eitc", year)[0] == 0 + expected = 825 if year == 2040 else afa.calculate("ctc", year)[0] + assert expected > 0 + for credit in CREDITS: + assert afa.calculate(credit, year)[0] == pytest.approx(expected) diff --git a/policyengine_us/tests/policy/reform/ctc_expansion.yaml b/policyengine_us/tests/policy/reform/ctc_expansion.yaml index d337d47b9fe..6ca07ff18a1 100644 --- a/policyengine_us/tests/policy/reform/ctc_expansion.yaml +++ b/policyengine_us/tests/policy/reform/ctc_expansion.yaml @@ -41,3 +41,20 @@ output: # 10,000 - 2,500 = 9,500 | 9,500 * 0.3 = 2,250 refundable_ctc: 2_250 + +- name: Section 911 exclusion still denies the refundable credit under the reform + period: 2024 + reforms: policyengine_us.reforms.congress.wyden_smith.ctc_expansion.ctc_expansion + input: + gov.contrib.congress.wyden_smith.per_child_actc_phase_in: True + gov.contrib.congress.wyden_smith.actc_lookback: True + tax_unit_earned_income: 8_000 + tax_unit_earned_income_last_year: 10_000 + ctc_qualifying_children: 2 + ctc: 4_000 + ctc_refundable_maximum: 3_200 # $1,600 * 2 + foreign_earned_income_exclusion: 20_000 + output: + # The bill amends section 24(h); section 24(d)(3) still applies. + # Without the exclusion this filer would get $2,250 (case above). + refundable_ctc: 0 diff --git a/policyengine_us/tests/test_ctc_foreign_earned_income_exclusion.py b/policyengine_us/tests/test_ctc_foreign_earned_income_exclusion.py new file mode 100644 index 00000000000..3b6156e640c --- /dev/null +++ b/policyengine_us/tests/test_ctc_foreign_earned_income_exclusion.py @@ -0,0 +1,213 @@ +"""Invariants for the section 911 bar on the refundable Child Tax Credit. + +26 U.S.C. 24(d)(3): "Paragraph (1) shall not apply to any taxpayer for any +taxable year if such taxpayer elects to exclude any amount from gross income +under section 911 for such taxable year." The 2025 Schedule 8812 +instructions (Part II-A) put it as "If you file Form 2555, you cannot claim +the additional child tax credit." + +Each batch of households is computed twice: under current law and under a +reform that switches the bar off, which reproduces the formula without it. +Households live in Texas and take the standard deduction, so the CTC's +tax-liability limit equals the tax the model computes. + +Both runs tax an excluding filer's included income at the rates it would face +if the excluded amount were added back (section 911(f), the Foreign Earned +Income Tax Worksheet), so the tax identity below holds for that stacked tax. +The grid includes filers whose stacked tax leaves part of the credit to +refund. + +Schedule 8812 Credit Limit Worksheet B is not for Form 2555 filers, and it +reads the bar, so a barred filer never completes it. + +Section 32(c)(1)(C) also denies the EITC to a section 911 claimant, in every +year, so the EITC of a filer with an exclusion is zero under both runs. + +The Hypothesis version of these checks is in +test_ctc_foreign_earned_income_exclusion_property.py, which skips without +the dev extra; the grid tests here always run. +""" + +from functools import cache + +import numpy as np +from policyengine_core.reforms import Reform + +from policyengine_us import CountryTaxBenefitSystem, Simulation + +BAR = "gov.irs.credits.ctc.refundable.foreign_earned_income_exclusion_bar_applies" + + +@cache +def no_bar_system(): + # One reformed system serves every household batch. + reform = Reform.from_dict({BAR: {"2010-01-01.2100-12-31": False}}, country_id="us") + return CountryTaxBenefitSystem(reform=reform) + + +OUTPUTS = [ + "tax_unit_itemizes", + "foreign_earned_income_exclusion", + "income_tax_before_credits", + "ctc", + "ctc_refundable_maximum", + "refundable_ctc_barred_by_section_911_exclusion", + "ctc_credit_limit_worksheet_b_applies", + "refundable_ctc", + "non_refundable_ctc", + "income_tax_capped_non_refundable_credits", + # No residence test in the formula, so it is computed for every household. + "pr_refundable_ctc", + "eitc", + "income_tax", +] + + +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: 40}, "employment_income": {year: h["wages"]}} + members = [head] + marital_units[f"marital_unit_{i}"] = {"members": [head]} + if h["married"]: + spouse = f"spouse_{i}" + people[spouse] = {"age": {year: 40}} + 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_units[f"tax_unit_{i}"] = { + "members": members, + "foreign_earned_income_exclusion": {year: h["exclusion"]}, + } + 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 calculate(households, year, bar=True): + system = {} if bar else {"tax_benefit_system": no_bar_system()} + simulation = Simulation(situation=build_situation(households, year), **system) + return {v: np.asarray(simulation.calculate(v, year)) for v in OUTPUTS} + + +def assert_invariants(law, no_bar): + tol = 0.01 + assert not law["tax_unit_itemizes"].any() + excludes = law["foreign_earned_income_exclusion"] > 0 + barred = "refundable_ctc_barred_by_section_911_exclusion" + # Section 24(d)(3): the bar is exactly the filers with an exclusion, and + # none of them has a refundable CTC. + assert np.array_equal(law[barred], excludes) + assert not no_bar[barred].any() + assert (law["refundable_ctc"][excludes] == 0).all() + # The Puerto Rico credit's social security route is barred as well. + assert (law["pr_refundable_ctc"][excludes] == 0).all() + # Section 32(c)(1)(C): no EITC for a section 911 claimant, bar or not. + assert (law["eitc"][excludes] == 0).all() + assert (no_bar["eitc"][excludes] == 0).all() + # Schedule 8812: a filer the bar denies the refund skips Credit Limit + # Worksheet B. With the bar switched off, excluders complete it as other + # filers do, so the bar is its only Form 2555 test. + worksheet_b = "ctc_credit_limit_worksheet_b_applies" + assert not (law[worksheet_b] & law[barred]).any() + assert np.array_equal(law[worksheet_b], no_bar[worksheet_b] & ~law[barred]) + # Filers without the exclusion are untouched, bit for bit. + for v in [ + "refundable_ctc", + "non_refundable_ctc", + "pr_refundable_ctc", + "income_tax", + ]: + assert np.array_equal(law[v][~excludes], no_bar[v][~excludes]), v + # The bar only ever removes refundable credit. + assert (law["refundable_ctc"] <= no_bar["refundable_ctc"]).all() + for r in [law, no_bar]: + # True by the definition of non_refundable_ctc; kept as a guard on it. + np.testing.assert_allclose( + r["refundable_ctc"] + r["non_refundable_ctc"], r["ctc"], atol=tol + ) + assert (r["refundable_ctc"] >= 0).all() + assert ( + r["refundable_ctc"] + <= np.minimum(r["ctc"], r["ctc_refundable_maximum"]) + tol + ).all() + # The CTC that offsets tax is the same either way: section 24(d)(1) refunds + # only what tax could not absorb, so the section 26(a)-limited credits are + # equal even though a barred filer's non-refundable CTC is the whole + # credit. The bar therefore raises the tax by exactly the refund it denies. + np.testing.assert_allclose( + law["income_tax_capped_non_refundable_credits"], + no_bar["income_tax_capped_non_refundable_credits"], + atol=tol, + ) + np.testing.assert_allclose( + law["income_tax"] - no_bar["income_tax"], + np.where(excludes, no_bar["refundable_ctc"], 0), + atol=tol, + ) + + +def check(households, year=2025): + law = calculate(households, year) + no_bar = calculate(households, year, bar=False) + assert_invariants(law, no_bar) + return law, no_bar + + +GRID = [ + { + "married": married, + "dependent_ages": ages, + "wages": wages, + "exclusion": exclusion, + } + for married in (True, False) + for ages in ([], [5], [5, 8], [3, 6, 9], [17], [5, 17]) + for wages in (0, 8_000, 30_000, 60_000, 120_000, 420_000) + for exclusion in (0, 1, 50_000, 130_000) +] + + +def test_grid_invariants(): + law, no_bar = check(GRID) + excludes = law["foreign_earned_income_exclusion"] > 0 + # The grid reaches filers whose refund the bar removes. + assert (excludes & (no_bar["refundable_ctc"] > 0)).any() + # Some of them have section 911(f) stacked tax above the tax on the same + # household without the exclusion, so the tax identity is checked on + # stacked tax. + twin = [GRID.index({**h, "exclusion": 0}) for h in GRID] + tax = law["income_tax_before_credits"] + stacked = tax > tax[twin] + 1 + assert (excludes & stacked & (no_bar["refundable_ctc"] > 0)).any() + + +def test_bar_applies_from_2015_but_not_in_2021(): + # In 2021, section 24(i)(1)(A) switched off all of subsection (d) for + # filers living in the United States, which the model assumes. + law = calculate(GRID, 2021) + no_bar = calculate(GRID, 2021, bar=False) + for v in ["refundable_ctc", "income_tax"]: + assert np.array_equal(law[v], no_bar[v]), v + assert not law["refundable_ctc_barred_by_section_911_exclusion"].any() + # Public Law 114-27, section 807, applies to taxable years beginning + # after 2014 (refundable_ctc.yaml covers 2014: household simulations + # need parameters that start later). + law, no_bar = check(GRID, 2015) + excludes = law["foreign_earned_income_exclusion"] > 0 + assert (excludes & (no_bar["refundable_ctc"] > 0)).any() diff --git a/policyengine_us/tests/test_ctc_foreign_earned_income_exclusion_property.py b/policyengine_us/tests/test_ctc_foreign_earned_income_exclusion_property.py new file mode 100644 index 00000000000..dfc154f04c3 --- /dev/null +++ b/policyengine_us/tests/test_ctc_foreign_earned_income_exclusion_property.py @@ -0,0 +1,39 @@ +"""Hypothesis checks for the section 911 bar on the refundable Child Tax Credit. + +The invariants and helpers are in test_ctc_foreign_earned_income_exclusion.py. +Hypothesis is a dev extra, so this module skips without it. +""" + +import pytest + +from test_ctc_foreign_earned_income_exclusion import check + +hypothesis = pytest.importorskip("hypothesis") +st = pytest.importorskip("hypothesis.strategies") + +household_strategy = st.fixed_dictionaries( + { + "married": st.booleans(), + # Age 17 at most: the model treats an unmarried filer's 18-year-old + # as a spouse. + "dependent_ages": st.lists(st.integers(0, 17), max_size=4), + "wages": st.integers(0, 500_000), + "exclusion": st.one_of(st.just(0), st.integers(1, 130_000)), + } +) + + +@hypothesis.settings( + max_examples=10, + deadline=None, + suppress_health_check=[ + hypothesis.HealthCheck.too_slow, + hypothesis.HealthCheck.data_too_large, + ], +) +@hypothesis.given( + st.lists(household_strategy, min_size=1, max_size=25), + st.sampled_from([2018, 2022, 2025, 2026]), +) +def test_random_households(households, year): + check(households, year) diff --git a/policyengine_us/tools/state_eitc_helpers.py b/policyengine_us/tools/state_eitc_helpers.py index 02f891b989c..caa6ad87f5f 100644 --- a/policyengine_us/tools/state_eitc_helpers.py +++ b/policyengine_us/tools/state_eitc_helpers.py @@ -14,6 +14,16 @@ def eitc_filing_requirement_met(tax_unit, period): return is_required | files_voluntarily | would_file_for_credits +def eitc_section_911_eligible(tax_unit, period): + """26 U.S.C. 32(c)(1)(C): no EITC for anyone claiming section 911 benefits. + + State credits that recompute a federal-style EITC waive only the federal + rules they name (age, SSN, separate filing), so this one still applies. + """ + + return tax_unit("foreign_earned_income_exclusion", period) <= 0 + + def eitc_filing_status_eligible( tax_unit, period, parameters, separate_filer_eligible=None ): @@ -127,6 +137,7 @@ def calculate_eitc_like_amount( ) is_filer = eitc_filing_requirement_met(tax_unit, period) takes_up_eitc = tax_unit("takes_up_eitc", period) + section_911_eligible = eitc_section_911_eligible(tax_unit, period) return ( calculate_eitc_amount_from_parameters( tax_unit, period, eitc_parameters, child_count @@ -135,6 +146,7 @@ def calculate_eitc_like_amount( * filer_identification_eligible * investment_income_eligible * filing_status_eligible + * section_911_eligible * is_filer * takes_up_eitc ) 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 07af7bbaf36..533956cea34 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,10 +31,7 @@ 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 - excluded = ( - files_form_2555 & p.refundable.foreign_earned_income_exclusion_bar_applies - ) - return has_qualifying_child & ~excluded + # Form 2555 filers skip Worksheet B in the years section 24(d)(3) + # denies them the refundable CTC (from 2015). + barred = tax_unit("refundable_ctc_barred_by_section_911_exclusion", period) + return has_qualifying_child & ~barred diff --git a/policyengine_us/variables/gov/irs/credits/ctc/refundable/refundable_ctc.py b/policyengine_us/variables/gov/irs/credits/ctc/refundable/refundable_ctc.py index 5b2f9bcaa95..e2a65576a39 100644 --- a/policyengine_us/variables/gov/irs/credits/ctc/refundable/refundable_ctc.py +++ b/policyengine_us/variables/gov/irs/credits/ctc/refundable/refundable_ctc.py @@ -8,7 +8,11 @@ class refundable_ctc(Variable): unit = USD documentation = "The portion of the Child Tax Credit that is refundable." definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/24#d" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/24#d", + # No refundable CTC for filers electing a section 911 exclusion. + "https://www.law.cornell.edu/uscode/text/26/24#d_3", + ) def formula(tax_unit, period, parameters): # This line corresponds to "the credit which would be allowed under this section [the CTC section]" @@ -35,4 +39,8 @@ def formula(tax_unit, period, parameters): ctc_capped_by_tax = min_(total_ctc, limiting_tax) ctc_capped_by_increased_tax = min_(total_ctc, limiting_tax + phase_in) amount_ctc_would_increase = ctc_capped_by_increased_tax - ctc_capped_by_tax - return min_(maximum_refundable_ctc, amount_ctc_would_increase) + refundable_amount = min_(maximum_refundable_ctc, amount_ctc_would_increase) + # Section 24(d)(3): the CTC is not refundable for a filer who elects to + # exclude any amount from gross income under section 911 (Form 2555). + barred = tax_unit("refundable_ctc_barred_by_section_911_exclusion", period) + return where(barred, 0, refundable_amount) diff --git a/policyengine_us/variables/gov/irs/credits/ctc/refundable/refundable_ctc_barred_by_section_911_exclusion.py b/policyengine_us/variables/gov/irs/credits/ctc/refundable/refundable_ctc_barred_by_section_911_exclusion.py new file mode 100644 index 00000000000..c460aabcf1c --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/ctc/refundable/refundable_ctc_barred_by_section_911_exclusion.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class refundable_ctc_barred_by_section_911_exclusion(Variable): + value_type = bool + entity = TaxUnit + label = "Refundable CTC barred by a section 911 exclusion" + documentation = ( + "Whether section 24(d)(3) denies the refundable Child Tax Credit " + "because the filer elects to exclude foreign earned income or housing " + "amounts from gross income under section 911 (Form 2555). These " + "filers also skip Schedule 8812 Credit Limit Worksheet B." + ) + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/24#d_3", + # 2025 Instructions for Schedule 8812, Part II-A. + "https://www.irs.gov/pub/irs-prior/i1040s8--2025.pdf#page=3", + # 2021 Schedule 8812: Part I-B has no Form 2555 condition. + "https://www.irs.gov/pub/irs-prior/f1040s8--2021.pdf#page=1", + ) + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.irs.credits.ctc.refundable + elects_section_911_exclusion = ( + tax_unit("foreign_earned_income_exclusion", period) > 0 + ) + # Section 24(i)(1)(A) (2021) switched off all of subsection (d), + # including this bar, for filers whose principal place of abode was in + # the United States for more than half the year and for bona fide + # residents of Puerto Rico. The model assumes every filer meets that + # test in a fully refundable year. + bar_in_force = ( + p.foreign_earned_income_exclusion_bar_applies and not p.fully_refundable + ) + return elects_section_911_exclusion & bar_in_force diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_eligible.py b/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_eligible.py index cf22ac0d4b5..be4d58a42b0 100644 --- a/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_eligible.py +++ b/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_eligible.py @@ -6,7 +6,11 @@ class eitc_eligible(Variable): entity = TaxUnit label = "Eligible for EITC" definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/32#c_1_A" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/32#c_1_A", + # No EITC for an individual who claims the benefits of section 911. + "https://www.law.cornell.edu/uscode/text/26/32#c_1_C", + ) def formula(tax_unit, period, parameters): eitc = parameters.gov.irs.credits.eitc(period) @@ -16,8 +20,16 @@ def formula(tax_unit, period, parameters): filers_has_ssn = tax_unit( "filer_meets_eitc_identification_requirements", period ) + # Section 32(c)(1)(C): "eligible individual" does not include an + # individual who claims the benefits of section 911 (Form 2555). + claims_section_911 = tax_unit("foreign_earned_income_exclusion", period) > 0 # Define eligibility before considering separate filer limitation. - eligible = demographic_eligible & investment_income_eligible & filers_has_ssn + eligible = ( + demographic_eligible + & investment_income_eligible + & filers_has_ssn + & ~claims_section_911 + ) # This parameter is true if separate filers are eligible. if eitc.eligibility.separate_filer: return eligible diff --git a/policyengine_us/variables/gov/irs/tax_unit_is_filer.py b/policyengine_us/variables/gov/irs/tax_unit_is_filer.py index 8b50bfae600..8395e8c7b47 100644 --- a/policyengine_us/variables/gov/irs/tax_unit_is_filer.py +++ b/policyengine_us/variables/gov/irs/tax_unit_is_filer.py @@ -11,7 +11,9 @@ class tax_unit_is_filer(Variable): A tax unit files if any of the following apply: 1. They are legally required to file (IRC ยง 6012) 2. They are eligible for refundable credits and would file to claim them - 3. They would file voluntarily for other reasons (state requirements, + 3. They claim a section 911 exclusion and would file to claim a tax + benefit (the exclusion is claimed on Form 2555 with a return) + 4. They would file voluntarily for other reasons (state requirements, documentation, habit) The propensity variables (would_file_if_eligible_for_refundable_credit @@ -19,7 +21,18 @@ class tax_unit_is_filer(Variable): construction. """ definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/6012" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/6012", + # A section 911 election is filed with the return for its first year + # and then stays in effect. + "https://www.law.cornell.edu/cfr/text/26/1.911-7", + # "in the case of an individual claiming an exclusion under section + # 911, he shall attach Form 2555 to the return required under this + # paragraph" + "https://www.law.cornell.edu/cfr/text/26/1.6012-1", + # "Attach Form 2555 to Form 1040 or 1040-SR when filed." + "https://www.irs.gov/pub/irs-pdf/i2555.pdf#page=2", + ) def formula(tax_unit, period, parameters): # Required to file based on income thresholds @@ -35,4 +48,13 @@ def formula(tax_unit, period, parameters): # Would file voluntarily for other reasons files_voluntarily = tax_unit("would_file_taxes_voluntarily", period) - return required | files_for_credits | files_voluntarily + # A section 911 exclusion is claimed on Form 2555 attached to a + # return (Treas. Reg. 1.911-7(a)(1) and 1.6012-1(a)(3)). Sections + # 24(d)(3) and 32(c)(1)(C) deny the claimant the refundable credits, + # so those credits cannot be its reason to file. A claimant under the + # filing threshold need not file in a later year of a continuing + # election, so the propensity to file for a tax benefit decides. + claims_section_911 = tax_unit("foreign_earned_income_exclusion", period) > 0 + files_for_section_911 = claims_section_911 & would_file_for_credits + + return required | files_for_credits | files_for_section_911 | files_voluntarily diff --git a/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py b/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py index cf1f3f204cb..5c38c142427 100644 --- a/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py +++ b/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py @@ -11,10 +11,19 @@ class tax_unit_is_required_to_file(Variable): status. """ definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/6012" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/6012", + # Gross income counts amounts excluded under section 911. + "https://www.law.cornell.edu/uscode/text/26/6012#c", + ) def formula(tax_unit, period, parameters): - gross_income = add(tax_unit, period, ["irs_gross_income"]) + # Section 6012(c): for the filing requirement, gross income is + # computed without regard to the section 911 exclusion. + section_911_exclusion = tax_unit("foreign_earned_income_exclusion", period) + gross_income = add(tax_unit, period, ["irs_gross_income"]) + ( + section_911_exclusion + ) p = parameters(period).gov.irs.income.exemption exemption_amount = 0 if p.suspended else p.amount @@ -34,7 +43,12 @@ def formula(tax_unit, period, parameters): unearned_income_threshold = 500 + tax_unit( "additional_standard_deduction", period ) - unearned_income = gross_income - add(tax_unit, period, ["earned_income"]) + # The section 911 exclusion covers earned income only. + unearned_income = ( + gross_income + - add(tax_unit, period, ["earned_income"]) + - section_911_exclusion + ) unearned_income_over_threshold = unearned_income > unearned_income_threshold return income_over_exemption_amount | unearned_income_over_threshold diff --git a/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_refundable_ctc.py b/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_refundable_ctc.py index 94428d1fa5a..686cb83972e 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_refundable_ctc.py +++ b/policyengine_us/variables/gov/states/co/tax/income/credits/ctc/federal_ctc/co_refundable_ctc.py @@ -15,6 +15,11 @@ class co_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", + # 1 CCR 201-2, Rule 39-22-129(1): the Colorado credit is a percentage + # of the allowed federal child tax credit. + "https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=1+CCR+201-2&ruleVersionId=10063#page=47", + # No refundable federal CTC for filers electing a section 911 exclusion. + "https://www.law.cornell.edu/uscode/text/26/24#d_3", ) defined_for = StateCode.CO @@ -46,4 +51,8 @@ def formula(tax_unit, period, parameters): relevant_earnings, max_(relevant_earnings, social_security_excess), ) # Line 19 - return min_(total_statutory_cap, tax_increase) # Line 20 + refundable_amount = min_(total_statutory_cap, tax_increase) # Line 20 + # Colorado matches the allowed federal credit, so the federal section + # 24(d)(3) bar on section 911 filers carries over. + barred = tax_unit("refundable_ctc_barred_by_section_911_exclusion", period) + return where(barred, 0, refundable_amount) diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py index 1eb790af145..f62c122b95f 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py @@ -2,6 +2,7 @@ from policyengine_us.tools.state_eitc_helpers import ( eitc_filing_requirement_met, eitc_filing_status_eligible, + eitc_section_911_eligible, ) @@ -41,6 +42,7 @@ def formula(tax_unit, period, parameters): & tax_unit.any(age_eligible) & tax_unit("eitc_investment_income_eligible", period) & eitc_filing_status_eligible(tax_unit, period, parameters) + & eitc_section_911_eligible(tax_unit, period) & eitc_filing_requirement_met(tax_unit, period) & tax_unit("takes_up_eitc", period) ) diff --git a/policyengine_us/variables/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.py b/policyengine_us/variables/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.py index 810e72154e7..0dd34bc357e 100644 --- a/policyengine_us/variables/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.py +++ b/policyengine_us/variables/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.py @@ -7,7 +7,11 @@ class nj_childless_eitc_age_eligible(Variable): label = "New Jersey Eligible for EITC" definition_period = YEAR reference = ( - "https://law.justia.com/codes/new-jersey/2022/title-54a/section-54a-4-7/" + "https://law.justia.com/codes/new-jersey/2022/title-54a/section-54a-4-7/", + # P.L. 2021, c.130, section 1a(4): the filer "shall meet all + # qualifications, except for the minimum or maximum age, for the + # federal earned income tax credit". + "https://pub.njleg.state.nj.us/Bills/2020/PL21/130_.HTM", ) defined_for = StateCode.NJ @@ -27,9 +31,14 @@ def formula(tax_unit, period, parameters): age = person("age", period) age_eligible = age >= p.eligibility.age.min + # Section 32(c)(1)(C): a filer who claims the benefits of section 911 + # fails a federal qualification other than age. + claims_section_911 = tax_unit("foreign_earned_income_exclusion", period) > 0 + return ( ~separate & no_qualifying_children & tax_unit.any(age_eligible) & tax_unit("nj_eitc_income_eligible", period) + & ~claims_section_911 ) diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py index 99a2d7de224..642ef1a6b77 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py @@ -3,6 +3,7 @@ calculate_eitc_demographic_eligibility, eitc_filing_requirement_met, eitc_filing_status_eligible, + eitc_section_911_eligible, ) @@ -126,7 +127,9 @@ def formula(tax_unit, period, parameters): "wa_working_families_tax_credit_age_expansion_eligible", period ) - eligible = eitc_eligible | state_only_eitc_eligible | age_expansion_eligible + eligible = ( + eitc_eligible | state_only_eitc_eligible | age_expansion_eligible + ) & eitc_section_911_eligible(tax_unit, period) # Parameters are based on EITC-eligible children. # WFTC child count is the larger of the federally-counted children diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py index e583a27bd21..d890a4c84b2 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py @@ -1,6 +1,7 @@ from policyengine_us.model_api import * from policyengine_us.tools.state_eitc_helpers import ( eitc_filing_requirement_met, + eitc_section_911_eligible, ) @@ -77,6 +78,7 @@ def formula(tax_unit, period, parameters): & income_eligible & investment_income_eligible & filers_have_tin + & eitc_section_911_eligible(tax_unit, period) & is_filer & takes_up_eitc ) diff --git a/policyengine_us/variables/gov/territories/pr/tax/income/credits/refundable_ctc/pr_refundable_ctc.py b/policyengine_us/variables/gov/territories/pr/tax/income/credits/refundable_ctc/pr_refundable_ctc.py index 4b64f63cd5a..37afe4f2451 100644 --- a/policyengine_us/variables/gov/territories/pr/tax/income/credits/refundable_ctc/pr_refundable_ctc.py +++ b/policyengine_us/variables/gov/territories/pr/tax/income/credits/refundable_ctc/pr_refundable_ctc.py @@ -10,6 +10,8 @@ class pr_refundable_ctc(Variable): reference = ( "https://www.irs.gov/pub/irs-pdf/f1040s8.pdf", "https://www.law.cornell.edu/uscode/text/26/24#h_4_A", + "https://www.law.cornell.edu/uscode/text/26/24#k_2_B_ii", + "https://www.law.cornell.edu/uscode/text/26/24#d_3", ) # This provision is part of the federal CTC legal code @@ -30,4 +32,9 @@ def formula(tax_unit, period, parameters): base_credit = max_(tax_unit("refundable_ctc", period), reduced_ss_tax) # line 27 - return min_(base_credit, tax_unit("ctc", period)) + credit = min_(base_credit, tax_unit("ctc", period)) + # Section 24(k)(2)(B)(ii) drops only the three-children condition for + # residents of Puerto Rico. Section 24(d)(3) still denies the + # refundable credit to a filer electing a section 911 exclusion. + barred = tax_unit("refundable_ctc_barred_by_section_911_exclusion", period) + return where(barred, 0, credit)