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/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible_child_without_ssn.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible_child_without_ssn.yaml new file mode 100644 index 00000000000..185f3f0d5ee --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible_child_without_ssn.yaml @@ -0,0 +1,195 @@ +# 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: TY2021 married separate student parent age 20 whose only qualifying child lacks an SSN + # The child without an SSN does both jobs: it is the qualifying child the + # separated-spouse exception needs (IRC 32(d)(2)(B), counted before + # identification), and it establishes eligibility without the 2021 + # specified-student minimum age of 24 (32(c)(1)(A)(i)). It stays out of + # the amount (32(c)(3)(D)), so the zero-child schedule applies. + 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: SEPARATE + eitc_spouses_lived_apart_last_six_months: true + 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_marital_eligible: true + eitc_demographic_eligible: true + eitc_eligible: true + # Zero-child schedule: min($1,502, 15.3% x $8,000 = $1,224) = $1,224; + # $8,000 is below the $11,610 phase-out start. + eitc: 1_224 + +- 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/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 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/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 6e59297a498..75cb463cbd8 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 @@ -194,12 +194,14 @@ nj_childless_eitc_age_eligible: true nj_eitc: 259.60 -- name: Separate filer who meets the federal exception through a child without an SSN gets no NJ age fallback - # The child counts for IRC 32(d)(2)(B) but not for the federal schedule - # (32(c)(3)(D)), so eitc_child_count is 0 and the filer, at 22, is below - # the federal minimum age. The filer meets the federal marital test, but - # N.J.S.A. 54A:4-7a.(3) requires a married claimant to file jointly, so - # the age fallback pays 0. +- name: Separate filer who meets the federal exception through a child without an SSN gets the federal credit but no NJ EITC + # The child counts for IRC 32(d)(2)(B) and, from 2021, establishes federal + # eligibility without the childless age test (32(c)(1)(A)(i)), but not + # for the federal schedule (32(c)(3)(D)). So eitc_child_count is 0 and + # the 22-year-old gets the zero-child credit: min(649, 0.0765 x 8,000 = + # 612) = 612, below the 10,620 phase-out start. N.J.S.A. 54A:4-7a.(3) + # requires a married claimant to file jointly, so NJ pays 0 on both the + # federal route (otherwise 0.4 x 612 = 244.80) and the age fallback. period: 2025 input: people: @@ -221,6 +223,8 @@ output: eitc_marital_eligible: true eitc_child_count: 0 + eitc_demographic_eligible: true + eitc: 612 nj_eitc_separate_filer_eligible: false nj_childless_eitc_age_eligible: false nj_eitc: 0 @@ -253,11 +257,11 @@ nj_eitc_separate_filer_eligible: false nj_eitc: 0 -- name: Separate filer on the separation-instrument route with a child without an SSN gets no NJ age fallback - # The instrument route meets the federal marital test, and the child - # without an SSN leaves eitc_child_count at 0, so the 22-year-old reaches - # only the age fallback. N.J.S.A. 54A:4-7a.(3) bars a married separate - # filer from it. +- name: Separate filer on the separation-instrument route with a child without an SSN gets the federal credit but no NJ EITC + # The instrument route meets the federal marital test. The child without + # an SSN makes the 22-year-old federally eligible but leaves + # eitc_child_count at 0, so the federal credit is the zero-child 612. + # N.J.S.A. 54A:4-7a.(3) bars a married separate filer from both NJ routes. period: 2025 input: people: @@ -278,6 +282,7 @@ state_code: NJ output: eitc_marital_eligible: true + eitc: 612 nj_childless_eitc_age_eligible: false nj_eitc: 0 @@ -365,3 +370,83 @@ eitc: 0 nj_childless_eitc_age_eligible: true nj_eitc: 259.60 + +# 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 + # 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 + 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 new file mode 100644 index 00000000000..c0ceaf4fada --- /dev/null +++ b/policyengine_us/tests/test_eitc_child_without_ssn_eligibility_properties.py @@ -0,0 +1,126 @@ +"""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. 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 Simulation + +FILER = st.fixed_dictionaries( + { + "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(), + } +) + +VARIANTS = ("alone", "with_child", "adult_alone") + + +def _simulate(year, filers): + people, tax_units, households = {}, {}, {} + for i, filer in enumerate(filers): + for variant in VARIANTS: + name = f"u{i}_{variant}" + members = [f"{name}_f"] + people[f"{name}_f"] = { + "age": {year: 40 if variant == "adult_alone" else filer["age"]}, + "is_full_time_student": {year: filer["student"]}, + "ssn_card_type": {year: "CITIZEN"}, + "employment_income": {year: filer["earnings"]}, + } + if variant == "with_child": + members.append(f"{name}_c") + people[f"{name}_c"] = { + "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, + "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) + 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( + 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_gives_zero_child_amount_from_2021(year, filers): + alone, with_child, adult_alone, count, age = _simulate(year, filers) + assert (count == 0).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 +@given( + year=st.sampled_from([2018, 2019, 2020]), + filers=st.lists(FILER, min_size=1, max_size=10), +) +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() 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 311e62c1bf7..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 @@ -7,13 +7,27 @@ 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 + # 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 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. 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 )