From ded26eb283a430acfc5f1c40ce44f02b2b6feadc Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Thu, 18 Dec 2025 19:01:50 -0500 Subject: [PATCH 1/5] Fix Head of Household eligibility for qualifying relatives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for qualifying relatives in Head of Household filing status determination. Previously, only qualifying children (age-based) were considered, causing parents with 19+ year old non-student dependents to be incorrectly denied HOH status. Changes: - Add is_qualifying_child_dependent (pure age test: under 19, or under 24 if student) - Add is_qualifying_relative_dependent (income test using uprated personal exemption) - Add dependent_gross_income to calculate gross income for dependents - Update is_child_dependent to combine all three pathways (age, income, disability) - Simplify head_of_household_eligible since is_child_dependent now includes relatives The qualifying relative gross income limit uses the 2017 personal exemption ($4,050) with IRS uprating applied, continuing the pre-TCJA threshold. Fixes #6994 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- changelog_entry.yaml | 9 + .../irs/income/dependent_gross_income.yaml | 67 ++++++++ .../person/is_child_dependent.yaml | 102 +++++++++-- .../person/is_qualifying_child_dependent.yaml | 46 +++++ .../is_qualifying_relative_dependent.yaml | 162 ++++++++++++++++++ .../demographic/tax_unit/filing_status.yaml | 51 +++++- .../tax_unit/head_of_household_eligible.yaml | 109 +++++++++++- .../gov/irs/income/dependent_gross_income.py | 37 ++++ .../demographic/person/is_child_dependent.py | 30 +++- .../person/is_qualifying_child_dependent.py | 23 +++ .../is_qualifying_relative_dependent.py | 58 +++++++ .../tax_unit/head_of_household_eligible.py | 6 +- 12 files changed, 672 insertions(+), 28 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/dependent_gross_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/household/demographic/person/is_qualifying_child_dependent.yaml create mode 100644 policyengine_us/tests/policy/baseline/household/demographic/person/is_qualifying_relative_dependent.yaml create mode 100644 policyengine_us/variables/gov/irs/income/dependent_gross_income.py create mode 100644 policyengine_us/variables/household/demographic/person/is_qualifying_child_dependent.py create mode 100644 policyengine_us/variables/household/demographic/person/is_qualifying_relative_dependent.py diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..8e0762a3f4d 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,9 @@ +- bump: minor + changes: + added: + - is_qualifying_child_dependent variable for age-based qualifying child test + - is_qualifying_relative_dependent variable for income-based qualifying relative test + - dependent_gross_income variable for calculating dependent gross income + changed: + - is_child_dependent now includes qualifying child, qualifying relative, and disability pathways + - Head of household eligibility now correctly includes qualifying relatives (fixes issue 6994) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/dependent_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/dependent_gross_income.yaml new file mode 100644 index 00000000000..790b33089b4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/dependent_gross_income.yaml @@ -0,0 +1,67 @@ +- name: Dependent with employment income. + period: 2024 + input: + people: + parent: + age: 45 + is_tax_unit_head: true + employment_income: 50_000 + child: + age: 20 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + employment_income: 5_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + output: + # Only dependent's income counts, head's income excluded + dependent_gross_income: [0, 5_000] + +- name: Head or spouse has zero dependent gross income. + period: 2024 + input: + people: + head: + age: 45 + is_tax_unit_head: true + employment_income: 100_000 + spouse: + age: 43 + is_tax_unit_spouse: true + employment_income: 50_000 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + output: + # Head and spouse are not dependents, so their dependent_gross_income is 0 + dependent_gross_income: [0, 0] + +- name: Dependent with multiple income sources. + period: 2024 + input: + people: + parent: + age: 50 + is_tax_unit_head: true + child: + age: 22 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + employment_income: 3_000 + taxable_interest_income: 500 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + output: + # Sum of all income sources + dependent_gross_income: [0, 3_500] diff --git a/policyengine_us/tests/policy/baseline/household/demographic/person/is_child_dependent.yaml b/policyengine_us/tests/policy/baseline/household/demographic/person/is_child_dependent.yaml index 0cb3c421e7c..a0c3901d8ca 100644 --- a/policyengine_us/tests/policy/baseline/household/demographic/person/is_child_dependent.yaml +++ b/policyengine_us/tests/policy/baseline/household/demographic/person/is_child_dependent.yaml @@ -16,29 +16,105 @@ output: is_child_dependent: true -- name: Non-student dependent age ineligible. +- name: Non-student dependent age ineligible with high income. period: 2021 - input: - is_tax_unit_dependent: true - age: 19 - is_full_time_student: false + input: + people: + parent: + age: 45 + is_tax_unit_head: true + child: + age: 19 + is_full_time_student: false + is_tax_unit_dependent: true + is_tax_unit_spouse: false + # Income above qualifying relative threshold ($4,150 for 2021) + employment_income: 10_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] output: - is_child_dependent: false + # Fails age test (19+ non-student) and income test (above threshold) + is_child_dependent: [false, false] -- name: Student dependent age ineligible. +- name: Student dependent age ineligible with high income. period: 2021 - input: - is_tax_unit_dependent: true - age: 24 - is_full_time_student: true + input: + people: + parent: + age: 50 + is_tax_unit_head: true + student: + age: 24 + is_full_time_student: true + is_tax_unit_dependent: true + is_tax_unit_spouse: false + # Income above qualifying relative threshold ($4,150 for 2021) + employment_income: 10_000 + tax_units: + tax_unit: + members: [parent, student] + households: + household: + members: [parent, student] output: - is_child_dependent: false + # Fails age test (24+ student) and income test (above threshold) + is_child_dependent: [false, false] + +- name: Non-student age ineligible but qualifies as qualifying relative. + period: 2021 + input: + people: + parent: + age: 45 + is_tax_unit_head: true + child: + age: 19 + is_full_time_student: false + is_tax_unit_dependent: true + is_tax_unit_spouse: false + # Income below qualifying relative threshold ($4,150 for 2021) + employment_income: 3_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + output: + # Fails age test but qualifies as qualifying relative + is_child_dependent: [false, true] - name: Age eligible non-dependent. period: 2021 - input: + input: is_tax_unit_dependent: false age: 18 is_full_time_student: false output: is_child_dependent: false + +- name: Permanently disabled adult qualifies as child dependent. + period: 2024 + input: + is_tax_unit_dependent: true + age: 30 + is_full_time_student: false + is_permanently_and_totally_disabled: true + output: + # Per IRC 152(c)(3)(B), permanently disabled individuals are + # exempt from the age test for qualifying child status + is_child_dependent: true + +- name: Over-age disabled non-dependent does not qualify. + period: 2024 + input: + is_tax_unit_dependent: false + age: 30 + is_permanently_and_totally_disabled: true + output: + # Must still be a dependent to qualify + is_child_dependent: false diff --git a/policyengine_us/tests/policy/baseline/household/demographic/person/is_qualifying_child_dependent.yaml b/policyengine_us/tests/policy/baseline/household/demographic/person/is_qualifying_child_dependent.yaml new file mode 100644 index 00000000000..1be0fe079da --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/demographic/person/is_qualifying_child_dependent.yaml @@ -0,0 +1,46 @@ +- name: Child under 19 is a qualifying child. + period: 2024 + input: + is_tax_unit_dependent: true + age: 18 + is_full_time_student: false + output: + is_qualifying_child_dependent: true + +- name: Full-time student under 24 is a qualifying child. + period: 2024 + input: + is_tax_unit_dependent: true + age: 23 + is_full_time_student: true + output: + is_qualifying_child_dependent: true + +- name: 19-year-old non-student is not a qualifying child. + period: 2024 + input: + is_tax_unit_dependent: true + age: 19 + is_full_time_student: false + output: + # Fails age test - 19 is not less than 19 + is_qualifying_child_dependent: false + +- name: 24-year-old student is not a qualifying child. + period: 2024 + input: + is_tax_unit_dependent: true + age: 24 + is_full_time_student: true + output: + # Fails age test - 24 is not less than 24 + is_qualifying_child_dependent: false + +- name: Non-dependent does not qualify. + period: 2024 + input: + is_tax_unit_dependent: false + age: 10 + output: + # defined_for requires is_tax_unit_dependent + is_qualifying_child_dependent: false diff --git a/policyengine_us/tests/policy/baseline/household/demographic/person/is_qualifying_relative_dependent.yaml b/policyengine_us/tests/policy/baseline/household/demographic/person/is_qualifying_relative_dependent.yaml new file mode 100644 index 00000000000..a667c564fc8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/demographic/person/is_qualifying_relative_dependent.yaml @@ -0,0 +1,162 @@ +- name: 19-year-old non-student with low income is a qualifying relative. + period: 2024 + input: + people: + parent: + age: 45 + is_tax_unit_head: true + employment_income: 50_000 + adult_child: + age: 19 + is_full_time_student: false + is_tax_unit_dependent: true + is_tax_unit_spouse: false + employment_income: 3_000 + tax_units: + tax_unit: + members: [parent, adult_child] + households: + household: + members: [parent, adult_child] + output: + is_qualifying_relative_dependent: [false, true] + +- name: 19-year-old non-student with high income is not a qualifying relative. + period: 2024 + input: + people: + parent: + age: 45 + is_tax_unit_head: true + employment_income: 50_000 + adult_child: + age: 19 + is_full_time_student: false + is_tax_unit_dependent: true + is_tax_unit_spouse: false + # Income over $5,050 threshold for 2024 + employment_income: 10_000 + tax_units: + tax_unit: + members: [parent, adult_child] + households: + household: + members: [parent, adult_child] + output: + # Fails income test + is_qualifying_relative_dependent: [false, false] + +- name: Child under 19 is a qualifying child, not qualifying relative. + period: 2024 + input: + people: + parent: + age: 40 + is_tax_unit_head: true + child: + age: 10 + employment_income: 0 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + output: + # Under 19, so qualifies as a child dependent, not as qualifying relative + is_qualifying_relative_dependent: [false, false] + +- name: Full-time student under 24 is a qualifying child, not qualifying relative. + period: 2024 + input: + people: + parent: + age: 50 + is_tax_unit_head: true + student: + age: 21 + is_full_time_student: true + employment_income: 2_000 + tax_units: + tax_unit: + members: [parent, student] + households: + household: + members: [parent, student] + output: + # Full-time student under 24 qualifies as child dependent + is_qualifying_relative_dependent: [false, false] + +- name: 24-year-old student with low income is a qualifying relative. + period: 2024 + input: + people: + parent: + age: 55 + is_tax_unit_head: true + adult_student: + age: 24 + is_full_time_student: true + is_tax_unit_dependent: true + is_tax_unit_spouse: false + employment_income: 4_000 + tax_units: + tax_unit: + members: [parent, adult_student] + households: + household: + members: [parent, adult_student] + output: + # 24-year-old student is too old for qualifying child, but qualifies as + # qualifying relative since income is below threshold + is_qualifying_relative_dependent: [false, true] + +- name: Unrelated person living in household is not a qualifying relative for HOH. + period: 2024 + input: + people: + head: + age: 40 + is_tax_unit_head: true + unrelated_person: + age: 25 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_related_to_head_or_spouse: false + employment_income: 1_000 + tax_units: + tax_unit: + members: [head, unrelated_person] + households: + household: + members: [head, unrelated_person] + output: + # Unrelated person does not qualify for HOH purposes per IRC 2(b)(3) + is_qualifying_relative_dependent: [false, false] + +- name: Disabled adult is not a qualifying relative. + period: 2024 + input: + people: + parent: + age: 60 + is_tax_unit_head: true + employment_income: 50_000 + disabled_child: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_permanently_and_totally_disabled: true + employment_income: 2_000 + tax_units: + tax_unit: + members: [parent, disabled_child] + households: + household: + members: [parent, disabled_child] + output: + # Permanently disabled individuals are exempt from the age test per + # IRC 152(c)(3)(B), so they qualify as child dependents, not qualifying + # relatives. Per IRC 152(d)(1)(D), qualifying children cannot be + # qualifying relatives. + is_qualifying_relative_dependent: [false, false] diff --git a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/filing_status.yaml b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/filing_status.yaml index 507c514f743..a676f7d4a30 100644 --- a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/filing_status.yaml +++ b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/filing_status.yaml @@ -38,7 +38,7 @@ person1: is_separated: true age: 40 - person2: + person2: age: 10 tax_units: tax_unit: @@ -48,3 +48,52 @@ members: [person1, person2] output: filing_status: HEAD_OF_HOUSEHOLD + +- name: Parent with 19-year-old non-student dependent (low income) qualifies for HOH. + period: 2024 + input: + people: + parent: + age: 45 + employment_income: 50_000 + adult_child: + age: 19 + is_full_time_student: false + is_tax_unit_dependent: true + is_tax_unit_spouse: false + # Low income below $5,050 threshold for qualifying relative + employment_income: 3_000 + tax_units: + tax_unit: + members: [parent, adult_child] + households: + household: + members: [parent, adult_child] + output: + # The 19-year-old non-student is too old to be a qualifying child but + # can still be a qualifying relative for HOH purposes + filing_status: HEAD_OF_HOUSEHOLD + +- name: Parent with high-income adult child does not qualify for HOH. + period: 2024 + input: + people: + parent: + age: 45 + employment_income: 50_000 + adult_child: + age: 19 + is_full_time_student: false + is_tax_unit_dependent: true + is_tax_unit_spouse: false + # Income over $5,050 threshold - fails qualifying relative test + employment_income: 10_000 + tax_units: + tax_unit: + members: [parent, adult_child] + households: + household: + members: [parent, adult_child] + output: + # High-income adult child fails qualifying relative income test + filing_status: SINGLE diff --git a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_of_household_eligible.yaml b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_of_household_eligible.yaml index 7df1d1bfb73..6124b3757c3 100644 --- a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_of_household_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_of_household_eligible.yaml @@ -1,13 +1,110 @@ -- name: Default is single. +- name: Default is single with no dependents. period: 2022 output: head_of_household_eligible: false -- name: If unmarried and has children, then head of household is eligible. - period: 2022 +- name: Unmarried with qualifying child is eligible for HOH. + period: 2024 + input: + people: + parent: + age: 40 + is_tax_unit_head: true + child: + age: 10 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + output: + head_of_household_eligible: true + +- name: Unmarried parent with 19-year-old non-student child (low income) qualifies for HOH. + period: 2024 input: - tax_unit_married: false - tax_unit_child_dependents: 1 - surviving_spouse_eligible: false + people: + parent: + age: 45 + is_tax_unit_head: true + employment_income: 50_000 + adult_child: + age: 19 + is_full_time_student: false + is_tax_unit_dependent: true + is_tax_unit_spouse: false + # Low income under $5,050 threshold + employment_income: 3_000 + tax_units: + tax_unit: + members: [parent, adult_child] + households: + household: + members: [parent, adult_child] output: + # 19-year-old non-student fails qualifying child test but is a qualifying + # relative with income below $5,050 threshold head_of_household_eligible: true + +- name: Adult child with income over threshold does not qualify parent for HOH. + period: 2024 + input: + people: + parent: + age: 45 + is_tax_unit_head: true + employment_income: 50_000 + adult_child: + age: 19 + is_full_time_student: false + is_tax_unit_dependent: true + is_tax_unit_spouse: false + # Income over $5,050 threshold + employment_income: 10_000 + tax_units: + tax_unit: + members: [parent, adult_child] + households: + household: + members: [parent, adult_child] + output: + # 19-year-old with income over threshold fails qualifying relative test + head_of_household_eligible: false + +- name: Married person does not qualify for head of household. + period: 2024 + input: + people: + person1: + age: 40 + is_tax_unit_head: true + person2: + age: 38 + is_tax_unit_spouse: true + child: + age: 10 + tax_units: + tax_unit: + members: [person1, person2, child] + households: + household: + members: [person1, person2, child] + output: + head_of_household_eligible: false + +- name: Person with no dependents does not qualify for head of household. + period: 2024 + input: + people: + single_person: + age: 35 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [single_person] + households: + household: + members: [single_person] + output: + head_of_household_eligible: false diff --git a/policyengine_us/variables/gov/irs/income/dependent_gross_income.py b/policyengine_us/variables/gov/irs/income/dependent_gross_income.py new file mode 100644 index 00000000000..03be0724258 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/dependent_gross_income.py @@ -0,0 +1,37 @@ +from policyengine_us.model_api import * + + +# Sources that depend on filing_status and would cause circular dependencies +CIRCULAR_SOURCES = { + "taxable_unemployment_compensation": "unemployment_compensation", + "taxable_social_security": "social_security", +} + + +class dependent_gross_income(Variable): + value_type = float + entity = Person + label = "Gross income for dependents" + unit = USD + documentation = """ + Gross income for dependents, used for the qualifying relative income test + under IRC 152(d)(1)(B). Mirrors irs_gross_income but calculates income for + dependents (not head or spouse) instead of non-dependents. + """ + definition_period = YEAR + reference = [ + "https://www.law.cornell.edu/uscode/text/26/61", + "https://www.law.cornell.edu/uscode/text/26/152#d_1_B", + ] + + def formula(person, period, parameters): + sources = parameters(period).gov.irs.gross_income.sources + total = 0 + is_dependent = ~person("is_tax_unit_head_or_spouse", period) + for source in sources: + # Replace sources that would cause circular dependencies + if source in CIRCULAR_SOURCES: + source = CIRCULAR_SOURCES[source] + # Add positive values only - losses are deducted later. + total += is_dependent * max_(0, add(person, period, [source])) + return total diff --git a/policyengine_us/variables/household/demographic/person/is_child_dependent.py b/policyengine_us/variables/household/demographic/person/is_child_dependent.py index 4426774fab9..a9001c51f9c 100644 --- a/policyengine_us/variables/household/demographic/person/is_child_dependent.py +++ b/policyengine_us/variables/household/demographic/person/is_child_dependent.py @@ -5,13 +5,31 @@ class is_child_dependent(Variable): value_type = bool entity = Person label = "Is a child dependent based on the IRS definition" - reference = "https://www.law.cornell.edu/uscode/text/26/152#c_3_A_ii" + documentation = """ + A person qualifies as a child dependent if they are a tax unit dependent + and meet any of the following criteria: + 1. Qualifying child: meets the age test (under 19, or under 24 if student) + per IRC 152(c)(3)(A) + 2. Qualifying relative: fails age test but has gross income below threshold + per IRC 152(d) + 3. Permanently disabled: exempt from age test per IRC 152(c)(3)(B) + """ + reference = [ + "https://www.law.cornell.edu/uscode/text/26/152#c_3_A", + "https://www.law.cornell.edu/uscode/text/26/152#c_3_B", + "https://www.law.cornell.edu/uscode/text/26/152#d", + ] definition_period = YEAR defined_for = "is_tax_unit_dependent" def formula(person, period, parameters): - age = person("age", period) - p = parameters(period).gov.irs.dependent.ineligible_age - student = person("is_full_time_student", period) - age_limit = where(student, p.student, p.non_student) - return age < age_limit + # Three pathways to qualify as a child dependent: + # 1. Qualifying child (meets age test) + is_qualifying_child = person("is_qualifying_child_dependent", period) + # 2. Qualifying relative (fails age test but meets income test) + is_qualifying_relative = person( + "is_qualifying_relative_dependent", period + ) + # 3. Permanently disabled (exempt from age test) + is_disabled = person("is_permanently_and_totally_disabled", period) + return is_qualifying_child | is_qualifying_relative | is_disabled diff --git a/policyengine_us/variables/household/demographic/person/is_qualifying_child_dependent.py b/policyengine_us/variables/household/demographic/person/is_qualifying_child_dependent.py new file mode 100644 index 00000000000..3f745dece4a --- /dev/null +++ b/policyengine_us/variables/household/demographic/person/is_qualifying_child_dependent.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class is_qualifying_child_dependent(Variable): + value_type = bool + entity = Person + label = "Is a qualifying child dependent based on age test" + documentation = """ + A qualifying child must meet the age test under IRC 152(c)(3)(A): + under 19 at end of year, or under 24 if a full-time student. + This variable does NOT include the disability exception - that is + handled separately in is_child_dependent. + """ + reference = "https://www.law.cornell.edu/uscode/text/26/152#c_3_A" + definition_period = YEAR + defined_for = "is_tax_unit_dependent" + + def formula(person, period, parameters): + age = person("age", period) + p = parameters(period).gov.irs.dependent.ineligible_age + student = person("is_full_time_student", period) + age_limit = where(student, p.student, p.non_student) + return age < age_limit diff --git a/policyengine_us/variables/household/demographic/person/is_qualifying_relative_dependent.py b/policyengine_us/variables/household/demographic/person/is_qualifying_relative_dependent.py new file mode 100644 index 00000000000..cca879ca9c1 --- /dev/null +++ b/policyengine_us/variables/household/demographic/person/is_qualifying_relative_dependent.py @@ -0,0 +1,58 @@ +from policyengine_us.model_api import * + + +class is_qualifying_relative_dependent(Variable): + value_type = bool + entity = Person + label = "Is a qualifying relative dependent" + documentation = """ + A qualifying relative is a dependent who does not meet the qualifying child + age test but still qualifies as a dependent under IRC 152(d). Requirements: + 1. Relationship test (related to taxpayer or lives with them) + 2. Gross income below the threshold + 3. Support test (taxpayer provides over half of support - assumed) + 4. Not a qualifying child of anyone (per IRC 152(d)(1)(D)) + + For head of household purposes, the person must also be related (not just + living with the taxpayer) per IRC 2(b)(3). + """ + definition_period = YEAR + reference = [ + "https://www.law.cornell.edu/uscode/text/26/152#d", + "https://www.irs.gov/publications/p501", + ] + + def formula(person, period, parameters): + is_dependent = person("is_tax_unit_dependent", period) + # Per IRC 152(d)(1)(D), cannot be a qualifying child of anyone. + # This includes: + # - Those who meet the age test (under 19, or under 24 if student) + # - Those who are permanently disabled (exempt from age test per + # IRC 152(c)(3)(B)) + is_qualifying_child = person("is_qualifying_child_dependent", period) + is_disabled = person("is_permanently_and_totally_disabled", period) + not_qualifying_child = ~(is_qualifying_child | is_disabled) + + # Gross income must be below the exemption amount per IRC 152(d)(1)(B). + # Although TCJA set the personal exemption to $0 from 2018, the gross + # income limit continues at the 2017 level adjusted for inflation. + gross_income = person("dependent_gross_income", period) + p = parameters(period).gov.irs + # Get 2017 exemption amount and apply uprating + exemption_2017 = parameters( + "2017-01-01" + ).gov.irs.income.exemption.amount + uprating_2017 = parameters("2017-01-01").gov.irs.uprating + uprating_current = p.uprating + income_limit = exemption_2017 * uprating_current / uprating_2017 + meets_income_test = gross_income < income_limit + + # For HOH purposes, must be related (not just living with taxpayer) + is_related = person("is_related_to_head_or_spouse", period) + + return ( + is_dependent + & not_qualifying_child + & meets_income_test + & is_related + ) diff --git a/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py b/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py index 4682fa34190..b040c17343b 100644 --- a/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py +++ b/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py @@ -10,11 +10,13 @@ class head_of_household_eligible(Variable): def formula(tax_unit, period, parameters): married = tax_unit("tax_unit_married", period) - has_child_dependents = ( + # is_child_dependent includes qualifying children, qualifying + # relatives, and permanently disabled individuals + has_qualifying_person = ( tax_unit("tax_unit_child_dependents", period) > 0 ) return ( - has_child_dependents + has_qualifying_person & ~married & ~tax_unit("surviving_spouse_eligible", period) ) From 567174286d099694f7c5df365e038904c7991beb Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Thu, 18 Dec 2025 20:02:38 -0500 Subject: [PATCH 2/5] test fixes --- .../reforms/states/ny/wftc/ny_working_families_tax_credit.py | 4 ++-- .../gov/irs/credits/earned_income/eitc_child_count.py | 2 +- .../alternative_minimum_tax/exemption/ca_amt_exemption.py | 2 +- .../earned_income/ca_is_qualifying_child_for_caleitc.py | 2 +- .../family_affordability/co_family_affordability_credit.py | 2 +- .../income/credits/mn_child_and_working_families_credits.py | 2 +- .../exemptions/dependent/mt_dependent_exemptions_person.py | 2 +- .../exemption/or_disabled_child_dependent_exemptions.py | 2 +- .../pa/tax/income/forgiveness/pa_tax_forgiveness_rate.py | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/policyengine_us/reforms/states/ny/wftc/ny_working_families_tax_credit.py b/policyengine_us/reforms/states/ny/wftc/ny_working_families_tax_credit.py index 7ecbb83ece4..d1698649667 100644 --- a/policyengine_us/reforms/states/ny/wftc/ny_working_families_tax_credit.py +++ b/policyengine_us/reforms/states/ny/wftc/ny_working_families_tax_credit.py @@ -475,10 +475,10 @@ class ny_exemptions_dependent(Variable): def formula(person, period, parameters): p = parameters(period).gov.contrib.states.ny.wftc.exemptions if p.in_effect: - child_dependent = person("is_child_dependent", period) + child_dependent = person("is_qualifying_child_dependent", period) wftc_eligible_child = person("ny_wftc_eligible_child", period) return child_dependent & ~wftc_eligible_child - return person("is_child_dependent", period) + return person("is_qualifying_child_dependent", period) class ny_exemptions(Variable): value_type = float 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 f0147cb617f..47b5f9cf788 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 @@ -15,7 +15,7 @@ class eitc_child_count(Variable): def formula(tax_unit, period, parameters): person = tax_unit.members - is_child = person("is_child_dependent", period) + is_child = person("is_qualifying_child_dependent", period) meets_eitc_identification_requirements = person( "meets_eitc_identification_requirements", period ) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/alternative_minimum_tax/exemption/ca_amt_exemption.py b/policyengine_us/variables/gov/states/ca/tax/income/alternative_minimum_tax/exemption/ca_amt_exemption.py index 632073b8e07..9e89a72e598 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/alternative_minimum_tax/exemption/ca_amt_exemption.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/alternative_minimum_tax/exemption/ca_amt_exemption.py @@ -32,7 +32,7 @@ def formula(tax_unit, period, parameters): # Eligible children receive an increased exemption amount. # This applies when the HEAD of the tax unit is a child dependent. person = tax_unit.members - eligible_child = person("is_child_dependent", period) + eligible_child = person("is_qualifying_child_dependent", period) is_head = person("is_tax_unit_head", period) head_is_eligible_child = tax_unit.any(eligible_child & is_head) # Line 7 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 f247cae94f2..1beb61f08dd 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 @@ -11,4 +11,4 @@ class ca_is_qualifying_child_for_caleitc(Variable): def formula(person, period, parameters): # CalEITC uses federal EITC rules regarding qualifying children - return person("is_child_dependent", period) + return person("is_qualifying_child_dependent", period) diff --git a/policyengine_us/variables/gov/states/co/tax/income/credits/family_affordability/co_family_affordability_credit.py b/policyengine_us/variables/gov/states/co/tax/income/credits/family_affordability/co_family_affordability_credit.py index 7cc34f28478..0d569b90684 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/credits/family_affordability/co_family_affordability_credit.py +++ b/policyengine_us/variables/gov/states/co/tax/income/credits/family_affordability/co_family_affordability_credit.py @@ -14,7 +14,7 @@ class co_family_affordability_credit(Variable): def formula(person, period, parameters): age = person("age", period) - dependent = person("is_child_dependent", period) + dependent = person("is_qualifying_child_dependent", period) p = parameters( period ).gov.states.co.tax.income.credits.family_affordability 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 56163250340..4d782dfcc9e 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 @@ -33,7 +33,7 @@ def formula(tax_unit, period, parameters): earnings = tax_unit("filer_adjusted_earnings", period) base_wfc_credit = p.wfc.phase_in.calc(earnings) person = tax_unit.members - qualifying_child = person("is_child_dependent", period) + qualifying_child = person("is_qualifying_child_dependent", 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/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py index f0da3601a81..ae6696424d4 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py @@ -15,7 +15,7 @@ def formula(person, period, parameters): if p.applies: # Qualifying child under IRC 152(c), which defines for the EITC - qualifying_child = person("is_child_dependent", period) + qualifying_child = person("is_qualifying_child_dependent", period) # Disabled dependents get an additional exemption. disabled = person("is_disabled", period) diff --git a/policyengine_us/variables/gov/states/or/tax/income/credits/exemption/or_disabled_child_dependent_exemptions.py b/policyengine_us/variables/gov/states/or/tax/income/credits/exemption/or_disabled_child_dependent_exemptions.py index 0532718b688..417eed3babe 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/credits/exemption/or_disabled_child_dependent_exemptions.py +++ b/policyengine_us/variables/gov/states/or/tax/income/credits/exemption/or_disabled_child_dependent_exemptions.py @@ -17,7 +17,7 @@ def formula(tax_unit, period, parameters): person = tax_unit.members disabled = person("is_disabled", period) # Law references IRC Section 152, which defines child dependent for EITC. - eitc_qualifying_child = person("is_child_dependent", period) + eitc_qualifying_child = person("is_qualifying_child_dependent", period) # Determine eligibility from AGI. federal_agi = tax_unit("adjusted_gross_income", period) p = parameters(period).gov.states["or"].tax.income.credits.exemption diff --git a/policyengine_us/variables/gov/states/pa/tax/income/forgiveness/pa_tax_forgiveness_rate.py b/policyengine_us/variables/gov/states/pa/tax/income/forgiveness/pa_tax_forgiveness_rate.py index df1c4b23a4c..07a08e7776d 100644 --- a/policyengine_us/variables/gov/states/pa/tax/income/forgiveness/pa_tax_forgiveness_rate.py +++ b/policyengine_us/variables/gov/states/pa/tax/income/forgiveness/pa_tax_forgiveness_rate.py @@ -13,7 +13,7 @@ class pa_tax_forgiveness_rate(Variable): def formula(tax_unit, period, parameters): eligibility_income = tax_unit("pa_eligibility_income", period) person = tax_unit.members - is_child_dependent = person("is_child_dependent", period) + is_child_dependent = person("is_qualifying_child_dependent", period) child_dependents = tax_unit.sum(is_child_dependent) # filing status affects the base, where it doubles for married claimants filing_status = tax_unit("filing_status", period) From 052e2d25bdbbcc510e8a5335a35d191916a18fe2 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Thu, 18 Dec 2025 20:03:12 -0500 Subject: [PATCH 3/5] uv.lock --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index f260826736c..570b11f67df 100644 --- a/uv.lock +++ b/uv.lock @@ -1519,7 +1519,7 @@ wheels = [ [[package]] name = "policyengine-us" -version = "1.465.3" +version = "1.465.5" source = { editable = "." } dependencies = [ { name = "microdf-python" }, From 5b5eea73d66c3af5f29cd9fd7eef9e506db45bb3 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Thu, 18 Dec 2025 20:04:59 -0500 Subject: [PATCH 4/5] lint --- .../reforms/states/ny/wftc/ny_working_families_tax_credit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/policyengine_us/reforms/states/ny/wftc/ny_working_families_tax_credit.py b/policyengine_us/reforms/states/ny/wftc/ny_working_families_tax_credit.py index d1698649667..b3d26fa49ec 100644 --- a/policyengine_us/reforms/states/ny/wftc/ny_working_families_tax_credit.py +++ b/policyengine_us/reforms/states/ny/wftc/ny_working_families_tax_credit.py @@ -475,7 +475,9 @@ class ny_exemptions_dependent(Variable): def formula(person, period, parameters): p = parameters(period).gov.contrib.states.ny.wftc.exemptions if p.in_effect: - child_dependent = person("is_qualifying_child_dependent", period) + child_dependent = person( + "is_qualifying_child_dependent", period + ) wftc_eligible_child = person("ny_wftc_eligible_child", period) return child_dependent & ~wftc_eligible_child return person("is_qualifying_child_dependent", period) From c5623f79cbb3624a08bea4f2962ca3bcdb09513f Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Thu, 18 Dec 2025 20:17:30 -0500 Subject: [PATCH 5/5] more test fixes --- .../earned_income/eitc_child_count.yaml | 10 ++++---- .../co_family_affordability_credit.yaml | 14 +++++------ .../mt_dependent_exemptions_person.yaml | 24 +++++++++---------- .../eitc/nj_childless_eitc_age_eligible.yaml | 2 +- ...r_disabled_child_dependent_exemptions.yaml | 4 ++-- .../forgiveness/pa_tax_forgiveness_rate.yaml | 10 ++++---- 6 files changed, 32 insertions(+), 32 deletions(-) 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 5b04c3664ae..2722f4cfdc7 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 @@ -6,7 +6,7 @@ is_tax_unit_head_or_spouse: true meets_eitc_identification_requirements: true person2: - is_child_dependent: true + is_qualifying_child_dependent: true meets_eitc_identification_requirements: true tax_units: tax_unit: @@ -22,10 +22,10 @@ is_tax_unit_head_or_spouse: true meets_eitc_identification_requirements: true person2: - is_child_dependent: true + is_qualifying_child_dependent: true meets_eitc_identification_requirements: true person3: - is_child_dependent: true + is_qualifying_child_dependent: true meets_eitc_identification_requirements: true tax_units: tax_unit: @@ -41,10 +41,10 @@ is_tax_unit_head_or_spouse: true meets_eitc_identification_requirements: true person2: - is_child_dependent: true + is_qualifying_child_dependent: true meets_eitc_identification_requirements: true person3: - is_child_dependent: true + is_qualifying_child_dependent: true meets_eitc_identification_requirements: false tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/family_affordability/co_family_affordability_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/family_affordability/co_family_affordability_credit.yaml index 750e4f08f6c..5d6b0e59273 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/family_affordability/co_family_affordability_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/family_affordability/co_family_affordability_credit.yaml @@ -7,7 +7,7 @@ is_tax_unit_dependent: false dependent: age: 1 - is_child_dependent: true + is_qualifying_child_dependent: true tax_units: tax_unit: members: [head, dependent] @@ -35,10 +35,10 @@ is_tax_unit_dependent: false dependent: age: 1 - is_child_dependent: true + is_qualifying_child_dependent: true dependent2: age: 15 - is_child_dependent: true + is_qualifying_child_dependent: true tax_units: tax_unit: members: [head, dependent, dependent2] @@ -66,10 +66,10 @@ is_tax_unit_dependent: false dependent: age: 1 - is_child_dependent: true + is_qualifying_child_dependent: true dependent2: age: 15 - is_child_dependent: true + is_qualifying_child_dependent: true tax_units: tax_unit: members: [head, dependent, dependent2] @@ -97,10 +97,10 @@ is_tax_unit_dependent: false dependent: age: 1 - is_child_dependent: true + is_qualifying_child_dependent: true dependent2: age: 15 - is_child_dependent: true + is_qualifying_child_dependent: true tax_units: tax_unit: members: [head, dependent, dependent2] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml index cd10b34db88..406960f7e07 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml @@ -3,13 +3,13 @@ input: people: person1: - is_child_dependent: false + is_qualifying_child_dependent: false is_disabled: false person3: - is_child_dependent: false + is_qualifying_child_dependent: false is_disabled: false person4: - is_child_dependent: true + is_qualifying_child_dependent: true is_disabled: false tax_units: tax_unit: @@ -27,10 +27,10 @@ people: person1: {} person3: - is_child_dependent: true + is_qualifying_child_dependent: true is_disabled: false person4: - is_child_dependent: true + is_qualifying_child_dependent: true is_disabled: false tax_units: tax_unit: @@ -49,10 +49,10 @@ person1: is_tax_unit_head: true person3: - is_child_dependent: true + is_qualifying_child_dependent: true is_disabled: false person4: - is_child_dependent: true + is_qualifying_child_dependent: true is_disabled: true tax_units: tax_unit: @@ -71,10 +71,10 @@ person1: is_tax_unit_head: true person3: - is_child_dependent: false + is_qualifying_child_dependent: false is_disabled: false person4: - is_child_dependent: false + is_qualifying_child_dependent: false is_disabled: true tax_units: tax_unit: @@ -91,13 +91,13 @@ input: people: person1: - is_child_dependent: false + is_qualifying_child_dependent: false is_disabled: false person3: - is_child_dependent: false + is_qualifying_child_dependent: false is_disabled: false person4: - is_child_dependent: true + is_qualifying_child_dependent: true is_disabled: false tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.yaml index 87597c30778..5759df32b3a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/eitc/nj_childless_eitc_age_eligible.yaml @@ -63,7 +63,7 @@ age: 30 person2: age: 15 - is_child_dependent: false + is_qualifying_child_dependent: false households: household: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/exemption/or_disabled_child_dependent_exemptions.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/exemption/or_disabled_child_dependent_exemptions.yaml index 9f80cc55832..11da9ff4b3a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/exemption/or_disabled_child_dependent_exemptions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/exemption/or_disabled_child_dependent_exemptions.yaml @@ -4,7 +4,7 @@ state_code: OR adjusted_gross_income: 100_000 is_disabled: true - is_child_dependent: true + is_qualifying_child_dependent: true output: or_disabled_child_dependent_exemptions: 1 @@ -14,6 +14,6 @@ state_code: OR adjusted_gross_income: 100_001 is_disabled: true - is_child_dependent: true + is_qualifying_child_dependent: true output: or_disabled_child_dependent_exemptions: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/forgiveness/pa_tax_forgiveness_rate.yaml b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/forgiveness/pa_tax_forgiveness_rate.yaml index 41a630c5d91..11feb60665d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/forgiveness/pa_tax_forgiveness_rate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/forgiveness/pa_tax_forgiveness_rate.yaml @@ -31,9 +31,9 @@ people: filer: {} grandparent: - is_child_dependent: false + is_qualifying_child_dependent: false child: - is_child_dependent: true + is_qualifying_child_dependent: true tax_units: tax_unit: members: [filer, grandparent, child] @@ -52,7 +52,7 @@ father: {} mother: {} child: - is_child_dependent: true + is_qualifying_child_dependent: true tax_units: tax_unit: members: [father, mother, child] @@ -71,9 +71,9 @@ father: {} mother: {} child 1: - is_child_dependent: true + is_qualifying_child_dependent: true child 2: - is_child_dependent: true + is_qualifying_child_dependent: true tax_units: tax_unit: members: [father, mother, child 1, child 2]