From 37608a41d856640b9fc2a97874daaff4d62092d5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 19:38:51 -0400 Subject: [PATCH 1/6] Apply the unearned income filing test only to filers who can be claimed as dependents The federal filing requirement tested every tax unit's unearned income against $500 plus the additional standard deduction. Under 26 U.S.C. 6012(a)(1)(C)(i)(I) and (f)(2)(D) that test applies only to an individual described in section 63(c)(5), and the amount is the section 63(c)(5)(A) amount for the year ($1,350 in 2025), not the unindexed $500. - Apply the test to the head, and on a joint return each spouse, who can be claimed as a dependent on another return. - Read the amount from gov.irs.deductions.standard.dependent.amount and add that person's own additional standard deduction for age or blindness. - Count gross income from every source except wages and self-employment income, so a self-employment loss or a tax unit dependent's wages no longer change the filer's unearned income. Co-Authored-By: Claude Opus 5.5 --- ...fix-filing-requirement-dependents.fixed.md | 1 + ...x_unit_is_required_to_file_dependents.yaml | 614 ++++++++++++++++++ .../gov/irs/tax_unit_is_required_to_file.py | 62 +- 3 files changed, 668 insertions(+), 9 deletions(-) create mode 100644 changelog.d/fix-filing-requirement-dependents.fixed.md create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml diff --git a/changelog.d/fix-filing-requirement-dependents.fixed.md b/changelog.d/fix-filing-requirement-dependents.fixed.md new file mode 100644 index 00000000000..f14abcc4a9d --- /dev/null +++ b/changelog.d/fix-filing-requirement-dependents.fixed.md @@ -0,0 +1 @@ +Stop testing every filer's unearned income against $500 in the federal filing requirement (26 U.S.C. 6012): the unearned income test now applies only to a filer who can be claimed as a dependent on another return, uses the section 63(c)(5)(A) amount for the year ($1,350 in 2025) plus that filer's additional standard deduction for age or blindness, and counts gross income other than wages and self-employment income, so a self-employment loss no longer counts as unearned income. diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml new file mode 100644 index 00000000000..ffcb3b3eb79 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml @@ -0,0 +1,614 @@ +# Filing requirement for a filer who can be claimed as a dependent on another +# return, and the absence of an unearned income test for everyone else. +# +# 26 U.S.C. 6012(a)(1)(C)(i) and (f), 26 U.S.C. 63(c)(5), and Chart A and +# Chart B of the Form 1040 instructions (Tables 1 and 2 of Publication 501). +# 2025 amounts (Chart B, page 10 of +# https://www.irs.gov/pub/irs-prior/i1040gi--2025.pdf): +# Single dependents, under 65 and not blind, must file if +# unearned income was over $1,350, or +# earned income was over $15,750, or +# gross income was more than the larger of $1,350 or earned income (up to +# $15,300) plus $450. +# 65 or older or blind: $3,350, $17,750, and the larger of $3,350 or earned +# income (up to $15,300) plus $2,450. 65 or older and blind: $5,350, +# $19,750, and the larger of $5,350 or earned income plus $4,450. +# Married dependents, 65 or older or blind: unearned income over $2,950. + +# Filers nobody can claim: Chart A, gross income only. + +- name: 2025, a single filer nobody can claim with $600 of interest need not file + period: 2025 + input: + filing_status: SINGLE + age: 40 + taxable_interest_income: 600 + output: + tax_unit_is_required_to_file: false + +- name: 2025, unearned income up to the standard deduction does not require a return + period: 2025 + input: + filing_status: SINGLE + age: 40 + taxable_interest_income: 15_750 + output: + # Chart A: single, under 65, $15,750. + tax_unit_is_required_to_file: false + +- name: 2025, gross income over the standard deduction requires a return + period: 2025 + input: + filing_status: SINGLE + age: 40 + taxable_interest_income: 15_751 + output: + tax_unit_is_required_to_file: true + +- name: 2025, a single filer aged 70 nobody can claim with $3,000 of interest need not file + period: 2025 + input: + filing_status: SINGLE + age: 70 + taxable_interest_income: 3_000 + output: + # Before, the test was $500 plus the $2,000 additional standard deduction. + tax_unit_is_required_to_file: false + +- name: 2025, a self-employment loss is not unearned income + period: 2025 + input: + filing_status: SINGLE + age: 40 + self_employment_income: -10_000 + output: + # Before, gross income (0) less earned income (-10,000) counted as + # $10,000 of unearned income. + tax_unit_is_required_to_file: false + +- name: 2025, the unearned income test reads the filer, not a child in the tax unit + period: 2025 + input: + people: + parent: + age: 40 + employment_income: 10_000 + claimed_as_dependent_on_another_return: true + child: + age: 10 + taxable_interest_income: 5_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: TX + output: + # The parent has no unearned income, and gross income of $10,000 is not + # more than earned income plus $450. The child's interest belongs on the + # child's own return. + tax_unit_is_required_to_file: false + +# Single dependents, under 65 and not blind. + +- name: 2025, single dependent with unearned income of $1,350 need not file + period: 2025 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_350 + output: + tax_unit_is_required_to_file: false + +- name: 2025, single dependent with unearned income over $1,350 must file + period: 2025 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_351 + output: + tax_unit_is_required_to_file: true + +- name: 2025, single dependent with earned income of $15,750 need not file + period: 2025 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 15_750 + output: + tax_unit_is_required_to_file: false + +- name: 2025, single dependent with earned income over $15,750 must file + period: 2025 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 15_751 + output: + tax_unit_is_required_to_file: true + +- name: 2025, single dependent with gross income equal to earned income plus $450 need not file + period: 2025 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 3_000 + taxable_interest_income: 450 + output: + # $3,450 is not more than $3,000 + $450. + tax_unit_is_required_to_file: false + +- name: 2025, single dependent with gross income over earned income plus $450 must file + period: 2025 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 3_000 + taxable_interest_income: 451 + output: + tax_unit_is_required_to_file: true + +- name: 2025, the earned income in the gross income test stops at $15,300 + period: 2025 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 15_500 + taxable_interest_income: 251 + output: + # $15,751 is more than $15,300 + $450, though not more than + # $15,500 + $450. + tax_unit_is_required_to_file: true + +- name: 2025, the same income does not require a return from a filer nobody can claim + period: 2025 + input: + filing_status: SINGLE + age: 19 + employment_income: 3_000 + taxable_interest_income: 1_351 + output: + tax_unit_is_required_to_file: false + +# Single dependents, 65 or older or blind. + +- name: 2025, single dependent aged 65 with unearned income of $3,350 need not file + period: 2025 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 3_350 + output: + tax_unit_is_required_to_file: false + +- name: 2025, single dependent aged 65 with unearned income over $3,350 must file + period: 2025 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 3_351 + output: + tax_unit_is_required_to_file: true + +- name: 2025, blind single dependent under 65 with unearned income of $3,350 need not file + period: 2025 + input: + filing_status: SINGLE + age: 30 + is_blind: true + claimed_as_dependent_on_another_return: true + taxable_interest_income: 3_350 + output: + tax_unit_is_required_to_file: false + +- name: 2025, blind single dependent under 65 with unearned income over $3,350 must file + period: 2025 + input: + filing_status: SINGLE + age: 30 + is_blind: true + claimed_as_dependent_on_another_return: true + taxable_interest_income: 3_351 + output: + tax_unit_is_required_to_file: true + +- name: 2025, blind single dependent aged 65 with unearned income of $5,350 need not file + period: 2025 + input: + filing_status: SINGLE + age: 65 + is_blind: true + claimed_as_dependent_on_another_return: true + taxable_interest_income: 5_350 + output: + tax_unit_is_required_to_file: false + +- name: 2025, blind single dependent aged 65 with unearned income over $5,350 must file + period: 2025 + input: + filing_status: SINGLE + age: 65 + is_blind: true + claimed_as_dependent_on_another_return: true + taxable_interest_income: 5_351 + output: + tax_unit_is_required_to_file: true + +- name: 2025, single dependent aged 65 with earned income of $17,750 need not file + period: 2025 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + employment_income: 17_750 + output: + tax_unit_is_required_to_file: false + +- name: 2025, single dependent aged 65 with earned income over $17,750 must file + period: 2025 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + employment_income: 17_751 + output: + tax_unit_is_required_to_file: true + +- name: 2025, single dependent aged 65 with gross income equal to earned income plus $2,450 need not file + period: 2025 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + employment_income: 3_000 + taxable_interest_income: 2_450 + output: + tax_unit_is_required_to_file: false + +- name: 2025, single dependent aged 65 with gross income over earned income plus $2,450 must file + period: 2025 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + employment_income: 3_000 + taxable_interest_income: 2_451 + output: + tax_unit_is_required_to_file: true + +- name: 2025, blind single dependent aged 65 with earned income of $19,750 need not file + period: 2025 + input: + filing_status: SINGLE + age: 65 + is_blind: true + claimed_as_dependent_on_another_return: true + employment_income: 19_750 + output: + tax_unit_is_required_to_file: false + +- name: 2025, blind single dependent aged 65 with earned income over $19,750 must file + period: 2025 + input: + filing_status: SINGLE + age: 65 + is_blind: true + claimed_as_dependent_on_another_return: true + employment_income: 19_751 + output: + tax_unit_is_required_to_file: true + +# Joint returns: the unearned income test applies to each spouse who can be +# claimed, with that spouse's own income and additional standard deduction. + +- name: 2025, joint return whose spouse can be claimed, unearned income of $1,350 + period: 2025 + input: + people: + head: + age: 40 + spouse: + age: 40 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_350 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + tax_unit_is_required_to_file: false + +- name: 2025, joint return whose spouse can be claimed, unearned income over $1,350 + period: 2025 + input: + people: + head: + age: 40 + spouse: + age: 40 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_351 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + tax_unit_is_required_to_file: true + +- name: 2025, the other spouse's unearned income is not the claimed spouse's + period: 2025 + input: + people: + head: + age: 40 + taxable_interest_income: 5_000 + spouse: + age: 40 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_350 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + # Gross income of $6,350 is under the $31,500 joint standard deduction. + tax_unit_is_required_to_file: false + +- name: 2025, joint return whose spouse aged 65 can be claimed, unearned income of $2,950 + period: 2025 + input: + people: + head: + age: 40 + spouse: + age: 65 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 2_950 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + # Chart B, married dependents, 65 or older or blind: $1,350 + $1,600. + tax_unit_is_required_to_file: false + +- name: 2025, joint return whose spouse aged 65 can be claimed, unearned income over $2,950 + period: 2025 + input: + people: + head: + age: 40 + spouse: + age: 65 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 2_951 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + tax_unit_is_required_to_file: true + +- name: 2025, the head's age does not raise the claimed spouse's unearned income test + period: 2025 + input: + people: + head: + age: 70 + spouse: + age: 40 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_351 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + tax_unit_is_required_to_file: true + +# Married filing separately: any gross income requires a return, as before. + +- name: 2025, a separate filer who can be claimed must file with any gross income + period: 2025 + input: + filing_status: SEPARATE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 100 + output: + tax_unit_is_required_to_file: true + +# 2024 (Chart B: $1,300, $14,600, earned income plus $450; 65 or older or +# blind $3,250): https://www.irs.gov/pub/irs-prior/i1040gi--2024.pdf#page=10 + +- name: 2024, single dependent with unearned income of $1,300 need not file + period: 2024 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_300 + output: + tax_unit_is_required_to_file: false + +- name: 2024, single dependent with unearned income over $1,300 must file + period: 2024 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_301 + output: + tax_unit_is_required_to_file: true + +- name: 2024, single dependent with earned income of $14,600 need not file + period: 2024 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 14_600 + output: + tax_unit_is_required_to_file: false + +- name: 2024, single dependent with earned income over $14,600 must file + period: 2024 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 14_601 + output: + tax_unit_is_required_to_file: true + +- name: 2024, single dependent aged 65 with unearned income of $3,250 need not file + period: 2024 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 3_250 + output: + tax_unit_is_required_to_file: false + +- name: 2024, single dependent aged 65 with unearned income over $3,250 must file + period: 2024 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 3_251 + output: + tax_unit_is_required_to_file: true + +- name: 2024, a single filer nobody can claim with $600 of interest need not file + period: 2024 + input: + filing_status: SINGLE + age: 40 + taxable_interest_income: 600 + output: + tax_unit_is_required_to_file: false + +# 2026 (Rev. Proc. 2025-32, sec. 4.14: $1,350, $450, $16,100 and $2,050): +# https://www.irs.gov/pub/irs-drop/rp-25-32.pdf#page=18 + +- name: 2026, single dependent with unearned income of $1,350 need not file + period: 2026 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_350 + output: + tax_unit_is_required_to_file: false + +- name: 2026, single dependent with unearned income over $1,350 must file + period: 2026 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 1_351 + output: + tax_unit_is_required_to_file: true + +- name: 2026, single dependent with earned income of $16,100 need not file + period: 2026 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 16_100 + output: + tax_unit_is_required_to_file: false + +- name: 2026, single dependent with earned income over $16,100 must file + period: 2026 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 16_101 + output: + tax_unit_is_required_to_file: true + +- name: 2026, single dependent with gross income equal to earned income plus $450 need not file + period: 2026 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 3_000 + taxable_interest_income: 450 + output: + tax_unit_is_required_to_file: false + +- name: 2026, single dependent with gross income over earned income plus $450 must file + period: 2026 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 3_000 + taxable_interest_income: 451 + output: + tax_unit_is_required_to_file: true + +- name: 2026, single dependent aged 65 with unearned income of $3,400 need not file + period: 2026 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 3_400 + output: + # $1,350 + $2,050. + tax_unit_is_required_to_file: false + +- name: 2026, single dependent aged 65 with unearned income over $3,400 must file + period: 2026 + input: + filing_status: SINGLE + age: 65 + claimed_as_dependent_on_another_return: true + taxable_interest_income: 3_401 + output: + tax_unit_is_required_to_file: true + +- name: 2026, a single filer nobody can claim with $600 of interest need not file + period: 2026 + input: + filing_status: SINGLE + age: 40 + taxable_interest_income: 600 + output: + tax_unit_is_required_to_file: false diff --git a/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py b/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py index 5c38c142427..71106879388 100644 --- a/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py +++ b/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py @@ -13,6 +13,12 @@ class tax_unit_is_required_to_file(Variable): definition_period = YEAR reference = ( "https://www.law.cornell.edu/uscode/text/26/6012", + # A filer who can be claimed as a dependent: section 6012(a)(1)(C)(i) + # and (f)(2)(D), the section 63(c)(5) limited standard deduction, and + # Publication 501, Table 2 (Chart B of the Form 1040 instructions). + "https://www.law.cornell.edu/uscode/text/26/6012#a_1_C", + "https://www.law.cornell.edu/uscode/text/26/63#c_5", + "https://www.irs.gov/pub/irs-prior/p501--2025.pdf#page=4", # Gross income counts amounts excluded under section 911. "https://www.law.cornell.edu/uscode/text/26/6012#c", ) @@ -39,16 +45,54 @@ def formula(tax_unit, period, parameters): income_over_exemption_amount = gross_income > threshold - # (a)(1)(C) - unearned income threshold for dependents - unearned_income_threshold = 500 + tax_unit( - "additional_standard_deduction", period + # A filer who can be claimed as a dependent on another return (an + # individual described in section 63(c)(5)) must also file when their + # income other than earned income is over the section 63(c)(5)(A) + # amount plus their own additional standard deduction for age or + # blindness: section 6012(a)(1)(C)(i)(I) and Publication 501, Table 2. + # On a joint return the test applies to each spouse who can be claimed + # (section 6012(f)(2)(D)). Nobody else has an unearned income test. + # + # Table 2's other two tests need no code here. The standard deduction + # in the threshold above is already the limited one for these filers + # (basic_standard_deduction): the greater of the section 63(c)(5)(A) + # amount or earned income plus the section 63(c)(5)(B) amount, capped + # at the regular amount, plus the additional standard deduction. That + # is Table 2's gross income test, and earned income over the regular + # amount plus the additional standard deduction always meets it. + person = tax_unit.members + head_can_be_claimed = tax_unit("head_is_dependent_elsewhere", period) + spouse_can_be_claimed = tax_unit("spouse_is_dependent_elsewhere", period) + can_be_claimed = ( + person("is_tax_unit_head", period) & tax_unit.project(head_can_be_claimed) + ) | ( + person("is_tax_unit_spouse", period) + & tax_unit.project(spouse_can_be_claimed) ) - # The section 911 exclusion covers earned income only. - unearned_income = ( - gross_income - - add(tax_unit, period, ["earned_income"]) - - section_911_exclusion + # Income other than earned income: every gross income source except + # wages and self-employment income, the items in earned_income. As in + # irs_gross_income, a loss in one source does not reduce the total. + earned_income_sources = [ + "irs_employment_income", + "self_employment_income", + "sstb_self_employment_income", + ] + unearned_income = 0 + for source in parameters(period).gov.irs.gross_income.sources: + if source not in earned_income_sources: + unearned_income += max_(0, add(person, period, [source])) + p_standard = parameters(period).gov.irs.deductions.standard + aged_or_blind_count = person("is_irs_aged", period).astype(int) + person( + "is_blind", period + ).astype(int) + additional_standard_deduction = aged_or_blind_count * tax_unit.project( + p_standard.aged_or_blind.amount[tax_unit("filing_status", period)] + ) + unearned_income_threshold = ( + p_standard.dependent.amount + additional_standard_deduction + ) + unearned_income_over_threshold = tax_unit.any( + can_be_claimed & (unearned_income > unearned_income_threshold) ) - unearned_income_over_threshold = unearned_income > unearned_income_threshold return income_over_exemption_amount | unearned_income_over_threshold From cd87bf88f50f426b2a6f88eb0f6fb19f4172720c Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 20:11:45 -0400 Subject: [PATCH 2/6] Add invariant tests and boundary cases for the dependent filing requirement - Hypothesis properties: split invariance for filers nobody can claim, monotonicity in income, claimability never removing the requirement, and tax unit dependents' income never mattering. - A boundary grid checked against Publication 501 (2025), Table 2, computed directly from its published amounts, and against medicaid_person_is_required_to_file, the other Table 2 implementation. - Keep the YAML cases a dollar away from the two thresholds where the statute and the IRS charts differ at the exact dollar, and say in the formula that the joint return test follows Table 2. Co-Authored-By: Claude Opus 5.5 --- ...filing_requirement_dependent_invariants.py | 470 ++++++++++++++++++ ...x_unit_is_required_to_file_dependents.yaml | 33 +- .../gov/irs/tax_unit_is_required_to_file.py | 7 +- 3 files changed, 503 insertions(+), 7 deletions(-) create mode 100644 policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py diff --git a/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py new file mode 100644 index 00000000000..79866be1b85 --- /dev/null +++ b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py @@ -0,0 +1,470 @@ +"""Invariants for the filing requirement of a filer who can be claimed. + +`tax_unit_is_required_to_file` gives a filer who can be claimed as a dependent +on another return the three tests of Publication 501 (2025), Table 2 (Chart B +of the Form 1040 instructions; 26 USC 6012(a)(1)(C)(i) and (f), 63(c)(5)). It +codes only the unearned income test. The earned income and gross income tests +come from comparing gross income with `standard_deduction`, which is already +the limited section 63(c)(5) deduction for these filers. Nobody else has an +unearned income test. YAML cases pin the published amounts one case at a time; +these tests check what must hold for every input, which YAML cannot state. + +`test_table_2_boundary_grid` runs one simulation of single filers on a grid of +earned and unearned income at every Table 2 threshold, a dollar under and a +dollar over, for each combination of age 65 and blindness: + +1. Differential: for a filer who can be claimed, the result equals Table 2's + three tests computed directly from its published 2025 amounts. This is the + check that the two tests with no code of their own are still applied. +2. Differential: it also equals `medicaid_person_is_required_to_file`, the + other Table 2 implementation, which serves Medicaid and ACA household + income for dependents inside a tax unit. +3. For a filer nobody can claim, the result depends on gross income alone: it + is the same however that income is split between wages and interest. +4. On a joint return with no other income, where one spouse can be claimed, + the result is Table 2's unearned income test for a married dependent, with + that spouse's own age and blindness and not the other spouse's. + +`test_filing_requirement_properties` draws batches of tax units with +Hypothesis (single, head of household, surviving spouse, separate and joint; +any mix of age, blindness, claimability, wages, self-employment income or +loss, interest and dividends) and runs each batch, with four variants of every +unit, as one simulation for 2025 and 2026: + +5. Split invariance: moving a filer's wages into interest, or interest into + wages, never changes the result for a tax unit whose head and spouse cannot + be claimed. +6. Monotonicity: more wages, interest or self-employment profit never turns + "required" into "not required", for any tax unit. +7. Claimability never removes the requirement: marking the head as claimable + never turns "required" into "not required" (filers who are not blind; see + below). +8. A tax unit dependent's wages and interest never change the result: that + income belongs on the dependent's own return. +9. Table 2 and the Medicaid implementation, as in 1 and 2, on the drawn + single filers who can be claimed and have no self-employment income. +10. Lower bound: on a single, separate or joint return, a head or spouse who + can be claimed and whose interest and dividends are over Table 2's + unearned income amount for them always makes the return required. + +Intended exceptions, each from the law rather than the code: + +- Monotonicity does not hold for a smaller self-employment loss. A loss does + not reduce gross income ("don't reduce your income by any losses", Form 1040 + instructions, Chart A) but it does reduce the earned income that sets the + limited standard deduction, so a smaller loss raises the threshold and not + the income. `test_smaller_loss_can_end_the_requirement` pins the case. +- Property 7 leaves out blind filers. The model counts the additional + standard deduction for blindness in every filer's threshold. Chart A does + not (only age), while Table 2 does, so on the charts a blind filer under 65 + with wages between the two amounts must file only if nobody can claim them. + The property holds in the model today, and leaving blind filers out keeps it + true under either reading. +- Properties 1, 2 and 9 use wages as the only earned income. Earned income for + the limited deduction is net of the deductible part of self-employment tax + on the IRS worksheet, which Table 2 does not spell out. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +YEARS = (2025, 2026) + +# Publication 501 (2025), Table 2, single dependents: +# https://www.irs.gov/pub/irs-prior/p501--2025.pdf#page=4 +TABLE_2_FLOOR = 1_350 +TABLE_2_EARNED_INCOME_ADDITION = 450 +TABLE_2_EARNED_INCOME_LIMIT = 15_750 +TABLE_2_AGED_OR_BLIND_ADDITION = 2_000 +# The same table, married dependents: $2,950 less $1,350. +TABLE_2_MARRIED_AGED_OR_BLIND_ADDITION = 1_600 + +ADULT_INCOME_INPUTS = [ + "employment_income", + "self_employment_income", + "taxable_interest_income", + "qualified_dividend_income", +] +VARIANTS = ["base", "split", "raised", "claimed", "with_child"] + + +def table_2_single_dependent(earned, unearned, aged, blind): + """Table 2's three tests for a single dependent, from its 2025 amounts.""" + additional = TABLE_2_AGED_OR_BLIND_ADDITION * (aged.astype(int) + blind.astype(int)) + gross = earned + unearned + unearned_test = unearned > TABLE_2_FLOOR + additional + earned_test = earned > TABLE_2_EARNED_INCOME_LIMIT + additional + gross_test = gross > np.maximum( + TABLE_2_FLOOR + additional, + np.minimum(earned, TABLE_2_EARNED_INCOME_LIMIT - TABLE_2_EARNED_INCOME_ADDITION) + + TABLE_2_EARNED_INCOME_ADDITION + + additional, + ) + return unearned_test | earned_test | gross_test + + +def simulate(units): + """One simulation of independent tax units. + + Each unit is {"filing_status": name, "adults": [...], "children": [...]}, + with one dict of person inputs per member. Every unit states its filing + status: an input given to one tax unit is an input for all of them, so a + unit left without one would take the default, SINGLE, not the status the + model would work out. + """ + people, tax_units, households = {}, {}, {} + groups = {"marital_units": {}, "spm_units": {}, "families": {}} + for index, unit in enumerate(units): + adults = [f"adult_{index}_{n}" for n in range(len(unit["adults"]))] + children = [f"child_{index}_{n}" for n in range(len(unit["children"]))] + members = adults + children + for name, inputs in zip(members, unit["adults"] + unit["children"]): + people[name] = { + variable: {year: value for year in YEARS} + for variable, value in inputs.items() + } + tax_units[f"tax_unit_{index}"] = { + "members": members, + "filing_status": {year: unit["filing_status"] for year in YEARS}, + } + households[f"household_{index}"] = { + "members": members, + "state_code": {year: "TX" for year in YEARS}, + } + for name in ("spm_units", "families"): + groups[name][f"{name}_{index}"] = {"members": members} + groups["marital_units"][f"marital_unit_{index}"] = {"members": adults} + for child in children: + groups["marital_units"][f"marital_unit_{child}"] = {"members": [child]} + return Simulation( + situation={ + "people": people, + "tax_units": tax_units, + "households": households, + **groups, + } + ) + + +def single_filer(age, blind, claimed, wages, interest): + return { + "filing_status": "SINGLE", + "adults": [ + { + "age": age, + "is_blind": blind, + "claimed_as_dependent_on_another_return": claimed, + "employment_income": wages, + "taxable_interest_income": interest, + } + ], + "children": [], + } + + +def head_values(units, name): + return np.array([unit["adults"][0].get(name, 0) for unit in units]) + + +def head_medicaid_requirement(simulation, units, year): + """`medicaid_person_is_required_to_file` for each unit's head.""" + values = simulation.calculate("medicaid_person_is_required_to_file", year) + first = np.cumsum([0] + [len(u["adults"]) + len(u["children"]) for u in units]) + return np.asarray(values)[first[:-1]] + + +def unearned_test_met(units): + """Whether Table 2's unearned income test is met by a head or spouse who + can be claimed, for single, separate and joint returns (2025 amounts).""" + met = [] + for unit in units: + married = unit["filing_status"] in ("SEPARATE", "JOINT") + per_condition = ( + TABLE_2_MARRIED_AGED_OR_BLIND_ADDITION + if married + else TABLE_2_AGED_OR_BLIND_ADDITION + ) + met.append( + unit["filing_status"] in ("SINGLE", "SEPARATE", "JOINT") + and any( + adult["claimed_as_dependent_on_another_return"] + and adult["taxable_interest_income"] + + adult.get("qualified_dividend_income", 0) + > TABLE_2_FLOOR + + per_condition * ((adult["age"] >= 65) + adult["is_blind"]) + for adult in unit["adults"] + ) + ) + return np.array(met) + + +def assert_table_2(simulation, units, required, rows, year): + """Properties 1 and 2 (and 9) on the given rows of single claimable filers.""" + if not rows.any(): + return + np.testing.assert_array_equal( + required[rows], + head_medicaid_requirement(simulation, units, year)[rows], + err_msg=f"{year}: differs from medicaid_person_is_required_to_file", + ) + if year != 2025: + return + expected = table_2_single_dependent( + earned=head_values(units, "employment_income"), + unearned=head_values(units, "taxable_interest_income") + + head_values(units, "qualified_dividend_income"), + aged=head_values(units, "age") >= 65, + blind=head_values(units, "is_blind").astype(bool), + ) + np.testing.assert_array_equal( + required[rows], expected[rows], err_msg="2025: differs from Table 2" + ) + + +# Every Table 2 threshold for a single dependent, a dollar under and over. +EARNED_GRID = [0, 449, 899, 900, 901, 3_000] +EARNED_GRID += [15_299, 15_300, 15_301, 15_749, 15_750, 15_751] +EARNED_GRID += [17_749, 17_750, 17_751, 19_749, 19_750, 19_751] +UNEARNED_GRID = [0, 1, 449, 450, 451, 1_349, 1_350, 1_351] +UNEARNED_GRID += [2_449, 2_450, 2_451, 3_349, 3_350, 3_351] +UNEARNED_GRID += [4_449, 4_450, 4_451, 5_349, 5_350, 5_351] +GROSS_GRID = [600, 1_351, 5_351, 15_749, 15_751, 17_751, 19_751, 21_751, 40_000] + + +def test_table_2_boundary_grid(): + claimable = [ + single_filer(age, blind, True, wages, interest) + for age in (19, 65) + for blind in (False, True) + for wages in EARNED_GRID + for interest in UNEARNED_GRID + ] + # Four ways to hold the same gross income, for a filer nobody can claim. + splits = [ + (age, blind, gross, wages) + for age in (40, 65) + for blind in (False, True) + for gross in GROSS_GRID + for wages in (gross, 0, gross // 2, gross - 600) + ] + unclaimable = [ + single_filer(age, blind, False, wages, gross - wages) + for age, blind, gross, wages in splits + ] + # Joint returns whose younger spouse can be claimed and has only interest. + joint = [ + { + "filing_status": "JOINT", + "adults": [ + { + "age": head_age, + "is_blind": head_blind, + "claimed_as_dependent_on_another_return": False, + "taxable_interest_income": 0, + }, + { + "age": spouse_age, + "is_blind": spouse_blind, + "claimed_as_dependent_on_another_return": True, + "taxable_interest_income": interest, + }, + ], + "children": [], + } + for head_age, spouse_age in ((45, 40), (70, 40), (70, 65), (80, 65)) + for head_blind in (False, True) + for spouse_blind in (False, True) + for interest in UNEARNED_GRID + ] + units = claimable + unclaimable + joint + simulation = simulate(units) + is_claimable = np.arange(len(units)) < len(claimable) + is_joint = np.arange(len(units)) >= len(claimable) + len(unclaimable) + is_unclaimable = ~is_claimable & ~is_joint + for year in YEARS: + required = np.asarray( + simulation.calculate("tax_unit_is_required_to_file", year) + ) + if year == 2025: + # Property 4. + np.testing.assert_array_equal( + required[is_joint], unearned_test_met(units)[is_joint] + ) + # Properties 1 and 2. + assert_table_2(simulation, units, required, is_claimable, year) + # Property 3: one answer for each gross income, whatever the split. + by_split = required[is_unclaimable].reshape(-1, 4) + assert (by_split == by_split[:, :1]).all(), year + # Both answers occur, so the property is not vacuous. + assert by_split.any() and not by_split.all(), year + # The grid reaches both answers of each Table 2 test. + assert required[is_claimable].any() and not required[is_claimable].all() + + +boundary = st.sampled_from(sorted(set(EARNED_GRID + UNEARNED_GRID + [31_499, 31_501]))) +amount = st.one_of(st.just(0), boundary, st.integers(0, 45_000)) +loss = st.integers(-20_000, -1) + + +@st.composite +def adults(draw): + return { + "age": draw(st.sampled_from([19, 40, 64, 65, 80])), + "is_blind": draw(st.booleans()), + "claimed_as_dependent_on_another_return": draw(st.booleans()), + "employment_income": draw(amount), + "self_employment_income": draw(st.one_of(st.just(0), amount, loss)), + "taxable_interest_income": draw(amount), + "qualified_dividend_income": draw(st.one_of(st.just(0), amount)), + } + + +@st.composite +def unit_variants(draw): + """A drawn tax unit and its four variants, in VARIANTS order.""" + status = draw( + st.sampled_from( + ["SINGLE", "HEAD_OF_HOUSEHOLD", "SURVIVING_SPOUSE", "SEPARATE", "JOINT"] + ) + ) + base = { + "filing_status": status, + "adults": [draw(adults()) for _ in range(2 if status == "JOINT" else 1)], + "children": [], + } + + def variant(change): + copy = {**base, "adults": [dict(adult) for adult in base["adults"]]} + change(copy) + return copy + + who = draw(st.integers(0, len(base["adults"]) - 1)) + + # Property 5: move part of one filer's wages into interest, or back. + moved = draw(st.integers(1, 20_000)) + to_interest = draw(st.booleans()) + + def split(unit): + adult = unit["adults"][who] + source, target = "employment_income", "taxable_interest_income" + if not to_interest: + source, target = target, source + shift = min(moved, adult[source]) + adult[source] -= shift + adult[target] += shift + + # Property 6: more of one nonnegative source. A smaller loss is the + # intended exception, so a loss is left alone. + raised_source = draw(st.sampled_from(ADULT_INCOME_INPUTS)) + increase = draw(st.integers(1, 20_000)) + + def raise_income(unit): + adult = unit["adults"][who] + source = raised_source + if adult[source] < 0: + source = "taxable_interest_income" + adult[source] += increase + + # Property 7. + def claim_head(unit): + # The older adult is the head; mark both when the ages tie. + oldest = max(adult["age"] for adult in unit["adults"]) + for adult in unit["adults"]: + if adult["age"] == oldest: + adult["claimed_as_dependent_on_another_return"] = True + + # Property 8. + child = { + "age": draw(st.integers(0, 17)), + "employment_income": draw(amount), + "taxable_interest_income": draw(amount), + } + + def add_child(unit): + unit["children"] = [child] + + return [ + base, + variant(split), + variant(raise_income), + variant(claim_head), + variant(add_child), + ] + + +@settings( + max_examples=6, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.lists(unit_variants(), min_size=8, max_size=24)) +def test_filing_requirement_properties(drawn): + units = [unit for variants in drawn for unit in variants] + simulation = simulate(units) + anyone_claimable = np.array( + [ + any(a["claimed_as_dependent_on_another_return"] for a in unit["adults"]) + for unit in units + ] + ).reshape(-1, len(VARIANTS)) + anyone_blind = np.array( + [any(adult["is_blind"] for adult in unit["adults"]) for unit in units] + ).reshape(-1, len(VARIANTS)) + # Properties 1, 2 and 9 apply to a single filer who can be claimed and has + # wages as their only earned income. + table_2_rows = np.array( + [ + unit["filing_status"] == "SINGLE" + and unit["adults"][0]["claimed_as_dependent_on_another_return"] + and unit["adults"][0]["self_employment_income"] == 0 + for unit in units + ] + ) + for year in YEARS: + flat = np.asarray(simulation.calculate("tax_unit_is_required_to_file", year)) + required = dict(zip(VARIANTS, flat.reshape(-1, len(VARIANTS)).T)) + nobody_claimable = ~anyone_claimable[:, 0] + # Property 5. + np.testing.assert_array_equal( + required["split"][nobody_claimable], + required["base"][nobody_claimable], + err_msg=f"{year}: the earned/unearned split changed the result", + ) + # Property 6. + assert not (required["base"] & ~required["raised"]).any(), ( + f"{year}: more income ended the requirement" + ) + # Property 7. + sighted = ~anyone_blind[:, 0] + assert not (required["base"] & ~required["claimed"])[sighted].any(), ( + f"{year}: being claimable ended the requirement" + ) + # Property 8. + np.testing.assert_array_equal( + required["with_child"], + required["base"], + err_msg=f"{year}: a tax unit dependent's income changed the result", + ) + # Property 9. + assert_table_2(simulation, units, flat, table_2_rows, year) + # Property 10. + if year == 2025: + assert flat[unearned_test_met(units)].all(), ( + "2025: a claimable filer over the unearned income amount need not file" + ) + + +def test_smaller_loss_can_end_the_requirement(): + # Intended exception to monotonicity. With $2,000 of wages and a $5,000 + # loss, earned income is negative, the limited standard deduction is the + # $1,350 floor, and $2,000 of gross income is over it. With a $100 loss, + # earned income is $1,900 and the limited deduction is $2,350. + units = [single_filer(19, False, True, 2_000, 0) for _ in range(2)] + units[0]["adults"][0]["self_employment_income"] = -5_000 + units[1]["adults"][0]["self_employment_income"] = -100 + required = simulate(units).calculate("tax_unit_is_required_to_file", 2025) + assert required.tolist() == [True, False] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml index ffcb3b3eb79..7e76d514895 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml @@ -3,6 +3,8 @@ # # 26 U.S.C. 6012(a)(1)(C)(i) and (f), 26 U.S.C. 63(c)(5), and Chart A and # Chart B of the Form 1040 instructions (Tables 1 and 2 of Publication 501). +# Section 6012(f) governs 2018 to 2025 and section 6012(a)(1) governs 2026. +# For an unmarried filer who can be claimed, both give Chart B's result. # 2025 amounts (Chart B, page 10 of # https://www.irs.gov/pub/irs-prior/i1040gi--2025.pdf): # Single dependents, under 65 and not blind, must file if @@ -15,7 +17,10 @@ # $19,750, and the larger of $5,350 or earned income plus $4,450. # Married dependents, 65 or older or blind: unearned income over $2,950. -# Filers nobody can claim: Chart A, gross income only. +# Filers nobody can claim: gross income only. The cases stay a dollar away +# from the threshold itself, where Chart A ("at least") and section 6012(f)(1) +# ("does not exceed the standard deduction") differ; this change does not +# touch that comparison. - name: 2025, a single filer nobody can claim with $600 of interest need not file period: 2025 @@ -26,12 +31,12 @@ output: tax_unit_is_required_to_file: false -- name: 2025, unearned income up to the standard deduction does not require a return +- name: 2025, unearned income under the standard deduction does not require a return period: 2025 input: filing_status: SINGLE age: 40 - taxable_interest_income: 15_750 + taxable_interest_income: 15_749 output: # Chart A: single, under 65, $15,750. tax_unit_is_required_to_file: false @@ -178,6 +183,19 @@ output: tax_unit_is_required_to_file: false +- name: 2025, a self-employment loss does not add to a single dependent's unearned income + period: 2025 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + self_employment_income: -10_000 + taxable_interest_income: 1_000 + output: + # Unearned income is the $1,000 of interest, not gross income less the + # negative earned income ($11,000). + tax_unit_is_required_to_file: false + # Single dependents, 65 or older or blind. - name: 2025, single dependent aged 65 with unearned income of $3,350 need not file @@ -520,6 +538,11 @@ # 2026 (Rev. Proc. 2025-32, sec. 4.14: $1,350, $450, $16,100 and $2,050): # https://www.irs.gov/pub/irs-drop/rp-25-32.pdf#page=18 +# Section 6012(f) ends with 2025, so section 6012(a)(1) applies. Its unearned +# income and gross income tests for these filers say "in excess of", as Chart +# B does. Its earned income threshold comes from (a)(1)(A)(i), which says +# "less than", and the 2026 instructions are not yet published, so the earned +# income cases stay a dollar away from $16,100. - name: 2026, single dependent with unearned income of $1,350 need not file period: 2026 @@ -541,13 +564,13 @@ output: tax_unit_is_required_to_file: true -- name: 2026, single dependent with earned income of $16,100 need not file +- name: 2026, single dependent with earned income under $16,100 need not file period: 2026 input: filing_status: SINGLE age: 19 claimed_as_dependent_on_another_return: true - employment_income: 16_100 + employment_income: 16_099 output: tax_unit_is_required_to_file: false diff --git a/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py b/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py index 71106879388..c3c9f45210e 100644 --- a/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py +++ b/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py @@ -50,8 +50,11 @@ def formula(tax_unit, period, parameters): # income other than earned income is over the section 63(c)(5)(A) # amount plus their own additional standard deduction for age or # blindness: section 6012(a)(1)(C)(i)(I) and Publication 501, Table 2. - # On a joint return the test applies to each spouse who can be claimed - # (section 6012(f)(2)(D)). Nobody else has an unearned income test. + # On a joint return the test applies to each spouse who can be claimed. + # Section 6012(f)(2)(D), which governs joint returns for 2018 to 2025, + # names the section 63(c)(5)(A) amount alone; Table 2 adds the + # additional standard deduction, and this follows Table 2. Nobody else + # has an unearned income test. # # Table 2's other two tests need no code here. The standard deduction # in the threshold above is already the limited one for these filers From a9ed8bdd0e3078353522281fb3033e4e64820d46 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 21:05:39 -0400 Subject: [PATCH 3/6] Keep the joint return attribution case valid under the limited joint deduction When a spouse can be claimed, the Standard Deduction Worksheet for Dependents limits the joint standard deduction, which PR #10024 adopts. The case now isolates the per-spouse unearned income test under either deduction. Co-Authored-By: Claude Opus 5.5 --- .../irs/tax_unit_is_required_to_file_dependents.yaml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml index 7e76d514895..7102389df46 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml @@ -374,12 +374,12 @@ input: people: head: - age: 40 - taxable_interest_income: 5_000 + age: 70 + taxable_interest_income: 1_500 spouse: age: 40 claimed_as_dependent_on_another_return: true - taxable_interest_income: 1_350 + taxable_interest_income: 1_000 tax_units: tax_unit: members: [head, spouse] @@ -388,7 +388,11 @@ members: [head, spouse] state_code: TX output: - # Gross income of $6,350 is under the $31,500 joint standard deduction. + # The claimed spouse's unearned income is $1,000, not over $1,350. The + # couple's $2,500 would be. Gross income of $2,500 is also under the + # joint standard deduction, whether that is the regular $31,500 plus + # $1,600 or the limited $1,350 plus $1,600 that the Standard Deduction + # Worksheet for Dependents gives when a spouse can be claimed. tax_unit_is_required_to_file: false - name: 2025, joint return whose spouse aged 65 can be claimed, unearned income of $2,950 From 1a5dd0586af9bf6d8a1fe461b4a6827c4f028a1d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 21:09:19 -0400 Subject: [PATCH 4/6] Keep the 2026 dependent cases off the exact-dollar boundary Section 6012(a)(1)(A) excuses a filer only when gross income is less than the basic standard deduction, while Chart B says more than; the 2026 instructions are not yet out. Co-Authored-By: Claude Opus 5.5 --- ...x_unit_is_required_to_file_dependents.yaml | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml index 7102389df46..3affc163f9c 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml @@ -543,18 +543,20 @@ # 2026 (Rev. Proc. 2025-32, sec. 4.14: $1,350, $450, $16,100 and $2,050): # https://www.irs.gov/pub/irs-drop/rp-25-32.pdf#page=18 # Section 6012(f) ends with 2025, so section 6012(a)(1) applies. Its unearned -# income and gross income tests for these filers say "in excess of", as Chart -# B does. Its earned income threshold comes from (a)(1)(A)(i), which says -# "less than", and the 2026 instructions are not yet published, so the earned -# income cases stay a dollar away from $16,100. - -- name: 2026, single dependent with unearned income of $1,350 need not file +# income and gross income tests for these filers, in (a)(1)(C)(i), say "in +# excess of", as Chart B does. But a filer who meets neither is excused only +# by (a)(1)(A)(i), whose gross income must be "less than" the basic standard +# deduction, so on the literal text a return is due at exactly the limited +# deduction. The 2026 instructions are not yet published, so every 2026 case +# stays a dollar away from a threshold. + +- name: 2026, single dependent with unearned income under $1,350 need not file period: 2026 input: filing_status: SINGLE age: 19 claimed_as_dependent_on_another_return: true - taxable_interest_income: 1_350 + taxable_interest_income: 1_349 output: tax_unit_is_required_to_file: false @@ -588,14 +590,14 @@ output: tax_unit_is_required_to_file: true -- name: 2026, single dependent with gross income equal to earned income plus $450 need not file +- name: 2026, single dependent with gross income under earned income plus $450 need not file period: 2026 input: filing_status: SINGLE age: 19 claimed_as_dependent_on_another_return: true employment_income: 3_000 - taxable_interest_income: 450 + taxable_interest_income: 449 output: tax_unit_is_required_to_file: false @@ -610,13 +612,13 @@ output: tax_unit_is_required_to_file: true -- name: 2026, single dependent aged 65 with unearned income of $3,400 need not file +- name: 2026, single dependent aged 65 with unearned income under $3,400 need not file period: 2026 input: filing_status: SINGLE age: 65 claimed_as_dependent_on_another_return: true - taxable_interest_income: 3_400 + taxable_interest_income: 3_399 output: # $1,350 + $2,050. tax_unit_is_required_to_file: false From a9e58977ffbbb30f667afe523270e5f71bf6f4c1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 23:19:52 -0400 Subject: [PATCH 5/6] Address review round 1: earned sources parameter, open points kept out of tests - Move the earned income sources of the unearned income test into gov.irs.income.filing_requirement.dependent.earned_income_sources, citing the IRS dependent worksheet, and count Schedule F farm income as earned. A test checks that every listed source is a gross income source. - Stop pinning decision d1395: drop the 2025 case of a claimed spouse aged 65 at exactly $2,950 and the case of a claimable filer filing separately, limit the joint grid to claimed spouses under 65 and not blind, and compare with medicaid_person_is_required_to_file only in 2025. - Start every Hypothesis batch with a unit that must file and one that need not, and assert each property has rows to check. - Drop the duplicate section 6012 reference entry. Co-Authored-By: Claude Opus 5.5 --- ...fix-filing-requirement-dependents.fixed.md | 2 +- .../dependent/earned_income_sources.yaml | 24 ++++ ...filing_requirement_dependent_invariants.py | 126 +++++++++++++----- ...x_unit_is_required_to_file_dependents.yaml | 44 ++---- .../gov/irs/tax_unit_is_required_to_file.py | 23 ++-- 5 files changed, 134 insertions(+), 85 deletions(-) create mode 100644 policyengine_us/parameters/gov/irs/income/filing_requirement/dependent/earned_income_sources.yaml diff --git a/changelog.d/fix-filing-requirement-dependents.fixed.md b/changelog.d/fix-filing-requirement-dependents.fixed.md index f14abcc4a9d..48846578166 100644 --- a/changelog.d/fix-filing-requirement-dependents.fixed.md +++ b/changelog.d/fix-filing-requirement-dependents.fixed.md @@ -1 +1 @@ -Stop testing every filer's unearned income against $500 in the federal filing requirement (26 U.S.C. 6012): the unearned income test now applies only to a filer who can be claimed as a dependent on another return, uses the section 63(c)(5)(A) amount for the year ($1,350 in 2025) plus that filer's additional standard deduction for age or blindness, and counts gross income other than wages and self-employment income, so a self-employment loss no longer counts as unearned income. +Stop testing every filer's unearned income against $500 in the federal filing requirement (26 U.S.C. 6012): the unearned income test now applies only to a head or spouse who can be claimed as a dependent on another return, uses the section 63(c)(5)(A) amount for the year ($1,350 in 2025) plus that person's own additional standard deduction for age or blindness, and counts that person's gross income other than wages, business income and farm income (the earned income of the IRS dependent worksheet), so a self-employment loss no longer counts as unearned income. diff --git a/policyengine_us/parameters/gov/irs/income/filing_requirement/dependent/earned_income_sources.yaml b/policyengine_us/parameters/gov/irs/income/filing_requirement/dependent/earned_income_sources.yaml new file mode 100644 index 00000000000..22410a45918 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/income/filing_requirement/dependent/earned_income_sources.yaml @@ -0,0 +1,24 @@ +description: The Internal Revenue Service counts these gross income sources as earned income when it tests the unearned income of a filer who can be claimed as a dependent; every other gross income source is unearned income. +values: + 2010-01-01: + # Form 1040 line 1z (wages). + - irs_employment_income + # Schedule 1 line 3 (business income, Schedule C). + - self_employment_income + - sstb_self_employment_income + # Schedule 1 line 6 (farm income, Schedule F). + - farm_operations_income +metadata: + unit: list + period: year + label: IRS earned income sources for the dependent unearned income filing test + reference: + - title: 26 U.S. Code ยง 6012(a)(1)(C)(i)(I) and (f)(2)(D) - income other than earned income + href: https://www.law.cornell.edu/uscode/text/26/6012 + # "Generally, your earned income is the total of the amount(s) you + # reported on Form 1040 or 1040-SR, line 1z, and Schedule 1, lines 3, 6, + # 8r, 8t, and 8u minus the amount, if any, on Schedule 1, line 15." + # Schedule 1 line 5 (rents, royalties, partnerships, S corporations and + # trusts) is not on the list. + - title: 2025 Form 1040 instructions, Standard Deduction Worksheet for Dependents + href: https://www.irs.gov/pub/irs-prior/i1040gi--2025.pdf#page=35 diff --git a/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py index 79866be1b85..406fbfa2f48 100644 --- a/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py +++ b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py @@ -9,6 +9,11 @@ unearned income test. YAML cases pin the published amounts one case at a time; these tests check what must hold for every input, which YAML cannot state. +Where the statute and the IRS charts differ (decision d1395: the age and +blindness addition in the 2018-2025 joint test, a dependent filing +separately, and in 2026 blindness and the exact dollar), the tests below +assert nothing that depends on the difference. + `test_table_2_boundary_grid` runs one simulation of single filers on a grid of earned and unearned income at every Table 2 threshold, a dollar under and a dollar over, for each combination of age 65 and blindness: @@ -16,20 +21,26 @@ 1. Differential: for a filer who can be claimed, the result equals Table 2's three tests computed directly from its published 2025 amounts. This is the check that the two tests with no code of their own are still applied. -2. Differential: it also equals `medicaid_person_is_required_to_file`, the - other Table 2 implementation, which serves Medicaid and ACA household - income for dependents inside a tax unit. +2. Differential: in 2025 it also equals `medicaid_person_is_required_to_file`, + the other Table 2 implementation, which serves Medicaid and ACA household + income for dependents inside a tax unit. In 2026 the rule is section + 6012(a)(1), whose treatment of blindness and the exact dollar is open, so + the two are not compared there. 3. For a filer nobody can claim, the result depends on gross income alone: it is the same however that income is split between wages and interest. -4. On a joint return with no other income, where one spouse can be claimed, - the result is Table 2's unearned income test for a married dependent, with - that spouse's own age and blindness and not the other spouse's. +4. On a 2025 joint return with no other income, where the spouse who can be + claimed is under 65 and not blind, the result is the unearned income test + (over $1,350), whatever the other spouse's age and blindness. (For a + claimed spouse who is 65 or older or blind, section 6012(f)(2)(D) and + Table 2 differ; that is d1395.) `test_filing_requirement_properties` draws batches of tax units with Hypothesis (single, head of household, surviving spouse, separate and joint; any mix of age, blindness, claimability, wages, self-employment income or loss, interest and dividends) and runs each batch, with four variants of every -unit, as one simulation for 2025 and 2026: +unit, as one simulation for 2025 and 2026. Every batch starts with two fixed +units, one that must file and one that need not, so that each property below +has rows to check and both answers occur; the test asserts that it does. 5. Split invariance: moving a filer's wages into interest, or interest into wages, never changes the result for a tax unit whose head and spouse cannot @@ -43,9 +54,17 @@ income belongs on the dependent's own return. 9. Table 2 and the Medicaid implementation, as in 1 and 2, on the drawn single filers who can be claimed and have no self-employment income. -10. Lower bound: on a single, separate or joint return, a head or spouse who - can be claimed and whose interest and dividends are over Table 2's - unearned income amount for them always makes the return required. +10. Lower bound (2025): on a single, separate or joint return, a head or + spouse who can be claimed and whose interest and dividends are over + Table 2's unearned income amount for them always makes the return + required. Table 2's amount is never below the statute's, so this holds + under either reading of d1395. + +`test_earned_income_sources_are_gross_income_sources`: + +11. Every earned income source of the unearned income test is a gross income + source. Otherwise a renamed earned source would silently count as + unearned income. Intended exceptions, each from the law rather than the code: @@ -70,6 +89,7 @@ from hypothesis import strategies as st from policyengine_us import Simulation +from policyengine_us.system import system as SYSTEM YEARS = (2025, 2026) @@ -202,16 +222,16 @@ def unearned_test_met(units): def assert_table_2(simulation, units, required, rows, year): - """Properties 1 and 2 (and 9) on the given rows of single claimable filers.""" - if not rows.any(): + """Properties 1 and 2 (and 9) on the given rows of single claimable filers, + in 2025.""" + assert rows.any(), "no single claimable filer to compare" + if year != 2025: return np.testing.assert_array_equal( required[rows], head_medicaid_requirement(simulation, units, year)[rows], err_msg=f"{year}: differs from medicaid_person_is_required_to_file", ) - if year != 2025: - return expected = table_2_single_dependent( earned=head_values(units, "employment_income"), unearned=head_values(units, "taxable_interest_income") @@ -254,7 +274,8 @@ def test_table_2_boundary_grid(): single_filer(age, blind, False, wages, gross - wages) for age, blind, gross, wages in splits ] - # Joint returns whose younger spouse can be claimed and has only interest. + # Joint returns whose younger spouse, under 65 and not blind, can be + # claimed and has only interest. joint = [ { "filing_status": "JOINT", @@ -266,17 +287,16 @@ def test_table_2_boundary_grid(): "taxable_interest_income": 0, }, { - "age": spouse_age, - "is_blind": spouse_blind, + "age": 40, + "is_blind": False, "claimed_as_dependent_on_another_return": True, "taxable_interest_income": interest, }, ], "children": [], } - for head_age, spouse_age in ((45, 40), (70, 40), (70, 65), (80, 65)) + for head_age in (45, 70, 80) for head_blind in (False, True) - for spouse_blind in (False, True) for interest in UNEARNED_GRID ] units = claimable + unclaimable + joint @@ -290,9 +310,9 @@ def test_table_2_boundary_grid(): ) if year == 2025: # Property 4. - np.testing.assert_array_equal( - required[is_joint], unearned_test_met(units)[is_joint] - ) + expected = unearned_test_met(units)[is_joint] + np.testing.assert_array_equal(required[is_joint], expected) + assert expected.any() and not expected.all() # Properties 1 and 2. assert_table_2(simulation, units, required, is_claimable, year) # Property 3: one answer for each gross income, whatever the split. @@ -322,19 +342,33 @@ def adults(draw): } +# Fixed units at the start of every batch: a single filer who can be claimed +# with $5,000 of interest (must file; meets every property's row filter) and a +# single filer nobody can claim with $600 of interest (need not file). +ANCHORS = [ + single_filer(19, False, True, 0, 5_000), + single_filer(40, False, False, 0, 600), +] +for anchor in ANCHORS: + anchor["adults"][0].update(self_employment_income=0, qualified_dividend_income=0) + + @st.composite -def unit_variants(draw): - """A drawn tax unit and its four variants, in VARIANTS order.""" - status = draw( - st.sampled_from( - ["SINGLE", "HEAD_OF_HOUSEHOLD", "SURVIVING_SPOUSE", "SEPARATE", "JOINT"] +def unit_variants(draw, fixed=None): + """A tax unit (drawn, or `fixed`) and its four variants, in VARIANTS order.""" + if fixed is None: + status = draw( + st.sampled_from( + ["SINGLE", "HEAD_OF_HOUSEHOLD", "SURVIVING_SPOUSE", "SEPARATE", "JOINT"] + ) ) - ) - base = { - "filing_status": status, - "adults": [draw(adults()) for _ in range(2 if status == "JOINT" else 1)], - "children": [], - } + base = { + "filing_status": status, + "adults": [draw(adults()) for _ in range(2 if status == "JOINT" else 1)], + "children": [], + } + else: + base = {**fixed, "adults": [dict(adult) for adult in fixed["adults"]]} def variant(change): copy = {**base, "adults": [dict(adult) for adult in base["adults"]]} @@ -401,9 +435,12 @@ def add_child(unit): derandomize=True, suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], ) -@given(st.lists(unit_variants(), min_size=8, max_size=24)) -def test_filing_requirement_properties(drawn): - units = [unit for variants in drawn for unit in variants] +@given( + st.tuples(*(unit_variants(fixed=anchor) for anchor in ANCHORS)), + st.lists(unit_variants(), min_size=6, max_size=22), +) +def test_filing_requirement_properties(anchors, drawn): + units = [unit for variants in [*anchors, *drawn] for unit in variants] simulation = simulate(units) anyone_claimable = np.array( [ @@ -434,10 +471,12 @@ def test_filing_requirement_properties(drawn): required["base"][nobody_claimable], err_msg=f"{year}: the earned/unearned split changed the result", ) + assert nobody_claimable.any() # Property 6. assert not (required["base"] & ~required["raised"]).any(), ( f"{year}: more income ended the requirement" ) + assert required["base"].any() and not required["base"].all() # Property 7. sighted = ~anyone_blind[:, 0] assert not (required["base"] & ~required["claimed"])[sighted].any(), ( @@ -453,11 +492,24 @@ def test_filing_requirement_properties(drawn): assert_table_2(simulation, units, flat, table_2_rows, year) # Property 10. if year == 2025: - assert flat[unearned_test_met(units)].all(), ( + met = unearned_test_met(units) + assert met.any() + assert flat[met].all(), ( "2025: a claimable filer over the unearned income amount need not file" ) +def test_earned_income_sources_are_gross_income_sources(): + # Property 11. + for year in YEARS: + parameters = SYSTEM.parameters(f"{year}-01-01").gov.irs + earned = list( + parameters.income.filing_requirement.dependent.earned_income_sources + ) + gross = list(parameters.gross_income.sources) + assert earned and set(earned) <= set(gross), set(earned) - set(gross) + + def test_smaller_loss_can_end_the_requirement(): # Intended exception to monotonicity. With $2,000 of wages and a $5,000 # loss, earned income is negative, the limited standard deduction is the diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml index 3affc163f9c..f2afb20aed4 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml @@ -4,7 +4,11 @@ # 26 U.S.C. 6012(a)(1)(C)(i) and (f), 26 U.S.C. 63(c)(5), and Chart A and # Chart B of the Form 1040 instructions (Tables 1 and 2 of Publication 501). # Section 6012(f) governs 2018 to 2025 and section 6012(a)(1) governs 2026. -# For an unmarried filer who can be claimed, both give Chart B's result. +# For an unmarried filer who can be claimed, section 6012(f)(1) gives Chart +# B's result. Where the statute and the charts differ (decision d1395: the +# age and blindness addition in the 2018-2025 joint test, a dependent filing +# separately, and in 2026 blindness and the exact dollar), the cases stay +# clear of the difference. # 2025 amounts (Chart B, page 10 of # https://www.irs.gov/pub/irs-prior/i1040gi--2025.pdf): # Single dependents, under 65 and not blind, must file if @@ -15,7 +19,8 @@ # 65 or older or blind: $3,350, $17,750, and the larger of $3,350 or earned # income (up to $15,300) plus $2,450. 65 or older and blind: $5,350, # $19,750, and the larger of $5,350 or earned income plus $4,450. -# Married dependents, 65 or older or blind: unearned income over $2,950. +# Married dependents, 65 or older or blind: unearned income over $2,950 +# (section 6012(f)(2)(D): over $1,350, with no addition). # Filers nobody can claim: gross income only. The cases stay a dollar away # from the threshold itself, where Chart A ("at least") and section 6012(f)(1) @@ -395,28 +400,9 @@ # Worksheet for Dependents gives when a spouse can be claimed. tax_unit_is_required_to_file: false -- name: 2025, joint return whose spouse aged 65 can be claimed, unearned income of $2,950 - period: 2025 - input: - people: - head: - age: 40 - spouse: - age: 65 - claimed_as_dependent_on_another_return: true - taxable_interest_income: 2_950 - tax_units: - tax_unit: - members: [head, spouse] - households: - household: - members: [head, spouse] - state_code: TX - output: - # Chart B, married dependents, 65 or older or blind: $1,350 + $1,600. - tax_unit_is_required_to_file: false - - name: 2025, joint return whose spouse aged 65 can be claimed, unearned income over $2,950 + # Required under both readings: over $1,350 + $1,600 (Chart B) and over + # $1,350 (section 6012(f)(2)(D)). At $2,950 or less they differ (d1395). period: 2025 input: people: @@ -456,18 +442,6 @@ output: tax_unit_is_required_to_file: true -# Married filing separately: any gross income requires a return, as before. - -- name: 2025, a separate filer who can be claimed must file with any gross income - period: 2025 - input: - filing_status: SEPARATE - age: 19 - claimed_as_dependent_on_another_return: true - employment_income: 100 - output: - tax_unit_is_required_to_file: true - # 2024 (Chart B: $1,300, $14,600, earned income plus $450; 65 or older or # blind $3,250): https://www.irs.gov/pub/irs-prior/i1040gi--2024.pdf#page=10 diff --git a/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py b/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py index c3c9f45210e..512f2c32b26 100644 --- a/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py +++ b/policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py @@ -14,9 +14,9 @@ class tax_unit_is_required_to_file(Variable): reference = ( "https://www.law.cornell.edu/uscode/text/26/6012", # A filer who can be claimed as a dependent: section 6012(a)(1)(C)(i) - # and (f)(2)(D), the section 63(c)(5) limited standard deduction, and - # Publication 501, Table 2 (Chart B of the Form 1040 instructions). - "https://www.law.cornell.edu/uscode/text/26/6012#a_1_C", + # and (f)(2)(D) (the section above), the section 63(c)(5) limited + # standard deduction, and Publication 501, Table 2 (Chart B of the + # Form 1040 instructions). "https://www.law.cornell.edu/uscode/text/26/63#c_5", "https://www.irs.gov/pub/irs-prior/p501--2025.pdf#page=4", # Gross income counts amounts excluded under section 911. @@ -53,8 +53,8 @@ def formula(tax_unit, period, parameters): # On a joint return the test applies to each spouse who can be claimed. # Section 6012(f)(2)(D), which governs joint returns for 2018 to 2025, # names the section 63(c)(5)(A) amount alone; Table 2 adds the - # additional standard deduction, and this follows Table 2. Nobody else - # has an unearned income test. + # additional standard deduction, and this follows Table 2 (open as + # decision d1395). Nobody else has an unearned income test. # # Table 2's other two tests need no code here. The standard deduction # in the threshold above is already the limited one for these filers @@ -73,13 +73,12 @@ def formula(tax_unit, period, parameters): & tax_unit.project(spouse_can_be_claimed) ) # Income other than earned income: every gross income source except - # wages and self-employment income, the items in earned_income. As in - # irs_gross_income, a loss in one source does not reduce the total. - earned_income_sources = [ - "irs_employment_income", - "self_employment_income", - "sstb_self_employment_income", - ] + # the earned income sources of the IRS dependent worksheet (wages, + # business income and farm income). As in irs_gross_income, a loss in + # one source does not reduce the total. + earned_income_sources = parameters( + period + ).gov.irs.income.filing_requirement.dependent.earned_income_sources unearned_income = 0 for source in parameters(period).gov.irs.gross_income.sources: if source not in earned_income_sources: From 00570b48b7168d91d20278fe6aa2fbd18e800885 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 12 Oct 2026 06:30:01 -0400 Subject: [PATCH 6/6] Address review round 2: keep separate filers out of the claimability invariant, test farm income - Property 7 (claimability never removes a requirement) now leaves out separate filers as well as blind ones: Table 2 and the statute differ for a claimable married dependent filing separately (decision d1395). - Add a 2025 joint case where a claimed spouse's $2,000 of farm profit is earned income; dropping farm income from the earned sources fails it. - Correct the docstring's claim about what the anchor units guarantee. Co-Authored-By: Claude Opus 5.5 --- ...filing_requirement_dependent_invariants.py | 16 ++++++++---- ...x_unit_is_required_to_file_dependents.yaml | 26 +++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py index 406fbfa2f48..ddb9c56bba1 100644 --- a/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py +++ b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py @@ -40,7 +40,7 @@ loss, interest and dividends) and runs each batch, with four variants of every unit, as one simulation for 2025 and 2026. Every batch starts with two fixed units, one that must file and one that need not, so that each property below -has rows to check and both answers occur; the test asserts that it does. +has rows to check and the batch holds both answers; the test asserts both. 5. Split invariance: moving a filer's wages into interest, or interest into wages, never changes the result for a tax unit whose head and spouse cannot @@ -48,8 +48,8 @@ 6. Monotonicity: more wages, interest or self-employment profit never turns "required" into "not required", for any tax unit. 7. Claimability never removes the requirement: marking the head as claimable - never turns "required" into "not required" (filers who are not blind; see - below). + never turns "required" into "not required" (filers who are not blind and + do not file separately; see below). 8. A tax unit dependent's wages and interest never change the result: that income belongs on the dependent's own return. 9. Table 2 and the Medicaid implementation, as in 1 and 2, on the drawn @@ -78,7 +78,10 @@ not (only age), while Table 2 does, so on the charts a blind filer under 65 with wages between the two amounts must file only if nobody can claim them. The property holds in the model today, and leaving blind filers out keeps it - true under either reading. + true under either reading. It also leaves out separate filers: Table 2 lets + a claimable married dependent filing separately skip a return unless the + spouse itemizes, while sections 6012(f) and (a)(1) require one with any + gross income (decision d1395). - Properties 1, 2 and 9 use wages as the only earned income. Earned income for the limited deduction is net of the deductible part of self-employment tax on the IRS worksheet, which Table 2 does not spell out. @@ -448,6 +451,9 @@ def test_filing_requirement_properties(anchors, drawn): for unit in units ] ).reshape(-1, len(VARIANTS)) + separate = np.array( + [unit["filing_status"] == "SEPARATE" for unit in units] + ).reshape(-1, len(VARIANTS)) anyone_blind = np.array( [any(adult["is_blind"] for adult in unit["adults"]) for unit in units] ).reshape(-1, len(VARIANTS)) @@ -478,7 +484,7 @@ def test_filing_requirement_properties(anchors, drawn): ) assert required["base"].any() and not required["base"].all() # Property 7. - sighted = ~anyone_blind[:, 0] + sighted = ~anyone_blind[:, 0] & ~separate[:, 0] assert not (required["base"] & ~required["claimed"])[sighted].any(), ( f"{year}: being claimable ended the requirement" ) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml index f2afb20aed4..17aff4458bf 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file_dependents.yaml @@ -422,6 +422,32 @@ output: tax_unit_is_required_to_file: true +- name: 2025, a claimed spouse's farm profit is earned income, not unearned income + # Farm income (Schedule 1 line 6) is earned income on the IRS dependent + # worksheet, so the unearned income test does not apply to it. Gross income + # of $2,000 is under the joint standard deduction, whether the regular + # $31,500 plus $1,600 or the limited $1,350 plus $1,600. (Section 6017 + # separately requires a return with $400 or more of net self-employment + # earnings; tax_unit_is_required_to_file covers section 6012 only.) + period: 2025 + input: + people: + head: + age: 70 + spouse: + age: 40 + claimed_as_dependent_on_another_return: true + farm_operations_income: 2_000 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + tax_unit_is_required_to_file: false + - name: 2025, the head's age does not raise the claimed spouse's unearned income test period: 2025 input: