From 7d651dee547630b59ff05b75c80c959e57b3ae74 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 07:25:59 +0000 Subject: [PATCH 1/6] Let a qualifying child without an SSN establish EITC eligibility from 2021 IRC 32(c)(1)(A)(i) makes an individual with a qualifying child an eligible individual; 32(c)(3)(D) and (m) leave a child without a valid SSN out only of the amount under subsection (b). ARPA section 9622 struck former 32(c)(1)(F) from 2021. The model based eligibility on the SSN-filtered count, so such a parent under 25 failed the childless age test and got nothing. Eligibility now also counts the identification- independent qualifying child from 2021; the amount still uses the filtered count, giving the zero-child schedule. Co-Authored-By: Claude Opus 5.5 --- .../eitc-no-ssn-child-age-test.fixed.md | 1 + ...dentification_establishes_eligibility.yaml | 19 +++ .../eitc_qualifying_child_without_ssn.yaml | 156 ++++++++++++++++++ ...hild_without_ssn_eligibility_properties.py | 104 ++++++++++++ .../eligibility/eitc_demographic_eligible.py | 19 ++- 5 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 changelog.d/eitc-no-ssn-child-age-test.fixed.md create mode 100644 policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_qualifying_child_without_ssn.yaml create mode 100644 policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py diff --git a/changelog.d/eitc-no-ssn-child-age-test.fixed.md b/changelog.d/eitc-no-ssn-child-age-test.fixed.md new file mode 100644 index 00000000000..6b1994d62b8 --- /dev/null +++ b/changelog.d/eitc-no-ssn-child-age-test.fixed.md @@ -0,0 +1 @@ +From 2021, a filer whose only qualifying child lacks a valid SSN gets the zero-child EITC without the childless age test (IRC 32(c)(1)(A)(i); ARPA section 9622). diff --git a/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml b/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml new file mode 100644 index 00000000000..27a5ff295b5 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml @@ -0,0 +1,19 @@ +description: The US treats a filer whose qualifying child lacks the required identification as having a qualifying child for EITC eligibility when this is true. +metadata: + unit: bool + label: EITC qualifying child without identification establishes eligibility + reference: + # 32(c)(1)(A)(i) makes "any individual who has a qualifying child" an + # eligible individual; 32(c)(3)(D) and (m) exclude a child without a + # valid SSN only from the amount under subsection (b). + - title: 26 U.S. Code § 32(c)(1)(A), (c)(3)(D) - Earned income credit (2021 edition) + href: https://www.govinfo.gov/content/pkg/USCODE-2021-title26/pdf/USCODE-2021-title26-subtitleA-chap1-subchapA-partIV-subpartC-sec32.pdf#page=2 + # ARPA section 9622 struck former 32(c)(1)(F) for taxable years + # beginning after December 31, 2020. + - title: American Rescue Plan Act of 2021, section 9622 + href: https://www.congress.gov/117/bills/hr1319/BILLS-117hr1319enr.pdf#page=150 + - title: 2025 Publication 596, Chapter 2 (qualifying child rules apply even if the child doesn't have a valid SSN) + href: https://www.irs.gov/pub/irs-prior/p596--2025.pdf#page=10 +values: + 2013-01-01: false + 2021-01-01: true diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_qualifying_child_without_ssn.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_qualifying_child_without_ssn.yaml new file mode 100644 index 00000000000..b4368372cc0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_qualifying_child_without_ssn.yaml @@ -0,0 +1,156 @@ +# From 2021, a filer whose only qualifying child lacks a valid SSN is still an +# eligible individual with a qualifying child (IRC 32(c)(1)(A)(i)); the child +# is left out only of the amount (32(c)(3)(D), (m)). ARPA section 9622 struck +# former 32(c)(1)(F). So the filer gets the zero-child amount without the +# childless age test. + +- name: TY2023 parent age 20 with only qualifying child lacking SSN + period: 2023 + input: + people: + filer: + age: 20 + is_tax_unit_head: true + is_full_time_student: false + is_eitc_qualifying_child: false + ssn_card_type: CITIZEN + child: + age: 2 + is_tax_unit_dependent: true + is_eitc_qualifying_child: true + ssn_card_type: NONE + tax_units: + unit: + members: [filer, child] + filing_status: SINGLE + eitc_earned_income: 8_000 + adjusted_gross_income: 8_000 + eitc_relevant_investment_income: 0 + would_file_taxes_voluntarily: true + takes_up_eitc: true + output: + eitc_child_count: 0 + eitc_demographic_eligible: true + eitc_eligible: true + # Zero-child schedule: min($600, 7.65% x $8,000 = $612) = $600; $8,000 is + # below the $9,800 phase-out start. + eitc: 600 + +- name: TY2021 student parent age 20 with only qualifying child lacking SSN + period: 2021 + input: + people: + filer: + age: 20 + is_tax_unit_head: true + is_full_time_student: true + is_eitc_qualifying_child: false + ssn_card_type: CITIZEN + child: + age: 2 + is_tax_unit_dependent: true + is_eitc_qualifying_child: true + ssn_card_type: NONE + tax_units: + unit: + members: [filer, child] + filing_status: SINGLE + eitc_earned_income: 10_000 + adjusted_gross_income: 10_000 + eitc_relevant_investment_income: 0 + would_file_taxes_voluntarily: true + takes_up_eitc: true + output: + eitc_child_count: 0 + # The 2021 specified-student minimum age of 24 does not apply: the filer + # has a qualifying child. + eitc_demographic_eligible: true + eitc_eligible: true + # Zero-child schedule: min($1,502, 15.3% x $10,000 = $1,530) = $1,502; + # $10,000 is below the $11,610 phase-out start. + eitc: 1_502 + +- name: TY2020 parent age 20 with only qualifying child lacking SSN stays ineligible + period: 2020 + input: + people: + filer: + age: 20 + is_tax_unit_head: true + is_full_time_student: false + is_eitc_qualifying_child: false + ssn_card_type: CITIZEN + child: + age: 2 + is_tax_unit_dependent: true + is_eitc_qualifying_child: true + ssn_card_type: NONE + tax_units: + unit: + members: [filer, child] + filing_status: SINGLE + eitc_earned_income: 6_000 + adjusted_gross_income: 6_000 + eitc_relevant_investment_income: 0 + would_file_taxes_voluntarily: true + takes_up_eitc: true + output: + # Before 2021 the identification-filtered count decides, as on main: the + # filer is under the childless minimum age of 25. + eitc_child_count: 0 + eitc_demographic_eligible: false + eitc: 0 + +- name: TY2023 parent age 20 whose child has an SSN gets the one-child credit + period: 2023 + input: + people: + filer: + age: 20 + is_tax_unit_head: true + is_full_time_student: false + is_eitc_qualifying_child: false + ssn_card_type: CITIZEN + child: + age: 2 + is_tax_unit_dependent: true + is_eitc_qualifying_child: true + ssn_card_type: CITIZEN + tax_units: + unit: + members: [filer, child] + filing_status: SINGLE + eitc_earned_income: 8_000 + adjusted_gross_income: 8_000 + eitc_relevant_investment_income: 0 + would_file_taxes_voluntarily: true + takes_up_eitc: true + output: + eitc_child_count: 1 + eitc_demographic_eligible: true + eitc_eligible: true + # One child: 34% x $8,000 = $2,720, below the $3,995 maximum. + eitc: 2_720 + +- name: TY2023 parent age 20 with no qualifying child stays ineligible + period: 2023 + input: + people: + filer: + age: 20 + is_tax_unit_head: true + is_full_time_student: false + is_eitc_qualifying_child: false + ssn_card_type: CITIZEN + tax_units: + unit: + members: [filer] + filing_status: SINGLE + eitc_earned_income: 8_000 + adjusted_gross_income: 8_000 + eitc_relevant_investment_income: 0 + would_file_taxes_voluntarily: true + takes_up_eitc: true + output: + eitc_demographic_eligible: false + eitc: 0 diff --git a/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py b/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py new file mode 100644 index 00000000000..b8b677fd5c7 --- /dev/null +++ b/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py @@ -0,0 +1,104 @@ +"""Properties of EITC eligibility for a filer whose qualifying child lacks a +valid SSN. + +From 2021 (ARPA section 9622 struck former IRC 32(c)(1)(F)), such a filer is +an eligible individual with a qualifying child (32(c)(1)(A)(i)); the child is +left out only of the amount (32(c)(3)(D), (m)). So from 2021: + +1. Adding a qualifying child without an SSN never lowers the EITC. +2. When the SSN-filtered child count is zero, the EITC never exceeds the + zero-child maximum. + +Before 2021 the model keeps the identification-filtered count, so: + +3. A filer with only a child without an SSN gets the same EITC as the same + filer alone. + +Each example builds one simulation in which every filer appears twice: alone +and with a child without an SSN. +""" + +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import CountryTaxBenefitSystem, Simulation + +SYSTEM = CountryTaxBenefitSystem() + +FILER = st.fixed_dictionaries( + { + "age": st.integers(min_value=16, max_value=80), + "student": st.booleans(), + "earnings": st.integers(min_value=0, max_value=30_000), + } +) + + +def _simulate(year, filers): + people, tax_units, households = {}, {}, {} + for i, filer in enumerate(filers): + for with_child in (False, True): + name = f"u{i}_{int(with_child)}" + members = [f"{name}_f"] + people[f"{name}_f"] = { + "age": {year: filer["age"]}, + "is_full_time_student": {year: filer["student"]}, + "is_eitc_qualifying_child": {year: False}, + "ssn_card_type": {year: "CITIZEN"}, + "employment_income": {year: filer["earnings"]}, + } + if with_child: + members.append(f"{name}_c") + people[f"{name}_c"] = { + "age": {year: 2}, + "is_tax_unit_dependent": {year: True}, + "is_eitc_qualifying_child": {year: True}, + "ssn_card_type": {year: "NONE"}, + } + tax_units[name] = { + "members": members, + "filing_status": {year: "SINGLE"}, + "would_file_taxes_voluntarily": {year: True}, + "takes_up_eitc": {year: True}, + } + households[name] = {"members": members, "state_code": {year: "TX"}} + sim = Simulation( + situation={ + "people": people, + "tax_units": tax_units, + "households": households, + } + ) + eitc = sim.calculate("eitc", year) + count = sim.calculate("eitc_child_count", year) + return eitc[0::2], eitc[1::2], count[1::2] + + +SETTINGS = settings( + max_examples=8, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) + + +@SETTINGS +@given( + year=st.sampled_from([2021, 2022, 2023, 2024, 2025]), + filers=st.lists(FILER, min_size=1, max_size=10), +) +def test_child_without_ssn_never_lowers_eitc_from_2021(year, filers): + alone, with_child, count = _simulate(year, filers) + zero_child_max = SYSTEM.parameters.gov.irs.credits.eitc.max(f"{year}-01-01").calc(0) + assert (count == 0).all() + assert (with_child >= alone - 0.01).all() + assert (with_child <= zero_child_max + 0.01).all() + + +@SETTINGS +@given( + year=st.sampled_from([2018, 2019, 2020]), + filers=st.lists(FILER, min_size=1, max_size=10), +) +def test_child_without_ssn_changes_nothing_before_2021(year, filers): + alone, with_child, _ = _simulate(year, filers) + assert (abs(with_child - alone) < 0.01).all() diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py b/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py index 9cb742b764f..99226bae0b2 100644 --- a/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py +++ b/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py @@ -7,13 +7,28 @@ class eitc_demographic_eligible(Variable): label = "Meets demographic eligibility for EITC" definition_period = YEAR reference = ( - "https://www.law.cornell.edu/uscode/text/26/32#c_1_A_ii", + "https://www.law.cornell.edu/uscode/text/26/32#c_1_A", + "https://www.law.cornell.edu/uscode/text/26/32#c_3_D", + "https://www.congress.gov/117/bills/hr1319/BILLS-117hr1319enr.pdf#page=150", "https://www.irs.gov/pub/irs-prior/p596--2025.pdf#page=18", ) def formula(tax_unit, period, parameters): person = tax_unit.members - has_child = tax_unit("eitc_child_count", period) > 0 + # IRC 32(c)(1)(A)(i): an individual who has a qualifying child is an + # eligible individual. 32(c)(3)(D) and (m) leave a child without a + # valid SSN out only of the amount under subsection (b), which + # eitc_child_count carries. Since ARPA section 9622 struck former + # (c)(1)(F) (from 2021), such a child still establishes eligibility, + # so the filer gets the zero-child amount without the childless age + # test. Years before 2021 keep the identification-filtered count. + p = parameters(period).gov.irs.credits.eitc.eligibility + counted_child = tax_unit("eitc_child_count", period) > 0 + any_qualifying_child = tax_unit.any(person("is_eitc_qualifying_child", period)) + has_child = counted_child | ( + p.child_without_identification_establishes_eligibility + & any_qualifying_child + ) age = person("age", period) # Relative parameter reference break branching in some states that # modify EITC age limits. From 5d09b585833427d4ad6a28ee00f426e07775f836 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 2 Nov 2026 12:23:31 -0500 Subject: [PATCH 2/6] Give the under-25 DC case the federal self-only credit and DC's match From 2021 a filer whose only qualifying child lacks a TIN is an eligible individual under IRC 32(c)(1)(A)(i), so the federal credit is the zero-child $459. D.C. Code 47-1806.04(f)(4) keeps the child a qualifying child, and (f)(1)(B-1) allows 70% of that credit: $321.30. Update the with-child formula's comment to match. Co-Authored-By: Claude Opus 5.5 --- .../gov/states/dc/tax/income/dc_eitc.yaml | 20 ++++++++++++------- .../eitc/dc_eitc_with_qualifying_child.py | 7 ++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc.yaml index f440ab68ecf..f450ae8a65b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc.yaml @@ -508,7 +508,7 @@ dc_eitc_without_qualifying_child: 0 dc_eitc: 321.3 -- name: Without the TIN rule, a filer under 25 whose qualifying child has no TIN gets no DC EITC in 2023 +- name: Without the TIN rule, a filer under 25 whose qualifying child has no TIN gets the with-child match on the self-only credit in 2023 period: 2023 absolute_error_margin: 0.01 input: @@ -533,14 +533,20 @@ members: [head, dependent] state_code: DC output: - # With no child in the count, the federal demographic test falls back on - # the childless minimum age of 25, as the federal credit that is the - # pre-2023 base does, so the self-only amount is 0. - eitc: 0 + # From 2021 a filer whose only qualifying child lacks a TIN is an + # eligible individual under IRC 32(c)(1)(A)(i), which has no age test: + # ARPA section 9622 struck former 32(c)(1)(F). 32(c)(3)(D)(i) leaves the + # child out of the amount, so the federal credit is the zero-child + # amount: min($600, 7.65% x $6,000 = $459) = $459, with earnings and AGI + # of $6,000 below the $9,800 phase-out start. + eitc: 459 + # D.C. Code 47-1806.04(f)(4) gives "qualifying child" its IRC 32 + # meaning, so the filer still has one, and (f)(1)(B-1) allows 70% of the + # federal credit allowed: 0.7 x 459 = 321.30. dc_eitc_has_qualifying_child: true - dc_eitc_with_qualifying_child: 0 + dc_eitc_with_qualifying_child: 321.3 dc_eitc_without_qualifying_child: 0 - dc_eitc: 0 + dc_eitc: 321.3 - name: DC EITC uses the one-child credit for an ITIN child in TY2023 at low earnings period: 2023 diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py index 9c4b3815058..377284ee1e2 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py @@ -29,9 +29,10 @@ def formula(tax_unit, period, parameters): is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) child_count = tax_unit.sum(person("is_eitc_qualifying_child", period) & has_tin) filer_has_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 - # The federal demographic test with these children counted: a unit - # with none in the count qualifies only through the childless age - # rules, as for the federal credit. + # The federal demographic test with these children counted. A unit + # with none in the count falls back on eitc_demographic_eligible, + # which from 2021 passes a filer who has any qualifying child + # (IRC 32(c)(1)(A)(i)) and otherwise applies the childless age rules. demographic_eligible = (child_count > 0) | tax_unit( "eitc_demographic_eligible", period ) From d48a26e84ae0e97fc56bce9a10b0d3635cb3648c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 21:24:58 +0000 Subject: [PATCH 3/6] Pin both NJ EITC routes, make the no-SSN property test search, and fix a page range The property test now leaves qualifying-child status to the formulas and compares each filer with an age-40 childless twin, so it fails if the no-SSN child stops establishing eligibility or if the head counts as a qualifying child. The pre-2021 property becomes an upper bound that holds whether or not former 32(c)(1)(F) is modeled. Two NJ cases pin 54A:4-7(a)(1) for the federally eligible parent and (a)(4) for the childless filer under the federal age floor. 32(c)(3)(D) is on file page 3 of the 2021 Code PDF. Co-Authored-By: Claude Opus 5.5 --- ...dentification_establishes_eligibility.yaml | 2 +- .../nj/tax/income/credits/eitc/nj_eitc.yaml | 81 +++++++++++++++++++ ...hild_without_ssn_eligibility_properties.py | 75 ++++++++++------- 3 files changed, 128 insertions(+), 30 deletions(-) diff --git a/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml b/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml index 27a5ff295b5..c3cb61cae7d 100644 --- a/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml +++ b/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml @@ -6,7 +6,7 @@ metadata: # 32(c)(1)(A)(i) makes "any individual who has a qualifying child" an # eligible individual; 32(c)(3)(D) and (m) exclude a child without a # valid SSN only from the amount under subsection (b). - - title: 26 U.S. Code § 32(c)(1)(A), (c)(3)(D) - Earned income credit (2021 edition) + - title: "26 U.S. Code § 32(c)(1)(A), (c)(3)(D) - Earned income credit (2021 edition) #page 2-3" href: https://www.govinfo.gov/content/pkg/USCODE-2021-title26/pdf/USCODE-2021-title26-subtitleA-chap1-subchapA-partIV-subpartC-sec32.pdf#page=2 # ARPA section 9622 struck former 32(c)(1)(F) for taxable years # beginning after December 31, 2020. 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 be6996b945b..514de723c09 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 @@ -138,3 +138,84 @@ state_code: NJ output: nj_eitc: 0 + +# N.J.S.A. 54A:4-7(a), as amended by P.L. 2021, c.130. Paragraph (1): a +# resident eligible for the federal credit gets a percentage of "the federal +# earned income tax credit that would be allowed". Paragraph (4): a resident +# at least 18 who "cannot claim a qualifying child" and is ineligible for the +# federal credit "due to age requirements" gets a credit "predicated on the +# federal maximum amount for taxpayers with no qualifying child". + +- name: 2023 NJ parent aged 20 whose only child lacks an SSN gets 40% of the federal credit allowed + period: 2023 + absolute_error_margin: 0.01 + input: + people: + filer: + age: 20 + is_tax_unit_head: true + is_full_time_student: false + is_eitc_qualifying_child: false + ssn_card_type: CITIZEN + employment_income: 3_000 + child: + age: 2 + is_tax_unit_dependent: true + is_eitc_qualifying_child: true + ssn_card_type: NONE + tax_units: + unit: + members: [filer, child] + filing_status: SINGLE + eitc_relevant_investment_income: 0 + would_file_taxes_voluntarily: true + takes_up_eitc: true + households: + household: + members: [filer, child] + state_code: NJ + output: + # From 2021 the child without an SSN makes the filer federally eligible + # (IRC 32(c)(1)(A)(i)) but is left out of the amount (32(c)(3)(D)), so the + # zero-child schedule applies: 7.65% x $3,000 = $229.50, below the $600 + # maximum. + eitc_child_count: 0 + eitc_eligible: true + eitc: 229.50 + # The filer also meets the age route's terms, but paragraph (1) governs + # once the federal credit is allowed: 40% x $229.50 = $91.80 + # (54A:4-7(a)(1)). + nj_childless_eitc_age_eligible: true + nj_eitc: 91.80 + +- name: 2023 NJ childless filer aged 20 gets 40% of the federal zero-child maximum + period: 2023 + absolute_error_margin: 0.01 + input: + people: + filer: + age: 20 + is_tax_unit_head: true + is_full_time_student: false + is_eitc_qualifying_child: false + ssn_card_type: CITIZEN + employment_income: 3_000 + tax_units: + unit: + members: [filer] + filing_status: SINGLE + eitc_relevant_investment_income: 0 + would_file_taxes_voluntarily: true + takes_up_eitc: true + households: + household: + members: [filer] + state_code: NJ + output: + # Under the federal childless minimum age of 25, so no federal credit. + eitc_eligible: false + eitc: 0 + # Ineligible federally only because of age and without a qualifying + # child: 40% x the $600 zero-child maximum = $240 (54A:4-7(a)(4)). + nj_childless_eitc_age_eligible: true + nj_eitc: 240 diff --git a/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py b/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py index b8b677fd5c7..584d733839c 100644 --- a/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py +++ b/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py @@ -5,25 +5,30 @@ an eligible individual with a qualifying child (32(c)(1)(A)(i)); the child is left out only of the amount (32(c)(3)(D), (m)). So from 2021: -1. Adding a qualifying child without an SSN never lowers the EITC. -2. When the SSN-filtered child count is zero, the EITC never exceeds the - zero-child maximum. - -Before 2021 the model keeps the identification-filtered count, so: - -3. A filer with only a child without an SSN gets the same EITC as the same - filer alone. - -Each example builds one simulation in which every filer appears twice: alone -and with a child without an SSN. +1. The filer with the child gets the same EITC as an age-40 childless twin + with the same earnings: the zero-child amount does not depend on age, and + the twin always meets the childless age test. +2. The SSN-filtered child count stays zero. +3. The filer alone gets the twin's EITC if the filer meets the childless age + test (32(c)(1)(A)(ii)(II)), and nothing otherwise: being the head never + stands in for a qualifying child. + +Before 2021 the identification-filtered count decides eligibility, and former +32(c)(1)(F) can only deny the credit, so: + +4. Adding a qualifying child without an SSN never raises the EITC. + +Every input that decides qualifying-child status is left to the formulas: no +person sets is_eitc_qualifying_child. Each example builds one simulation in +which every filer appears three times: alone, with a child without an SSN, +and as an age-40 childless twin. """ from hypothesis import HealthCheck, given, settings from hypothesis import strategies as st +import numpy as np -from policyengine_us import CountryTaxBenefitSystem, Simulation - -SYSTEM = CountryTaxBenefitSystem() +from policyengine_us import Simulation FILER = st.fixed_dictionaries( { @@ -33,26 +38,26 @@ } ) +VARIANTS = ("alone", "with_child", "adult_alone") + def _simulate(year, filers): people, tax_units, households = {}, {}, {} for i, filer in enumerate(filers): - for with_child in (False, True): - name = f"u{i}_{int(with_child)}" + for variant in VARIANTS: + name = f"u{i}_{variant}" members = [f"{name}_f"] people[f"{name}_f"] = { - "age": {year: filer["age"]}, + "age": {year: 40 if variant == "adult_alone" else filer["age"]}, "is_full_time_student": {year: filer["student"]}, - "is_eitc_qualifying_child": {year: False}, "ssn_card_type": {year: "CITIZEN"}, "employment_income": {year: filer["earnings"]}, } - if with_child: + if variant == "with_child": members.append(f"{name}_c") people[f"{name}_c"] = { "age": {year: 2}, "is_tax_unit_dependent": {year: True}, - "is_eitc_qualifying_child": {year: True}, "ssn_card_type": {year: "NONE"}, } tax_units[name] = { @@ -71,7 +76,16 @@ def _simulate(year, filers): ) eitc = sim.calculate("eitc", year) count = sim.calculate("eitc_child_count", year) - return eitc[0::2], eitc[1::2], count[1::2] + n = len(VARIANTS) + age = sim.tax_benefit_system.parameters.gov.irs.credits.eitc.eligibility.age + return eitc[0::n], eitc[1::n], eitc[2::n], count[1::n], age(f"{year}-01-01") + + +def _meets_childless_age_test(filers, age): + filer_age = np.array([filer["age"] for filer in filers]) + student = np.array([filer["student"] for filer in filers]) + min_age = np.where(student, age.min_student, age.min) + return (filer_age >= min_age) & (filer_age <= age.max) SETTINGS = settings( @@ -86,12 +100,12 @@ def _simulate(year, filers): year=st.sampled_from([2021, 2022, 2023, 2024, 2025]), filers=st.lists(FILER, min_size=1, max_size=10), ) -def test_child_without_ssn_never_lowers_eitc_from_2021(year, filers): - alone, with_child, count = _simulate(year, filers) - zero_child_max = SYSTEM.parameters.gov.irs.credits.eitc.max(f"{year}-01-01").calc(0) +def test_child_without_ssn_gives_zero_child_amount_from_2021(year, filers): + alone, with_child, adult_alone, count, age = _simulate(year, filers) assert (count == 0).all() - assert (with_child >= alone - 0.01).all() - assert (with_child <= zero_child_max + 0.01).all() + assert (abs(with_child - adult_alone) < 0.01).all() + expected_alone = np.where(_meets_childless_age_test(filers, age), adult_alone, 0) + assert (abs(alone - expected_alone) < 0.01).all() @SETTINGS @@ -99,6 +113,9 @@ def test_child_without_ssn_never_lowers_eitc_from_2021(year, filers): year=st.sampled_from([2018, 2019, 2020]), filers=st.lists(FILER, min_size=1, max_size=10), ) -def test_child_without_ssn_changes_nothing_before_2021(year, filers): - alone, with_child, _ = _simulate(year, filers) - assert (abs(with_child - alone) < 0.01).all() +def test_child_without_ssn_never_raises_eitc_before_2021(year, filers): + # Before 2021 a qualifying child without an SSN never helps: the + # identification-filtered count decides eligibility, and former + # 32(c)(1)(F) can only deny the credit. + alone, with_child, _, _, _ = _simulate(year, filers) + assert (with_child <= alone + 0.01).all() From 7d53806d19a186379352a520dc84edb747098412 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 2 Nov 2026 19:43:44 -0500 Subject: [PATCH 4/6] Search the EITC property at its age boundaries and with a TIN Round 2's mutants found that random ages rarely reach 64 and that no property case had a child with has_tin false. The New Jersey parent case also pinned nj_childless_eitc_age_eligible at a value the ruling rejects; nj_eitc alone kills the same mutant. Co-Authored-By: Claude Opus 5.5 --- .../gov/states/nj/tax/income/credits/eitc/nj_eitc.yaml | 7 +++---- .../test_eitc_child_without_ssn_eligibility_properties.py | 7 ++++++- 2 files changed, 9 insertions(+), 5 deletions(-) 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 514de723c09..f0c74c1d0f2 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 @@ -182,10 +182,9 @@ eitc_child_count: 0 eitc_eligible: true eitc: 229.50 - # The filer also meets the age route's terms, but paragraph (1) governs - # once the federal credit is allowed: 40% x $229.50 = $91.80 - # (54A:4-7(a)(1)). - nj_childless_eitc_age_eligible: true + # Paragraph (4) reaches only a resident ineligible for the federal credit + # "due to age requirements". This filer is federally eligible, so + # paragraph (1) governs: 40% x $229.50 = $91.80 (54A:4-7(a)(1)). nj_eitc: 91.80 - name: 2023 NJ childless filer aged 20 gets 40% of the federal zero-child maximum diff --git a/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py b/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py index 584d733839c..c0ceaf4fada 100644 --- a/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py +++ b/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py @@ -32,9 +32,13 @@ FILER = st.fixed_dictionaries( { - "age": st.integers(min_value=16, max_value=80), + "age": st.one_of( + st.sampled_from([17, 18, 19, 23, 24, 25, 64, 65]), + st.integers(min_value=16, max_value=80), + ), "student": st.booleans(), "earnings": st.integers(min_value=0, max_value=30_000), + "child_has_tin": st.booleans(), } ) @@ -59,6 +63,7 @@ def _simulate(year, filers): "age": {year: 2}, "is_tax_unit_dependent": {year: True}, "ssn_card_type": {year: "NONE"}, + "has_tin": {year: filer["child_has_tin"]}, } tax_units[name] = { "members": members, From f82f490813f8208da709647fe66332fb6662fb8c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 04:26:52 +0000 Subject: [PATCH 5/6] Expect the federal zero-child credit for the separated CA parent whose child lacks an SSN Main's case from #10307 expected eitc_eligible false for a 2025 separated parent aged 20, since the parent is under the childless minimum age. On this branch the child without an SSN establishes federal eligibility from 2021 (IRC 32(c)(1)(A)(i)) while staying out of the amount, so the parent gets the zero-child 649. The CalEITC outputs do not move. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6HSDrJsaVhwknS3Czs --- .../tax/income/credits/earned_income/ca_eitc_mfs.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/earned_income/ca_eitc_mfs.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/earned_income/ca_eitc_mfs.yaml index 8f64e17295f..4e0f4307d30 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/earned_income/ca_eitc_mfs.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/earned_income/ca_eitc_mfs.yaml @@ -222,7 +222,13 @@ ca_eitc_eligible: true ca_yctc: 1_189 -- name: CalEITC retains its own age rules for an eligible separated parent +- name: CalEITC and the federal zero-child credit both reach a separated parent aged 20 whose child lacks an SSN + # The child without an SSN meets the federal separated-spouse exception + # (IRC 32(d)(2)(B), counted before identification) and, from 2021, + # establishes federal eligibility without the childless age test + # (32(c)(1)(A)(i)), but stays out of the amount (32(c)(3)(D)). Federal + # zero-child 2025: min(649, 0.0765 x 8,639 = 660.88) = 649, below the + # 10,620 phase-out start. CalEITC applies its own rules and is unchanged. period: 2025 input: people: @@ -245,7 +251,8 @@ output: eitc_child_count: 0 eitc_marital_eligible: true - eitc_eligible: false + eitc_eligible: true + eitc: 649 ca_eitc_eligible: true ca_yctc: 1_189 From 96419cabb44d84c8e08e33eedc9e52ab1b7474b3 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 5 Nov 2026 01:09:47 -0500 Subject: [PATCH 6/6] Read main's EITC identification bar instead of a second parameter #10297's `qualifying_child_without_identification_bar` already encodes ARPA section 9622, so `eitc_demographic_eligible` reads it and this PR's complement goes. Move the federal cases to a file named for the variable. Co-Authored-By: Claude Opus 5.5 --- ...dentification_establishes_eligibility.yaml | 19 ------------------- ...mographic_eligible_child_without_ssn.yaml} | 0 .../eligibility/eitc_demographic_eligible.py | 11 +++++------ 3 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml rename policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/{eitc_qualifying_child_without_ssn.yaml => eitc_demographic_eligible_child_without_ssn.yaml} (100%) diff --git a/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml b/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml deleted file mode 100644 index c3cb61cae7d..00000000000 --- a/policyengine_us/parameters/gov/irs/credits/eitc/eligibility/child_without_identification_establishes_eligibility.yaml +++ /dev/null @@ -1,19 +0,0 @@ -description: The US treats a filer whose qualifying child lacks the required identification as having a qualifying child for EITC eligibility when this is true. -metadata: - unit: bool - label: EITC qualifying child without identification establishes eligibility - reference: - # 32(c)(1)(A)(i) makes "any individual who has a qualifying child" an - # eligible individual; 32(c)(3)(D) and (m) exclude a child without a - # valid SSN only from the amount under subsection (b). - - title: "26 U.S. Code § 32(c)(1)(A), (c)(3)(D) - Earned income credit (2021 edition) #page 2-3" - href: https://www.govinfo.gov/content/pkg/USCODE-2021-title26/pdf/USCODE-2021-title26-subtitleA-chap1-subchapA-partIV-subpartC-sec32.pdf#page=2 - # ARPA section 9622 struck former 32(c)(1)(F) for taxable years - # beginning after December 31, 2020. - - title: American Rescue Plan Act of 2021, section 9622 - href: https://www.congress.gov/117/bills/hr1319/BILLS-117hr1319enr.pdf#page=150 - - title: 2025 Publication 596, Chapter 2 (qualifying child rules apply even if the child doesn't have a valid SSN) - href: https://www.irs.gov/pub/irs-prior/p596--2025.pdf#page=10 -values: - 2013-01-01: false - 2021-01-01: true diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_qualifying_child_without_ssn.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible_child_without_ssn.yaml similarity index 100% rename from policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_qualifying_child_without_ssn.yaml rename to policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible_child_without_ssn.yaml diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py b/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py index e72d80cba9e..9bfa45317ed 100644 --- a/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py +++ b/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py @@ -23,12 +23,11 @@ def formula(tax_unit, period, parameters): # so the filer gets the zero-child amount without the childless age # test. Years before 2021 keep the identification-filtered count. p = parameters(period).gov.irs.credits.eitc.eligibility - counted_child = tax_unit("eitc_child_count", period) > 0 - any_qualifying_child = tax_unit.any(person("is_eitc_qualifying_child", period)) - has_child = counted_child | ( - p.child_without_identification_establishes_eligibility - & any_qualifying_child - ) + has_child = tax_unit("eitc_child_count", period) > 0 + if not p.qualifying_child_without_identification_bar: + has_child = has_child | tax_unit.any( + person("is_eitc_qualifying_child", period) + ) age = person("age", period) # Relative parameter reference break branching in some states that # modify EITC age limits.