From d9b504aff3395d29c2b78d17249bd4f271833bd9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 08:31:30 +0000 Subject: [PATCH 1/8] Keep ABLE contributions in the saver's credit and apply the 2027 rules Pub. L. 119-21 preserves ABLE contributions by the designated beneficiary in the section 25B credit, which main dropped from 2026. From 2027 the credit covers ABLE contributions only, with a $2,100 cap, and 25B(d)(2) reduces them by relevant ABLE distributions rather than retirement-plan distributions; a new person input records those. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6K4grkKNkfjkW6hbaN --- .../statute-bugs-savers-filing-2026.fixed.md | 1 + .../retirement_saving/contributions_cap.yaml | 3 ++ .../retirement_saving/distributions.yaml | 17 ++++++++++ ...fied_retirement_savings_contributions.yaml | 8 +++++ .../savers_credit_person.yaml | 33 ++++++++++++++++++ ...savers_credit_qualified_contributions.yaml | 34 +++++++++++++++++++ .../savers_credit_qualified_contributions.py | 9 +++-- .../retirement/able_contributions_person.py | 6 +++- ...vers_credit_relevant_able_distributions.py | 21 ++++++++++++ 9 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 changelog.d/statute-bugs-savers-filing-2026.fixed.md create mode 100644 policyengine_us/parameters/gov/irs/credits/retirement_saving/distributions.yaml create mode 100644 policyengine_us/variables/household/expense/retirement/savers_credit_relevant_able_distributions.py diff --git a/changelog.d/statute-bugs-savers-filing-2026.fixed.md b/changelog.d/statute-bugs-savers-filing-2026.fixed.md new file mode 100644 index 00000000000..5cb0a381c9f --- /dev/null +++ b/changelog.d/statute-bugs-savers-filing-2026.fixed.md @@ -0,0 +1 @@ +Keep ABLE contributions in the saver's credit for 2026, and from 2027 count only ABLE contributions, reduced by relevant ABLE distributions, under a $2,100 cap (Pub. L. 119-21). diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/contributions_cap.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/contributions_cap.yaml index 8060fe6644b..923c7853998 100644 --- a/policyengine_us/parameters/gov/irs/credits/retirement_saving/contributions_cap.yaml +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/contributions_cap.yaml @@ -1,6 +1,7 @@ description: The IRS caps the qualifying contributions at the following amount under the saver's credit. values: 2018-01-01: 2_000 + 2027-01-01: 2_100 metadata: unit: currency-USD period: year @@ -8,6 +9,8 @@ metadata: reference: - title: 26 U.S. Code § 25B(a) href: https://www.law.cornell.edu/uscode/text/26/25B + - title: Public Law 119-21, saver's credit contribution cap from 2027 + href: https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=97 - title: IRS | Retirement Savings Contributions Credit (Saver's Credit) href: https://www.irs.gov/retirement-plans/plan-participant-employee/retirement-savings-contributions-savers-credit - title: Publication 590-A diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/distributions.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/distributions.yaml new file mode 100644 index 00000000000..e3c92f5c730 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/distributions.yaml @@ -0,0 +1,17 @@ +description: The IRS subtracts these distributions from qualified contributions under the saver's credit. +values: + 2018-01-01: + - retirement_distributions + # From 2027 the reduction uses relevant distributions from the + # beneficiary's ABLE account, not retirement-plan distributions. + 2027-01-01: + - savers_credit_relevant_able_distributions +metadata: + unit: list + period: year + label: Saver's Credit distributions that reduce qualified contributions + reference: + - title: 26 U.S. Code § 25B(d)(2) + href: https://www.law.cornell.edu/uscode/text/26/25B#d_2 + - title: Public Law 119-21, ABLE contributions in the saver's credit + href: https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=97 diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/qualified_retirement_savings_contributions.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/qualified_retirement_savings_contributions.yaml index f7ea26c772b..70d9e8d2835 100644 --- a/policyengine_us/parameters/gov/irs/credits/retirement_saving/qualified_retirement_savings_contributions.yaml +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/qualified_retirement_savings_contributions.yaml @@ -10,6 +10,8 @@ values: - self_employed_pension_contributions - able_contributions_person + # Pub. L. 119-21 keeps ABLE contributions by the designated beneficiary + # after 2025. 2026-01-01: - traditional_ira_contributions - roth_ira_contributions @@ -18,6 +20,10 @@ values: - roth_401k_contributions - roth_403b_contributions - self_employed_pension_contributions + - able_contributions_person + # From 2027 the section 25B credit covers ABLE contributions only. + 2027-01-01: + - able_contributions_person metadata: unit: list @@ -26,6 +32,8 @@ metadata: reference: - title: 26 U.S. Code § 25B - (d) href: https://www.law.cornell.edu/uscode/text/26/25B#d_1 + - title: Public Law 119-21, ABLE contributions in the saver's credit + href: https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=97 - title: 2023 Publication 590-A href: https://www.irs.gov/pub/irs-pdf/p590a.pdf#page=45 - title: 2023 Form 8880 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_person.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_person.yaml index 11d92ee3b0d..1be355321c8 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_person.yaml @@ -317,3 +317,36 @@ # 48,000 on the joint schedule (divided by 0.5), so the rate is 20%, # not the 50% that 22,000 (44,000) would give. savers_credit_person: 400 + +# Pub. L. 119-21 raises the contribution cap to $2,100 from 2027. +- name: 2027 cap of $2,100 on ABLE contributions + period: 2027 + input: + people: + person1: + savers_credit_eligible_person: true + able_contributions_person: 3_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + adjusted_gross_income: 10_000 + output: + # 50% x min(3,000, 2,100) = 1,050. + savers_credit_person: 1_050 + +- name: 2026 cap stays $2,000 + period: 2026 + input: + people: + person1: + savers_credit_eligible_person: true + able_contributions_person: 3_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + adjusted_gross_income: 10_000 + output: + # 50% x min(3,000, 2,000) = 1,000. + savers_credit_person: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yaml index 672916a2ac5..804dde9a498 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yaml @@ -41,3 +41,37 @@ output: self_employed_pension_contributions: 72_000 savers_credit_qualified_contributions: 72_000 + +# Pub. L. 119-21 keeps ABLE contributions by the designated beneficiary in +# the saver's credit after 2025. +- name: ABLE contributions still count in 2026 + period: 2026 + input: + ira_compensation: 10_000 + traditional_ira_contributions_desired: 1_000 + able_contributions_person: 500 + output: + savers_credit_qualified_contributions: 1_500 + +# From 2027 the credit covers ABLE contributions only, reduced by relevant +# ABLE distributions rather than retirement-plan distributions. +- name: From 2027 only ABLE contributions count + period: 2027 + input: + ira_compensation: 10_000 + traditional_ira_contributions_desired: 1_000 + traditional_401k_contributions_desired: 3_000 + able_contributions_person: 800 + retirement_distributions: 5_000 + output: + # 800 of ABLE contributions; IRA and 401(k) contributions no longer + # count, and retirement-plan distributions don't reduce them. + savers_credit_qualified_contributions: 800 + +- name: From 2027 relevant ABLE distributions reduce ABLE contributions + period: 2027 + input: + able_contributions_person: 800 + savers_credit_relevant_able_distributions: 300 + output: + savers_credit_qualified_contributions: 500 diff --git a/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_qualified_contributions.py b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_qualified_contributions.py index f620c9fb1e2..a32a91d2b65 100644 --- a/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_qualified_contributions.py +++ b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_qualified_contributions.py @@ -7,12 +7,17 @@ class savers_credit_qualified_contributions(Variable): label = "Retirement Savings Credit qualified contributions" unit = USD definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/25B#d_2" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/25B#d_2", + "https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=97", + ) def formula(person, period, parameters): p = parameters(period).gov.irs.credits.retirement_saving contributions = add( person, period, p.qualified_retirement_savings_contributions ) - distributions = add(person, period, ["retirement_distributions"]) + # 25B(d)(2): from 2027, relevant ABLE distributions rather than + # retirement-plan distributions. + distributions = add(person, period, p.distributions) return max_(contributions - distributions, 0) diff --git a/policyengine_us/variables/household/expense/retirement/able_contributions_person.py b/policyengine_us/variables/household/expense/retirement/able_contributions_person.py index a30e0f04167..0a6955b2609 100644 --- a/policyengine_us/variables/household/expense/retirement/able_contributions_person.py +++ b/policyengine_us/variables/household/expense/retirement/able_contributions_person.py @@ -5,6 +5,10 @@ class able_contributions_person(Variable): value_type = float entity = Person label = "Person-level ABLE contributions" - documentation = "Contributions made to an ABLE account by each individual." + documentation = ( + "Contributions each person made, as the designated beneficiary, to " + "their own ABLE account. The saver's credit counts only these." + ) + reference = "https://www.law.cornell.edu/uscode/text/26/25B#d_1" unit = USD definition_period = YEAR diff --git a/policyengine_us/variables/household/expense/retirement/savers_credit_relevant_able_distributions.py b/policyengine_us/variables/household/expense/retirement/savers_credit_relevant_able_distributions.py new file mode 100644 index 00000000000..c8596ba18a8 --- /dev/null +++ b/policyengine_us/variables/household/expense/retirement/savers_credit_relevant_able_distributions.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class savers_credit_relevant_able_distributions(Variable): + value_type = float + entity = Person + label = "ABLE distributions that reduce saver's credit contributions" + documentation = ( + "Distributions from the person's ABLE account that reduce their " + "qualified contributions for the saver's credit from 2027 under 26 " + "U.S.C. 25B(d)(2): those received in the statutory testing period, " + "after the statute's exceptions and including any amount the spouse " + "rule attributes to the person. Distributions from retirement plans " + "and IRAs do not belong here." + ) + unit = USD + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/25B#d_2", + "https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=97", + ) From b99a7e813cbf0114711bf496d0616ea5cf1fb736 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 09:16:58 +0000 Subject: [PATCH 2/8] Apply the 6012(a)(1)(A) and (B) threshold to every 2026 filer From 2026 section 6012(a)(1)(A) excuses a return only when gross income is less than the exemption amount plus the basic standard deduction. With a zero exemption amount, a married individual filing separately has no exception, so even $0 of gross income requires a return; main kept a positive-income gate. Subparagraph (C) withdraws the (A) exception from a filer who can be claimed in further cases; it does not replace (A), so a claimable filer meets the (A) threshold too, with the section 63(c)(5) limited basic deduction and the age-only addition. Tests and invariant references that endorsed the zero-income exemption or the dependent bypass are updated. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6K4grkKNkfjkW6hbaN --- .../core/test_disability_filing_invariants.py | 8 ++- ...filing_requirement_dependent_invariants.py | 70 ++++++++----------- .../test_filing_requirement_invariants.py | 41 ++++++++--- .../gov/irs/tax_unit_is_required_to_file.yaml | 37 +++++----- ...x_unit_is_required_to_file_dependents.yaml | 17 ++++- .../gov/irs/tax_unit_is_required_to_file.py | 40 ++++++----- 6 files changed, 122 insertions(+), 91 deletions(-) diff --git a/policyengine_us/tests/core/test_disability_filing_invariants.py b/policyengine_us/tests/core/test_disability_filing_invariants.py index cc2617c1442..a98d1960241 100644 --- a/policyengine_us/tests/core/test_disability_filing_invariants.py +++ b/policyengine_us/tests/core/test_disability_filing_invariants.py @@ -22,7 +22,8 @@ - 2026 amounts: Rev. Proc. 2025-32, section 4.14: https://www.irs.gov/pub/irs-drop/rp-25-32.pdf#page=18 - IRC 6012(f) for 2024-2025, and 6012(a)(1) for 2026. Claimable filers - retain the dependent worksheet in every year (decision d1395): + keep the dependent worksheet in every year, and from 2026 also meet the + (A) and (B) threshold with the worksheet's limited basic deduction: https://www.law.cornell.edu/uscode/text/26/6012 The federal deduction worksheet and Medicaid Table 2 reference use W-2 box 1 @@ -223,8 +224,13 @@ def reference(person, year): # A claimed dependent keeps the section 63(c)(5) worksheet and Table 2's # unearned-income test. Otherwise 6012(f) includes blindness through 2025, # and 6012(a)(1) in 2026 permits the age addition only, with >= at equality. + # From 2026 that (A) and (B) threshold binds a claimed dependent too, with + # the limited basic deduction; (C) adds to it. + aged_threshold = basic + addition * int(person["age"] >= 65) if claimed: required = gross > standard or unearned > floor + extra + if year >= 2026: + required = required or gross >= aged_threshold elif year <= 2025: required = gross > standard else: 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 9b895cdd293..74e74dee57a 100644 --- a/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py +++ b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py @@ -9,10 +9,13 @@ 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. +Where the statute and the IRS charts differ for 2018-2025 (the age and +blindness addition in the joint test, a dependent filing separately), the +tests below assert nothing that depends on the difference. From 2026 the +model follows section 6012(a)(1): its (A) and (B) threshold, with the limited +basic standard deduction and the addition for age only, binds a filer who +can be claimed too, and (C)(i) adds that filer's unearned income and +standard deduction tests. `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 @@ -48,8 +51,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 and - do not file separately; see below). + never turns "required" into "not required" (every filer from 2026; for + 2025, 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 @@ -73,22 +76,14 @@ 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. Chart A does not count blindness (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. From - 2026 the model follows Chart A here: section 6012(a)(1)(B) raises the - threshold of a filer nobody can claim for age only, while a filer who can - be claimed keeps the standard deduction test (decision d1395). -- Property 7 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). -- From 2026, property 7 also fails at one dollar: gross income equal to the - threshold of a sighted filer nobody can claim. Section 6012(a)(1)(A) - excuses that filer only if gross income is "less than" the threshold, while - a filer who can be claimed is tested with "in excess of" (d1395). The - property skips those units, and - `test_claimability_can_end_the_requirement_at_the_threshold` pins the case. +- For 2025, property 7 leaves out blind filers and separate filers. Table 2 + adds blindness to a claimable filer's threshold, while Chart A doesn't for + a filer nobody can claim, and Table 2 lets a claimable married dependent + filing separately skip a return unless the spouse itemizes. From 2026, + property 7 checks every unit: the (A) threshold, which a claimable filer + also meets, counts age only, requires a return at equality, and reaches + any separate filer. `test_claimability_does_not_end_the_requirement_at_the_threshold` + pins the equality case. - 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. @@ -498,18 +493,13 @@ def test_filing_requirement_properties(anchors, drawn): f"{year}: more income ended the requirement" ) assert required["base"].any() and not required["base"].all() - # Property 7, except at the 2026 threshold (see the docstring). - sighted = ~anyone_blind[:, 0] & ~separate[:, 0] - ended = required["base"] & ~required["claimed"] + # Property 7; for 2025, sighted filers who don't file separately + # (see the docstring). + checked = ~anyone_blind[:, 0] & ~separate[:, 0] if year >= 2026: - gross_income = unit_totals( - simulation, units, "irs_gross_income", year - ).reshape(-1, len(VARIANTS))[:, 0] - standard_deduction = np.asarray( - simulation.calculate("standard_deduction", year) - ).reshape(-1, len(VARIANTS))[:, 0] - ended &= ~(nobody_claimable & (gross_income == standard_deduction)) - assert not ended[sighted].any(), ( + checked = np.ones_like(checked) + ended = required["base"] & ~required["claimed"] + assert not ended[checked].any(), ( f"{year}: being claimable ended the requirement" ) # Property 8. @@ -552,12 +542,12 @@ def test_smaller_loss_can_end_the_requirement(): assert required.tolist() == [True, False] -def test_claimability_can_end_the_requirement_at_the_threshold(): - # Intended exception to property 7 from 2026. Nobody can claim the first - # filer, whose $16,100 of wages equals the threshold of section - # 6012(a)(1)(A)(i), so a return is due. The second filer can be claimed; - # $16,100 does not exceed their standard deduction of $16,100 - # ((a)(1)(C)(i)(II) and Chart B), so no return is due (decision d1395). +def test_claimability_does_not_end_the_requirement_at_the_threshold(): + # From 2026, $16,100 of wages equals the section 6012(a)(1)(A)(i) + # threshold, so a return is due whether or not the filer can be claimed: + # (a)(1)(C) adds to (A) for a claimable filer, whose limited basic + # standard deduction is also $16,100 here; it doesn't replace (A) with + # the "in excess of" standard deduction test. units = [single_filer(40, False, claimed, 16_100, 0) for claimed in (0, 1)] required = simulate(units).calculate("tax_unit_is_required_to_file", 2026) - assert required.tolist() == [True, False] + assert required.tolist() == [True, True] diff --git a/policyengine_us/tests/core/test_filing_requirement_invariants.py b/policyengine_us/tests/core/test_filing_requirement_invariants.py index 6c54dc4ff9c..45c5b8f91a6 100644 --- a/policyengine_us/tests/core/test_filing_requirement_invariants.py +++ b/policyengine_us/tests/core/test_filing_requirement_invariants.py @@ -15,8 +15,9 @@ - 6012(f)(1) and (2)(A): a return is required when gross income exceeds the standard deduction, which adds the additional standard deduction for age and for blindness. -- Under both, a married individual filing separately must file with any - gross income. +- A married individual filing separately must file with any gross income + under 6012(f), and with any gross income, even none, under 6012(a)(1), + whose (A) excuses only gross income "less than" the zero exemption amount. `test_threshold_grid` runs one simulation over every filing status, every combination of age 65 and blindness for the head and spouse, and gross income @@ -41,13 +42,16 @@ deduction for a reason that has nothing to do with age. 6. Inclusion: in 2026, the model never excuses a unit nobody can claim that 6012(f), with the 2026 amounts, would require to file. -7. Claimable units keep one rule in both years: for a unit whose head or - spouse can be claimed, a return is required exactly when gross income is - over the unit's standard deduction (any gross income on a separate - return), or a claimable adult's interest and dividends are over the - section 63(c)(5)(A) amount ($1,350 in 2025 and 2026) plus that adult's - own additions for age and blindness. How 6012(a)(1) applies to these - filers is decision d1395, so the change leaves them alone. +7. Claimable units: for a unit whose head or spouse can be claimed, a + return is required exactly when gross income is over the unit's standard + deduction (6012(f) and (a)(1)(C)(i)(II); for 2025, any gross income on a + separate return), or a claimable adult's interest and dividends are over + the section 63(c)(5)(A) amount ($1,350 in 2025 and 2026) plus that + adult's own additions for age and blindness ((C)(i)(I)). From 2026 the + (A) and (B) threshold also binds them: gross income at least the unit's + (limited) basic standard deduction plus the age-only additions, or any + separate return. (C) adds to (A) for these filers; it does not replace + it. The tests use the default tax-benefit system only. A reform that kept 6012(f) for 2026 would compare the two rules directly, but it costs a second @@ -132,7 +136,8 @@ def reference(unit, year, rule): income = gross_income(unit) limit = threshold(unit, year, rule) if limit is None: - return income > 0 + # (a)(1)(A): no gross income is "less than" a zero exemption amount. + return True if rule == "6012(a)(1)" else income > 0 if rule == "6012(a)(1)": return income >= limit return income > limit @@ -391,6 +396,17 @@ def results(variable, year): ) # Property 7. standard_deduction = results("standard_deduction", year)["base"] + basic_deduction = results("basic_standard_deduction", year)["base"] + + def section_a(unit, basic): + if year < 2026: + return False + if unit["filing_status"] == "SEPARATE": + return True + aged = sum(adult["age"] >= 65 for adult in unit["adults"]) + addition = ADDITIONAL[year][unit["filing_status"]] + return gross_income(unit) >= basic + aged * addition + expected_claimable = np.array( [ ( @@ -399,7 +415,10 @@ def results(variable, year): else gross_income(unit) > deduction ) or claimable_unearned_test(unit, year) - for unit, deduction in zip(by_variant["base"], standard_deduction) + or section_a(unit, basic) + for unit, deduction, basic in zip( + by_variant["base"], standard_deduction, basic_deduction + ) ] ) np.testing.assert_array_equal( diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml index f441bda36ee..54c311738bc 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml @@ -280,11 +280,10 @@ filing_status: SEPARATE age: 40 output: - # With a zero exemption amount, (a)(1)(A) read literally reaches even - # $0 of gross income; the IRS set $5 for 2018 to 2025. The model, as - # before, requires a return only for positive gross income (decision - # d1403). - tax_unit_is_required_to_file: false + # With a zero exemption amount, (a)(1)(A) requires a return unless gross + # income is "less than" zero, and a married individual filing separately + # has no other exception, so even $0 of gross income meets it. + tax_unit_is_required_to_file: true - name: Case 20, 2025, single filer under 65 with gross income of exactly $15,750. period: 2025 @@ -390,14 +389,14 @@ output: tax_unit_is_required_to_file: false -# A tax unit whose head or spouse can be claimed as a dependent keeps the -# standard deduction test from 2026 too: (a)(1)(C)(i)(II), "total gross income -# in excess of the standard deduction", which is what Chart B applies. -# Whether the (A) threshold, which leaves out blindness and requires a return -# at equality, also binds these filers is decision d1395. These cases pin the -# model's reading until it is ruled. +# From 2026 a filer who can be claimed as a dependent meets the same (A) and +# (B) threshold as anyone else, with the section 63(c)(5) limited basic +# standard deduction and the age-only addition: (a)(1)(C) withdraws the (A) +# exception in further cases ((C)(i)(I), unearned income, and (C)(i)(II), +# gross income over the standard deduction); it does not replace (A) for +# these filers. -- name: Case 28, 2026, blind single filer who can be claimed keeps the blindness addition. +- name: Case 28, 2026, blind single filer who can be claimed gets only the age addition. period: 2026 input: filing_status: SINGLE @@ -406,10 +405,12 @@ claimed_as_dependent_on_another_return: true employment_income: 17_000 output: - # Limited standard deduction min($16,100, $17,000 + $450) + $2,050 = - # $18,150, over gross income. Nobody can claim the filer in Case 3. + # Limited basic standard deduction min($16,100, $17,000 + $450) = + # $16,100; blindness doesn't raise the (A) threshold, and $17,000 is + # at least $16,100. The standard deduction itself still has the $2,050 + # blindness addition. Compare Case 3, where nobody can claim the filer. standard_deduction: 18_150 - tax_unit_is_required_to_file: false + tax_unit_is_required_to_file: true - name: Case 29, 2026, joint return whose spouse can be claimed, at exactly the joint amount. period: 2026 @@ -429,9 +430,9 @@ members: [person1, person2] state_code: TX output: - # Gross income does not exceed the $32,200 standard deduction. Compare - # Case 9, where nobody can be claimed. - tax_unit_is_required_to_file: false + # Gross income is not less than the $32,200 basic standard deduction, so + # a return is due, as in Case 9, where nobody can be claimed. + tax_unit_is_required_to_file: true # 26 U.S.C. 6012(c): for the filing requirement, gross income is computed # without regard to the section 911 exclusion. With Form 2555 leaf inputs, 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 f7ba92e3c6d..83db6d25da5 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 @@ -547,8 +547,8 @@ # 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. +# deduction. The model follows the statute there; the other 2026 cases stay a +# dollar away from a threshold. - name: 2026, single dependent with unearned income under $1,350 need not file period: 2026 @@ -580,6 +580,19 @@ output: tax_unit_is_required_to_file: false +- name: 2026, single dependent with earned income of exactly $16,100 must file + period: 2026 + input: + filing_status: SINGLE + age: 19 + claimed_as_dependent_on_another_return: true + employment_income: 16_100 + output: + # (a)(1)(A) excuses only gross income "less than" the (limited) basic + # standard deduction, $16,100; (C) adds requirements, it doesn't replace + # (A). + tax_unit_is_required_to_file: true + - name: 2026, single dependent with earned income over $16,100 must file period: 2026 input: 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 d63cc80d891..9f7482b8e7d 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 @@ -12,9 +12,12 @@ class tax_unit_is_required_to_file(Variable): status. For 2018 to 2025, section 6012(f) compares gross income with the standard deduction. In other years, section 6012(a)(1) compares it with the basic standard deduction plus the additions for age, but not - blindness, and gross income equal to that amount requires a return. A - tax unit whose head or spouse can be claimed as a dependent keeps the - standard deduction test in every year. Under section 6012(c), both + blindness, and gross income equal to that amount requires a return; a + married individual filing separately must file even with no gross income. + That test applies to every filer, including one who can be claimed as a + dependent (with the section 63(c)(5) limited basic standard deduction), + and section 6012(a)(1)(C) adds that filer's unearned income and standard + deduction tests. Under section 6012(c), both tests count gross section 911 exclusions: Form 2555 line 43 when leaf inputs are supplied, otherwise the nonnegative legacy foreign_earned_income_exclusion amount. @@ -85,11 +88,10 @@ def formula(tax_unit, period, parameters): # (zero unless that reform is on). # # A married individual filing separately has no exception. With an - # exemption amount of zero, (A) read literally reaches even an - # individual with no gross income; the National Taxpayer Advocate read - # the 2018 to 2025 rule that way, and the IRS set $5 as relief. As for - # 2018 to 2025, the model requires a return only for positive gross - # income (decision d1403). + # exemption amount of zero, (A) reaches even an individual with no + # gross income, since no gross income is "less than" zero. + # For a filer who can be claimed as a dependent, basic_standard_deduction + # is already the section 63(c)(5) limited amount. aged_count = tax_unit("aged_head", period).astype(int) + tax_unit( "aged_spouse", period ).astype(int) @@ -99,22 +101,22 @@ def formula(tax_unit, period, parameters): + tax_unit("bonus_guaranteed_deduction", period) + aged_count * p.deductions.standard.aged_or_blind.amount[filing_status] ) - income_at_least_basic_threshold = (gross_income > 0) & ( - gross_income >= where(separate, exemption_amount, basic_threshold) + income_at_least_basic_threshold = gross_income >= where( + separate, exemption_amount, basic_threshold ) - # A tax unit whose head or spouse can be claimed as a dependent keeps - # the standard deduction test in every year. From 2026 it is - # (a)(1)(C)(i)(II), and it is what Chart B applies under (a)(1); the - # (A) threshold, which leaves out blindness and requires a return at - # equality, is not applied to these filers (decision d1395). + # Under (a)(1), (C) withdraws the (A) exception from a filer who can + # be claimed as a dependent in further cases; it does not replace + # (A) for that filer. So the (A) and (B) threshold binds every filer, + # and (C)(i)(II), gross income over the standard deduction, is added + # for a tax unit whose head or spouse can be claimed. (C)(i)(I), the + # unearned income test, is below. if p.income.filing_requirement.standard_deduction_rule.applies: income_over_threshold = income_over_standard_deduction else: - income_over_threshold = where( - head_can_be_claimed | spouse_can_be_claimed, - income_over_standard_deduction, - income_at_least_basic_threshold, + income_over_threshold = income_at_least_basic_threshold | ( + (head_can_be_claimed | spouse_can_be_claimed) + & income_over_standard_deduction ) # A filer who can be claimed as a dependent on another return (an From afdeb924cce91439870ae217b00f0604415503fb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 09:23:08 +0000 Subject: [PATCH 3/8] Describe both 2026 fixes in the changelog fragment Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6K4grkKNkfjkW6hbaN --- changelog.d/statute-bugs-savers-filing-2026.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/statute-bugs-savers-filing-2026.fixed.md b/changelog.d/statute-bugs-savers-filing-2026.fixed.md index 5cb0a381c9f..ddad70ee7fc 100644 --- a/changelog.d/statute-bugs-savers-filing-2026.fixed.md +++ b/changelog.d/statute-bugs-savers-filing-2026.fixed.md @@ -1 +1 @@ -Keep ABLE contributions in the saver's credit for 2026, and from 2027 count only ABLE contributions, reduced by relevant ABLE distributions, under a $2,100 cap (Pub. L. 119-21). +Keep ABLE contributions in the saver's credit for 2026, and from 2027 count only ABLE contributions, reduced by relevant ABLE distributions, under a $2,100 cap (Pub. L. 119-21). From 2026, apply the 26 U.S.C. 6012(a)(1)(A) and (B) filing threshold to every filer, including a married individual filing separately with no gross income and a filer who can be claimed as a dependent. From 39f60ed5c528a02156cd91b69b5469ff79bc4e7e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 16:04:43 +0000 Subject: [PATCH 4/8] Apply 6012(a)(1)(A)(iv)'s claimable-spouse exclusion and (C)(ii) Under 6012(a)(1), clause (iv) does not apply when another taxpayer is entitled to an exemption for the spouse, so on a joint return the other spouse has no exception and must file once their own gross income reaches the exemption amount ($0 from 2026). And (C)(ii) withdraws the (A) exception when section 63(c)(6) zeroes the standard deduction, which the age addition could otherwise revive. 6012(f), 2018 to 2025, is unchanged. Adds hand-computed YAML cases for 2026 and 2017 and Hypothesis properties for joint returns, and drops an internal id from a comment. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6KWdpQsFeBJEmuZXhx --- .../statute-bugs-savers-filing-2026.fixed.md | 2 +- .../test_filing_requirement_invariants.py | 106 ++++++++- .../gov/irs/tax_unit_is_required_to_file.yaml | 206 +++++++++++++++++- .../gov/irs/tax_unit_is_required_to_file.py | 64 +++++- 4 files changed, 364 insertions(+), 14 deletions(-) diff --git a/changelog.d/statute-bugs-savers-filing-2026.fixed.md b/changelog.d/statute-bugs-savers-filing-2026.fixed.md index ddad70ee7fc..ca063c6406d 100644 --- a/changelog.d/statute-bugs-savers-filing-2026.fixed.md +++ b/changelog.d/statute-bugs-savers-filing-2026.fixed.md @@ -1 +1 @@ -Keep ABLE contributions in the saver's credit for 2026, and from 2027 count only ABLE contributions, reduced by relevant ABLE distributions, under a $2,100 cap (Pub. L. 119-21). From 2026, apply the 26 U.S.C. 6012(a)(1)(A) and (B) filing threshold to every filer, including a married individual filing separately with no gross income and a filer who can be claimed as a dependent. +Keep ABLE contributions in the saver's credit for 2026, and from 2027 count only ABLE contributions, reduced by relevant ABLE distributions, under a $2,100 cap (Pub. L. 119-21). From 2026, apply the 26 U.S.C. 6012(a)(1)(A) and (B) filing threshold to every filer, including a married individual filing separately with no gross income and a filer who can be claimed as a dependent. Under section 6012(a)(1), require a joint return when either spouse can be claimed as a dependent and the other spouse's own gross income is at least the exemption amount ((A)(iv)), and withdraw the (A) exception when the standard deduction is zero under section 63(c)(6) ((C)(ii)). diff --git a/policyengine_us/tests/core/test_filing_requirement_invariants.py b/policyengine_us/tests/core/test_filing_requirement_invariants.py index 45c5b8f91a6..867cc8ef79e 100644 --- a/policyengine_us/tests/core/test_filing_requirement_invariants.py +++ b/policyengine_us/tests/core/test_filing_requirement_invariants.py @@ -51,7 +51,30 @@ (A) and (B) threshold also binds them: gross income at least the unit's (limited) basic standard deduction plus the age-only additions, or any separate return. (C) adds to (A) for these filers; it does not replace - it. + it. And from 2026 every joint return among them is required: the spouse + of a claimable adult loses the (A)(iv) exception, and any own gross + income, even none, meets the zero exemption amount. + +`test_joint_return_properties` draws batches of joint returns (any mix of +age, blindness, claimability of each spouse, income and +separate_filer_itemizes) and checks section 6012(a)(1)(A)(iv) and (C)(ii): + +8. Claimable spouse: in 2026, every joint return whose head or spouse can be + claimed is required. (iv) "shall not apply if ... any other taxpayer is + entitled to an exemption for such spouse under section 151(c)", so the + other spouse has no exception, and own gross income of $0 meets the $0 + exemption amount. It can't pass trivially: every batch holds a claimable + joint unit with gross income under the $1,350 section 63(c)(5)(A) floor, + which the formula before (iv) was coded excused, since both the (A) + threshold and the limited standard deduction are at least that floor; + the test asserts that such a row exists. +9. No change elsewhere: a joint return nobody can claim, whose + separate_filer_itemizes is false, gets the result of the formula before + (iv) and (C)(ii) were coded, frozen in `frozen_joint_reference` from the + published amounts. It can't pass trivially: incomes are often a dollar + under, at or over a threshold, and the test asserts that both answers + occur among the checked rows. Coding (iv) or (C)(ii) too broadly (for a + unit nobody can claim, or ignoring separate_filer_itemizes) fails it. The tests use the default tax-benefit system only. A reform that kept 6012(f) for 2026 would compare the two rules directly, but it costs a second @@ -241,8 +264,8 @@ def test_threshold_grid(): @st.composite -def units(draw): - status = draw(st.sampled_from(STATUSES)) +def units(draw, status=None): + status = status or draw(st.sampled_from(STATUSES)) size = 2 if status == "JOINT" else 1 claimable = draw(st.booleans()) adults = [ @@ -416,6 +439,8 @@ def section_a(unit, basic): ) or claimable_unearned_test(unit, year) or section_a(unit, basic) + # Section 6012(a)(1)(A)(iv)'s claimable-spouse exclusion. + or (year >= 2026 and unit["filing_status"] == "JOINT") for unit, deduction, basic in zip( by_variant["base"], standard_deduction, basic_deduction ) @@ -426,3 +451,78 @@ def section_a(unit, basic): expected_claimable[~unclaimable], err_msg=f"{year}: the rule for claimable units changed", ) + + +def frozen_joint_reference(unit, year): + """The filing requirement of a joint return nobody can claim, as the + formula gave it before 6012(a)(1)(A)(iv)'s exclusion and (C)(ii) were + coded, from the published amounts: (a)(1)(A) and (B) for 2026, gross + income at least $32,200 plus $1,650 for each spouse aged 65 or older; + (f) for 2025, gross income over $31,500 plus $1,600 for each spouse aged + 65 or older and each blind spouse.""" + income = gross_income(unit) + aged = sum(adult["age"] >= 65 for adult in unit["adults"]) + if year >= 2026: + return income >= 32_200 + 1_650 * aged + blind = sum(adult["is_blind"] for adult in unit["adults"]) + return income > 31_500 + 1_600 * (aged + blind) + + +# A claimable spouse with no income, which the formula before (iv) excused; +# and joint returns nobody can claim a dollar under and at the 2026 threshold. +JOINT_ANCHORS = [ + (tax_unit("JOINT", [adult(30, False), adult(20, False, True)]), False), + ( + tax_unit( + "JOINT", [adult(40, False, employment_income=32_199), adult(40, False)] + ), + False, + ), + ( + tax_unit( + "JOINT", [adult(40, False, employment_income=32_200), adult(40, False)] + ), + False, + ), +] + + +@settings( + max_examples=6, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given( + drawn=st.lists( + st.tuples(units(status="JOINT"), st.booleans()), min_size=8, max_size=24 + ) +) +def test_joint_return_properties(drawn): + drawn = JOINT_ANCHORS + drawn + joint = [unit for unit, _ in drawn] + itemizes = np.array([flag for _, flag in drawn]) + simulation = simulate(joint) + for year in YEARS: + simulation.set_input("separate_filer_itemizes", year, itemizes) + claimable = ~np.array([nobody_claimable(unit) for unit in joint]) + income = np.array([gross_income(unit) for unit in joint]) + for year in YEARS: + required = np.asarray( + simulation.calculate("tax_unit_is_required_to_file", year) + ) + # Property 8. + if year >= 2026: + assert required[claimable].all(), ( + f"{year}: excused a joint return with a claimable spouse" + ) + assert (claimable & (income < DEPENDENT_AMOUNT[year])).any() + # Property 9. + checked = ~claimable & ~itemizes + expected = np.array([frozen_joint_reference(unit, year) for unit in joint]) + np.testing.assert_array_equal( + required[checked], + expected[checked], + err_msg=f"{year}: changed a joint return nobody can claim", + ) + assert required[checked].any() and not required[checked].all() diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml index 54c311738bc..efb819a3940 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml @@ -293,7 +293,7 @@ taxable_interest_income: 15_750 output: # (f)(1): gross income does not exceed the standard deduction. Chart A of - # the 2025 instructions says "at least" $15,750 (decision d1403). + # the 2025 instructions says "at least" $15,750. tax_unit_is_required_to_file: false - name: Case 21, 2025, single filer under 65 with gross income of $15,751. @@ -314,7 +314,7 @@ employment_income: 17_750 output: # (f)(1): the standard deduction is $15,750 + $2,000 for blindness. - # Chart A of the 2025 instructions lists only age (decision d1403). + # Chart A of the 2025 instructions lists only age. tax_unit_is_required_to_file: false - name: Case 23, 2025, blind single filer under 65 with gross income of $17,751. @@ -593,3 +593,205 @@ # $10,000 of gross income, all earned, under the $14,600 single # threshold, so no unearned income over the (a)(1)(C) amount either. tax_unit_is_required_to_file: false + +# Section 6012(a)(1)(A)(iv), the joint-return exception, "shall not apply if +# for the taxable year ... any other taxpayer is entitled to an exemption for +# such spouse under section 151(c)". The spouse of a claimable head or spouse +# then has no exception and must file when their own gross income equals or +# exceeds the exemption amount ($0 from 2026, $4,050 in 2017). + +- name: 6012(a)(1)(A)(iv), 2026 joint return with a claimable spouse and $10,000 of wages + period: 2026 + input: + people: + head: + age: 30 + employment_income: 10_000 + spouse: + age: 20 + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + filing_status: JOINT + # Limited basic standard deduction (section 63(c)(5)): + # min($32,200, max($1,350, $10,000 + $450)) = $10,450. The (A) threshold + # is 2 x $0 + $10,450 = $10,450, and $10,000 < $10,450, so (iv) alone + # would excuse the couple. But the spouse can be claimed, so the head + # loses (iv): own gross income $10,000 >= $0 exemption amount. + basic_standard_deduction: 10_450 + tax_unit_is_required_to_file: true + +- name: 6012(a)(1)(A)(iv), 2026 joint return with $10,000 of wages and no claimable spouse + period: 2026 + input: + people: + head: + age: 30 + employment_income: 10_000 + spouse: + age: 20 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + filing_status: JOINT + # (iv) applies: $10,000 < 2 x $0 + $32,200. + tax_unit_is_required_to_file: false + +- name: 6012(a)(1)(A)(iv), 2026 joint return with no income and a claimable spouse + period: 2026 + input: + people: + head: + age: 30 + spouse: + age: 20 + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + filing_status: JOINT + head_is_dependent_elsewhere: false + spouse_is_dependent_elsewhere: true + # The head loses (iv): own gross income $0 >= $0 exemption amount. + tax_unit_is_required_to_file: true + +- name: 6012(a)(1)(A)(iv), 2026 joint return with no income and a claimable head + period: 2026 + input: + people: + head: + age: 20 + claimable_as_dependent_on_another_return: true + tax_unit_role_input: HEAD + spouse: + age: 30 + tax_unit_role_input: SPOUSE + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + filing_status: JOINT + # Roles are set so that the younger, claimable adult heads the unit. + head_is_dependent_elsewhere: true + spouse_is_dependent_elsewhere: false + # The spouse loses (iv): own gross income $0 >= $0 exemption amount. + tax_unit_is_required_to_file: true + +- name: 6012(a)(1)(A)(iv), 2017 joint return with a claimable spouse and $3,000 of wages + period: 2017 + input: + people: + head: + age: 30 + employment_income: 3_000 + spouse: + age: 20 + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + filing_status: JOINT + # 2017 exemption amount $4,050 (gov.irs.income.exemption.amount). The head + # loses (iv), but own gross income $3,000 < $4,050. + # Limited basic standard deduction: max($1,050, $3,000 + $350) = $3,350. + # (A) threshold: 2 x $4,050 + $3,350 = $11,450 > $3,000. + # (C)(i)(II): $3,000 does not exceed $3,350 + $4,050 = $7,400. + # No unearned income. + basic_standard_deduction: 3_350 + tax_unit_is_required_to_file: false + +- name: 6012(a)(1)(A)(iv), 2017 joint return with a claimable spouse and $5,000 of wages + period: 2017 + input: + people: + head: + age: 30 + employment_income: 5_000 + spouse: + age: 20 + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + filing_status: JOINT + # The head loses (iv): own gross income $5,000 >= $4,050. + # Without that: limited basic standard deduction max($1,050, $5,350) = + # $5,350; (A) threshold 2 x $4,050 + $5,350 = $13,450 > $5,000; and + # (C)(i)(II): $5,000 does not exceed $5,350 + $4,050 = $9,400. + basic_standard_deduction: 5_350 + tax_unit_is_required_to_file: true + +- name: 6012(a)(1)(A)(iv), 2017, the comparison uses the unclaimable spouse's own gross income + period: 2017 + input: + people: + head: + age: 30 + employment_income: 3_000 + spouse: + age: 20 + employment_income: 2_000 + claimable_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + filing_status: JOINT + # The head loses (iv), but the head's own gross income $3,000 < $4,050. + # Combined gross income $5,000 would meet $4,050; (iv) compares only the + # head's own. The claimable spouse keeps (iv), since the head can't be + # claimed. Limited basic standard deduction: max($1,050, $5,000 + $350) + # = $5,350. (A) threshold 2 x $4,050 + $5,350 = $13,450 > $5,000. + # (C)(i)(II): $5,000 does not exceed $5,350 + $4,050 = $9,400. + # No unearned income. + basic_standard_deduction: 5_350 + tax_unit_is_required_to_file: false + +- name: 6012(a)(1)(C)(ii), 2026 head of household aged 70 whose spouse files separately and itemizes + period: 2026 + input: + filing_status: HEAD_OF_HOUSEHOLD + age: 70 + separate_filer_itemizes: true + employment_income: 1_000 + output: + # Section 63(c)(6) makes the standard deduction zero, so (C)(ii) + # withdraws the (A) exception and the threshold is the $0 exemption + # amount: $1,000 >= $0. Without (C)(ii) the threshold would be $0 basic + # + $2,050 age addition, and $1,000 < $2,050. + basic_standard_deduction: 0 + tax_unit_is_required_to_file: true 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 9f7482b8e7d..55ef34970a3 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 @@ -17,7 +17,11 @@ class tax_unit_is_required_to_file(Variable): That test applies to every filer, including one who can be claimed as a dependent (with the section 63(c)(5) limited basic standard deduction), and section 6012(a)(1)(C) adds that filer's unearned income and standard - deduction tests. Under section 6012(c), both + deduction tests. Under (a)(1), a joint return whose head or spouse can be + claimed loses the (A)(iv) exception for the other spouse, who must file + with own gross income at or above the exemption amount, and a filer whose + standard deduction is zero under section 63(c)(6) loses the (A) + exception (section 6012(a)(1)(C)(ii)). Under section 6012(c), both tests count gross section 911 exclusions: Form 2555 line 43 when leaf inputs are supplied, otherwise the nonnegative legacy foreign_earned_income_exclusion amount. @@ -101,8 +105,49 @@ def formula(tax_unit, period, parameters): + tax_unit("bonus_guaranteed_deduction", period) + aged_count * p.deductions.standard.aged_or_blind.amount[filing_status] ) + # Section 6012(a)(1)(C)(ii): the (A) exception does not apply "to any + # individual ... for whom the standard deduction is zero under section + # 63(c)(6)". separate_filer_itemizes is PE-US's only 63(c)(6) path, so + # the threshold falls to the exemption amount, without the age + # addition, whatever the filing status. + no_exception = separate | tax_unit("separate_filer_itemizes", period) income_at_least_basic_threshold = gross_income >= where( - separate, exemption_amount, basic_threshold + no_exception, exemption_amount, basic_threshold + ) + + # Section 6012(a)(1)(A)(iv), the joint-return exception, "shall not + # apply if for the taxable year ... any other taxpayer is entitled to + # an exemption for such spouse under section 151(c)". So on a joint + # return, the spouse of a head or spouse who can be claimed loses + # (iv). That spouse then has no exception and must file if their own + # gross income equals or exceeds the exemption amount (zero from + # 2026). When both can be claimed, both lose (iv). Own gross income is + # person-level irs_gross_income alone: the section 911 exclusion is + # computed only per tax unit (policyengine_us/tools/section_911.py), + # so it can't be split between the spouses here. + # + # (iv) also applies only if the spouses "at the close of the taxable + # year, had the same household as their home". PE-US has no year-end + # household input and puts a tax unit in one household, so the spouses + # of a joint return are treated as sharing one. + # + # The separate-return half of the exclusion needs no code: a married + # individual filing separately already has no exception. + person = tax_unit.members + joint = filing_status == filing_status.possible_values.JOINT + loses_joint_exception = tax_unit.project(joint) & ( + ( + person("is_tax_unit_head", period) + & tax_unit.project(spouse_can_be_claimed) + ) + | ( + person("is_tax_unit_spouse", period) + & tax_unit.project(head_can_be_claimed) + ) + ) + own_gross_income_at_least_exemption = tax_unit.any( + loses_joint_exception + & (person("irs_gross_income", period) >= exemption_amount) ) # Under (a)(1), (C) withdraws the (A) exception from a filer who can @@ -114,9 +159,13 @@ def formula(tax_unit, period, parameters): if p.income.filing_requirement.standard_deduction_rule.applies: income_over_threshold = income_over_standard_deduction else: - income_over_threshold = income_at_least_basic_threshold | ( - (head_can_be_claimed | spouse_can_be_claimed) - & income_over_standard_deduction + income_over_threshold = ( + income_at_least_basic_threshold + | own_gross_income_at_least_exemption + | ( + (head_can_be_claimed | spouse_can_be_claimed) + & income_over_standard_deduction + ) ) # A filer who can be claimed as a dependent on another return (an @@ -127,8 +176,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 (open as - # decision d1395). Nobody else has an unearned income test. + # 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 @@ -137,7 +186,6 @@ def formula(tax_unit, period, parameters): # 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 can_be_claimed = ( person("is_tax_unit_head", period) & tax_unit.project(head_can_be_claimed) ) | ( From 374e9a93ca14226a877d43941e6912e20cacb7bb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 16:04:43 +0000 Subject: [PATCH 5/8] Drop internal decision ids from filing requirement test comments The comments keep their meaning; the ids point to records outside the repository. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6KWdpQsFeBJEmuZXhx --- .../core/test_filing_requirement_dependent_invariants.py | 4 ++-- .../gov/irs/tax_unit_is_required_to_file_dependents.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 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 74e74dee57a..ee666057310 100644 --- a/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py +++ b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py @@ -35,7 +35,7 @@ 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.) + Table 2 differ, and the test leaves it out.) `test_filing_requirement_properties` draws batches of tax units with Hypothesis (single, head of household, surviving spouse, separate and joint; @@ -61,7 +61,7 @@ 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. + under either reading. `test_earned_income_sources_are_gross_income_sources`: 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 83db6d25da5..cdffba15a65 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 @@ -5,7 +5,7 @@ # 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, section 6012(f)(1) gives Chart -# B's result. Where the statute and the charts differ (decision d1395: the +# B's result. Where the statute and the charts differ (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. @@ -402,7 +402,7 @@ - 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). + # $1,350 (section 6012(f)(2)(D)). At $2,950 or less they differ. period: 2025 input: people: From e8e8a75323f734b30cd696f5970f414d256d68f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 20:26:31 +0000 Subject: [PATCH 6/8] Describe (C)(i)(II) as the statute does, widen ABLE input docs, anchor a claimable head The 2017 (C)(i)(II) comments called the code's pre-2018 threshold, which adds the exemption amount, the statute's test; the statute compares gross income with the standard deduction alone, and 6012(a)(1)(D)(ii) zeroes the exemption amount for a 151(d)(2) individual. No formula change. able_contributions_person also feeds able_contributions, which Arizona and Colorado subtract, so its documentation no longer limits it to a beneficiary's own contributions. Property 8 passed with the (A)(iv) exclusion applied only for a claimable spouse; a joint anchor with the claimable adult as head, and an assertion that such a row exists, now catch that. Co-Authored-By: Claude Opus 5.5 --- .../core/test_filing_requirement_invariants.py | 14 +++++++++++--- .../gov/irs/tax_unit_is_required_to_file.yaml | 6 +++--- .../gov/irs/tax_unit_is_required_to_file.py | 7 +++++-- .../retirement/able_contributions_person.py | 6 ++++-- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/policyengine_us/tests/core/test_filing_requirement_invariants.py b/policyengine_us/tests/core/test_filing_requirement_invariants.py index 867cc8ef79e..5d2159cfd42 100644 --- a/policyengine_us/tests/core/test_filing_requirement_invariants.py +++ b/policyengine_us/tests/core/test_filing_requirement_invariants.py @@ -67,7 +67,9 @@ joint unit with gross income under the $1,350 section 63(c)(5)(A) floor, which the formula before (iv) was coded excused, since both the (A) threshold and the limited standard deduction are at least that floor; - the test asserts that such a row exists. + the test asserts that such a row exists, and that one exists whose + claimable adult is the head, so an exclusion applied only when the + spouse can be claimed fails it. 9. No change elsewhere: a joint return nobody can claim, whose separate_filer_itemizes is false, gets the result of the formula before (iv) and (C)(ii) were coded, frozen in `frozen_joint_reference` from the @@ -468,10 +470,12 @@ def frozen_joint_reference(unit, year): return income > 31_500 + 1_600 * (aged + blind) -# A claimable spouse with no income, which the formula before (iv) excused; -# and joint returns nobody can claim a dollar under and at the 2026 threshold. +# A claimable spouse with no income, and a claimable head with no income, +# which the formula before (iv) excused; and joint returns nobody can claim a +# dollar under and at the 2026 threshold. JOINT_ANCHORS = [ (tax_unit("JOINT", [adult(30, False), adult(20, False, True)]), False), + (tax_unit("JOINT", [adult(20, False, True), adult(30, False)]), False), ( tax_unit( "JOINT", [adult(40, False, employment_income=32_199), adult(40, False)] @@ -506,6 +510,9 @@ def test_joint_return_properties(drawn): for year in YEARS: simulation.set_input("separate_filer_itemizes", year, itemizes) claimable = ~np.array([nobody_claimable(unit) for unit in joint]) + head_claimable = np.array( + [unit["adults"][0]["claimed_as_dependent_on_another_return"] for unit in joint] + ) income = np.array([gross_income(unit) for unit in joint]) for year in YEARS: required = np.asarray( @@ -517,6 +524,7 @@ def test_joint_return_properties(drawn): f"{year}: excused a joint return with a claimable spouse" ) assert (claimable & (income < DEPENDENT_AMOUNT[year])).any() + assert (head_claimable & (income < DEPENDENT_AMOUNT[year])).any() # Property 9. checked = ~claimable & ~itemizes expected = np.array([frozen_joint_reference(unit, year) for unit in joint]) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml index efb819a3940..b8520e5ae29 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml @@ -720,7 +720,7 @@ # loses (iv), but own gross income $3,000 < $4,050. # Limited basic standard deduction: max($1,050, $3,000 + $350) = $3,350. # (A) threshold: 2 x $4,050 + $3,350 = $11,450 > $3,000. - # (C)(i)(II): $3,000 does not exceed $3,350 + $4,050 = $7,400. + # (C)(i)(II): $3,000 does not exceed the $3,350 standard deduction. # No unearned income. basic_standard_deduction: 3_350 tax_unit_is_required_to_file: false @@ -747,7 +747,7 @@ # The head loses (iv): own gross income $5,000 >= $4,050. # Without that: limited basic standard deduction max($1,050, $5,350) = # $5,350; (A) threshold 2 x $4,050 + $5,350 = $13,450 > $5,000; and - # (C)(i)(II): $5,000 does not exceed $5,350 + $4,050 = $9,400. + # (C)(i)(II): $5,000 does not exceed the $5,350 standard deduction. basic_standard_deduction: 5_350 tax_unit_is_required_to_file: true @@ -776,7 +776,7 @@ # head's own. The claimable spouse keeps (iv), since the head can't be # claimed. Limited basic standard deduction: max($1,050, $5,000 + $350) # = $5,350. (A) threshold 2 x $4,050 + $5,350 = $13,450 > $5,000. - # (C)(i)(II): $5,000 does not exceed $5,350 + $4,050 = $9,400. + # (C)(i)(II): $5,000 does not exceed the $5,350 standard deduction. # No unearned income. basic_standard_deduction: 5_350 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 55ef34970a3..59272998119 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 @@ -154,8 +154,11 @@ def formula(tax_unit, period, parameters): # be claimed as a dependent in further cases; it does not replace # (A) for that filer. So the (A) and (B) threshold binds every filer, # and (C)(i)(II), gross income over the standard deduction, is added - # for a tax unit whose head or spouse can be claimed. (C)(i)(I), the - # unearned income test, is below. + # for a tax unit whose head or spouse can be claimed. Before 2018 the + # threshold used for (C)(i)(II) adds the exemption amount, which + # (C)(i)(II) doesn't, and section 6012(a)(1)(D)(ii) sets the exemption + # amount to zero for an individual described in section 151(d)(2). + # (C)(i)(I), the unearned income test, is below. if p.income.filing_requirement.standard_deduction_rule.applies: income_over_threshold = income_over_standard_deduction else: diff --git a/policyengine_us/variables/household/expense/retirement/able_contributions_person.py b/policyengine_us/variables/household/expense/retirement/able_contributions_person.py index 0a6955b2609..d6767140557 100644 --- a/policyengine_us/variables/household/expense/retirement/able_contributions_person.py +++ b/policyengine_us/variables/household/expense/retirement/able_contributions_person.py @@ -6,8 +6,10 @@ class able_contributions_person(Variable): entity = Person label = "Person-level ABLE contributions" documentation = ( - "Contributions each person made, as the designated beneficiary, to " - "their own ABLE account. The saver's credit counts only these." + "Contributions each person made to an ABLE account. The saver's " + "credit (26 U.S.C. 25B(d)(1)) counts only contributions to the " + "person's own account as designated beneficiary; Arizona and " + "Colorado subtract ABLE contributions through able_contributions." ) reference = "https://www.law.cornell.edu/uscode/text/26/25B#d_1" unit = USD From 3849cf4a249b7e281a9fe332707e73db475af192 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 23:29:16 +0000 Subject: [PATCH 7/8] Reduce saver's credit contributions by ABLE distributions in every year 25B(d)(2)(A) reaches distributions from any entity that can take (d)(1) contributions, and ABLE accounts take them through 2026 too, so the 2018 distribution list now holds the relevant ABLE distribution input beside retirement_distributions. New 2023, 2026 and 2027 cases pin the list on each side of the 2027 change. Also correct review text without moving expected values: the (C)(ii) case now says it pins the model's separate_filer_itemizes rule rather than law for a head of household; the 2017 (iv) comments name (D)(ii); the ABLE input documentation says the saver's credit counts the whole amount; stale "open" and "stay clear" sentences on the 2026 rule are updated; and the source-conflict candidates match the 2026 code, with a row for the statute-over-Chart-B ruling. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6FzZjERFUaWqE3b64t --- .../statute-bugs-savers-filing-2026.fixed.md | 2 +- .../retirement_saving/distributions.yaml | 8 +++- ...filing_requirement_dependent_invariants.py | 4 +- ...savers_credit_qualified_contributions.yaml | 35 +++++++++++++++++ .../gov/irs/tax_unit_is_required_to_file.yaml | 34 +++++++++------- ...x_unit_is_required_to_file_dependents.yaml | 9 +++-- .../savers_credit_qualified_contributions.py | 4 +- .../gov/irs/tax_unit_is_required_to_file.py | 23 ++++++----- .../retirement/able_contributions_person.py | 8 ++-- ...vers_credit_relevant_able_distributions.py | 10 ++--- source_conflicts/candidates.yaml | 39 +++++++++++++++---- 11 files changed, 126 insertions(+), 50 deletions(-) diff --git a/changelog.d/statute-bugs-savers-filing-2026.fixed.md b/changelog.d/statute-bugs-savers-filing-2026.fixed.md index ca063c6406d..a30786a6855 100644 --- a/changelog.d/statute-bugs-savers-filing-2026.fixed.md +++ b/changelog.d/statute-bugs-savers-filing-2026.fixed.md @@ -1 +1 @@ -Keep ABLE contributions in the saver's credit for 2026, and from 2027 count only ABLE contributions, reduced by relevant ABLE distributions, under a $2,100 cap (Pub. L. 119-21). From 2026, apply the 26 U.S.C. 6012(a)(1)(A) and (B) filing threshold to every filer, including a married individual filing separately with no gross income and a filer who can be claimed as a dependent. Under section 6012(a)(1), require a joint return when either spouse can be claimed as a dependent and the other spouse's own gross income is at least the exemption amount ((A)(iv)), and withdraw the (A) exception when the standard deduction is zero under section 63(c)(6) ((C)(ii)). +Keep ABLE contributions in the saver's credit for 2026, reduce qualified contributions by relevant ABLE distributions through a new input, and from 2027 count only ABLE contributions, reduced only by those distributions, under a $2,100 cap (Pub. L. 119-21). From 2026, apply the 26 U.S.C. 6012(a)(1)(A) and (B) filing threshold to every filer, including a married individual filing separately with no gross income and a filer who can be claimed as a dependent. Under section 6012(a)(1), require a joint return when either spouse can be claimed as a dependent and the other spouse's own gross income is at least the exemption amount ((A)(iv)), and withdraw the (A) exception when the standard deduction is zero under section 63(c)(6) ((C)(ii)). diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/distributions.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/distributions.yaml index e3c92f5c730..f4a5e62f3b9 100644 --- a/policyengine_us/parameters/gov/irs/credits/retirement_saving/distributions.yaml +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/distributions.yaml @@ -1,9 +1,13 @@ description: The IRS subtracts these distributions from qualified contributions under the saver's credit. values: + # 25B(d)(2)(A) reaches any entity of a type that can take contributions + # under 25B(d)(1), so ABLE distributions count for as long as ABLE + # contributions do (Form 8880, line 4). 2018-01-01: - retirement_distributions - # From 2027 the reduction uses relevant distributions from the - # beneficiary's ABLE account, not retirement-plan distributions. + - savers_credit_relevant_able_distributions + # From 2027 only ABLE contributions count, so only ABLE distributions + # reduce them. 2027-01-01: - savers_credit_relevant_able_distributions metadata: 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 ee666057310..f678b19da99 100644 --- a/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py +++ b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py @@ -27,8 +27,8 @@ 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. + 6012(a)(1), which leaves out blindness and requires a return at the exact + dollar, unlike Table 2, 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 2025 joint return with no other income, where the spouse who can be diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yaml index 804dde9a498..15d986812cd 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yaml @@ -75,3 +75,38 @@ savers_credit_relevant_able_distributions: 300 output: savers_credit_qualified_contributions: 500 + +- name: In 2026 relevant ABLE distributions reduce ABLE contributions too + period: 2026 + input: + able_contributions_person: 800 + savers_credit_relevant_able_distributions: 300 + output: + savers_credit_qualified_contributions: 500 + +# These cases pin the distribution list on each side of the 2027 change. +# Through 2026 retirement-plan and relevant ABLE distributions both reduce +# qualified contributions. +- name: In 2023 retirement-plan and relevant ABLE distributions both reduce contributions + period: 2023 + input: + ira_compensation: 10_000 + traditional_ira_contributions_desired: 1_000 + able_contributions_person: 500 + retirement_distributions: 400 + savers_credit_relevant_able_distributions: 300 + output: + # 1,000 + 500 - 400 - 300 = 800. + savers_credit_qualified_contributions: 800 + +# From 2027 relevant ABLE distributions reduce contributions, and +# retirement-plan distributions don't. +- name: In 2027 only relevant ABLE distributions reduce contributions + period: 2027 + input: + able_contributions_person: 800 + retirement_distributions: 400 + savers_credit_relevant_able_distributions: 300 + output: + # 800 - 300 = 500; the 400 of retirement-plan distributions doesn't count. + savers_credit_qualified_contributions: 500 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml index b8520e5ae29..c150201ee60 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml @@ -691,7 +691,7 @@ state_code: TX output: filing_status: JOINT - # Roles are set so that the younger, claimable adult heads the unit. + # The input roles make the younger, claimable adult the head. head_is_dependent_elsewhere: true spouse_is_dependent_elsewhere: false # The spouse loses (iv): own gross income $0 >= $0 exemption amount. @@ -719,7 +719,9 @@ # 2017 exemption amount $4,050 (gov.irs.income.exemption.amount). The head # loses (iv), but own gross income $3,000 < $4,050. # Limited basic standard deduction: max($1,050, $3,000 + $350) = $3,350. - # (A) threshold: 2 x $4,050 + $3,350 = $11,450 > $3,000. + # The claimable spouse keeps (iv): $3,000 is under 2 x $0 ((D)(ii)) + + # $3,350. The formula's (A) threshold, 2 x $4,050 + $3,350 = $11,450, + # gives the same answer. # (C)(i)(II): $3,000 does not exceed the $3,350 standard deduction. # No unearned income. basic_standard_deduction: 3_350 @@ -746,8 +748,10 @@ filing_status: JOINT # The head loses (iv): own gross income $5,000 >= $4,050. # Without that: limited basic standard deduction max($1,050, $5,350) = - # $5,350; (A) threshold 2 x $4,050 + $5,350 = $13,450 > $5,000; and - # (C)(i)(II): $5,000 does not exceed the $5,350 standard deduction. + # $5,350. The claimable spouse keeps (iv): $5,000 is under 2 x $0 + # ((D)(ii)) + $5,350. The formula's (A) threshold, 2 x $4,050 + $5,350 = + # $13,450, gives the same answer. And (C)(i)(II): $5,000 does not exceed + # the $5,350 standard deduction. basic_standard_deduction: 5_350 tax_unit_is_required_to_file: true @@ -772,16 +776,18 @@ output: filing_status: JOINT # The head loses (iv), but the head's own gross income $3,000 < $4,050. - # Combined gross income $5,000 would meet $4,050; (iv) compares only the - # head's own. The claimable spouse keeps (iv), since the head can't be - # claimed. Limited basic standard deduction: max($1,050, $5,000 + $350) - # = $5,350. (A) threshold 2 x $4,050 + $5,350 = $13,450 > $5,000. + # Combined gross income $5,000 would meet $4,050; once (iv) is gone, (A) + # compares only the head's own. The claimable spouse keeps (iv), since + # the head can't be claimed. Limited basic standard deduction: + # max($1,050, $5,000 + $350) = $5,350. The claimable spouse keeps (iv): + # $5,000 is under 2 x $0 ((D)(ii)) + $5,350. The formula's (A) + # threshold, 2 x $4,050 + $5,350 = $13,450, gives the same answer. # (C)(i)(II): $5,000 does not exceed the $5,350 standard deduction. # No unearned income. basic_standard_deduction: 5_350 tax_unit_is_required_to_file: false -- name: 6012(a)(1)(C)(ii), 2026 head of household aged 70 whose spouse files separately and itemizes +- name: 6012(a)(1)(C)(ii), 2026, separate_filer_itemizes withdraws the exception whatever the filing status period: 2026 input: filing_status: HEAD_OF_HOUSEHOLD @@ -789,9 +795,11 @@ separate_filer_itemizes: true employment_income: 1_000 output: - # Section 63(c)(6) makes the standard deduction zero, so (C)(ii) - # withdraws the (A) exception and the threshold is the $0 exemption - # amount: $1,000 >= $0. Without (C)(ii) the threshold would be $0 basic - # + $2,050 age addition, and $1,000 < $2,050. + # The model zeroes the basic standard deduction whenever this input is + # true, and the formula then withdraws the (A) exception: $1,000 >= $0. + # Without that the threshold would be $0 basic + $2,050 age addition. In + # law section 63(c)(6)(A) reaches only a married individual filing a + # separate return, and a head of household isn't married (sections 2(b)(1), + # 2(c), 63(g), 7703(b)), so this case pins the model's input rule. basic_standard_deduction: 0 tax_unit_is_required_to_file: true 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 cdffba15a65..0267330fddb 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 @@ -5,10 +5,11 @@ # 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, section 6012(f)(1) gives Chart -# B's result. Where the statute and the charts differ (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. +# B's result. Where the statute and the charts differ for 2018 to 2025 (the +# age and blindness addition in the joint test, a dependent filing +# separately), the cases stay clear of the difference. From 2026 the model +# follows section 6012(a)(1), and the 2026 section below pins the exact +# dollar. # 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 diff --git a/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_qualified_contributions.py b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_qualified_contributions.py index a32a91d2b65..423ff7bbf4f 100644 --- a/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_qualified_contributions.py +++ b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_qualified_contributions.py @@ -17,7 +17,7 @@ def formula(person, period, parameters): contributions = add( person, period, p.qualified_retirement_savings_contributions ) - # 25B(d)(2): from 2027, relevant ABLE distributions rather than - # retirement-plan distributions. + # 25B(d)(2): relevant ABLE distributions in every year, and + # retirement-plan distributions through 2026. distributions = add(person, period, p.distributions) return max_(contributions - distributions, 0) 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 59272998119..ea6d7fe339e 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 @@ -21,10 +21,11 @@ class tax_unit_is_required_to_file(Variable): claimed loses the (A)(iv) exception for the other spouse, who must file with own gross income at or above the exemption amount, and a filer whose standard deduction is zero under section 63(c)(6) loses the (A) - exception (section 6012(a)(1)(C)(ii)). Under section 6012(c), both - tests count gross section 911 exclusions: Form 2555 line 43 when leaf - inputs are supplied, otherwise the nonnegative legacy - foreign_earned_income_exclusion amount. + exception (section 6012(a)(1)(C)(ii)). Under section 6012(c), the + tax-unit tests count gross section 911 exclusions: Form 2555 line 43 when + leaf inputs are supplied, otherwise the nonnegative legacy + foreign_earned_income_exclusion amount. The (A)(iv) own gross income test + doesn't, because PE-US computes the exclusion only per tax unit. """ definition_period = YEAR reference = ( @@ -128,8 +129,8 @@ def formula(tax_unit, period, parameters): # # (iv) also applies only if the spouses "at the close of the taxable # year, had the same household as their home". PE-US has no year-end - # household input and puts a tax unit in one household, so the spouses - # of a joint return are treated as sharing one. + # household input and puts a tax unit in one household, so the model + # treats the spouses of a joint return as sharing one. # # The separate-return half of the exclusion needs no code: a married # individual filing separately already has no exception. @@ -154,10 +155,12 @@ def formula(tax_unit, period, parameters): # be claimed as a dependent in further cases; it does not replace # (A) for that filer. So the (A) and (B) threshold binds every filer, # and (C)(i)(II), gross income over the standard deduction, is added - # for a tax unit whose head or spouse can be claimed. Before 2018 the - # threshold used for (C)(i)(II) adds the exemption amount, which - # (C)(i)(II) doesn't, and section 6012(a)(1)(D)(ii) sets the exemption - # amount to zero for an individual described in section 151(d)(2). + # for a tax unit whose head or spouse can be claimed. Before 2018 this + # formula uses the full exemption amount in the (C)(i)(II) threshold, + # the (A) threshold and the own gross income test. (C)(i)(II) names no + # exemption amount, and section 6012(a)(1)(D)(ii) sets it to zero for + # an individual described in section 151(d)(2), so before 2018 the + # formula can excuse a claimable filer the statute requires to file. # (C)(i)(I), the unearned income test, is below. if p.income.filing_requirement.standard_deduction_rule.applies: income_over_threshold = income_over_standard_deduction diff --git a/policyengine_us/variables/household/expense/retirement/able_contributions_person.py b/policyengine_us/variables/household/expense/retirement/able_contributions_person.py index d6767140557..09925f3d8af 100644 --- a/policyengine_us/variables/household/expense/retirement/able_contributions_person.py +++ b/policyengine_us/variables/household/expense/retirement/able_contributions_person.py @@ -7,9 +7,11 @@ class able_contributions_person(Variable): label = "Person-level ABLE contributions" documentation = ( "Contributions each person made to an ABLE account. The saver's " - "credit (26 U.S.C. 25B(d)(1)) counts only contributions to the " - "person's own account as designated beneficiary; Arizona and " - "Colorado subtract ABLE contributions through able_contributions." + "credit (26 U.S.C. 25B(d)(1)) allows only contributions to the " + "person's own account as designated beneficiary, but PE-US has no " + "separate input for those, so its saver's credit counts this whole " + "amount. Arizona and Colorado subtract ABLE contributions through " + "able_contributions." ) reference = "https://www.law.cornell.edu/uscode/text/26/25B#d_1" unit = USD diff --git a/policyengine_us/variables/household/expense/retirement/savers_credit_relevant_able_distributions.py b/policyengine_us/variables/household/expense/retirement/savers_credit_relevant_able_distributions.py index c8596ba18a8..1f16c331b29 100644 --- a/policyengine_us/variables/household/expense/retirement/savers_credit_relevant_able_distributions.py +++ b/policyengine_us/variables/household/expense/retirement/savers_credit_relevant_able_distributions.py @@ -7,11 +7,11 @@ class savers_credit_relevant_able_distributions(Variable): label = "ABLE distributions that reduce saver's credit contributions" documentation = ( "Distributions from the person's ABLE account that reduce their " - "qualified contributions for the saver's credit from 2027 under 26 " - "U.S.C. 25B(d)(2): those received in the statutory testing period, " - "after the statute's exceptions and including any amount the spouse " - "rule attributes to the person. Distributions from retirement plans " - "and IRAs do not belong here." + "qualified contributions for the saver's credit under 26 U.S.C. " + "25B(d)(2): those received in the statutory testing period, after " + "the statute's exceptions and including any amount the spouse rule " + "attributes to the person. Distributions from retirement plans and " + "IRAs do not belong here." ) unit = USD definition_period = YEAR diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 025bb5dd42b..91a12d734d6 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -10363,10 +10363,9 @@ candidates: - policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py prs: - https://github.com/PolicyEngine/policyengine-us/pull/10135 + - https://github.com/PolicyEngine/policyengine-us/pull/10590 state: open - note: 'PE-US follows the statute: a married individual filing separately, claimable or not, must file with any - positive gross income, since the separate threshold is the exemption amount, zero while it is suspended (tax_unit_is_required_to_file.py). - Verify the married-dependent Chart B conditions and applicable years; the decision remains open.' + note: 'PE-US follows the statute: a married individual filing separately, claimable or not, must file with any positive gross income through 2025 and even with no gross income from 2026, since the separate threshold is the exemption amount, zero while it is suspended (tax_unit_is_required_to_file.py). Verify the married-dependent Chart B conditions and applicable years; the decision remains open.' - id: us:filing-claimable-spouse-unearned-income-additions program: income-tax years: @@ -10401,6 +10400,31 @@ candidates: on joint returns too, and the comment records that section 6012(f)(2)(D) names the 63(c)(5)(A) amount alone (tax_unit_is_required_to_file.py). The statute-first rule requires removing the extra amounts from this statutory test.' +- id: us:filing-requirement-2026-statute-over-chart-b + program: income-tax + years: + from: 2026 + type: tax + question: Does a claimable filer's section 6012(a)(1) threshold leave out blindness and require a return at equality, or does it add blindness and require a return only above the amount, as Chart B did under the same text? + sources: + - kind: statute + citation: 26 U.S.C. 6012(a)(1)(A)(i) and (B) + url: https://www.law.cornell.edu/uscode/text/26/6012#a_1 + - kind: instructions + citation: 2017 Form 1040 instructions, Chart B + url: null + readings: + statute: Only gross income less than the limited basic standard deduction plus the age addition excuses a return, so income equal to that amount requires one and blindness doesn't count. + other: Chart B adds blindness and requires a return only when gross income exceeds the amount. + example: A 2026 blind single filer who can be claimed and has $17,000 of wages must file under the statute, since $17,000 reaches the limited $16,100, and not under Chart B's method, since $17,000 doesn't exceed $18,150. + pe_us: + follows: statute + code: + - policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10590 + state: ruled + note: 'Ruled 2026-11-02 for the statute; PolicyEngine/policyengine-us#10590 holds the record. The (C)(i)(I) unearned income test keeps the additional standard deduction, which its text names (tax_unit_is_required_to_file.py).' - id: us:hsa-limit-indexing-windows program: income-tax years: @@ -10549,17 +10573,16 @@ candidates: readings: statute: With a zero exemption amount, the literal filing rule includes a separate filer with no gross income. other: IRS relief requires filing only at $5 or more of gross income. - example: The supplied 2026-or-later case for a separate filer aged 40 with no income returns false in the model. + example: In the model a separate filer aged 40 with no income must file in 2026 under section 6012(a)(1)(A) and need not file in 2025 under section 6012(f). pe_us: - follows: unknown + follows: statute code: - policyengine_us/variables/gov/irs/tax_unit_is_required_to_file.py prs: - https://github.com/PolicyEngine/policyengine-us/pull/10137 + - https://github.com/PolicyEngine/policyengine-us/pull/10590 state: open - note: PE-US requires a separate filer to file only with positive gross income, matching neither the literal statute, - which reaches a separate filer with none, nor the IRS's $5 relief (tax_unit_is_required_to_file.py). The statute-first - rule requires a model change, and no completed ruling date is recorded. + note: 'From 2026 PE-US requires a separate filer to file even with no gross income, since $0 equals the zero exemption amount in section 6012(a)(1)(A). For 2018 to 2025 it requires a return only with positive gross income, since section 6012(f) reaches "every individual who has gross income". It applies the IRS $5 relief in neither period (tax_unit_is_required_to_file.py). No completed ruling date is recorded.' - id: us:snap-bbce-income-chart-rounding program: snap years: From 549d2666e1c6775a9d4ab2b5f57083638c833ad3 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 3 Nov 2026 03:04:16 -0500 Subject: [PATCH 8/8] Move the (C)(i)(I) joint case to 2017 and pin 2026 under (A)(iv) #10297's 2026 joint case, a claimed spouse aged 65 with $2,000 of interest, expected no return. Under (A)(iv) the head must file in 2026, since the head's own $0 equals the zero exemption amount. Run the case in 2017, with its $4,050 exemption amount, and add a 2026 case with the same inputs that expects a return. Limit the docstring's zero-income rule for separate filers to 2026 on, record the 2026 ruling in the source-conflict note, and drop a repeated `joint` assignment. Co-Authored-By: Claude Opus 5.5 --- ...x_unit_is_required_to_file_dependents.yaml | 38 ++++++++++++++++--- .../gov/irs/tax_unit_is_required_to_file.py | 6 +-- source_conflicts/candidates.yaml | 2 +- 3 files changed, 36 insertions(+), 10 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 3b04b302389..ef59680dd8a 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 @@ -525,12 +525,15 @@ # "In excess of" $1,350: equal is not over. tax_unit_is_required_to_file: false -- name: 2026, section 6012(a)(1)(C)(i)(I) adds the additional standard deduction - # From 2026 the test is income other than earned income "in excess of the - # sum of the amount in effect under section 63(c)(5)(A) plus the additional - # standard deduction (if any) to which the individual is entitled", so the - # same $2,000 of interest does not require a return for the aged spouse. - period: 2026 +- name: 2017, an aged claimable spouse with $2,000 of interest need not file + # Under section 6012(a)(1)(C)(i)(I), a dependent must file when income other + # than earned income exceeds "the sum of the amount in effect under section + # 63(c)(5)(A) plus the additional standard deduction (if any) to which the + # individual is entitled". The spouse's $2,000 of interest doesn't exceed + # $1,050 plus the aged addition. The head loses (iv) but has $0, short of the + # $4,050 exemption amount. With a zero exemption amount in 2026, the head + # must file (next case). + period: 2017 input: people: head: @@ -549,6 +552,29 @@ output: tax_unit_is_required_to_file: false +- name: 2026, the same couple must file because the head loses (iv) + # The spouse's $2,000 of interest still doesn't exceed $1,350 plus the aged + # addition. But with a zero exemption amount, the head must file on $0 of + # own gross income. + period: 2026 + input: + people: + head: + age: 40 + spouse: + age: 65 + claimed_as_dependent_on_another_return: true + taxable_interest_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: 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 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 65d475175d3..18c25df18a4 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 @@ -12,8 +12,9 @@ class tax_unit_is_required_to_file(Variable): status. For 2018 to 2025, section 6012(f) compares gross income with the standard deduction. In other years, section 6012(a)(1) compares it with the basic standard deduction plus the additions for age, but not - blindness, and gross income equal to that amount requires a return; a - married individual filing separately must file even with no gross income. + blindness, and gross income equal to that amount requires a return. From + 2026, with a zero exemption amount, a married individual filing separately + must file even with no gross income. That test applies to every filer, including one who can be claimed as a dependent (with the section 63(c)(5) limited basic standard deduction), and section 6012(a)(1)(C) adds that filer's unearned income and standard @@ -231,7 +232,6 @@ def formula(tax_unit, period, parameters): additional_standard_deduction = aged_or_blind_count * tax_unit.project( p_standard.aged_or_blind.amount[filing_status] ) - joint = filing_status == filing_status.possible_values.JOINT statutory_joint_test = ( joint & p.income.filing_requirement.standard_deduction_rule.applies ) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 102d858a85f..1dea8c3ee33 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -10689,7 +10689,7 @@ candidates: - https://github.com/PolicyEngine/policyengine-us/pull/10137 - https://github.com/PolicyEngine/policyengine-us/pull/10590 state: open - note: 'From 2026 PE-US requires a separate filer to file even with no gross income, since $0 equals the zero exemption amount in section 6012(a)(1)(A). For 2018 to 2025 it requires a return only with positive gross income, since section 6012(f) reaches "every individual who has gross income". It applies the IRS $5 relief in neither period (tax_unit_is_required_to_file.py). No completed ruling date is recorded.' + note: 'Ruled 2026-11-03 for the statute from 2026. Congress limited section 6012(f) to 2018 through 2025, so under section 6012(a)(1)(A) a married individual filing separately must file even with no gross income: $0 equals the exemption amount, zero under section 151(d)(5)(A). The spouse who loses (A)(iv) and a filer whose standard deduction is zero under section 63(c)(6) face the same test, and PE-US applies it to all three (tax_unit_is_required_to_file.py). For 2018 to 2025 PE-US requires a return only with positive gross income, reading section 6012(f)''s "every individual who has gross income" to leave out $0; that half stays open. PE-US applies the IRS $5 relief in neither period.' - id: us:snap-bbce-income-chart-rounding program: snap years: