From 06f7d19db9d514a04fad4f353ec27a54305ed66f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 26 Sep 2026 23:23:12 -0400 Subject: [PATCH 1/5] Gate the DC EITC ITIN extension to 2023 and apply the IRC 152(c)(2) relationship test to EITC qualifying children - Cite D.C. Law 24-167 section 7012 (D.C. Code 47-1806.04(f)(1)(D)) for the DC ITIN rule instead of D.C. Law 23-149, which has no EITC language. - Add gov.states.dc.tax.income.credits.eitc.itin_eligible (true from 2023-01-01). Before 2023 the DC with-child credit matches the federal credit allowed and the childless credit needs a Social Security number. - Add dc_eitc_has_qualifying_child so the router and both branches share one qualifying-child test. - Add is_eitc_qualifying_child (IRC 32(c)(3)(A) / 152(c)): age test or the 152(c)(3)(B) disability waiver, and not a parent or grandparent of the filer. Use it in the federal eitc_child_count and the CA, CO, DC, IL, MN and WA credits. - YAML cases for TY2021/2022/2023 ITIN households and disabled parent/grandparent dependents, plus Hypothesis invariant tests. Co-Authored-By: Claude Opus 5.5 --- .../dc-eitc-itin-relationship-fixes.fixed.md | 1 + .../income/credits/eitc/itin_eligible.yaml | 14 + .../earned_income/eitc_child_count.yaml | 62 +++ .../is_eitc_qualifying_child.yaml | 106 +++++ .../test_eitc_qualifying_child_invariants.py | 252 +++++++++++ .../ca_is_qualifying_child_for_caleitc.yaml | 21 + .../co/tax/income/credits/eitc/co_eitc.yaml | 36 ++ .../gov/states/dc/tax/income/dc_eitc.yaml | 409 +++++++++++++++++- .../states/il/tax/income/credits/il_eitc.yaml | 35 ++ ...mn_child_and_working_families_credits.yaml | 31 ++ ...ing_families_tax_credit_disabled_itin.yaml | 39 ++ .../credits/earned_income/eitc_child_count.py | 11 +- .../earned_income/is_eitc_qualifying_child.py | 42 ++ .../ca_is_qualifying_child_for_caleitc.py | 15 +- .../co/tax/income/credits/eitc/co_eitc.py | 15 +- .../dc/tax/income/credits/eitc/dc_eitc.py | 16 +- .../eitc/dc_eitc_has_qualifying_child.py | 30 ++ .../eitc/dc_eitc_with_qualifying_child.py | 31 +- .../eitc/dc_eitc_without_qualifying_child.py | 26 +- .../states/il/tax/income/credits/il_eitc.py | 15 +- .../mn_child_and_working_families_credits.py | 11 +- .../credits/wa_working_families_tax_credit.py | 15 +- ...es_tax_credit_maximum_qualifying_income.py | 10 +- pyproject.toml | 1 + uv.lock | 90 ++++ 25 files changed, 1227 insertions(+), 107 deletions(-) create mode 100644 changelog.d/dc-eitc-itin-relationship-fixes.fixed.md create mode 100644 policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/itin_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/is_eitc_qualifying_child.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py create mode 100644 policyengine_us/variables/gov/irs/credits/earned_income/is_eitc_qualifying_child.py create mode 100644 policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py diff --git a/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md b/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md new file mode 100644 index 00000000000..051e3173d7d --- /dev/null +++ b/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md @@ -0,0 +1 @@ +Limit the DC EITC ITIN extension to tax years from 2023 (D.C. Code 47-1806.04(f)(1)(D), added by D.C. Law 24-167 section 7012, not D.C. Law 23-149), and stop counting a disabled parent or grandparent dependent as an EITC qualifying child under IRC 152(c)(2) in the federal, CA, CO, DC, IL, MN and WA credits. diff --git a/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/itin_eligible.yaml b/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/itin_eligible.yaml new file mode 100644 index 00000000000..b23b36c78ef --- /dev/null +++ b/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/itin_eligible.yaml @@ -0,0 +1,14 @@ +description: The District of Columbia accepts an individual taxpayer identification number for the filer, spouse and qualifying children under its earned income tax credit if this is true. +values: + 2015-01-01: false + # (f)(1)(D)(ii) applies to taxable years beginning after December 31, 2022. + 2023-01-01: true +metadata: + unit: bool + period: year + label: DC EITC ITIN eligibility + reference: + - title: Code of the District of Columbia § 47–1806.04 (f)(1)(D)(ii) + href: https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04#(f)(1)(D)(ii) + - title: D.C. Law 24-167, Fiscal Year 2023 Budget Support Act of 2022, § 7012 + href: https://code.dccouncil.gov/us/dc/council/laws/24-167 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_child_count.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_child_count.yaml index eee9530b246..d51c725e8e9 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_child_count.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_child_count.yaml @@ -85,3 +85,65 @@ members: [person1, person2] output: eitc_child_count: 0 + +- name: Case 6, disabled parent dependent fails the relationship test. + period: 2026 + input: + people: + person1: + is_tax_unit_head_or_spouse: true + meets_eitc_identification_requirements: true + person2: + age: 75 + is_permanently_and_totally_disabled: true + is_tax_unit_dependent: true + is_parent_of_filer_or_spouse: true + meets_eitc_identification_requirements: true + tax_units: + tax_unit: + members: [person1, person2] + output: + # IRC 152(c)(2): a parent is a qualifying relative, not a qualifying child. + eitc_child_count: 0 + +- name: Case 7, disabled grandparent dependent fails the relationship test. + period: 2026 + input: + people: + person1: + is_tax_unit_head_or_spouse: true + meets_eitc_identification_requirements: true + person2: + age: 85 + is_permanently_and_totally_disabled: true + is_tax_unit_dependent: true + is_grandparent_of_filer_or_spouse: true + meets_eitc_identification_requirements: true + tax_units: + tax_unit: + members: [person1, person2] + output: + eitc_child_count: 0 + +- name: Case 8, federal EITC uses the childless schedule for a filer supporting a disabled parent. + period: 2023 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 6_000 + parent: + age: 75 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + is_parent_of_filer_or_spouse: true + tax_units: + tax_unit: + members: [head, parent] + filing_status: HEAD_OF_HOUSEHOLD + output: + eitc_child_count: 0 + # Rev. Proc. 2022-38: 7.65% x 6,000 = 459, not the one-child 34% x 6,000. + eitc: 459 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/is_eitc_qualifying_child.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/is_eitc_qualifying_child.yaml new file mode 100644 index 00000000000..3bdd49ebc3c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/is_eitc_qualifying_child.yaml @@ -0,0 +1,106 @@ +# IRC 32(c)(3)(A) applies the IRC 152(c) qualifying-child definition: the +# 152(c)(2) relationship test and the 152(c)(3) age test, which 152(c)(3)(B) +# waives for a permanently and totally disabled individual. The identification +# requirement of IRC 32(c)(3)(D) is applied by each credit. + +- name: A dependent child under the age limit is an EITC qualifying child. + period: 2026 + input: + people: + head: + age: 40 + is_tax_unit_head: true + child: + age: 10 + is_tax_unit_dependent: true + output: + is_eitc_qualifying_child: [false, true] + +- name: A permanently and totally disabled adult child is an EITC qualifying child. + period: 2026 + input: + people: + head: + age: 55 + is_tax_unit_head: true + adult_child: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + output: + is_eitc_qualifying_child: [false, true] + +- name: A non-disabled adult dependent is not an EITC qualifying child. + period: 2026 + input: + people: + head: + age: 55 + is_tax_unit_head: true + adult_child: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: false + output: + is_eitc_qualifying_child: [false, false] + +- name: A permanently and totally disabled parent fails the relationship test. + period: 2026 + input: + people: + head: + age: 50 + is_tax_unit_head: true + parent: + age: 75 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + is_parent_of_filer_or_spouse: true + output: + # A parent can be a qualifying relative under IRC 152(d)(2)(C) but never + # a qualifying child under IRC 152(c)(2). + is_eitc_qualifying_child: [false, false] + +- name: A permanently and totally disabled grandparent fails the relationship test. + period: 2026 + input: + people: + head: + age: 50 + is_tax_unit_head: true + grandparent: + age: 85 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + is_grandparent_of_filer_or_spouse: true + output: + is_eitc_qualifying_child: [false, false] + +- name: A disabled non-dependent is not an EITC qualifying child. + period: 2026 + input: + people: + head: + age: 50 + is_tax_unit_head: true + is_permanently_and_totally_disabled: true + output: + is_eitc_qualifying_child: false + +- name: The identification requirement is not part of this test. + period: 2026 + input: + people: + head: + age: 40 + is_tax_unit_head: true + child: + age: 10 + is_tax_unit_dependent: true + ssn_card_type: NONE + output: + is_eitc_qualifying_child: [false, true] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py new file mode 100644 index 00000000000..981bb03f852 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py @@ -0,0 +1,252 @@ +"""Invariants of the EITC qualifying-child test and the DC EITC branches. + +Hypothesis draws earnings, investment income and ages. Each draw becomes one +vectorized simulation holding every combination of dependent, identification +status and state, so each property is checked on every combination for the +drawn incomes. + +EITC qualifying child (IRC 32(c)(3)(A), applying IRC 152(c)): + +1. A dependent flagged as a parent or grandparent of the filer is never an + EITC qualifying child. The IRC 152(c)(3)(B) disability waiver reaches only + the age test, not the IRC 152(c)(2) relationship test. +2. Relationship dominates disability: for a parent or grandparent dependent, + the disability flag changes no EITC-style credit (federal, CA, CO, DC, IL, + MN, WA). +3. Disability monotonicity: making an adult child dependent disabled never + moves a DC unit from the with-child schedule to the childless one. + +DC EITC (D.C. Code 47-1806.04(f)): + +4. Both branches are non-negative, at most one is positive, and dc_eitc + equals the branch selected by dc_eitc_has_qualifying_child, which is also + max(with, without). +5. The with-child branch is zero without a qualifying child, and the + childless branch is zero with one. +6. Before 2023, the with-child branch is the match times the federal credit + allowed (a differential check against the federal eitc variable). +7. Before 2023 an ITIN filer receives no DC EITC, and giving a qualifying + child an ITIN instead of a Social Security number never raises dc_eitc. +8. From 2023, giving a qualifying child an ITIN instead of a Social Security + number leaves dc_eitc unchanged (D.C. Code 47-1806.04(f)(1)(D)(ii)). +""" + +import itertools + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +YEARS = [2021, 2022, 2023, 2025] +ITIN_START = 2023 + +# Dependent kinds: (age, disabled, parent, grandparent). A kind of None has no +# dependent. "child" takes the drawn child age. +KINDS = { + "none": None, + "child": ("draw", False, False, False), + "disabled_adult_child": (30, True, False, False), + "adult_child": (30, False, False, False), + "disabled_parent": (75, True, True, False), + "parent": (75, False, True, False), + "disabled_grandparent": (90, True, False, True), + "grandparent": (90, False, False, True), +} +QUALIFYING_CHILD_KINDS = ["child", "disabled_adult_child"] +ASCENDANT_KINDS = ["disabled_parent", "parent", "disabled_grandparent", "grandparent"] +# (ssn_card_type, has_tin) +IDS = {"ssn": ("CITIZEN", True), "itin": ("NONE", True), "none": ("NONE", False)} +STATES = ["DC", "CA", "CO", "IL", "MN", "WA", "TX"] +CREDITS = [ + "eitc", + "dc_eitc", + "ca_eitc", + "co_eitc", + "il_eitc", + "mn_child_and_working_families_credits", + "wa_working_families_tax_credit", +] + +household_draw = st.fixed_dictionaries( + { + "earnings": st.integers(0, 60_000), + "interest": st.one_of(st.just(0), st.integers(0, 15_000)), + "head_age": st.integers(19, 70), + "child_age": st.integers(0, 18), + } +) + + +def build(draw, rows): + """One tax unit per row; returns the simulation and row metadata.""" + situation = {"people": {}, "tax_units": {}, "households": {}} + dependent_index = [] + person_count = 0 + for i, (kind, dep_id, filer_id, state) in enumerate(rows): + card, has_tin = IDS[filer_id] + head = f"h{i}" + situation["people"][head] = { + "age": {y: draw["head_age"] for y in YEARS}, + "is_tax_unit_head": {y: True for y in YEARS}, + "employment_income": {y: draw["earnings"] for y in YEARS}, + "taxable_interest_income": {y: draw["interest"] for y in YEARS}, + "ssn_card_type": {y: card for y in YEARS}, + "has_tin": {y: has_tin for y in YEARS}, + } + members = [head] + person_count += 1 + spec = KINDS[kind] + if spec is None: + dependent_index.append(-1) + else: + age, disabled, parent, grandparent = spec + age = draw["child_age"] if age == "draw" else age + dep_card, dep_has_tin = IDS[dep_id] + dependent = f"d{i}" + situation["people"][dependent] = { + "age": {y: age for y in YEARS}, + "is_tax_unit_dependent": {y: True for y in YEARS}, + "is_tax_unit_spouse": {y: False for y in YEARS}, + "is_permanently_and_totally_disabled": {y: disabled for y in YEARS}, + "is_parent_of_filer_or_spouse": {y: parent for y in YEARS}, + "is_grandparent_of_filer_or_spouse": {y: grandparent for y in YEARS}, + "ssn_card_type": {y: dep_card for y in YEARS}, + "has_tin": {y: dep_has_tin for y in YEARS}, + } + members.append(dependent) + dependent_index.append(person_count) + person_count += 1 + situation["tax_units"][f"t{i}"] = {"members": members} + situation["households"][f"hh{i}"] = { + "members": members, + "state_code": {y: state for y in YEARS}, + } + return Simulation(situation=situation), np.array(dependent_index) + + +def index_rows(rows): + return {row: i for i, row in enumerate(rows)} + + +DC_ROWS = [ + (kind, dep_id, filer_id, "DC") + for kind, dep_id, filer_id in itertools.product(KINDS, IDS, ["ssn", "itin"]) + if not (kind == "none" and dep_id != "ssn") +] +STATE_ROWS = [ + (kind, dep_id, filer_id, state) + for kind, dep_id, filer_id, state in itertools.product( + ASCENDANT_KINDS + ["adult_child"], ["ssn", "itin"], ["ssn", "itin"], STATES + ) +] + +SETTINGS = settings( + max_examples=8, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow], +) + + +@SETTINGS +@given(draw=household_draw) +def test_dc_eitc_invariants(draw): + sim, dependent_index = build(draw, DC_ROWS) + row = index_rows(DC_ROWS) + has_dependent = dependent_index >= 0 + for year in YEARS: + with_child = sim.calculate("dc_eitc_with_qualifying_child", year) + without = sim.calculate("dc_eitc_without_qualifying_child", year) + dc_eitc = sim.calculate("dc_eitc", year) + has_child = sim.calculate("dc_eitc_has_qualifying_child", year) + federal = sim.calculate("eitc", year) + qualifying = sim.calculate("is_eitc_qualifying_child", year) + match = sim.tax_benefit_system.parameters( + f"{year}-01-01" + ).gov.states.dc.tax.income.credits.eitc.with_children.match + context = f"year {year}, draw {draw}" + + # 1. Ascendants are never qualifying children. + for (kind, *_), i in row.items(): + if kind in ASCENDANT_KINDS: + assert not qualifying[dependent_index[i]], (kind, context) + assert not has_child[i], (kind, context) + # A dependent's qualifying-child status matches its unit's routing + # whenever the dependent has a TIN. + dep_qualifies = np.where( + has_dependent, qualifying[np.maximum(dependent_index, 0)], False + ) + dep_has_tin = np.array([r[1] != "none" for r in DC_ROWS]) & has_dependent + assert np.array_equal(has_child, dep_qualifies & dep_has_tin), context + + # 4 and 5. Branch structure. + assert np.all(with_child >= 0) and np.all(without >= 0), context + assert np.all(with_child * without == 0), context + assert np.all(with_child[~has_child] == 0), context + assert np.all(without[has_child] == 0), context + assert np.allclose(dc_eitc, np.where(has_child, with_child, without)), context + assert np.allclose(dc_eitc, np.maximum(with_child, without)), context + + # 3. Disability never moves a unit off the with-child schedule. + for dep_id, filer_id in itertools.product(IDS, ["ssn", "itin"]): + disabled = row[("disabled_adult_child", dep_id, filer_id, "DC")] + plain = row[("adult_child", dep_id, filer_id, "DC")] + assert has_child[disabled] >= has_child[plain], context + + if year < ITIN_START: + # 6. Differential check against the federal credit. + assert np.allclose(with_child, np.where(has_child, match * federal, 0)), ( + context + ) + # 7. No ITIN path before 2023. + itin_filer = np.array([r[2] == "itin" for r in DC_ROWS]) + assert np.all(dc_eitc[itin_filer] == 0), context + for kind, filer_id in itertools.product( + QUALIFYING_CHILD_KINDS, ["ssn", "itin"] + ): + ssn = row[(kind, "ssn", filer_id, "DC")] + itin = row[(kind, "itin", filer_id, "DC")] + assert dc_eitc[itin] <= dc_eitc[ssn] + 1e-6, (kind, context) + else: + # 8. ITIN invariance from 2023. + for kind, filer_id in itertools.product( + QUALIFYING_CHILD_KINDS, ["ssn", "itin"] + ): + ssn = row[(kind, "ssn", filer_id, "DC")] + itin = row[(kind, "itin", filer_id, "DC")] + assert np.isclose(dc_eitc[itin], dc_eitc[ssn]), (kind, context) + + +@SETTINGS +@given(draw=household_draw) +def test_relationship_dominates_disability_across_credits(draw): + sim, dependent_index = build(draw, STATE_ROWS) + row = index_rows(STATE_ROWS) + for year in YEARS: + qualifying = sim.calculate("is_eitc_qualifying_child", year) + child_count = sim.calculate("eitc_child_count", year) + values = {credit: sim.calculate(credit, year) for credit in CREDITS} + context = f"year {year}, draw {draw}" + for (kind, dep_id, filer_id, state), i in row.items(): + if kind in ASCENDANT_KINDS: + # 1. Ascendants never qualify, whatever their disability. + assert not qualifying[dependent_index[i]], (kind, context) + assert child_count[i] == 0, (kind, context) + # 2. For an ascendant, disability changes no credit. + for ascendant in ["parent", "grandparent"]: + for dep_id, filer_id, state in itertools.product( + ["ssn", "itin"], ["ssn", "itin"], STATES + ): + disabled = row[(f"disabled_{ascendant}", dep_id, filer_id, state)] + plain = row[(ascendant, dep_id, filer_id, state)] + for credit, value in values.items(): + assert np.isclose(value[disabled], value[plain]), ( + credit, + ascendant, + dep_id, + filer_id, + state, + context, + ) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/earned_income/ca_is_qualifying_child_for_caleitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/earned_income/ca_is_qualifying_child_for_caleitc.yaml index bd53d5ea72d..cefea311401 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/earned_income/ca_is_qualifying_child_for_caleitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/earned_income/ca_is_qualifying_child_for_caleitc.yaml @@ -61,3 +61,24 @@ state_code: CA output: ca_is_qualifying_child_for_caleitc: false + +- name: Disabled parent dependent is not a CalEITC qualifying child. + period: 2026 + input: + people: + head: + age: 50 + is_tax_unit_head: true + disabled_parent: + age: 75 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + is_parent_of_filer_or_spouse: true + households: + household: + members: [head, disabled_parent] + state_code: CA + output: + # IRC 152(c)(2), which the RTC 17052 qualifying-child definition follows. + ca_is_qualifying_child_for_caleitc: [false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml index 061ddff486b..024f0913570 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml @@ -361,3 +361,39 @@ # child under IRC 152(c)(3)(B), so the CO EITC pays its match on the # 1-child federal-style EITC rather than the childless amount. co_eitc: 1_700 + +- name: Colorado EITC uses the childless schedule for a disabled parent dependent (IRC 152(c)(2)) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + employment_income: 10_000 + is_tax_unit_head: true + has_tin: true + ssn_card_type: CITIZEN + disabled_parent: + age: 75 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + is_parent_of_filer_or_spouse: true + has_tin: true + ssn_card_type: CITIZEN + tax_units: + tax_unit: + members: [head, disabled_parent] + filing_status: HEAD_OF_HOUSEHOLD + spm_units: + spm_unit: + members: [head, disabled_parent] + households: + household: + members: [head, disabled_parent] + state_code: CO + output: + # A parent fails the IRC 152(c)(2) relationship test, so the federal + # credit is the childless 7.65% x 10,000 capped at 649; 50% x 649. + eitc_child_count: 0 + co_eitc: 324.5 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 236fd0dc8cf..caf1af7bbaf 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 @@ -74,6 +74,7 @@ disabled_adult: age: 30 is_tax_unit_dependent: true + is_tax_unit_spouse: false is_permanently_and_totally_disabled: true ssn_card_type: NONE has_tin: true @@ -86,11 +87,415 @@ members: [head, disabled_adult] state_code: DC output: - # The federal credit needs a Social Security number, D.C. Law 23-149 - # does not. The childless branch also counts this dependent as a child, + # The federal credit needs a Social Security number; from 2023 D.C. Code + # 47-1806.04(f)(1)(D)(ii), added by D.C. Law 24-167 section 7012, accepts + # an ITIN. The childless branch also counts this dependent as a child, # so before the router matched it the unit got neither credit. eitc_child_count: 0 dc_eitc_without_qualifying_child: 0 dc_eitc_with_qualifying_child: 2_796.5 dc_eitc: 2_796.5 + +- name: DC EITC does not count an ITIN child before 2023 (TY2022) + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # (f)(1)(D)(ii) applies only to tax years beginning after December 31, + # 2022. Before then IRC 32(c)(3)(D) keeps the ITIN child out of the + # federal credit, which for this filer is the childless amount: + # 560 - 7.65% x (20,000 - 9,160) < 0. The child is still a qualifying + # child, so the (f)(1)(C) childless schedule does not apply. + eitc: 0 + dc_eitc_has_qualifying_child: true + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 0 + +- name: DC EITC counts an ITIN child from 2023 (TY2023) + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # 70% x the one-child federal-like credit of 3,995, as if the ITIN met + # IRC 32(m). + eitc: 0 + dc_eitc_with_qualifying_child: 2_796.5 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 2_796.5 + +- name: DC EITC matches the childless federal credit for a filer with only an ITIN child in TY2021 + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # ARPA's 2021 childless credit: 1,502 - 15.3% x (20,000 - 11,610) = + # 218.33 is the federal credit allowed; DC matches 40% of it. + eitc: 218.33 + dc_eitc_with_qualifying_child: 87.33 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 87.33 + +- name: DC EITC matches the childless federal credit for a filer with only an ITIN child in TY2022 at low earnings + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 6_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # Federal credit allowed: 7.65% x 6,000 = 459. DC matches 70% of it. + eitc: 459 + dc_eitc_with_qualifying_child: 321.3 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 321.3 + +- name: DC EITC uses the one-child credit for an ITIN child in TY2023 at low earnings + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 6_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # 70% x 34% x 6,000 = 1,428, not 70% of the 459 federal credit allowed. + eitc: 459 + dc_eitc_with_qualifying_child: 1_428 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 1_428 + +- name: DC EITC denies an ITIN filer with a Social Security number child before 2023 + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: NONE + dependent: + age: 10 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: false + ssn_card_type: CITIZEN + has_tin: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # The filer has no Social Security number and (f)(1)(D) is not yet in + # force. + eitc_child_count: 1 + eitc: 0 + dc_eitc_with_qualifying_child: 0 + dc_eitc: 0 + +- name: DC EITC allows an ITIN filer with a Social Security number child from 2023 + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: NONE + dependent: + age: 10 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: false + ssn_card_type: CITIZEN + has_tin: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # 70% x 3,995. + eitc_child_count: 1 + eitc: 0 + dc_eitc_with_qualifying_child: 2_796.5 + dc_eitc: 2_796.5 + +- name: DC EITC with-child branch for a disabled adult child with a Social Security number + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: CITIZEN + has_tin: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # IRC 152(c)(3)(B) waives the age test for the disabled adult child. + eitc_child_count: 1 + eitc: 3_995 + dc_eitc_with_qualifying_child: 2_796.5 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 2_796.5 + +- name: DC EITC childless amount for a disabled parent dependent + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 75 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: CITIZEN + has_tin: true + is_parent_of_filer_or_spouse: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # A parent fails the IRC 152(c)(2) relationship test, so the disabled + # parent is a qualifying relative, not a qualifying child. (f)(1)(C): + # min(7.65% x 20,000, 600) - 8.48% x max(0, 20,000 - 21,888) = 600. + is_eitc_qualifying_child: [false, false] + eitc_child_count: 0 + dc_eitc_has_qualifying_child: false + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 600 + dc_eitc: 600 + +- name: DC EITC childless amount for a disabled parent dependent at low earnings + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 6_000 + ssn_card_type: CITIZEN + dependent: + age: 75 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: CITIZEN + has_tin: true + is_parent_of_filer_or_spouse: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # 7.65% x 6,000 = 459 under both the federal and DC childless rules. + eitc_child_count: 0 + eitc: 459 + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 459 + dc_eitc: 459 + +- name: DC EITC childless amount for a disabled grandparent dependent + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 80 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: CITIZEN + has_tin: true + is_grandparent_of_filer_or_spouse: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # A grandparent also fails the IRC 152(c)(2) relationship test. + eitc_child_count: 0 + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 600 + dc_eitc: 600 + +- name: DC EITC childless amount for a disabled parent dependent with an ITIN + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 75 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: true + is_parent_of_filer_or_spouse: true + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # Before this fix the ITIN parent routed the unit to the with-child + # branch and received 70% x 3,995 = 2,796.50. + dc_eitc_has_qualifying_child: false + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 600 + dc_eitc: 600 diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_eitc.yaml index 14c1be44cb2..d43f6df3d5d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_eitc.yaml @@ -174,3 +174,38 @@ # child under IRC 152(c)(3)(B), so the IL EIC reflects the 1-child federal # EITC (rather than the childless schedule) times the IL match. il_eitc: 680 + +- name: Illinois EITC uses the childless schedule for a disabled parent dependent (IRC 152(c)(2)) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + employment_income: 10_000 + is_tax_unit_head: true + has_tin: true + ssn_card_type: CITIZEN + disabled_parent: + age: 75 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + is_parent_of_filer_or_spouse: true + has_tin: true + ssn_card_type: CITIZEN + tax_units: + tax_unit: + members: [head, disabled_parent] + filing_status: HEAD_OF_HOUSEHOLD + spm_units: + spm_unit: + members: [head, disabled_parent] + households: + household: + members: [head, disabled_parent] + state_code: IL + output: + # A parent fails the IRC 152(c)(2) relationship test: 20% x the childless + # federal-like credit of 649. + il_eitc: 129.8 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/credits/mn_child_and_working_families_credits.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/credits/mn_child_and_working_families_credits.yaml index 4de751088e0..d7516e3a7ce 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/credits/mn_child_and_working_families_credits.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/credits/mn_child_and_working_families_credits.yaml @@ -566,3 +566,34 @@ state_code: MN output: mn_child_and_working_families_credits: 2_129.2 + +- name: MN qualifying older child bonus excludes a disabled parent dependent (IRC 152(c)(2)) + # A parent fails the IRC 152(c)(2) relationship test, so the disabled parent + # is not an IRC 32(c) qualifying child and not a qualifying older child. + period: 2025 + input: + people: + person1: + age: 45 + is_full_time_student: false + disabled_parent: + age: 75 + is_tax_unit_dependent: true + is_full_time_student: false + is_permanently_and_totally_disabled: true + is_parent_of_filer_or_spouse: true + tax_units: + tax_unit: + members: [person1, disabled_parent] + ctc_qualifying_children: 0 + mn_wfc_eligible: true + filer_adjusted_earnings: 10_000 + adjusted_gross_income: 10_000 + filing_status: JOINT + households: + household: + members: [person1, disabled_parent] + state_code: MN + output: + # WFC base only: min($10,000, $9,480) * 0.04 = $379.20. + mn_child_and_working_families_credits: 379.2 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_disabled_itin.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_disabled_itin.yaml index b4c2b9c1d9f..54cfe8e7ce4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_disabled_itin.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_disabled_itin.yaml @@ -271,3 +271,42 @@ eitc_child_count: 0 # The Washington exception requires a valid SSN or ITIN for the child. wa_working_families_tax_credit: 0 + +- name: Disabled ITIN parent dependent does not count as a qualifying child. + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + employment_income: 10_000 + is_tax_unit_head: true + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + disabled_parent: + age: 75 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + is_parent_of_filer_or_spouse: true + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + is_disabled: true + tax_units: + tax_unit: + members: [head, disabled_parent] + filing_status: HEAD_OF_HOUSEHOLD + would_file_taxes_voluntarily: true + takes_up_eitc: true + spm_units: + spm_unit: + members: [head, disabled_parent] + households: + household: + members: [head, disabled_parent] + state_code: WA + output: + # A parent fails the IRC 152(c)(2) relationship test, so the filer gets the + # childless $315 maximum rather than the one-child $625. + eitc_child_count: 0 + wa_working_families_tax_credit: 315 diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/eitc_child_count.py b/policyengine_us/variables/gov/irs/credits/earned_income/eitc_child_count.py index 28120110d0b..b6df0c2be59 100644 --- a/policyengine_us/variables/gov/irs/credits/earned_income/eitc_child_count.py +++ b/policyengine_us/variables/gov/irs/credits/earned_income/eitc_child_count.py @@ -11,18 +11,13 @@ class eitc_child_count(Variable): reference = ( # IRC 32(c)(3)(D)(i) specifies qualifying children need a taxpayer identification number. "https://www.law.cornell.edu/uscode/text/26/32#c_3_D_i", - # IRC 152(c)(3)(B) waives the qualifying-child age test for permanently and totally disabled individuals. - "https://www.law.cornell.edu/uscode/text/26/152#c_3_B", + # IRC 32(c)(3)(A) applies the IRC 152(c) qualifying-child definition. + "https://www.law.cornell.edu/uscode/text/26/32#c_3_A", ) def formula(tax_unit, period, parameters): person = tax_unit.members - is_disabled_dependent = person("is_tax_unit_dependent", period) & person( - "is_permanently_and_totally_disabled", period - ) - is_child = ( - person("is_qualifying_child_dependent", period) | is_disabled_dependent - ) + is_child = person("is_eitc_qualifying_child", period) meets_eitc_identification_requirements = person( "meets_eitc_identification_requirements", period ) diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/is_eitc_qualifying_child.py b/policyengine_us/variables/gov/irs/credits/earned_income/is_eitc_qualifying_child.py new file mode 100644 index 00000000000..47841c8c963 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/earned_income/is_eitc_qualifying_child.py @@ -0,0 +1,42 @@ +from policyengine_us.model_api import * + + +class is_eitc_qualifying_child(Variable): + value_type = bool + entity = Person + label = "Is an EITC qualifying child before the identification requirement" + documentation = """ + Whether a tax unit dependent is a qualifying child under IRC 32(c)(3)(A), + which applies the IRC 152(c) qualifying-child definition. The taxpayer + identification number requirement of IRC 32(c)(3)(D) is applied by each + credit, because some states accept an ITIN where the federal credit + requires a Social Security number. + """ + definition_period = YEAR + reference = ( + # IRC 32(c)(3)(A) applies the IRC 152(c) qualifying-child definition. + "https://www.law.cornell.edu/uscode/text/26/32#c_3_A", + # IRC 152(c)(2) is the relationship test. + "https://www.law.cornell.edu/uscode/text/26/152#c_2", + # IRC 152(c)(3)(B) waives the age test for permanently and totally disabled individuals. + "https://www.law.cornell.edu/uscode/text/26/152#c_3_B", + ) + + def formula(person, period, parameters): + # IRC 152(c)(3)(B) treats the age test of 152(c)(3)(A) as met for a + # permanently and totally disabled dependent of any age. + is_disabled_dependent = person("is_tax_unit_dependent", period) & person( + "is_permanently_and_totally_disabled", period + ) + meets_age_test = ( + person("is_qualifying_child_dependent", period) | is_disabled_dependent + ) + # The waiver does not reach the IRC 152(c)(2) relationship test, which + # admits only the taxpayer's children, siblings and their descendants. + # A parent or grandparent fails it and can be a qualifying relative + # (IRC 152(d)(2)(C)-(D)) but not a qualifying child. Other relatives + # outside 152(c)(2) are not recorded, so they are not excluded here. + fails_relationship_test = person( + "is_parent_of_filer_or_spouse", period + ) | person("is_grandparent_of_filer_or_spouse", period) + return meets_age_test & ~fails_relationship_test diff --git a/policyengine_us/variables/gov/states/ca/tax/income/credits/earned_income/ca_is_qualifying_child_for_caleitc.py b/policyengine_us/variables/gov/states/ca/tax/income/credits/earned_income/ca_is_qualifying_child_for_caleitc.py index 6d82511a734..7cb17b9f4b6 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/credits/earned_income/ca_is_qualifying_child_for_caleitc.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/credits/earned_income/ca_is_qualifying_child_for_caleitc.py @@ -10,16 +10,13 @@ class ca_is_qualifying_child_for_caleitc(Variable): "https://www.ftb.ca.gov/file/personal/credits/EITC-calculator/Help/QualifyingChildren", # RTC 17052 conforms CalEITC to the IRC 32 qualifying-child definition. "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17052", - # IRC 152(c)(3)(B) waives the qualifying-child age test for permanently and totally disabled individuals. - "https://www.law.cornell.edu/uscode/text/26/152#c_3_B", + # IRC 32(c)(3)(A) applies the IRC 152(c) qualifying-child definition. + "https://www.law.cornell.edu/uscode/text/26/32#c_3_A", ) defined_for = StateCode.CA def formula(person, period, parameters): - # CalEITC uses federal EITC rules regarding qualifying children. - # IRC 152(c)(3)(B) waives the age test for a permanently and totally - # disabled dependent, so such a dependent qualifies at any age. - is_disabled_dependent = person("is_tax_unit_dependent", period) & person( - "is_permanently_and_totally_disabled", period - ) - return person("is_qualifying_child_dependent", period) | is_disabled_dependent + # CalEITC uses federal EITC rules regarding qualifying children, + # including the IRC 152(c)(3)(B) age waiver for a permanently and + # totally disabled dependent and the IRC 152(c)(2) relationship test. + return person("is_eitc_qualifying_child", period) diff --git a/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py b/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py index cec12bfd7e6..c941c366164 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py +++ b/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py @@ -14,8 +14,8 @@ class co_eitc(Variable): "https://leg.colorado.gov/sites/default/files/te19_colorado_earned_income_tax_credit.pdf", # C.R.S. 39-22-123.5 pays a percentage of the federal IRC 32 credit. "https://leg.colorado.gov/sites/default/files/images/olls/crs2023-title-39.pdf", - # IRC 152(c)(3)(B) waives the qualifying-child age test for permanently and totally disabled individuals. - "https://www.law.cornell.edu/uscode/text/26/152#c_3_B", + # IRC 32(c)(3)(A) applies the IRC 152(c) qualifying-child definition. + "https://www.law.cornell.edu/uscode/text/26/32#c_3_A", ) defined_for = StateCode.CO @@ -32,14 +32,9 @@ def formula(tax_unit, period, parameters): filer_has_federal_identification = ( tax_unit.sum(is_head_or_spouse & ~federal_identification) == 0 ) - # IRC 152(c)(3)(B) waives the age test for a permanently and totally - # disabled dependent, matching the federal eitc_child_count. - is_disabled_dependent = person("is_tax_unit_dependent", period) & person( - "is_permanently_and_totally_disabled", period - ) - qualifying_child_with_tin = ( - person("is_qualifying_child_dependent", period) | is_disabled_dependent - ) & has_tin + # Same qualifying-child test as the federal eitc_child_count, with an + # ITIN accepted in place of a Social Security number. + qualifying_child_with_tin = person("is_eitc_qualifying_child", period) & has_tin child_count_with_tin = tax_unit.sum(qualifying_child_with_tin) federal_eitc_parameters = parameters(period).gov.irs.credits.eitc student = person("is_full_time_student", period) diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py index 24aef3ae7a5..853ace34e03 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py @@ -9,24 +9,14 @@ class dc_eitc(Variable): definition_period = YEAR reference = ( "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04", # (f) - "https://code.dccouncil.gov/us/dc/council/laws/23-149", # D.C. Law 23-149 ITIN expansion ) defined_for = StateCode.DC def formula(tax_unit, period, parameters): - # D.C. Law 23-149 extends EITC eligibility to filers and qualifying - # children with ITINs. - person = tax_unit.members - # IRC 152(c)(3)(B) waives the age test for a permanently and totally - # disabled dependent, as both branch variables do. - is_disabled_dependent = person("is_tax_unit_dependent", period) & person( - "is_permanently_and_totally_disabled", period - ) - dc_qualifying_child = ( - person("is_qualifying_child_dependent", period) | is_disabled_dependent - ) & person("has_tin", period) + # The with-children and childless schedules are mutually exclusive; + # both branch variables apply the same qualifying-child test. return where( - tax_unit.sum(dc_qualifying_child) > 0, + tax_unit("dc_eitc_has_qualifying_child", period), tax_unit("dc_eitc_with_qualifying_child", period), tax_unit("dc_eitc_without_qualifying_child", period), ) diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py new file mode 100644 index 00000000000..b4a8217868e --- /dev/null +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * + + +class dc_eitc_has_qualifying_child(Variable): + value_type = bool + entity = TaxUnit + label = "Has a qualifying child for the DC EITC" + definition_period = YEAR + reference = ( + "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04#(f)(1)", + # (f)(4) gives "qualifying child" its IRC 32 meaning. + "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04#(f)(4)", + # IRC 32(c)(3)(D) keeps a child without a Social Security number out of + # the credit computation without making it a non-qualifying child. + "https://www.law.cornell.edu/uscode/text/26/32#c_3_D", + ) + defined_for = StateCode.DC + + def formula(tax_unit, period, parameters): + # Selects between the with-children schedule of (f)(1)(B)-(B-2) and + # the childless schedule of (f)(1)(C). A child with an ITIN is a + # qualifying child in every year: before 2023 IRC 32(c)(3)(D) only + # keeps such a child out of the credit computation (see + # dc_eitc_with_qualifying_child), and the unit still cannot use the + # childless schedule. + person = tax_unit.members + qualifying_child = person("is_eitc_qualifying_child", period) & person( + "has_tin", period + ) + return tax_unit.any(qualifying_child) 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 141cd99c32b..b45a25856d2 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 @@ -12,28 +12,24 @@ class dc_eitc_with_qualifying_child(Variable): definition_period = YEAR reference = ( "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04", # (f) - # IRC 152(c)(3)(B) waives the qualifying-child age test for permanently and totally disabled individuals. - "https://www.law.cornell.edu/uscode/text/26/152#c_3_B", ) - defined_for = StateCode.DC + defined_for = "dc_eitc_has_qualifying_child" def formula(tax_unit, period, parameters): - # D.C. Law 23-149 extends the EITC to ITIN filers and ITIN qualifying - # children, overriding the federal IRC section 32 SSN-only rule. + # D.C. Code 47-1806.04(f)(1)(B)-(B-2) match the federal credit + # allowed under IRC 32. Before 2023 that is the federal credit itself, + # which leaves out a qualifying child without a Social Security + # number (IRC 32(c)(3)(D)) and denies ITIN filers (IRC 32(c)(1)(E)). + federal_eitc = tax_unit("eitc", period) + # From 2023, (f)(1)(D)(ii) computes the credit as if an ITIN met the + # IRC 32(m) Social Security number rule for the filer, spouse and + # qualifying children. person = tax_unit.members has_tin = person("has_tin", period) is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) - # IRC 152(c)(3)(B) waives the age test for a permanently and totally - # disabled dependent, matching the federal eitc_child_count. - is_disabled_dependent = person("is_tax_unit_dependent", period) & person( - "is_permanently_and_totally_disabled", period - ) - qualifying_child = ( - person("is_qualifying_child_dependent", period) | is_disabled_dependent - ) & has_tin - child_count = tax_unit.sum(qualifying_child) + 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 - federal_like_eitc = calculate_eitc_like_amount( + itin_eitc = calculate_eitc_like_amount( tax_unit, period, parameters, @@ -41,5 +37,6 @@ def formula(tax_unit, period, parameters): child_count > 0, filer_has_tin, ) - p = parameters(period).gov.states.dc.tax.income.credits - return federal_like_eitc * p.eitc.with_children.match + p = parameters(period).gov.states.dc.tax.income.credits.eitc + federal_like_eitc = where(p.itin_eligible, itin_eitc, federal_eitc) + return federal_like_eitc * p.with_children.match 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 324b243f10f..1eb790af145 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 @@ -13,34 +13,30 @@ class dc_eitc_without_qualifying_child(Variable): definition_period = YEAR reference = ( "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04", # (f) - # IRC 152(c)(3)(B) waives the qualifying-child age test for permanently and totally disabled individuals. - "https://www.law.cornell.edu/uscode/text/26/152#c_3_B", ) defined_for = StateCode.DC def formula(tax_unit, period, parameters): - # D.C. Law 23-149 extends the EITC to ITIN filers, overriding the - # federal IRC section 32 SSN-only identification rule. + # From 2023, D.C. Code 47-1806.04(f)(1)(D)(ii) extends the credit to + # ITIN filers, overriding the IRC 32(m) Social Security number rule. person = tax_unit.members age = person("age", period) has_tin = person("has_tin", period) is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) filer_has_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 + p = parameters(period).gov.states.dc.tax.income.credits.eitc + filer_meets_identification = where( + p.itin_eligible, + filer_has_tin, + tax_unit("filer_meets_eitc_identification_requirements", period), + ) min_age = parameters.gov.irs.credits.eitc.eligibility.age.min(period) max_age = parameters.gov.irs.credits.eitc.eligibility.age.max(period) age_eligible = is_head_or_spouse & (age >= min_age) & (age <= max_age) - # IRC 152(c)(3)(B) waives the age test for a permanently and totally - # disabled dependent, so such a dependent is a qualifying child and - # routes the unit to the with-children schedule. - is_disabled_dependent = person("is_tax_unit_dependent", period) & person( - "is_permanently_and_totally_disabled", period - ) - qualifying_child = ( - person("is_qualifying_child_dependent", period) | is_disabled_dependent - ) & has_tin - no_qualifying_child = tax_unit.sum(qualifying_child) == 0 + # (f)(1)(C) is limited to an individual without a qualifying child. + no_qualifying_child = ~tax_unit("dc_eitc_has_qualifying_child", period) us_eligible = ( - filer_has_tin + filer_meets_identification & no_qualifying_child & tax_unit.any(age_eligible) & tax_unit("eitc_investment_income_eligible", period) diff --git a/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py b/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py index 87f889bb63b..e53a14095ca 100644 --- a/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py +++ b/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py @@ -14,8 +14,8 @@ class il_eitc(Variable): "https://www2.illinois.gov/rev/programs/EIC/Pages/default.aspx", # 35 ILCS 5/212 bases the IL EIC on the federal IRC 32 credit. "https://www.ilga.gov/legislation/ilcs/fulltext.asp?DocName=003500050K212", - # IRC 152(c)(3)(B) waives the qualifying-child age test for permanently and totally disabled individuals. - "https://www.law.cornell.edu/uscode/text/26/152#c_3_B", + # IRC 32(c)(3)(A) applies the IRC 152(c) qualifying-child definition. + "https://www.law.cornell.edu/uscode/text/26/32#c_3_A", ) defined_for = StateCode.IL @@ -24,14 +24,9 @@ def formula(tax_unit, period, parameters): age = person("age", period) has_tin = person("has_tin", period) is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) - # IRC 152(c)(3)(B) waives the age test for a permanently and totally - # disabled dependent, matching the federal eitc_child_count. - is_disabled_dependent = person("is_tax_unit_dependent", period) & person( - "is_permanently_and_totally_disabled", period - ) - qualifying_child = ( - person("is_qualifying_child_dependent", period) | is_disabled_dependent - ) & has_tin + # Same qualifying-child test as the federal eitc_child_count, with an + # ITIN accepted in place of a Social Security number. + qualifying_child = person("is_eitc_qualifying_child", period) & has_tin child_count = tax_unit.sum(qualifying_child) filer_has_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 p = parameters(period).gov.states.il.tax.income.credits.eitc diff --git a/policyengine_us/variables/gov/states/mn/tax/income/credits/mn_child_and_working_families_credits.py b/policyengine_us/variables/gov/states/mn/tax/income/credits/mn_child_and_working_families_credits.py index bede2b80156..6ba87c22067 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/credits/mn_child_and_working_families_credits.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/credits/mn_child_and_working_families_credits.py @@ -37,15 +37,14 @@ def formula(tax_unit, period, parameters): # an IRC 32(c) qualifying child that attained at least age 18; the # IRC 32(c)(3) definition incorporates IRC 152(c)(3)(B), which waives # the age (and student) test for permanently and totally disabled - # individuals. Schedule M1DQC step 4 routes a disabled dependent aged - # 18 or older directly to the qualifying-older-child row (row 11) - # without the full-time-student test. + # individuals, and the IRC 152(c)(2) relationship test. Schedule M1DQC + # step 4 routes a disabled dependent aged 18 or older directly to the + # qualifying-older-child row (row 11) without the full-time-student + # test. is_disabled_dependent = person("is_tax_unit_dependent", period) & person( "is_permanently_and_totally_disabled", period ) - qualifying_child = ( - person("is_qualifying_child_dependent", period) | is_disabled_dependent - ) + qualifying_child = person("is_eitc_qualifying_child", period) age = person("age", period) full_time_student = person("is_full_time_student", period) qualifying_older_child = ( 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 f36bdc3260b..f8cb84b82b7 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 @@ -51,16 +51,11 @@ def formula(tax_unit, period, parameters): person = tax_unit.members has_tin = person("has_tin", period) is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) - # IRC 152(c)(3)(B) (part of the frozen 2022-06-09 federal EITC rules) - # waives the age test for a permanently and totally disabled - # dependent, matching the federal eitc_child_count. - is_disabled_dependent = person("is_tax_unit_dependent", period) & person( - "is_permanently_and_totally_disabled", period - ) - child_count = tax_unit.sum( - (person("is_qualifying_child_dependent", period) | is_disabled_dependent) - & has_tin - ) + # The IRC 152(c) qualifying-child definition, including the + # 152(c)(3)(B) disabled age waiver and the 152(c)(2) relationship test + # (both part of the frozen 2022-06-09 federal EITC rules), matching + # the federal eitc_child_count. + 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 federal_identification_eligible = tax_unit( "filer_meets_eitc_identification_requirements", period diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_maximum_qualifying_income.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_maximum_qualifying_income.py index 72ec64a6c64..9573440aad6 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_maximum_qualifying_income.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_maximum_qualifying_income.py @@ -38,14 +38,10 @@ def formula(tax_unit, period, parameters): eitc = parameters(period).gov.irs.credits.eitc person = tax_unit.members federal_child_count = tax_unit("eitc_child_count", period) - # Apply the IRC 152(c)(3)(B) age waiver to ITIN children too, using - # the same qualifying-child count as the WFTC amount formula. - is_disabled_dependent = person("is_tax_unit_dependent", period) & person( - "is_permanently_and_totally_disabled", period - ) + # Count ITIN children too, using the same qualifying-child test as the + # WFTC amount formula. washington_child_count = tax_unit.sum( - (person("is_qualifying_child_dependent", period) | is_disabled_dependent) - & person("has_tin", period) + person("is_eitc_qualifying_child", period) & person("has_tin", period) ) child_count = max_(federal_child_count, washington_child_count) federal_max_agi = calculate_eitc_max_agi_limit( diff --git a/pyproject.toml b/pyproject.toml index 960c5cd20c2..08aa9d2d517 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ dev = [ "setuptools>=80.9.0", "build>=1.2.2.post1", "towncrier>=24.8.0", + "hypothesis>=6.100", ] [tool.hatch.build.targets.wheel] diff --git a/uv.lock b/uv.lock index 77b4c9ccf56..b4596dd8487 100644 --- a/uv.lock +++ b/uv.lock @@ -766,6 +766,94 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/07/3d0c34c345043c6a398a5882e196b2220dc5861adfa18322448b90908f26/huggingface_hub-1.3.4-py3-none-any.whl", hash = "sha256:a0c526e76eb316e96a91e8a1a7a93cf66b0dd210be1a17bd5fc5ae53cba76bfd", size = 536611, upload-time = "2026-01-26T14:05:08.549Z" }, ] +[[package]] +name = "hypothesis" +version = "6.168.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/31/b54cd138ee36a32d0047d40fe7bdcb63cefd57ce098a26024161332d15fd/hypothesis-6.168.1.tar.gz", hash = "sha256:fd8acb5c67f260dfbdcc1b2dc95ff48b2b3f90708ae595c76f591bbf3f6f8eeb", size = 510823, upload-time = "2026-09-23T04:36:04.09Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/95/3091b9ad24baa9666d49feea433a8c4060ad47245f0d529d736af3672ddd/hypothesis-6.168.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5d9cc2d2a4779c92788389564fc6760df7c2909185f6431378133dea6933194f", size = 791345, upload-time = "2026-09-23T04:35:39.379Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b3/b954ffa69f91d111f801030c28d6721f422222af245a846571fc87874242/hypothesis-6.168.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:d0e1ec15d32af11da6784d2506de0c901e4c1abf97a3cdcfc144225457783f4e", size = 787104, upload-time = "2026-09-23T04:35:21.799Z" }, + { url = "https://files.pythonhosted.org/packages/e5/dc/732df08f570d845a480d3729bfb46beccc441bfabe2804bb3a458649be73/hypothesis-6.168.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cc38519a57c4b919b2c59714a67501d20b46b0c96a0078cbdc9f86e728efce3", size = 1123848, upload-time = "2026-09-23T04:33:48.857Z" }, + { url = "https://files.pythonhosted.org/packages/88/06/e1e937df42243267ba25ede5e9b29921186f290f6c0515729481cc41726e/hypothesis-6.168.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e229e937e761830c7fa6437fb12cb801616a1737af0898abf5edba57d2ad691", size = 1147698, upload-time = "2026-09-23T04:35:33.439Z" }, + { url = "https://files.pythonhosted.org/packages/be/d1/3b41c008dc2a49e43bc4893dcd27ea9d9fef64fef50382ca5f7107680b1f/hypothesis-6.168.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:569ba21e5543840895d17633cc6716a9578ac83b1193a2978b3f24cc16b93085", size = 1149280, upload-time = "2026-09-23T04:35:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/71/ea/d54781058fe6a487b81ec6c636f3806fff6a417b48cf3fcd2b003d80f3ce/hypothesis-6.168.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a232665716607c3c50c9a3aeeac5929be1d4e58703f5e7cb51db7099445a8c6", size = 1191693, upload-time = "2026-09-23T04:35:57.896Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f2/43bdd07450978611d25989ffdcbe1579acd46c1500a484f8838359fe7880/hypothesis-6.168.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9022fe330d8839259da9b496573c4b83ea248bf32029eba23ff589f3c25754bf", size = 1169725, upload-time = "2026-09-23T04:34:32.59Z" }, + { url = "https://files.pythonhosted.org/packages/d1/72/fd4b657cba66940f208bc0eccaceaa777553009d26e86068ff3ab5e55b0b/hypothesis-6.168.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:69be660b831d55ab35619b30abd311a6cb8cd0068a1192210d194a776b211094", size = 1129181, upload-time = "2026-09-23T04:35:27.495Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3f/516a29d7a2bd31e28972de635350eab086c94faa07afcdd3ad0f06e70db8/hypothesis-6.168.1-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42e2cccca80ee4ffe8f66edf5b2adb2a16b8cd3c5110b3a774d70f2fc75a42aa", size = 1160162, upload-time = "2026-09-23T04:35:29.333Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/e2c7e32f281b31d4c19cc2fa935bfdde5b31ad4848a485e5ebb8de2389da/hypothesis-6.168.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:11cbd2a2539194f0960de4ed9f980882e3a3f55c476afcc6f4cc342eec5a07d2", size = 1299714, upload-time = "2026-09-23T04:35:12.529Z" }, + { url = "https://files.pythonhosted.org/packages/ba/bc/85850bdb1fc35d0fa9e299ca3b2e354f403959666c7ef4d0bfda9c308eee/hypothesis-6.168.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:aa1620111616c660a1db03347b1790b5914ef30f86c6e8ddd6c765785291f0b4", size = 1425335, upload-time = "2026-09-23T04:35:23.557Z" }, + { url = "https://files.pythonhosted.org/packages/40/c0/66bdb24f32d32aa5636655b6037af07962118b89fae16ee74bc9a1bb982e/hypothesis-6.168.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:a07c8665c95bb856f99ecd60b6f50d94dcf8157415387c32dcd5bb5b5061d032", size = 1376916, upload-time = "2026-09-23T04:34:59.742Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/7ad0b17bda81c473cbc539009148f3e0950880c4618028691bcf62eeb8d2/hypothesis-6.168.1-cp310-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:8a50b6335d5fa2312dd9f5d50faeb953e61475b301d2c073cd7a4c5efd7e6171", size = 1280975, upload-time = "2026-09-23T04:34:21.966Z" }, + { url = "https://files.pythonhosted.org/packages/e2/01/8d04d1eecf03a6097e26400e8393eff7a81fd52c877a664ee4e97adf7880/hypothesis-6.168.1-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:4ccb0276db8ad197a63dff25c4868e11123346347df6900625d2b1f794ce7c19", size = 1300238, upload-time = "2026-09-23T04:34:52.825Z" }, + { url = "https://files.pythonhosted.org/packages/f5/74/eda0c70b3845789c713fe2ac72d3d4586da83f38a34f1f7aefbda0b5b01e/hypothesis-6.168.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85ca7788574e06d0591d51e19ecbb1d09bc82d50c6eae5de0fe8317590b1e138", size = 1336088, upload-time = "2026-09-23T04:33:42.002Z" }, + { url = "https://files.pythonhosted.org/packages/80/c9/43d8528e6d43eebb7688d36e530d1fc7763809b8f923b62cc8b4d95f99ff/hypothesis-6.168.1-cp310-abi3-win32.whl", hash = "sha256:77378e04aa47a22a615af80f42970dc44782f02b8a82ac105b619a2cc7f5eee2", size = 678004, upload-time = "2026-09-23T04:34:29.564Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8b/f7d4de5b57972f7f68a6c7fb5698f60425589eae6fcb175f285f2525e8c0/hypothesis-6.168.1-cp310-abi3-win_amd64.whl", hash = "sha256:f94d3dbc31112f8fbcb3af19a662ee5e90e380e6ae676357903caa14a1604763", size = 684722, upload-time = "2026-09-23T04:34:41.279Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/528eb221a82da0aa477e34c7af6ed2b4a5109b326638d4418b9e49435e96/hypothesis-6.168.1-cp310-abi3-win_arm64.whl", hash = "sha256:9bd7e8c6e2c3f6a5befee6b03ebd2170da2e50658075c6decad184777c72a726", size = 682711, upload-time = "2026-09-23T04:33:46.085Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1b/620484ba32fbb655ccbd02f3c510e7a961e840a466c3672ef0b877ab8716/hypothesis-6.168.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9fd235c4ccfefe18fd97c181bdf247187cb3d4fdc8800a4d623322234ce4cfd3", size = 792049, upload-time = "2026-09-23T04:34:28.21Z" }, + { url = "https://files.pythonhosted.org/packages/03/df/2ad250c2c4ee4d5f43d6d9bc5d852e7b81c771e4c8946212931b8cafe849/hypothesis-6.168.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0722212cf825d001a0d3b4db9303cc31af467f6814b69acab91d91c3f629c95a", size = 787932, upload-time = "2026-09-23T04:35:20.079Z" }, + { url = "https://files.pythonhosted.org/packages/24/69/4f8e8bf1d5ad81352a05aa0835baef82e691e3e43933bafe6d6f2c171763/hypothesis-6.168.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94a1053575f133ff6978876fd4b4c2a153c1111f1b5f14d6cfb355c306fc4cb5", size = 1124000, upload-time = "2026-09-23T04:34:14.106Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e7/b72a34e0c6b434d3ad2b2e0040fa88d06a15a7880c59bddacf1e27d4902a/hypothesis-6.168.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2c74d496304574cfed537e4f657620be7fee3e287381c0aa4b481211e6ec92", size = 1170172, upload-time = "2026-09-23T04:34:30.93Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6c/afe1b73b58479ba0d75e5408259978d580e96fee812eb4ea2bb83a76f7ff/hypothesis-6.168.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cf4cc6819ba9689effba38dce98a3025554669f76be07e47cc1002e93e8c4918", size = 1300163, upload-time = "2026-09-23T04:35:53.549Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3d/aad54ffe7942c06da79c719fc98daa1dffdd8ac2d384ca64cc3611b1a833/hypothesis-6.168.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7888e0876e1d93fb92a6a4869834b0d0de18f07f6600edbf3d1cad16cf089357", size = 1336274, upload-time = "2026-09-23T04:35:10.655Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cb/8d2a3474e7921cfb5e59f933ac6fe1ad83b95cc98a871af8f0a68dcf946e/hypothesis-6.168.1-cp311-cp311-win_amd64.whl", hash = "sha256:4aa312b6447743a72283698292ea0e1c4d684279b38dc7f270729c4efda6c27a", size = 684479, upload-time = "2026-09-23T04:33:59.307Z" }, + { url = "https://files.pythonhosted.org/packages/7e/6f/9d5ae55d81e10f0fc46868c19ab9d468e9959e82ff8cc8c26990b16114f3/hypothesis-6.168.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:dfe14a88b1ed03ce47333d800cc37949da73feb0cd9767e7ef076d2202768928", size = 793124, upload-time = "2026-09-23T04:33:52.589Z" }, + { url = "https://files.pythonhosted.org/packages/14/11/54c19d259d7f92176aa2199c90f7951e4e396a26898a596dc875ccb9c389/hypothesis-6.168.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8acd000c3bf233cdcd28c1ff03f1bf56cb03821b1c742d00d57978b20fc6e6f8", size = 784674, upload-time = "2026-09-23T04:34:38.137Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ae/f80a9810f88bd654ab0d0ed0652c274e4c0384e22d4f9442b6753b9200a6/hypothesis-6.168.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29280d4743546ddcf74644f9acdc5726972cba9182699a86b7e77f3f8f6e5dde", size = 1122860, upload-time = "2026-09-23T04:33:51.375Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5d/aa5b7ffd57ec475a9252c55985c07a366823c3f85267d44648e94c1ec321/hypothesis-6.168.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd9a488d46b8c7da3103058c7569376a3a08994aee9a7d6f1d160d3d3b49b82d", size = 1168922, upload-time = "2026-09-23T04:34:44.567Z" }, + { url = "https://files.pythonhosted.org/packages/65/10/4862448192d4c9cc61a3b8a620730800f56b1e83b0bfda63dbc728526eb3/hypothesis-6.168.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451ee6c42955887a9e1850c53813e31182eab1663e4d3dacea4e535dedc45b5d", size = 1298568, upload-time = "2026-09-23T04:35:31.191Z" }, + { url = "https://files.pythonhosted.org/packages/15/ad/55e73fda9afbfcf3336b2eee564833bc9a4fee1f7a32fe5f9d183742da39/hypothesis-6.168.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bc2c036f4a9534af64977e38b2976295136f83417fdb95bed816a52cc3023497", size = 1335091, upload-time = "2026-09-23T04:34:23.42Z" }, + { url = "https://files.pythonhosted.org/packages/ef/99/b6340fa21a89b0088ed023fe984109e1c47a07dfd741fd6d8e17a9e2795f/hypothesis-6.168.1-cp312-cp312-win_amd64.whl", hash = "sha256:d603d7c96d030a63701b6ad9bff0bc4870e4c226335b3db509f1eed1f7fc061b", size = 682011, upload-time = "2026-09-23T04:34:34.565Z" }, + { url = "https://files.pythonhosted.org/packages/f5/23/093b9dc768e89d2cec74d29115f4004fe1be64218bac65f8d817d727d30b/hypothesis-6.168.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aebf7e9d7ba9f920e154abc65851d4affab497daeaea285474bed93840a983ea", size = 793062, upload-time = "2026-09-23T04:33:58.177Z" }, + { url = "https://files.pythonhosted.org/packages/df/d7/f191686b44b3f892ae1404e238a55260152a645a2deafe8b2949c7e90fe9/hypothesis-6.168.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b26616604cac458dd83e75900fefd9190f1074e67d5d9dc4d9e321d30f295eca", size = 784518, upload-time = "2026-09-23T04:34:02.012Z" }, + { url = "https://files.pythonhosted.org/packages/10/25/b7a5dca1911c9012a564d0b7aaeb44e393ade9424a695dea179ff32d79fe/hypothesis-6.168.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb6d89cff2edcd45e5af957c7b6a71ae7887419561f7b9c7055e1c50dea41017", size = 1122844, upload-time = "2026-09-23T04:35:06.897Z" }, + { url = "https://files.pythonhosted.org/packages/ea/60/ddacd0d244e6719ea9a9d322d4d4b6b054fd27ada94ec7aa7dce7ef11297/hypothesis-6.168.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a4ce6310ec21000446ae75cdc6667b81d4ee0f2d226a0dfaf8baca42dd151d0", size = 1168779, upload-time = "2026-09-23T04:33:56.885Z" }, + { url = "https://files.pythonhosted.org/packages/dc/07/343bab6676a41f38cb7c5912ab3ca059258bf40e41c181d1b77cc7251801/hypothesis-6.168.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a22c6e730c327d22a4d61b53f66639cdf6f0fe4a5a1b3595c36533e5b8127169", size = 1298434, upload-time = "2026-09-23T04:34:48.576Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bd/4b72fafa5f46a455024a9242b62505b1a53fad95ace162e831c4ffb233b9/hypothesis-6.168.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dcd66493fe180d38641b02dd0456cc1e429655f8940dd1396ab874808f16dd34", size = 1334975, upload-time = "2026-09-23T04:35:01.531Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ae/48643915cda400ca2dccb9bcdc363690b13bcd5c179172bc6450c3d76ce8/hypothesis-6.168.1-cp313-cp313-win_amd64.whl", hash = "sha256:a8a96f2e9fa2a57f95865aaf092943775e7ea96f72535371b2be20fe5464298c", size = 681978, upload-time = "2026-09-23T04:36:00.056Z" }, + { url = "https://files.pythonhosted.org/packages/0c/0e/e6999cddbaef975ecc1b0a7e9028a3d1a1014b542fa003889d0cb3870fa9/hypothesis-6.168.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:35b6582ecc22ed682ea39bbf972f8b8b1798043a3bc624ae1dce02d42fcf3ad4", size = 793097, upload-time = "2026-09-23T04:35:03.374Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f3/2ff6bf4bf43ab12641b163bd8fea4b6cddc9a0f07499c4779519099034c9/hypothesis-6.168.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:48e467c73b7fb46bc072323c5d67e8e683cf32218fbba47c4e6ee0fe0d72687f", size = 784662, upload-time = "2026-09-23T04:35:08.976Z" }, + { url = "https://files.pythonhosted.org/packages/10/3a/b3653de877981cdab6861476c3b68d02b7c682c2875557cff7b92dbde068/hypothesis-6.168.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c82fcc3dece367bd43f3eca5c2746cf60b73f61bba071ef9421d4cfd6188a85", size = 1123106, upload-time = "2026-09-23T04:34:26.476Z" }, + { url = "https://files.pythonhosted.org/packages/49/25/24c725cb94c8953a98d8bdec83971f76c8f4b103070df1fdacd8b7f37253/hypothesis-6.168.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36031c8f8fd4bec7185cf70ed080ba54d45c106cf4f575bc8c2482c01f5f9b21", size = 1168942, upload-time = "2026-09-23T04:35:41.237Z" }, + { url = "https://files.pythonhosted.org/packages/1d/e4/cbe45b9e78e4dd9c18ed2328ff95f659f59235b04ae3c73cf7e9c498adc9/hypothesis-6.168.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ddfe21791579fdc90423973c733578efa23b010cbfb685dcd5201cf7b0a928b6", size = 1298937, upload-time = "2026-09-23T04:33:50.181Z" }, + { url = "https://files.pythonhosted.org/packages/93/e9/cacaa3f58ee6eb98ce29a2182483d57686011bba35c8fb8a9bc8bf981c7f/hypothesis-6.168.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ecf86b1c9c8700f6d45324becbbe25dd581c69dc03e1357a64f4bf9a1afe823f", size = 1335198, upload-time = "2026-09-23T04:36:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/31/d8/ca51626959f59a557656cf8024d9afda4d213257c170412fa3193d11e11f/hypothesis-6.168.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:d5ab336a7dd233858bc3066a69479e295da788cef345d7e4efd7b77dad724db0", size = 624105, upload-time = "2026-09-23T04:34:08.466Z" }, + { url = "https://files.pythonhosted.org/packages/83/61/6ce96e7d58fc7a592ad7c84d8c7cf9ea36db51468b30ac8306bbf4c3e6ba/hypothesis-6.168.1-cp314-cp314-win_amd64.whl", hash = "sha256:63244ba76464202d3d24126d2e4c2c1abf36db60fffe16ebaa93292572c2a42a", size = 681859, upload-time = "2026-09-23T04:34:50.801Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b1/6c7345e2b37ea8a1df2520a0ecfbdc0243a897895ff950309ca7f687429a/hypothesis-6.168.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:6cec76c8cf76738ad4c0c0c5d6e4632a32d74748639ea6ce26fa672490d9bf95", size = 791685, upload-time = "2026-09-23T04:33:54.243Z" }, + { url = "https://files.pythonhosted.org/packages/72/8c/4f6cfafde4b31add263d6a562f615939cd49649e87a3695ac8693506c811/hypothesis-6.168.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9da10ba2dbbcaba93c6a11eb394fd1a0cfee6e2ba21345c3d6f2fc7401d80670", size = 783237, upload-time = "2026-09-23T04:34:25.049Z" }, + { url = "https://files.pythonhosted.org/packages/a0/25/646c301a9d1fe3825a2ff8362594c4aee0ceb2ce192f49469f7f2824a742/hypothesis-6.168.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3218c6d18a041c0eba0cccd88b20d74be24687e42a73338abd0a31ec5294c265", size = 1121414, upload-time = "2026-09-23T04:34:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/27/4f/f84ec8434ef765e6a5c4f9942bbb7652be444b23b9ff868da43e96e61e86/hypothesis-6.168.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760cad0526a84122bbe2a0157217e16d76d649d333422142853c7f1ba43ca44d", size = 1167536, upload-time = "2026-09-23T04:33:55.443Z" }, + { url = "https://files.pythonhosted.org/packages/38/74/c53334d793b0c54054ffc781cdf79ccae6ee51daf347bd9cfa7e0015cece/hypothesis-6.168.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:50a60b57b485dff68b56f99e4506b7f1b52994f801ee1aaa4cb9125a90bf7834", size = 1297122, upload-time = "2026-09-23T04:34:17.235Z" }, + { url = "https://files.pythonhosted.org/packages/43/e7/b115a1faea6d40bddce6342da703b7536d803d2384fa50d277dc55170c64/hypothesis-6.168.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:68017d433ca5c558c34e3308a6653fc63077749af8bf86c10850cb6bb3cb559f", size = 1334055, upload-time = "2026-09-23T04:35:37.298Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/f272620d304dd67ff7815509745e2fb8d2bf58e4e1da4b7842a83f1de2da/hypothesis-6.168.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ff92099a0185ba14bf4bc2111299b456074596199148ae84590eee4d480fac2e", size = 681804, upload-time = "2026-09-23T04:35:35.374Z" }, + { url = "https://files.pythonhosted.org/packages/54/e9/45595951f8bc9bd03f032325e7b176ca990a0c86bf2e09d481e8ee5a5cab/hypothesis-6.168.1-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:e9510e33467d586a7700d56b9a6ee65b44a06832c1cab3d270369d7c53381276", size = 791046, upload-time = "2026-09-23T04:35:55.724Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3e/718431d0ab72c4c07454f71040b4de298a63e765f20088557dc1bef0a48b/hypothesis-6.168.1-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:99970e3a75f11dde0414d3d6724bd8b7a2bd1deeb9a509ec9236c91d41a5cff0", size = 782982, upload-time = "2026-09-23T04:34:06.017Z" }, + { url = "https://files.pythonhosted.org/packages/d7/05/c60db6df1456a70a3c589e42acaa446c22f56ed342c1b8dd04b8e49df8fd/hypothesis-6.168.1-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5ea78773de5bcc25266e37eb086472e90a85fc81b8d4a9ca8afc15858ee15fb", size = 1120961, upload-time = "2026-09-23T04:33:43.494Z" }, + { url = "https://files.pythonhosted.org/packages/8b/fe/d943250395750ecc3b4ec95b1ab3c8119f6cbad08c1ba9169ffee7d89049/hypothesis-6.168.1-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13f502ce2423e46dfcd2af38950a86ecf97084d94e721f7f13dd5a70afb36e0a", size = 1143877, upload-time = "2026-09-23T04:34:56.065Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9d/8c1f8e11f20fd81ce40caed7ad6d6a9776e24775070e87f3aa9ba3683272/hypothesis-6.168.1-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b4bcaa8b0e753fc8921011d5d10c8a7c3caf814c6a968bc140a08917973fdc", size = 1146503, upload-time = "2026-09-23T04:34:04.791Z" }, + { url = "https://files.pythonhosted.org/packages/71/d8/ac2e9fa653d2f05a13724837007e857d376d81fdf728543ab85f6c7f7350/hypothesis-6.168.1-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ed811908d38ca8e05c20ad25ecb61edc1e20a519667d4b668be49dd09e6bf3e", size = 1189216, upload-time = "2026-09-23T04:34:36.271Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7b/640952448781c7df5098880e2e2d6e7702b3a1c0badd4c0ea5fca6d6ab53/hypothesis-6.168.1-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9239c868418088d58ae18d041f9f424bd7a75dd143f209c35fde38770b65f74f", size = 1166919, upload-time = "2026-09-23T04:35:16.498Z" }, + { url = "https://files.pythonhosted.org/packages/b6/de/0d46b330a28f7614850fb1a0564cf88af7c0b29cbd791e9d04ade7f66f13/hypothesis-6.168.1-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:5cac279798ecc92d3301ec4421c0458e22627f8a79fdde8a6c98ab1d4b8811f6", size = 1126652, upload-time = "2026-09-23T04:35:49.432Z" }, + { url = "https://files.pythonhosted.org/packages/92/36/6adeca1724ed4966095406ffe7308fcff338cf9f5b7443d5ea34a01ab6e1/hypothesis-6.168.1-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bc660dc14d633641e22e407417d35bf2925331273ed71bc6ab5536dfca5c72d", size = 1155687, upload-time = "2026-09-23T04:35:18.234Z" }, + { url = "https://files.pythonhosted.org/packages/bd/00/e9b7c7e4a0a75d1621be95a126999352e01a16d68b0a59b396695c7b4991/hypothesis-6.168.1-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:157a29e4d6bcb77ae732cb83d6546beef47265ecac88408fdd5d5b2bd325685d", size = 1296471, upload-time = "2026-09-23T04:34:12.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/52cb320e9e07cc67f718506617b5900344370394e39f4affada317aab313/hypothesis-6.168.1-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:b125aac89594ee2a76687b724f057b2423ea83dfee6b8ff59f54289d6eef19a1", size = 1421838, upload-time = "2026-09-23T04:34:20.451Z" }, + { url = "https://files.pythonhosted.org/packages/9b/93/969c33a87cd2865beffebe491294b8115e7e7421c6840f79a67486535bfc/hypothesis-6.168.1-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:4b960c9d31b85dbc6a25888897c02bbea5f498844932841612925184fc78d9eb", size = 1373994, upload-time = "2026-09-23T04:34:00.608Z" }, + { url = "https://files.pythonhosted.org/packages/34/c9/5fe4ade4bd23baa2e4cd27668bb8ac10e840ce19fbcfc74116ea529cbaa7/hypothesis-6.168.1-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:d4d76f35874471fe9b8e1bb696fb532363ef3d9588161a06d30520f638f15a39", size = 1278216, upload-time = "2026-09-23T04:34:07.253Z" }, + { url = "https://files.pythonhosted.org/packages/a1/17/30b9a1ad975e90700dea74ce8b08ab07b9e58c4d83918422bc8841942c1e/hypothesis-6.168.1-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:d5147c5f7497d109eed0267c498adaf5cad6e4beeaca6fad6057f2007bae70a7", size = 1297647, upload-time = "2026-09-23T04:35:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/e0/2f/233a9d5adafad6e187ab8f4d9275592cf120b56e63ea1cfb02213c01021e/hypothesis-6.168.1-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:a909f89758e184c4f86418598de297a2ced5b922979c0f98c5d73ff6bafe38e5", size = 1333790, upload-time = "2026-09-23T04:34:42.828Z" }, + { url = "https://files.pythonhosted.org/packages/10/d7/4c9d4d597f70316d781cd99b600adae090a6f5d675487196e10510a23afe/hypothesis-6.168.1-cp315-abi3.abi3t-win32.whl", hash = "sha256:66b9fb6f02840f8ffa2a4e6359aff6ecc1b2c6630c8a2066771044fa22956c7a", size = 675206, upload-time = "2026-09-23T04:35:14.592Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/f9fc0c0dbb9e80238fc7145cb4ce52e0897ea417a23217022911855bccda/hypothesis-6.168.1-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:9246eaa80044c33a182be661cbed7e6a7314c61359ef501ea9e9efd90d50ca3b", size = 681503, upload-time = "2026-09-23T04:34:11.223Z" }, + { url = "https://files.pythonhosted.org/packages/11/b4/962ae350c10860be37c9abf98c0ef1ef23e4baca3ee7ad77208941e370e5/hypothesis-6.168.1-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:52e0c804cbf697d4760400c84444a91cbd5dfe7ae0ef1bb3228f74fb5547eb9e", size = 679199, upload-time = "2026-09-23T04:34:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c5/2bfdcc601b08eb9611bc524d18ca0a8cdbbdd7bd07bc68c38e0196a00668/hypothesis-6.168.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:91e3700d9c35e184dd253cff2f151f890557242bede75a2535901e9062d42975", size = 792943, upload-time = "2026-09-23T04:35:25.308Z" }, + { url = "https://files.pythonhosted.org/packages/4b/52/36ae5128d9102d73dc578cd7b241c33f5483bd56e05799a2d06043a1ce7d/hypothesis-6.168.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:c19bc5da324a5899527ea749522e9ceb47f1eb54772869673abe59ae0b703aef", size = 788802, upload-time = "2026-09-23T04:34:39.655Z" }, + { url = "https://files.pythonhosted.org/packages/23/34/698fc3481757503dc60f07547c29098dce97ac035a1ee134d66d88814281/hypothesis-6.168.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a4c9e46b2f349658d3013a357e1fbf114bc08abced7ee8e91cd6e0bc718b195", size = 1124766, upload-time = "2026-09-23T04:34:57.869Z" }, + { url = "https://files.pythonhosted.org/packages/02/c9/1f871666106d90048027fc6b309ec505f0fb44e4519add73ddaad98bcd9a/hypothesis-6.168.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a0beee16fcbc0516951f2ef3ef1a479ad11d9726da49840139303b36ebc1d0", size = 1171766, upload-time = "2026-09-23T04:34:54.476Z" }, + { url = "https://files.pythonhosted.org/packages/17/44/eff662526259ac4447dde4abe4fc285d0e8e5ff5345d35af836c6f0f12cc/hypothesis-6.168.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4a8beb513c066fcb187b885fdfa93da4006f223d50d605b687bfb7e60aac33e9", size = 685449, upload-time = "2026-09-23T04:33:44.837Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -1746,6 +1834,7 @@ dev = [ { name = "build" }, { name = "coverage" }, { name = "furo" }, + { name = "hypothesis" }, { name = "jupyter-book" }, { name = "ruff" }, { name = "setuptools" }, @@ -1757,6 +1846,7 @@ requires-dist = [ { name = "build", marker = "extra == 'dev'", specifier = ">=1.2.2.post1" }, { name = "coverage", marker = "extra == 'dev'", specifier = ">=7.9.2" }, { name = "furo", marker = "extra == 'dev'", specifier = ">=2024.8.6" }, + { name = "hypothesis", marker = "extra == 'dev'", specifier = ">=6.100" }, { name = "jupyter-book", marker = "extra == 'dev'", specifier = ">=1.0.4.post1" }, { name = "microdf-python", specifier = ">=1.0.0" }, { name = "pandas", specifier = ">=3.0" }, From 60c5f63172bf5d5a95280e3fecbebfb231235d2e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 02:48:21 -0400 Subject: [PATCH 2/5] Route DC EITC filers with a no-TIN qualifying child by year Before 2023 a qualifying child without a Social Security number stays a qualifying child whether it has an ITIN or no number at all: D.C. Code 47-1806.04(f)(4) adopts the IRC 32 definition, IRC 32(c)(3)(D) only keeps the child out of the IRC 32(b) computation, and IRC 32(m) treats the two alike. dc_eitc_has_qualifying_child still required has_tin, so a TY2022 SSN filer aged 50 with $20,000 of wages got $0 with an ITIN child but $560 from the childless schedule once the child's TIN was removed. From 2023 the D-40 instructions (2023 and 2024 booklets p. 10, 2025 booklet p. 13) say: "If you and your spouse have SSNs or ITINs but your child(ren) do not, you need to claim the DC EITC for filers without a qualifying child." The 2021 and 2022 booklets have no such rule. A new parameter, qualifying_child_tin_required, is false before 2023 and true from 2023, and gates the TIN test in the router. The with-child branch's 2023+ eligibility now uses the router's predicate instead of child_count > 0. The two agree under current law; the change keeps the parameter coherent when it is switched off. Tests: five YAML cases (TY2021, TY2022 x3, TY2023) plus a counterfactual with the parameter off, and Hypothesis invariants 9 (routing follows qualifying-child status; no TIN equals ITIN before 2023) and 10 (removing a child's TIN never raises dc_eitc). Co-Authored-By: Claude Opus 5.5 --- .../dc-eitc-itin-relationship-fixes.fixed.md | 2 +- .../eitc/qualifying_child_tin_required.yaml | 24 ++ .../test_eitc_qualifying_child_invariants.py | 38 +++- .../gov/states/dc/tax/income/dc_eitc.yaml | 205 ++++++++++++++++++ .../eitc/dc_eitc_has_qualifying_child.py | 25 ++- .../eitc/dc_eitc_with_qualifying_child.py | 5 +- 6 files changed, 287 insertions(+), 12 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml diff --git a/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md b/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md index 051e3173d7d..3b0eb879505 100644 --- a/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md +++ b/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md @@ -1 +1 @@ -Limit the DC EITC ITIN extension to tax years from 2023 (D.C. Code 47-1806.04(f)(1)(D), added by D.C. Law 24-167 section 7012, not D.C. Law 23-149), and stop counting a disabled parent or grandparent dependent as an EITC qualifying child under IRC 152(c)(2) in the federal, CA, CO, DC, IL, MN and WA credits. +Limit the DC EITC ITIN extension to tax years from 2023 (D.C. Code 47-1806.04(f)(1)(D), added by D.C. Law 24-167 section 7012, not D.C. Law 23-149), keep a DC filer whose qualifying child has no Social Security number or ITIN on the with-child schedule before 2023 (the D-40 instructions send such a filer to the childless schedule only from 2023), and stop counting a disabled parent or grandparent dependent as an EITC qualifying child under IRC 152(c)(2) in the federal, CA, CO, DC, IL, MN and WA credits. diff --git a/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml b/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml new file mode 100644 index 00000000000..379433b77c4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml @@ -0,0 +1,24 @@ +description: The District of Columbia treats a filer whose qualifying children all lack a Social Security number or individual taxpayer identification number as a filer without a qualifying child under its earned income tax credit if this is true. +values: + # Before 2023 a qualifying child without a Social Security number stays a + # qualifying child (D.C. Code 47-1806.04(f)(4) and IRC 32(c)(3)(D)); the + # 2021 and 2022 D-40 instructions add no identification test for children. + 2015-01-01: false + # From 2023 the D-40 instructions for the (f)(1)(D)(ii) ITIN extension send + # a filer whose children have neither number to the childless schedule. + 2023-01-01: true +metadata: + unit: bool + period: year + label: DC EITC qualifying child identification requirement + reference: + - title: Code of the District of Columbia § 47–1806.04 (f)(1)(D)(ii) + href: https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04#(f)(1)(D)(ii) + - title: 2022 DC Form D-40 Booklet, Page 11, Taxpayers with a Qualifying Child + href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2022_D-40_Booklet_Final_blk_01_23_23_Ordc.pdf#page=11 + - title: 2023 DC Form D-40 Booklet, Page 10, Taxpayers with a Qualifying Child + href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2023_D40_Book_Final_012324.pdf#page=10 + - title: 2024 DC Form D-40 Booklet, Page 10, Taxpayers with a Qualifying Child + href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2024_D40_Booklet_011525.pdf#page=10 + - title: 2025 DC Form D-40 Booklet, Taxpayers with a Qualifying Child + href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2025_D40_Book_082026_v1.pdf#page=13 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py index 981bb03f852..7fc6dd5575c 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py @@ -29,6 +29,16 @@ child an ITIN instead of a Social Security number never raises dc_eitc. 8. From 2023, giving a qualifying child an ITIN instead of a Social Security number leaves dc_eitc unchanged (D.C. Code 47-1806.04(f)(1)(D)(ii)). +9. Schedule selection follows qualifying-child status, not identification. + Before 2023 a child with an ITIN or with no number at all is still a + qualifying child: (f)(4) adopts the IRC 32 definition, and IRC 32(c)(3)(D) + only keeps such a child out of the IRC 32(b) computation. IRC 32(m) treats + the two alike, so before 2023 a qualifying child without any TIN gives the + same routing and the same dc_eitc as one with an ITIN. From 2023 the DC + D-40 instructions require each qualifying child to have a Social Security + number or an ITIN, and send a filer whose children have neither to the + childless schedule. +10. Removing a qualifying child's TIN never raises dc_eitc. """ import itertools @@ -173,13 +183,17 @@ def test_dc_eitc_invariants(draw): if kind in ASCENDANT_KINDS: assert not qualifying[dependent_index[i]], (kind, context) assert not has_child[i], (kind, context) - # A dependent's qualifying-child status matches its unit's routing - # whenever the dependent has a TIN. + # 9. A dependent's qualifying-child status sets its unit's routing. + # Before 2023 that holds whatever the dependent's identification; from + # 2023 a qualifying child also needs a Social Security number or ITIN. dep_qualifies = np.where( has_dependent, qualifying[np.maximum(dependent_index, 0)], False ) dep_has_tin = np.array([r[1] != "none" for r in DC_ROWS]) & has_dependent - assert np.array_equal(has_child, dep_qualifies & dep_has_tin), context + if year < ITIN_START: + assert np.array_equal(has_child, dep_qualifies), context + else: + assert np.array_equal(has_child, dep_qualifies & dep_has_tin), context # 4 and 5. Branch structure. assert np.all(with_child >= 0) and np.all(without >= 0), context @@ -208,7 +222,11 @@ def test_dc_eitc_invariants(draw): ): ssn = row[(kind, "ssn", filer_id, "DC")] itin = row[(kind, "itin", filer_id, "DC")] + no_tin = row[(kind, "none", filer_id, "DC")] assert dc_eitc[itin] <= dc_eitc[ssn] + 1e-6, (kind, context) + # 9. No identification at all is treated like an ITIN. + assert has_child[no_tin] == has_child[itin], (kind, context) + assert np.isclose(dc_eitc[no_tin], dc_eitc[itin]), (kind, context) else: # 8. ITIN invariance from 2023. for kind, filer_id in itertools.product( @@ -218,6 +236,20 @@ def test_dc_eitc_invariants(draw): itin = row[(kind, "itin", filer_id, "DC")] assert np.isclose(dc_eitc[itin], dc_eitc[ssn]), (kind, context) + # 10. Removing a qualifying child's TIN never raises the credit. + for kind, filer_id in itertools.product( + QUALIFYING_CHILD_KINDS, ["ssn", "itin"] + ): + for dep_id in ["ssn", "itin"]: + with_tin = row[(kind, dep_id, filer_id, "DC")] + no_tin = row[(kind, "none", filer_id, "DC")] + assert dc_eitc[no_tin] <= dc_eitc[with_tin] + 1e-6, ( + kind, + dep_id, + filer_id, + context, + ) + @SETTINGS @given(draw=household_draw) 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 caf1af7bbaf..18705344a6a 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 @@ -232,6 +232,211 @@ dc_eitc_without_qualifying_child: 0 dc_eitc: 321.3 +- name: DC EITC keeps a filer whose qualifying child has no TIN on the with-child schedule before 2023 (TY2022) + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: false + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # IRC 32(m) treats a child with no TIN exactly like a child with an + # ITIN, and (f)(1)(D)(ii) does not apply before 2023, so this matches the + # ITIN case above. The child is still a qualifying child under (f)(4) and + # IRC 32(c)(3)(A); the 2022 D-40 worksheet says "If you have a qualifying + # child DO NOT USE THIS WORKSHEET". Routing on the child's TIN sent this + # unit to the (f)(1)(C) childless schedule and paid 560. + eitc: 0 + dc_eitc_has_qualifying_child: true + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 0 + +- name: DC EITC matches the childless federal credit for a filer whose minor qualifying child has no TIN in TY2022 + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 10 + is_tax_unit_dependent: true + ssn_card_type: NONE + has_tin: false + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # Same routing for an ordinary minor child: 70% x the federal credit + # allowed, 560 - 7.65% x (20,000 - 9,160) < 0. + eitc: 0 + dc_eitc_has_qualifying_child: true + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 0 + +- name: DC EITC matches the childless federal credit for a filer whose qualifying child has no TIN in TY2022 at low earnings + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 6_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: false + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # 70% x 7.65% x 6,000 = 321.30, as for the ITIN child. The childless + # schedule would have paid 459. + eitc: 459 + dc_eitc_with_qualifying_child: 321.3 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 321.3 + +- name: DC EITC matches the childless federal credit for a filer whose qualifying child has no TIN in TY2021 + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: false + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # 40% x the ARPA childless credit allowed, 218.33, as for the ITIN + # child. The childless schedule would have paid + # 1,502 - 8.48% x (20,000 - 19,743) = 1,480.21. + eitc: 218.33 + dc_eitc_has_qualifying_child: true + dc_eitc_with_qualifying_child: 87.33 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 87.33 + +- name: DC EITC sends a filer whose only qualifying child has no SSN or ITIN to the childless schedule from 2023 + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 10 + is_tax_unit_dependent: true + ssn_card_type: NONE + has_tin: false + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # 2023 D-40 instructions, p. 10: "If you and your spouse have SSNs or + # ITINs but your child(ren) do not, you need to claim the DC EITC for + # filers without a qualifying child." (f)(1)(C): 600, below the 21,888 + # phase-out start. + eitc: 0 + dc_eitc_has_qualifying_child: false + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 600 + dc_eitc: 600 + +- name: Without the TIN rule, a filer 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: + gov.states.dc.tax.income.credits.eitc.qualifying_child_tin_required: false + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 6_000 + ssn_card_type: CITIZEN + dependent: + age: 10 + is_tax_unit_dependent: true + ssn_card_type: NONE + has_tin: false + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # The child stays a qualifying child but is not counted in the IRC 32(b) + # computation, so the base is the self-only amount, 7.65% x 6,000 = 459, + # and the match is 70% of it. + dc_eitc_has_qualifying_child: true + dc_eitc_with_qualifying_child: 321.3 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 321.3 + - name: DC EITC uses the one-child credit for an ITIN child in TY2023 at low earnings period: 2023 absolute_error_margin: 0.01 diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py index b4a8217868e..ca24fa6e672 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py @@ -13,18 +13,29 @@ class dc_eitc_has_qualifying_child(Variable): # IRC 32(c)(3)(D) keeps a child without a Social Security number out of # the credit computation without making it a non-qualifying child. "https://www.law.cornell.edu/uscode/text/26/32#c_3_D", + # From 2023: "If you and your spouse have SSNs or ITINs but your + # child(ren) do not, you need to claim the DC EITC for filers without + # a qualifying child." + "https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2023_D40_Book_Final_012324.pdf#page=10", ) defined_for = StateCode.DC def formula(tax_unit, period, parameters): # Selects between the with-children schedule of (f)(1)(B)-(B-2) and - # the childless schedule of (f)(1)(C). A child with an ITIN is a - # qualifying child in every year: before 2023 IRC 32(c)(3)(D) only - # keeps such a child out of the credit computation (see - # dc_eitc_with_qualifying_child), and the unit still cannot use the - # childless schedule. + # the childless schedule of (f)(1)(C). Before 2023 identification plays + # no part: a child with an ITIN or with no number at all is still a + # qualifying child, since IRC 32(c)(3)(D) only keeps it out of the + # credit computation (see dc_eitc_with_qualifying_child) and IRC 32(m) + # treats the two alike, so the unit cannot use the childless schedule. + # From 2023 the D-40 instructions require a Social Security number or + # ITIN for each qualifying child claimed, and send a filer whose + # children have neither to the childless schedule. person = tax_unit.members - qualifying_child = person("is_eitc_qualifying_child", period) & person( - "has_tin", period + p = parameters(period).gov.states.dc.tax.income.credits.eitc + meets_identification = where( + p.qualifying_child_tin_required, person("has_tin", period), True + ) + qualifying_child = ( + person("is_eitc_qualifying_child", period) & meets_identification ) return tax_unit.any(qualifying_child) 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 b45a25856d2..f9fb58a3d92 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,12 +29,15 @@ 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 + # An individual who has a qualifying child is an eligible individual + # under IRC 32(c)(1)(A)(i), with no age test, even when no child is + # counted in the computation. itin_eitc = calculate_eitc_like_amount( tax_unit, period, parameters, child_count, - child_count > 0, + tax_unit("dc_eitc_has_qualifying_child", period), filer_has_tin, ) p = parameters(period).gov.states.dc.tax.income.credits.eitc From ca69e109de36cce9fb03d0df749fb65c3ef6c636 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 03:55:36 -0400 Subject: [PATCH 3/5] Cite Line 27a for the DC child TIN rule and keep the age test off-law An independent review of the routing fix found three loose ends: - The p. 10 sentence the router cited is framed around joint filers. Line 27a (2023 and 2024 booklets p. 18, 2025 booklet p. 21) states the rule for every filing status: "Each qualifying child must have a valid social security number (SSN) or individual taxpayer identification number (ITIN) issued by the IRS. If you have no children who qualify, you must claim the DC EITC without qualifying children." The router and the parameter now cite it. - With qualifying_child_tin_required switched off, the with-child branch's 2023+ base skipped the childless age test that the federal credit, the pre-2023 base, applies. It now uses (child_count > 0) | eitc_demographic_eligible, which equals the old child_count > 0 for every unit the branch covers under current law. A new counterfactual case (head aged 22) fails on the previous commit (321.30) and passes here (0). - The TY2022 $6,000 no-TIN case now asserts the routing too. Co-Authored-By: Claude Opus 5.5 --- .../eitc/qualifying_child_tin_required.yaml | 8 +++- .../gov/states/dc/tax/income/dc_eitc.yaml | 44 +++++++++++++++++-- .../eitc/dc_eitc_has_qualifying_child.py | 9 ++-- .../eitc/dc_eitc_with_qualifying_child.py | 11 +++-- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml b/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml index 379433b77c4..d2bd324c749 100644 --- a/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml +++ b/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml @@ -18,7 +18,13 @@ metadata: href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2022_D-40_Booklet_Final_blk_01_23_23_Ordc.pdf#page=11 - title: 2023 DC Form D-40 Booklet, Page 10, Taxpayers with a Qualifying Child href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2023_D40_Book_Final_012324.pdf#page=10 + - title: 2023 DC Form D-40 Booklet, Page 18, Line 27a + href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2023_D40_Book_Final_012324.pdf#page=18 - title: 2024 DC Form D-40 Booklet, Page 10, Taxpayers with a Qualifying Child href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2024_D40_Booklet_011525.pdf#page=10 - - title: 2025 DC Form D-40 Booklet, Taxpayers with a Qualifying Child + - title: 2024 DC Form D-40 Booklet, Page 18, Line 27a + href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2024_D40_Booklet_011525.pdf#page=18 + - title: 2025 DC Form D-40 Booklet, Page 13, Taxpayers with a Qualifying Child href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2025_D40_Book_082026_v1.pdf#page=13 + - title: 2025 DC Form D-40 Booklet, Page 21, Line 27a + href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2025_D40_Book_082026_v1.pdf#page=21 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 18705344a6a..312ab95a56a 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 @@ -331,6 +331,7 @@ # 70% x 7.65% x 6,000 = 321.30, as for the ITIN child. The childless # schedule would have paid 459. eitc: 459 + dc_eitc_has_qualifying_child: true dc_eitc_with_qualifying_child: 321.3 dc_eitc_without_qualifying_child: 0 dc_eitc: 321.3 @@ -394,10 +395,11 @@ members: [head, dependent] state_code: DC output: - # 2023 D-40 instructions, p. 10: "If you and your spouse have SSNs or - # ITINs but your child(ren) do not, you need to claim the DC EITC for - # filers without a qualifying child." (f)(1)(C): 600, below the 21,888 - # phase-out start. + # 2023 D-40 instructions, Line 27a (p. 18): "Each qualifying child must + # have a valid social security number (SSN) or individual taxpayer + # identification number (ITIN) issued by the IRS. If you have no children + # who qualify, you must claim the DC EITC without qualifying children." + # (f)(1)(C): 600, below the 21,888 phase-out start. eitc: 0 dc_eitc_has_qualifying_child: false dc_eitc_with_qualifying_child: 0 @@ -437,6 +439,40 @@ 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 + period: 2023 + absolute_error_margin: 0.01 + input: + gov.states.dc.tax.income.credits.eitc.qualifying_child_tin_required: false + people: + head: + age: 22 + is_tax_unit_head: true + employment_income: 6_000 + ssn_card_type: CITIZEN + dependent: + age: 10 + is_tax_unit_dependent: true + ssn_card_type: NONE + has_tin: false + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + 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 + dc_eitc_has_qualifying_child: true + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 0 + dc_eitc: 0 + - name: DC EITC uses the one-child credit for an ITIN child in TY2023 at low earnings period: 2023 absolute_error_margin: 0.01 diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py index ca24fa6e672..c28b8bc273a 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py @@ -13,10 +13,11 @@ class dc_eitc_has_qualifying_child(Variable): # IRC 32(c)(3)(D) keeps a child without a Social Security number out of # the credit computation without making it a non-qualifying child. "https://www.law.cornell.edu/uscode/text/26/32#c_3_D", - # From 2023: "If you and your spouse have SSNs or ITINs but your - # child(ren) do not, you need to claim the DC EITC for filers without - # a qualifying child." - "https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2023_D40_Book_Final_012324.pdf#page=10", + # From 2023, Line 27a: "Each qualifying child must have a valid social + # security number (SSN) or individual taxpayer identification number + # (ITIN) issued by the IRS. If you have no children who qualify, you + # must claim the DC EITC without qualifying children." + "https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2023_D40_Book_Final_012324.pdf#page=18", ) defined_for = StateCode.DC 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 f9fb58a3d92..9c4b3815058 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,15 +29,18 @@ 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 - # An individual who has a qualifying child is an eligible individual - # under IRC 32(c)(1)(A)(i), with no age test, even when no child is - # counted in the computation. + # 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. + demographic_eligible = (child_count > 0) | tax_unit( + "eitc_demographic_eligible", period + ) itin_eitc = calculate_eitc_like_amount( tax_unit, period, parameters, child_count, - tax_unit("dc_eitc_has_qualifying_child", period), + demographic_eligible, filer_has_tin, ) p = parameters(period).gov.states.dc.tax.income.credits.eitc From f63d0ba086c834a887fa4ef0483921d9ee6c4a85 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 17:09:59 -0400 Subject: [PATCH 4/5] Keep main's pre-2023 DC EITC routing for children with no SSN or ITIN Max's ruling d1008 (2026-10-06): before TY2023, a DC filer whose qualifying children have neither a Social Security number nor an ITIN gets the childless credit, as on main. - qualifying_child_tin_required is now true from 2015-01-01 (was false for 2015-2022). It cites D.C. Code 47-1806.04(f)(4), ARPA section 9622 and the 2021 D-40 booklet (pp. 3 and 12) and states both readings; setting it to false applies the statute-alone reading. - dc_eitc.yaml: the four TY2021-22 no-TIN cases expect main's values (560, 560, 459, 1,480.21); the two cases that held the other reading now switch the parameter off (321.30, 87.33). - Invariant 9 now compares a no-TIN child with the same filer alone; invariant 10 is checked from 2023, its earlier violation intended. New test_dc_eitc_without_tin_rule checks the switched-off reading. - Router comment and changelog fragment updated to match. Co-Authored-By: Claude Opus 5.5 --- .../dc-eitc-itin-relationship-fixes.fixed.md | 2 +- .../eitc/qualifying_child_tin_required.yaml | 29 +++- .../test_eitc_qualifying_child_invariants.py | 143 +++++++++++++----- .../gov/states/dc/tax/income/dc_eitc.yaml | 117 +++++++++++--- .../eitc/dc_eitc_has_qualifying_child.py | 20 +-- 5 files changed, 228 insertions(+), 83 deletions(-) diff --git a/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md b/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md index 3b0eb879505..22af1c534f2 100644 --- a/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md +++ b/changelog.d/dc-eitc-itin-relationship-fixes.fixed.md @@ -1 +1 @@ -Limit the DC EITC ITIN extension to tax years from 2023 (D.C. Code 47-1806.04(f)(1)(D), added by D.C. Law 24-167 section 7012, not D.C. Law 23-149), keep a DC filer whose qualifying child has no Social Security number or ITIN on the with-child schedule before 2023 (the D-40 instructions send such a filer to the childless schedule only from 2023), and stop counting a disabled parent or grandparent dependent as an EITC qualifying child under IRC 152(c)(2) in the federal, CA, CO, DC, IL, MN and WA credits. +Limit the DC EITC ITIN extension to tax years from 2023 (D.C. Code 47-1806.04(f)(1)(D), added by D.C. Law 24-167 section 7012, not D.C. Law 23-149), cite the D-40 Line 27a instructions for sending a DC filer whose qualifying children have no Social Security number or ITIN to the childless schedule, and stop counting a disabled parent or grandparent dependent as an EITC qualifying child under IRC 152(c)(2) in the federal, CA, CO, DC, IL, MN and WA credits. diff --git a/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml b/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml index d2bd324c749..ee328064c4f 100644 --- a/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml +++ b/policyengine_us/parameters/gov/states/dc/tax/income/credits/eitc/qualifying_child_tin_required.yaml @@ -1,12 +1,19 @@ description: The District of Columbia treats a filer whose qualifying children all lack a Social Security number or individual taxpayer identification number as a filer without a qualifying child under its earned income tax credit if this is true. values: - # Before 2023 a qualifying child without a Social Security number stays a - # qualifying child (D.C. Code 47-1806.04(f)(4) and IRC 32(c)(3)(D)); the - # 2021 and 2022 D-40 instructions add no identification test for children. - 2015-01-01: false - # From 2023 the D-40 instructions for the (f)(1)(D)(ii) ITIN extension send - # a filer whose children have neither number to the childless schedule. - 2023-01-01: true + # From 2023 the D-40 Line 27a instructions require each qualifying child to + # have a Social Security number or ITIN and send a filer whose children have + # neither to the childless schedule. No DC source settles earlier years; + # the 2021 and 2022 booklets' qualifying-child rules have no identification + # test. Read alone, D.C. Code 47-1806.04(f)(4) and IRC 32(c)(3)(D) keep such + # a child a qualifying child, which closes the (f)(1)(C) childless + # schedule; setting this to false applies that reading. ARPA section 9622, + # "Taxpayer eligible for childless earned income credit in case of + # qualifying children who fail to meet certain identification + # requirements", struck former IRC 32(c)(1)(F) from 2021, and the 2021 + # booklet's "New for 2021" note repeats it. PolicyEngine applies the + # childless treatment in every year, so a child with neither number never + # counts. + 2015-01-01: true metadata: unit: bool period: year @@ -14,6 +21,14 @@ metadata: reference: - title: Code of the District of Columbia § 47–1806.04 (f)(1)(D)(ii) href: https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04#(f)(1)(D)(ii) + - title: Code of the District of Columbia § 47–1806.04 (f)(4) + href: https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04#(f)(4) + - title: American Rescue Plan Act of 2021, Pub. L. 117-2, § 9622, 135 Stat. 153 + href: https://www.congress.gov/117/plaws/publ2/PLAW-117publ2.pdf#page=151 + - title: 2021 DC Form D-40 Booklet, Page 3, New for 2021, Earned Income Tax Credit + href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/52926_D-40_12.21.21_Final_Rev011122.pdf#page=3 + - title: 2021 DC Form D-40 Booklet, Page 12, Qualifying Child for EITC Purposes + href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/52926_D-40_12.21.21_Final_Rev011122.pdf#page=12 - title: 2022 DC Form D-40 Booklet, Page 11, Taxpayers with a Qualifying Child href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2022_D-40_Booklet_Final_blk_01_23_23_Ordc.pdf#page=11 - title: 2023 DC Form D-40 Booklet, Page 10, Taxpayers with a Qualifying Child diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py index 7fc6dd5575c..2981da058d6 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py @@ -29,25 +29,39 @@ child an ITIN instead of a Social Security number never raises dc_eitc. 8. From 2023, giving a qualifying child an ITIN instead of a Social Security number leaves dc_eitc unchanged (D.C. Code 47-1806.04(f)(1)(D)(ii)). -9. Schedule selection follows qualifying-child status, not identification. - Before 2023 a child with an ITIN or with no number at all is still a - qualifying child: (f)(4) adopts the IRC 32 definition, and IRC 32(c)(3)(D) - only keeps such a child out of the IRC 32(b) computation. IRC 32(m) treats - the two alike, so before 2023 a qualifying child without any TIN gives the - same routing and the same dc_eitc as one with an ITIN. From 2023 the DC - D-40 instructions require each qualifying child to have a Social Security - number or an ITIN, and send a filer whose children have neither to the - childless schedule. -10. Removing a qualifying child's TIN never raises dc_eitc. +9. A qualifying child with an ITIN routes its unit like one with a Social + Security number in every year; before 2023 it is still a qualifying child, + only left out of the IRC 32(b) computation. A qualifying child with + neither number counts for nothing: the unit gets the same routing and the + same dc_eitc as the same filer with no dependent. The D-40 instructions + say so from 2023 (Line 27a), and the model keeps that treatment for + earlier years (qualifying_child_tin_required). +10. From 2023, removing a qualifying child's TIN never raises dc_eitc. Before + 2023 it can, as intended: DC's childless schedule can pay more than the + with-child match on the federal credit allowed. In TY2022 at $6,000 of + earnings, an ITIN child gives 70% x 459 = 321.30 and a child with no + number gives the childless 459. + +With qualifying_child_tin_required switched off, the reading of (f)(4) and +IRC 32(c)(3)(D) alone: + +11. Schedule selection follows qualifying-child status whatever the child's + identification. +12. Before 2023 a qualifying child with no number gives the same dc_eitc as + one with an ITIN, since IRC 32(m) treats the two alike. +13. In every year, removing a qualifying child's TIN never raises dc_eitc. """ import itertools +from functools import cache import numpy as np from hypothesis import HealthCheck, given, settings from hypothesis import strategies as st -from policyengine_us import Simulation +from policyengine_core.reforms import Reform + +from policyengine_us import CountryTaxBenefitSystem, Simulation YEARS = [2021, 2022, 2023, 2025] ITIN_START = 2023 @@ -89,7 +103,7 @@ ) -def build(draw, rows): +def build(draw, rows, system=None): """One tax unit per row; returns the simulation and row metadata.""" situation = {"people": {}, "tax_units": {}, "households": {}} dependent_index = [] @@ -133,7 +147,8 @@ def build(draw, rows): "members": members, "state_code": {y: state for y in YEARS}, } - return Simulation(situation=situation), np.array(dependent_index) + simulation = Simulation(situation=situation, tax_benefit_system=system) + return simulation, np.array(dependent_index) def index_rows(rows): @@ -152,6 +167,21 @@ def index_rows(rows): ) ] + +@cache +def tin_rule_off_system(): + """The tax-benefit system with qualifying_child_tin_required off, built once.""" + reform = Reform.from_dict( + { + "gov.states.dc.tax.income.credits.eitc.qualifying_child_tin_required": { + "2015-01-01.2100-12-31": False + } + }, + country_id="us", + ) + return CountryTaxBenefitSystem(reform=reform) + + SETTINGS = settings( max_examples=8, deadline=None, @@ -165,7 +195,6 @@ def index_rows(rows): def test_dc_eitc_invariants(draw): sim, dependent_index = build(draw, DC_ROWS) row = index_rows(DC_ROWS) - has_dependent = dependent_index >= 0 for year in YEARS: with_child = sim.calculate("dc_eitc_with_qualifying_child", year) without = sim.calculate("dc_eitc_without_qualifying_child", year) @@ -183,17 +212,20 @@ def test_dc_eitc_invariants(draw): if kind in ASCENDANT_KINDS: assert not qualifying[dependent_index[i]], (kind, context) assert not has_child[i], (kind, context) - # 9. A dependent's qualifying-child status sets its unit's routing. - # Before 2023 that holds whatever the dependent's identification; from - # 2023 a qualifying child also needs a Social Security number or ITIN. - dep_qualifies = np.where( - has_dependent, qualifying[np.maximum(dependent_index, 0)], False - ) - dep_has_tin = np.array([r[1] != "none" for r in DC_ROWS]) & has_dependent - if year < ITIN_START: - assert np.array_equal(has_child, dep_qualifies), context - else: - assert np.array_equal(has_child, dep_qualifies & dep_has_tin), context + # 9. An ITIN child routes like a Social Security number child; a + # qualifying child with neither number leaves the unit where the same + # filer with no dependent would be. + for kind, filer_id in itertools.product( + QUALIFYING_CHILD_KINDS, ["ssn", "itin"] + ): + ssn = row[(kind, "ssn", filer_id, "DC")] + itin = row[(kind, "itin", filer_id, "DC")] + no_tin = row[(kind, "none", filer_id, "DC")] + alone = row[("none", "ssn", filer_id, "DC")] + assert qualifying[dependent_index[no_tin]], (kind, context) + assert has_child[ssn] and has_child[itin], (kind, context) + assert not has_child[no_tin] and not has_child[alone], (kind, context) + assert np.isclose(dc_eitc[no_tin], dc_eitc[alone]), (kind, context) # 4 and 5. Branch structure. assert np.all(with_child >= 0) and np.all(without >= 0), context @@ -222,11 +254,7 @@ def test_dc_eitc_invariants(draw): ): ssn = row[(kind, "ssn", filer_id, "DC")] itin = row[(kind, "itin", filer_id, "DC")] - no_tin = row[(kind, "none", filer_id, "DC")] assert dc_eitc[itin] <= dc_eitc[ssn] + 1e-6, (kind, context) - # 9. No identification at all is treated like an ITIN. - assert has_child[no_tin] == has_child[itin], (kind, context) - assert np.isclose(dc_eitc[no_tin], dc_eitc[itin]), (kind, context) else: # 8. ITIN invariance from 2023. for kind, filer_id in itertools.product( @@ -235,20 +263,53 @@ def test_dc_eitc_invariants(draw): ssn = row[(kind, "ssn", filer_id, "DC")] itin = row[(kind, "itin", filer_id, "DC")] assert np.isclose(dc_eitc[itin], dc_eitc[ssn]), (kind, context) + # 10. From 2023, removing a qualifying child's TIN never raises + # the credit. + assert_no_tin_never_raises(dc_eitc, row, context) - # 10. Removing a qualifying child's TIN never raises the credit. - for kind, filer_id in itertools.product( - QUALIFYING_CHILD_KINDS, ["ssn", "itin"] - ): - for dep_id in ["ssn", "itin"]: - with_tin = row[(kind, dep_id, filer_id, "DC")] + +def assert_no_tin_never_raises(dc_eitc, row, context): + for kind, filer_id in itertools.product(QUALIFYING_CHILD_KINDS, ["ssn", "itin"]): + for dep_id in ["ssn", "itin"]: + with_tin = row[(kind, dep_id, filer_id, "DC")] + no_tin = row[(kind, "none", filer_id, "DC")] + assert dc_eitc[no_tin] <= dc_eitc[with_tin] + 1e-6, ( + kind, + dep_id, + filer_id, + context, + ) + + +@SETTINGS +@given(draw=household_draw) +def test_dc_eitc_without_tin_rule(draw): + sim, dependent_index = build(draw, DC_ROWS, system=tin_rule_off_system()) + row = index_rows(DC_ROWS) + has_dependent = dependent_index >= 0 + for year in YEARS: + dc_eitc = sim.calculate("dc_eitc", year) + has_child = sim.calculate("dc_eitc_has_qualifying_child", year) + qualifying = sim.calculate("is_eitc_qualifying_child", year) + context = f"year {year}, draw {draw}, TIN rule off" + + # 11. Routing follows qualifying-child status alone. + dep_qualifies = np.where( + has_dependent, qualifying[np.maximum(dependent_index, 0)], False + ) + assert np.array_equal(has_child, dep_qualifies), context + + # 12. Before 2023 no number is treated like an ITIN. + if year < ITIN_START: + for kind, filer_id in itertools.product( + QUALIFYING_CHILD_KINDS, ["ssn", "itin"] + ): + itin = row[(kind, "itin", filer_id, "DC")] no_tin = row[(kind, "none", filer_id, "DC")] - assert dc_eitc[no_tin] <= dc_eitc[with_tin] + 1e-6, ( - kind, - dep_id, - filer_id, - context, - ) + assert np.isclose(dc_eitc[no_tin], dc_eitc[itin]), (kind, context) + + # 13. Removing a qualifying child's TIN never raises the credit. + assert_no_tin_never_raises(dc_eitc, row, context) @SETTINGS 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 312ab95a56a..f440ab68ecf 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 @@ -96,7 +96,6 @@ dc_eitc_with_qualifying_child: 2_796.5 dc_eitc: 2_796.5 - - name: DC EITC does not count an ITIN child before 2023 (TY2022) period: 2022 absolute_error_margin: 0.01 @@ -232,7 +231,7 @@ dc_eitc_without_qualifying_child: 0 dc_eitc: 321.3 -- name: DC EITC keeps a filer whose qualifying child has no TIN on the with-child schedule before 2023 (TY2022) +- name: DC EITC sends a filer whose disabled adult qualifying child has no TIN to the childless schedule (TY2022) period: 2022 absolute_error_margin: 0.01 input: @@ -258,19 +257,17 @@ members: [head, dependent] state_code: DC output: - # IRC 32(m) treats a child with no TIN exactly like a child with an - # ITIN, and (f)(1)(D)(ii) does not apply before 2023, so this matches the - # ITIN case above. The child is still a qualifying child under (f)(4) and - # IRC 32(c)(3)(A); the 2022 D-40 worksheet says "If you have a qualifying - # child DO NOT USE THIS WORKSHEET". Routing on the child's TIN sent this - # unit to the (f)(1)(C) childless schedule and paid 560. + # A qualifying child with neither a Social Security number nor an ITIN + # does not count (qualifying_child_tin_required), unlike the ITIN child + # above. (f)(1)(C): min(7.65% x 20,000, 560) = 560, below the 20,532 + # phase-out start. eitc: 0 - dc_eitc_has_qualifying_child: true + dc_eitc_has_qualifying_child: false dc_eitc_with_qualifying_child: 0 - dc_eitc_without_qualifying_child: 0 - dc_eitc: 0 + dc_eitc_without_qualifying_child: 560 + dc_eitc: 560 -- name: DC EITC matches the childless federal credit for a filer whose minor qualifying child has no TIN in TY2022 +- name: DC EITC sends a filer whose minor qualifying child has no TIN to the childless schedule (TY2022) period: 2022 absolute_error_margin: 0.01 input: @@ -294,15 +291,16 @@ members: [head, dependent] state_code: DC output: - # Same routing for an ordinary minor child: 70% x the federal credit - # allowed, 560 - 7.65% x (20,000 - 9,160) < 0. + # Same routing for an ordinary minor child. The federal credit is the + # self-only amount, 560 - 7.65% x (20,000 - 9,160) < 0, but DC's own + # childless schedule pays min(7.65% x 20,000, 560) = 560. eitc: 0 - dc_eitc_has_qualifying_child: true + dc_eitc_has_qualifying_child: false dc_eitc_with_qualifying_child: 0 - dc_eitc_without_qualifying_child: 0 - dc_eitc: 0 + dc_eitc_without_qualifying_child: 560 + dc_eitc: 560 -- name: DC EITC matches the childless federal credit for a filer whose qualifying child has no TIN in TY2022 at low earnings +- name: DC EITC sends a filer whose qualifying child has no TIN to the childless schedule in TY2022 at low earnings period: 2022 absolute_error_margin: 0.01 input: @@ -328,18 +326,90 @@ members: [head, dependent] state_code: DC output: - # 70% x 7.65% x 6,000 = 321.30, as for the ITIN child. The childless - # schedule would have paid 459. + # (f)(1)(C): 7.65% x 6,000 = 459. The ITIN child above instead gets + # 70% of the same 459 federal credit, 321.30. + eitc: 459 + dc_eitc_has_qualifying_child: false + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 459 + dc_eitc: 459 + +- name: DC EITC sends a filer whose qualifying child has no TIN to the childless schedule in TY2021 + period: 2021 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 20_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: false + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # (f)(1)(C) on ARPA's 2021 childless phase-in: + # min(15.3% x 20,000, 1,502) - 8.48% x (20,000 - 19,743) = 1,480.21. + # The ITIN child above instead gets 40% of the 218.33 federal credit. + eitc: 218.33 + dc_eitc_has_qualifying_child: false + dc_eitc_with_qualifying_child: 0 + dc_eitc_without_qualifying_child: 1_480.21 + dc_eitc: 1_480.21 + +- name: Without the TIN rule, a filer whose qualifying child has no TIN gets the with-child match on the federal credit in TY2022 + period: 2022 + absolute_error_margin: 0.01 + input: + gov.states.dc.tax.income.credits.eitc.qualifying_child_tin_required: false + people: + head: + age: 50 + is_tax_unit_head: true + employment_income: 6_000 + ssn_card_type: CITIZEN + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + ssn_card_type: NONE + has_tin: false + tax_units: + tax_unit: + members: [head, dependent] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [head, dependent] + state_code: DC + output: + # The reading of (f)(4) and IRC 32(c)(3)(D) alone: the child stays a + # qualifying child, so the filer gets the with-child match on the + # federal credit allowed, as for an ITIN child: 70% x 459 = 321.30. eitc: 459 dc_eitc_has_qualifying_child: true dc_eitc_with_qualifying_child: 321.3 dc_eitc_without_qualifying_child: 0 dc_eitc: 321.3 -- name: DC EITC matches the childless federal credit for a filer whose qualifying child has no TIN in TY2021 +- name: Without the TIN rule, a filer whose qualifying child has no TIN gets the with-child match on the federal credit in TY2021 period: 2021 absolute_error_margin: 0.01 input: + gov.states.dc.tax.income.credits.eitc.qualifying_child_tin_required: false people: head: age: 50 @@ -362,9 +432,8 @@ members: [head, dependent] state_code: DC output: - # 40% x the ARPA childless credit allowed, 218.33, as for the ITIN - # child. The childless schedule would have paid - # 1,502 - 8.48% x (20,000 - 19,743) = 1,480.21. + # 40% x the ARPA childless credit allowed, + # 1,502 - 15.3% x (20,000 - 11,610) = 218.33, giving 87.33. eitc: 218.33 dc_eitc_has_qualifying_child: true dc_eitc_with_qualifying_child: 87.33 diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py index c28b8bc273a..afac929572d 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_has_qualifying_child.py @@ -10,8 +10,8 @@ class dc_eitc_has_qualifying_child(Variable): "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04#(f)(1)", # (f)(4) gives "qualifying child" its IRC 32 meaning. "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04#(f)(4)", - # IRC 32(c)(3)(D) keeps a child without a Social Security number out of - # the credit computation without making it a non-qualifying child. + # IRC 32(c)(3)(D) keeps a child without a Social Security number out + # of the credit computation without making it a non-qualifying child. "https://www.law.cornell.edu/uscode/text/26/32#c_3_D", # From 2023, Line 27a: "Each qualifying child must have a valid social # security number (SSN) or individual taxpayer identification number @@ -23,14 +23,14 @@ class dc_eitc_has_qualifying_child(Variable): def formula(tax_unit, period, parameters): # Selects between the with-children schedule of (f)(1)(B)-(B-2) and - # the childless schedule of (f)(1)(C). Before 2023 identification plays - # no part: a child with an ITIN or with no number at all is still a - # qualifying child, since IRC 32(c)(3)(D) only keeps it out of the - # credit computation (see dc_eitc_with_qualifying_child) and IRC 32(m) - # treats the two alike, so the unit cannot use the childless schedule. - # From 2023 the D-40 instructions require a Social Security number or - # ITIN for each qualifying child claimed, and send a filer whose - # children have neither to the childless schedule. + # the childless schedule of (f)(1)(C). A qualifying child with an ITIN + # keeps the unit on the with-children schedule in every year; before + # 2023 IRC 32(c)(3)(D) only leaves it out of the credit computation + # (see dc_eitc_with_qualifying_child). A filer whose qualifying + # children have neither a Social Security number nor an ITIN claims + # the childless schedule: Line 27a says so from 2023, and + # qualifying_child_tin_required applies the same treatment to earlier + # years. Setting it to false keeps such a child a qualifying child. person = tax_unit.members p = parameters(period).gov.states.dc.tax.income.credits.eitc meets_identification = where( From e89c2b6f9c14c6ef3a97707352633cc60f9d957a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 19:18:57 -0400 Subject: [PATCH 5/5] Release the reformed system after the DC EITC invariants module test_dc_eitc_without_tin_rule built its tax-benefit system with qualifying_child_tin_required switched off through functools.cache, which kept a second full system resident for the rest of the pytest process. CI runs this file in the Rest "policy" group, which peaked at 12.4 GB RSS on the 16 GB runner at 92802f507c (run 37463810232). Build it in a module-scoped fixture instead, used read-only, and collect it when the module finishes. Co-Authored-By: Claude Opus 5.5 --- .../test_eitc_qualifying_child_invariants.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py index 2981da058d6..a0612249389 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/test_eitc_qualifying_child_invariants.py @@ -52,10 +52,11 @@ 13. In every year, removing a qualifying child's TIN never raises dc_eitc. """ +import gc import itertools -from functools import cache import numpy as np +import pytest from hypothesis import HealthCheck, given, settings from hypothesis import strategies as st @@ -168,9 +169,14 @@ def index_rows(rows): ] -@cache +@pytest.fixture(scope="module") def tin_rule_off_system(): - """The tax-benefit system with qualifying_child_tin_required off, built once.""" + """The tax-benefit system with qualifying_child_tin_required off. + + Built once for this module and only read. It is released when the module + finishes, so the rest of the pytest process does not keep a second full + system resident. + """ reform = Reform.from_dict( { "gov.states.dc.tax.income.credits.eitc.qualifying_child_tin_required": { @@ -179,7 +185,8 @@ def tin_rule_off_system(): }, country_id="us", ) - return CountryTaxBenefitSystem(reform=reform) + yield CountryTaxBenefitSystem(reform=reform) + gc.collect() SETTINGS = settings( @@ -283,8 +290,8 @@ def assert_no_tin_never_raises(dc_eitc, row, context): @SETTINGS @given(draw=household_draw) -def test_dc_eitc_without_tin_rule(draw): - sim, dependent_index = build(draw, DC_ROWS, system=tin_rule_off_system()) +def test_dc_eitc_without_tin_rule(tin_rule_off_system, draw): + sim, dependent_index = build(draw, DC_ROWS, system=tin_rule_off_system) row = index_rows(DC_ROWS) has_dependent = dependent_index >= 0 for year in YEARS: