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..a30786a6855 --- /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, 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/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..f4a5e62f3b9 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/distributions.yaml @@ -0,0 +1,21 @@ +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 + - 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: + 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/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 3fd639bdc12..6acf429e044 100644 --- a/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py +++ b/policyengine_us/tests/core/test_filing_requirement_dependent_invariants.py @@ -13,9 +13,12 @@ spouse's income other than earned income against the section 63(c)(5)(A) amount alone, while Table 2 adds the additional standard deduction for age or blindness; the model follows the statute, and property 10 states it. Where -the statute and the IRS charts differ otherwise (a dependent filing -separately, and in 2026 blindness and the exact dollar), the tests below -assert nothing that depends on the difference. +the statute and the IRS charts differ otherwise for 2018-2025 (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 @@ -27,8 +30,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 @@ -52,8 +55,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 @@ -77,22 +80,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. @@ -507,18 +502,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. @@ -561,12 +551,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..5d2159cfd42 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,41 @@ 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. 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, 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 + 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 @@ -132,7 +161,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 @@ -236,8 +266,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 = [ @@ -391,6 +421,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 +440,12 @@ 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) + # 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 + ) ] ) np.testing.assert_array_equal( @@ -407,3 +453,84 @@ def results(variable, year): 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, 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)] + ), + 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]) + 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( + 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() + 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]) + 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/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..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 @@ -41,3 +41,72 @@ 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 + +- 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 f441bda36ee..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 @@ -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 @@ -294,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. @@ -315,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. @@ -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, @@ -592,3 +593,213 @@ # $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 + # 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. + 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. + # 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 + 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. 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 + +- 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; 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, separate_filer_itemizes withdraws the exception whatever the filing status + period: 2026 + input: + filing_status: HEAD_OF_HOUSEHOLD + age: 70 + separate_filer_itemizes: true + employment_income: 1_000 + output: + # 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 b557341e25a..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 @@ -9,9 +9,10 @@ # the unearned income test of a claimable spouse aged 65 or blind: section # 6012(f)(2)(D) names the section 63(c)(5)(A) amount alone, Chart B adds the # additional standard deduction, and the model follows the statute (cases -# below). Where they differ otherwise (a dependent filing separately, and in -# 2026 blindness and the exact dollar), the cases stay clear of the -# difference. +# below). Where they differ otherwise for 2018 to 2025 (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 @@ -524,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: @@ -548,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 @@ -673,8 +700,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 @@ -706,6 +733,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/credits/retirement_savings/savers_credit_qualified_contributions.py b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_qualified_contributions.py index f620c9fb1e2..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 @@ -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): 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 a50add6845e..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,12 +12,21 @@ 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 - tests count gross section 911 exclusions: Form 2555 line 43 when leaf - inputs are supplied, otherwise the nonnegative legacy - foreign_earned_income_exclusion amount. + 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 + 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), 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 = ( @@ -85,11 +94,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 +107,72 @@ 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) + # 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( + no_exception, 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). + # 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 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. + 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 + # 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 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 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 + | 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 @@ -142,7 +200,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) ) | ( @@ -175,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/policyengine_us/variables/household/expense/retirement/able_contributions_person.py b/policyengine_us/variables/household/expense/retirement/able_contributions_person.py index a30e0f04167..09925f3d8af 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,14 @@ 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 to an ABLE account. The saver's " + "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 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..1f16c331b29 --- /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 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", + ) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 27b8fe84152..9d6856a0433 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -10536,10 +10536,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: @@ -10574,6 +10573,31 @@ candidates: the section 63(c)(5)(A) amount alone, with no addition for age or blindness (tax_unit_is_required_to_file.py, statutory_joint_test). Other filing statuses, and every return from 2026, keep Table 2's addition. Register the entry and add the marker once #10297 is on main. +- 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: @@ -10760,17 +10784,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: '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: