From f2561e8da9f5f378ed27c81f345ea0e3abf55752 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 23:54:22 +0000 Subject: [PATCH 1/3] Leave dependents' benefits out of the sales tax table income and WATCA MAGI The Optional State Sales Tax Table row is read at "the amount shown on your Form 1040 or 1040-SR, line 11b, plus any nontaxable items" (2025 Schedule A instructions), and WATCA's section 1A(d)(2) adds "the portion of the taxpayer's social security benefits" that section 86 leaves out. Both measure the filers' own return. Both read tax_exempt_social_security, which carries a tax unit dependent's whole benefit, and the table income also summed every member's person-level nontaxable items with add(). A dependent's benefits are income of "the person who has the legal right to receive the benefits" (IRS Pub. 915, 2025). - state_sales_tax_income_bracket sums its sources with tax_unit_non_dep_add, and its source list uses filer_tax_exempt_social_security. TANF is an SPM-unit amount, which tax_unit_non_dep_add adds as add() does, as every tax unit member's per-person share, so a family grant paid to the caretaker still counts in full. - watca_alternative_tax_magi adds filer_tax_exempt_social_security. Co-Authored-By: Claude Opus 5.5 --- changelog.d/per-return-tax-exempt-ss.fixed.md | 1 + .../state_sales_tax_table/income_sources.yaml | 5 +- .../watca/working_americans_tax_cut_act.py | 18 ++- .../state_sales_tax_income_bracket.yaml | 138 ++++++++++++++++++ .../tests/policy/contrib/congress/watca.yaml | 54 +++++++ .../tests/test_state_sales_tax_table.py | 2 +- .../state_sales_tax_income_bracket.py | 22 ++- 7 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 changelog.d/per-return-tax-exempt-ss.fixed.md diff --git a/changelog.d/per-return-tax-exempt-ss.fixed.md b/changelog.d/per-return-tax-exempt-ss.fixed.md new file mode 100644 index 00000000000..5804c7df2a8 --- /dev/null +++ b/changelog.d/per-return-tax-exempt-ss.fixed.md @@ -0,0 +1 @@ +Leave a tax unit dependent's Social Security benefits and other nontaxable income out of the income that picks the federal Optional State Sales Tax Table row and out of the Working Americans' Tax Cut Act alternative tax modified AGI; both measure the filers' own return. diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_table/income_sources.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_table/income_sources.yaml index c3f01b309b2..aec4f8da5f9 100644 --- a/policyengine_us/parameters/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_table/income_sources.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_table/income_sources.yaml @@ -45,8 +45,9 @@ values: # Nontaxable combat pay - not available. - workers_compensation # Nontaxable part of social security and railroad retirement benefits; - # railroad retirement benefits are not included. - - tax_exempt_social_security + # railroad retirement benefits are not included. The head's and spouse's + # benefits only: a tax unit dependent's benefits are the dependent's own. + - filer_tax_exempt_social_security # Nontaxable IRA, pension, or annuity distributions (excluding rollovers). - tax_exempt_pension_income - tax_exempt_retirement_distributions diff --git a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py index 27becb3a73f..bf3528f5d65 100644 --- a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py +++ b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py @@ -16,15 +16,27 @@ class watca_alternative_tax_magi(Variable): label = "WATCA alternative tax modified AGI" definition_period = YEAR unit = USD - reference = WATCA_REFERENCES - documentation = "Section 1A(d): AGI + foreign income exclusions (sec 911, 931, 933) + non-taxable Social Security." + reference = WATCA_REFERENCES + [ + { + # Section 1A(d)(2): "the portion of the taxpayer's social + # security benefits (as defined in section 86(d)) which is + # not included in gross income under section 86". + "title": "S. 4083 (119th Congress), section 2(a), new section 1A(d)", + "href": "https://www.govinfo.gov/content/pkg/BILLS-119s4083is/html/BILLS-119s4083is.htm", + }, + ] + documentation = ( + "Section 1A(d): AGI + foreign income exclusions (sec 911, 931, 933) " + "+ the filers' non-taxable Social Security. A tax unit dependent's " + "benefits are the dependent's own and stay out." + ) adds = [ "adjusted_gross_income", "foreign_earned_income_exclusion", "specified_possession_income", "puerto_rico_income", - "tax_exempt_social_security", + "filer_tax_exempt_social_security", ] class watca_surtax_magi(Variable): diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.yaml index 1bad25242fc..30d05487d03 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.yaml @@ -85,3 +85,141 @@ adjusted_gross_income: 300_000 output: state_sales_tax_income_bracket: 19 + +# Income is "the amount shown on your Form 1040 or 1040-SR, line 11b, plus any +# nontaxable items" (2025 Instructions for Schedule A). A tax unit dependent's +# benefits and nontaxable income are the dependent's own, not the filers'. + +- name: A dependent child's Social Security stays out of the filer's income + period: 2025 + input: + people: + parent: + age: 40 + employment_income: 10_000 + social_security_disability: 18_000 + child: + age: 10 + is_tax_unit_dependent: true + social_security_dependents: 6_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: TX + output: + # Combined income of 10_000 + 18_000 / 2 = 19_000 is below the $25,000 + # head of household base amount, so none of the parent's benefit is + # taxable. + adjusted_gross_income: 10_000 + filer_tax_exempt_social_security: 18_000 + # The household amount carries the child's benefit as well. + tax_exempt_social_security: 24_000 + # 10_000 + 18_000 = 28_000 is in the $20,000-$30,000 row. Counting the + # child's 6_000 would give 34_000, the $30,000-$40,000 row. + state_sales_tax_income_bracket: 2 + +- name: A dependent parent's nontaxable income stays out of the filer's income + period: 2025 + input: + people: + filer: + age: 50 + employment_income: 25_000 + dependent_parent: + age: 80 + is_tax_unit_dependent: true + social_security_retirement: 12_000 + ssi: 4_800 + veterans_benefits: 4_000 + tax_exempt_pension_income: 3_000 + tax_exempt_retirement_distributions: 2_000 + workers_compensation: 1_500 + tax_exempt_interest_income: 500 + tax_units: + tax_unit: + members: [filer, dependent_parent] + households: + household: + members: [filer, dependent_parent] + state_code: TX + output: + adjusted_gross_income: 25_000 + filer_tax_exempt_social_security: 0 + # 25_000 is in the $20,000-$30,000 row. Counting the parent's 27_800 of + # benefits and nontaxable income would give 52_800, the $50,000-$60,000 + # row. + state_sales_tax_income_bracket: 2 + +- name: The filers' own nontaxable items still count beside a dependent's + period: 2025 + input: + people: + head: + age: 70 + is_tax_unit_head: true + social_security_retirement: 20_000 + tax_exempt_interest_income: 4_000 + veterans_benefits: 3_000 + spouse: + age: 68 + is_tax_unit_spouse: true + employment_income: 15_000 + ssi: 2_400 + tax_exempt_pension_income: 6_000 + grandchild: + age: 12 + is_tax_unit_dependent: true + social_security_survivors: 9_000 + ssi: 7_200 + tax_exempt_interest_income: 1_000 + tax_units: + tax_unit: + members: [head, spouse, grandchild] + filing_status: JOINT + households: + household: + members: [head, spouse, grandchild] + state_code: TX + output: + # Combined income: 15_000 + 4_000 of tax-exempt interest + 20_000 / 2 = + # 29_000, below the $32,000 joint base amount, so no benefit is taxable. + adjusted_gross_income: 15_000 + filer_tax_exempt_social_security: 20_000 + # 15_000 + 4_000 + 3_000 + 20_000 + 2_400 + 6_000 = 50_400 is in the + # $50,000-$60,000 row. Counting the grandchild's 17_200 would give + # 67_600, the $60,000-$70,000 row. + state_sales_tax_income_bracket: 5 + +- name: A family's TANF counts in full, the children's shares included + period: 2025 + input: + people: + parent: + age: 30 + employment_income: 15_000 + child_1: + age: 6 + is_tax_unit_dependent: true + child_2: + age: 3 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child_1, child_2] + spm_units: + spm_unit: + members: [parent, child_1, child_2] + tanf: 6_000 + households: + household: + members: [parent, child_1, child_2] + state_code: TX + output: + # TANF is paid to the caretaker for the assistance unit, and the tax + # unit holds the whole SPM unit, so all 6_000 counts: 15_000 + 6_000 = + # 21_000 is in the $20,000-$30,000 row. The parent's per-person share + # alone (2_000) would give 17_000, the first row. + state_sales_tax_income_bracket: 2 diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index 2b40103ff7f..5635dff0c54 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -353,6 +353,60 @@ # MAGI = 50_000 + (20_000 - 5_000) = 65_000 watca_alternative_tax_magi: 65_000 +- name: A dependent's Social Security stays out of the alternative tax MAGI + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + people: + parent: + age: 45 + employment_income: 50_000 + child: + age: 10 + is_tax_unit_dependent: true + social_security_survivors: 10_000 + tax_units: + tax_unit: + members: [parent, child] + filing_status: HEAD_OF_HOUSEHOLD + output: + # Section 1A(d) adds the taxpayer's own nontaxable benefits. The child's + # 10_000 is the child's, on the child's own return, so MAGI is the + # parent's AGI: 50_000, not 60_000. + tax_exempt_social_security: 10_000 + filer_tax_exempt_social_security: 0 + watca_alternative_tax_magi: 50_000 + +- name: A dependent's Social Security does not cost the filer eligibility + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + people: + parent: + age: 67 + employment_income: 100_000 + social_security_retirement: 12_000 + child: + age: 10 + is_tax_unit_dependent: true + social_security_dependents: 10_000 + tax_units: + tax_unit: + members: [parent, child] + filing_status: HEAD_OF_HOUSEHOLD + output: + # Combined income 100_000 + 12_000 / 2 = 106_000 is above the $34,000 + # adjusted base amount, so the taxable part is the 85% cap: 10_200. + # AGI = 110_200 and the parent's nontaxable benefits are 1_800. + adjusted_gross_income: 110_200 + filer_tax_exempt_social_security: 1_800 + # MAGI = 110_200 + 1_800 = 112_000, below the 64_400 * 1.75 = 112_700 + # head of household limit. Adding the child's 10_000 would give 122_000. + watca_alternative_tax_magi: 112_000 + watca_alternative_tax_eligible: true + - name: Alternative tax MAGI includes specified possession income period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object diff --git a/policyengine_us/tests/test_state_sales_tax_table.py b/policyengine_us/tests/test_state_sales_tax_table.py index f23bdeacc93..4a20692f60b 100644 --- a/policyengine_us/tests/test_state_sales_tax_table.py +++ b/policyengine_us/tests/test_state_sales_tax_table.py @@ -101,7 +101,7 @@ "tax_exempt_interest_income", "veterans_benefits", "workers_compensation", - "tax_exempt_social_security", + "filer_tax_exempt_social_security", "tax_exempt_pension_income", "tax_exempt_retirement_distributions", "tanf", diff --git a/policyengine_us/variables/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.py b/policyengine_us/variables/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.py index 880c78fc7ee..49b8bfee9ab 100644 --- a/policyengine_us/variables/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.py +++ b/policyengine_us/variables/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.py @@ -6,10 +6,30 @@ class state_sales_tax_income_bracket(Variable): entity = TaxUnit definition_period = YEAR label = "State Sales Tax Income Bracket" + documentation = ( + "The Optional State Sales Tax Table income row for the filers' income: " + "their adjusted gross income plus their own nontaxable items. A tax " + "unit dependent's benefits and nontaxable income, such as a child's " + "Social Security or SSI, are the dependent's own and stay out. TANF is " + "an SPM unit amount, so every tax unit member's per-person share of it " + "counts, the dependents' included." + ) + reference = ( + # "Your 2025 income is the amount shown on your Form 1040 or 1040-SR, + # line 11b, plus any nontaxable items". + "https://www.irs.gov/pub/irs-prior/i1040sca--2025.pdf#page=5", + # IRS Pub. 915 (2025), "Who is taxed": benefits are income of "the + # person who has the legal right to receive the benefits". + "https://www.irs.gov/pub/irs-prior/p915--2025.pdf#page=5", + ) def formula(tax_unit, period, parameters): p = parameters( period ).gov.irs.deductions.itemized.salt_and_real_estate.state_sales_tax_table - income = add(tax_unit, period, p.income_sources) + # Person-level items are summed over the head and spouse. Tax-unit + # sources (adjusted gross income, the filers' nontaxable Social + # Security) are the filers' own already, and SPM-unit TANF is added + # as each member's per-person share. + income = tax_unit_non_dep_add(tax_unit, period, p.income_sources) return p.income_bracket.calc(income) From 16718d329e10a742837923c2aab82bcf5404b4aa Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 00:25:10 +0000 Subject: [PATCH 2/3] Leave dependents' benefits out of Colorado and Maine filer income; add a property test Colorado's sales tax refund adds to federal AGI the "Social security benefits excluded from federal adjusted gross income" (C.R.S. 39-22-2003(1.5)(b)), measured as "the qualified individual's adjusted gross income" ((3)(c)); DR 0104 line 35 is the filers' Form 1040 line 6a less 6b. Maine's fairness credits add the interest and Social Security left out of federal AGI (36 M.R.S. 5213-A(1)(B), 5219-KK(1)(D)), and Rule 813 .03 makes it "the taxpayer's". Both read tax_exempt_social_security, which carries a dependent's whole benefit, and Maine also summed every member's tax-exempt interest. - co magi_sources and me income_sources use filer_tax_exempt_social_security; Maine's income sums its sources with tax_unit_non_dep_add. - Colorado's 2025 Book 104 reference pointed at page 23 (military subtraction text); the MAGI lines are on pages 13 and 42. - A property test draws filers and one to three dependents, children or aged parents, with benefits from $1 to $1,000,000, and checks that the per-return readers are unchanged when the dependents' amounts are zero, as drawn or doubled. The table amount, the refund and the credits are checked for units with child dependents only: an aged parent's own income can decide whether the filer heads a household for them. - WATCA gets the same property as a YAML grid over the child's benefit, so CI builds no extra reformed system. - The grandchild bracket case now fails under each half of the sales tax fix alone, not only both together. Co-Authored-By: Claude Opus 5.5 --- changelog.d/per-return-tax-exempt-ss.fixed.md | 2 +- .../sales_tax_refund/magi_sources.yaml | 13 +- .../credits/fairness/income_sources.yaml | 12 +- ...t_social_security_per_return_invariants.py | 335 ++++++++++++++++++ .../state_sales_tax_income_bracket.yaml | 11 +- .../sales_tax_refund/co_modified_agi.yaml | 68 ++++ ...d_property_tax_fairness_credit_income.yaml | 71 ++++ .../tests/policy/contrib/congress/watca.yaml | 35 ++ ...and_property_tax_fairness_credit_income.py | 18 +- 9 files changed, 551 insertions(+), 14 deletions(-) create mode 100644 policyengine_us/tests/core/test_dependent_social_security_per_return_invariants.py create mode 100644 policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_modified_agi.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.yaml diff --git a/changelog.d/per-return-tax-exempt-ss.fixed.md b/changelog.d/per-return-tax-exempt-ss.fixed.md index 5804c7df2a8..d2647fa7425 100644 --- a/changelog.d/per-return-tax-exempt-ss.fixed.md +++ b/changelog.d/per-return-tax-exempt-ss.fixed.md @@ -1 +1 @@ -Leave a tax unit dependent's Social Security benefits and other nontaxable income out of the income that picks the federal Optional State Sales Tax Table row and out of the Working Americans' Tax Cut Act alternative tax modified AGI; both measure the filers' own return. +Leave a tax unit dependent's Social Security benefits and other nontaxable income out of income measures that describe the filers' own return: the federal Optional State Sales Tax Table income, the Working Americans' Tax Cut Act alternative tax modified AGI, Colorado's sales tax refund modified AGI and Maine's sales and property tax fairness credit income. diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/magi_sources.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/magi_sources.yaml index 9779bbf72d9..de0b81e062f 100644 --- a/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/magi_sources.yaml +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/sales_tax_refund/magi_sources.yaml @@ -2,20 +2,25 @@ description: Colorado accounts for these income sources for the modified adjuste values: 1999-01-01: - adjusted_gross_income - - tax_exempt_social_security + # "Social security benefits excluded from federal adjusted gross income": + # the filers' own, Form 1040 line 6a less line 6b (DR 0104 line 35). A tax + # unit dependent's benefits are the dependent's own. + - filer_tax_exempt_social_security # - non_taxable_interest_income_from_state_and_local_bonds metadata: period: year unit: list label: Colorado modified adjusted gross income sources reference: - - title: Colo. Code Regs. 39-22-2003 State Sales Tax Refund (1.5) + - title: C.R.S. 39-22-2003(1.5) href: https://law.justia.com/codes/colorado/2022/title-39/article-22/part-20/section-39-22-2003/ + - title: "Colorado Revised Statutes 2024, Title 39, C.R.S. 39-22-2003(1.5)(b) and (3)(c)-(d) #page 760-762" + href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf#page=760 - title: 2021 Colorado Individual Income Tax Filing Guide href: https://tax.colorado.gov/sites/tax/files/documents/DR_104_Book_2021.pdf#page=21 - title: 2022 Colorado Individual Income Tax Filing Guide href: https://tax.colorado.gov/sites/tax/files/documents/DR_104_Book_2022.pdf#page=23 - title: 2024 Colorado State Sales Tax Refund href: https://tax.colorado.gov/income-tax-topics-state-sales-tax-refund - - title: 2025 Colorado Individual Tax Filing Guide 104 Book - href: https://tax.colorado.gov/sites/tax/files/documents/Book104_2025.pdf#page=23 + - title: "2025 Colorado Individual Income Tax Filing Guide (Book 104), DR 0104 lines 34-37 #page 13,42" + href: https://tax.colorado.gov/sites/tax/files/documents/Book104_2025.pdf#page=13 diff --git a/policyengine_us/parameters/gov/states/me/tax/income/credits/fairness/income_sources.yaml b/policyengine_us/parameters/gov/states/me/tax/income/credits/fairness/income_sources.yaml index 69511996e9f..ae4a71b7c17 100644 --- a/policyengine_us/parameters/gov/states/me/tax/income/credits/fairness/income_sources.yaml +++ b/policyengine_us/parameters/gov/states/me/tax/income/credits/fairness/income_sources.yaml @@ -2,13 +2,21 @@ description: Maine accounts for the following income sources for the sales and p values: 2021-01-01: - irs_gross_income - - tax_exempt_social_security + # Social Security "to the extent not included in federal adjusted gross + # income": the filers' own, Form 1040 line 6a less line 6b (Schedule + # PTFC/STFC line 2b). A tax unit dependent's benefits are the dependent's + # own. + - filer_tax_exempt_social_security - tax_exempt_interest_income metadata: unit: list label: Maine sales and property tax credit income sources reference: - - title: §5213-A. Sales tax fairness credit (1) (B) + - title: §5213-A. Sales tax fairness credit (1) (B) href: https://legislature.maine.gov/statutes/36/title36sec5213-A.html - title: §5219-KK. Property tax fairness credit for tax (1) (D) href: https://legislature.maine.gov/statutes/36/title36sec5219-KK.html + - title: "Maine Revenue Services Rule 813, Property Tax Fairness Credit, .01(C) and .03 #page 1,4" + href: https://www.maine.gov/revenue/sites/maine.gov.revenue/files/inline-files/Rule_813_May_2023.pdf#page=1 + - title: 2025 Form 1040ME Schedule PTFC/STFC, lines 2a-3 + href: https://www.maine.gov/revenue/sites/maine.gov.revenue/files/inline-files/25_1040me_sch_ptfc_fillable.pdf#page=1 diff --git a/policyengine_us/tests/core/test_dependent_social_security_per_return_invariants.py b/policyengine_us/tests/core/test_dependent_social_security_per_return_invariants.py new file mode 100644 index 00000000000..dcec787d462 --- /dev/null +++ b/policyengine_us/tests/core/test_dependent_social_security_per_return_invariants.py @@ -0,0 +1,335 @@ +"""Invariants for a tax unit dependent's Social Security in per-return readers. + +Social Security benefits are income of "the person who has the legal right to +receive the benefits" (IRS Pub. 915, 2025, "Who is taxed"), so a tax unit +dependent's benefits belong on the dependent's own return. +`filer_tax_exempt_social_security` is the nontaxable part of the head's and +spouse's benefits, and the readers in READERS measure the filers' own return: + +- `state_sales_tax_income_bracket` and `state_sales_tax`: the Optional State + Sales Tax Table row is read at "the amount shown on your Form 1040 or + 1040-SR, line 11b, plus any nontaxable items" (2025 Schedule A + instructions). A dependent's other nontaxable items (SSI, tax-exempt + interest, veterans' benefits, workers' compensation, nontaxable pensions + and retirement distributions) are the dependent's own too. +- `co_modified_agi` and `co_sales_tax_refund`: C.R.S. 39-22-2003(1.5)(b) + adds the "Social security benefits excluded from federal adjusted gross + income", and subsection (3) measures the qualified individuals' own income. +- `me_sales_and_property_tax_fairness_credit_income` and the two credits + that read it: 36 M.R.S. 5213-A(1)(B) and 5219-KK(1)(D) add interest and + Social Security left out of federal adjusted gross income, and Rule 813 + .03 makes the income "the taxpayer's". + +`tax_exempt_social_security`, the household amount, still carries a +dependent's whole benefit for the readers whose law counts a household's +income. + +AMOUNTS are what those readers feed: the table amount, the refund and the +two credits. They also read the filing status and who counts as a +dependent, and a parent's or other relative's own income can decide that: +such a dependent must pass a gross income test, and a filer heads a +household for a parent only if the parent is the filer's dependent. So the +amounts are checked for the units whose dependents are all children. + +The tests draw a seeded random population of tax units: a single filer or a +married couple filing jointly, whose head and spouse have wages, Social +Security and the nontaxable items above, and whose head may pay rent, with +one to three dependents, each a child or an aged parent. A dependent draws benefits of $1 to $1,000,000, +log-uniformly, and the other nontaxable items, each of them zero about half +the time. Amounts are whole dollars and stay below 2**24, so their float32 +sums, and the subtraction that takes dependents' benefits out of the tax +unit's, are exact. Every unit is placed in each state in READERS, and each +scenario runs as one vectorized simulation. + +1. Each reader is the same whether the dependents' benefits and nontaxable + items are zero, as drawn, or doubled, and so is each amount for the units + whose dependents are all children. +2. The household amount moves with the dependents' benefits, so the + population exercises the property. +3. Moving the dependents' benefits and items onto the head changes each + reader and each amount for some units, so they count the filers' own + amounts. +""" + +import numpy as np +import pytest + +from policyengine_us import Simulation + +SEED = 20261102 +YEAR = 2025 +N_UNITS = 40 +TOLERANCE = 0.01 # dollars +# The readers that measure the filers' own return, by the state each runs in. +READERS = { + "TX": ["filer_tax_exempt_social_security", "state_sales_tax_income_bracket"], + "CO": ["co_modified_agi"], + "ME": ["me_sales_and_property_tax_fairness_credit_income"], +} +# What the readers feed, checked for units whose dependents are all children. +AMOUNTS = { + "TX": ["state_sales_tax"], + "CO": ["co_sales_tax_refund"], + "ME": ["me_sales_tax_fairness_credit", "me_property_tax_fairness_credit"], +} +STATES = list(READERS) +CHILD_AGE_LIMIT = 18 +# Person-level nontaxable items the sales tax table income counts. +NONTAXABLE_ITEMS = [ + "ssi", + "tax_exempt_interest_income", + "veterans_benefits", + "workers_compensation", + "tax_exempt_pension_income", + "tax_exempt_retirement_distributions", +] +SCENARIOS = {"zero": 0, "as_drawn": 1, "doubled": 2} + + +def _draw_units(n, rng): + def maybe(high): + # Zero about half the time, otherwise a whole-dollar amount. + return float(rng.choice([0, rng.integers(1, high)])) + + def filer(age): + if age >= 62: + benefit = "social_security_retirement" + else: + benefit = "social_security_disability" + return { + "age": age, + "employment_income": maybe(120_000), + "benefit": benefit, + "social_security": maybe(40_000), + "items": {item: maybe(8_000) for item in NONTAXABLE_ITEMS}, + } + + def dependent(): + aged_parent = bool(rng.random() < 0.3) + benefit = ( + "social_security_retirement" + if aged_parent + else str( + rng.choice(["social_security_survivors", "social_security_dependents"]) + ) + ) + # Log-uniform from $1 to $1,000,000. + amount = float(np.floor(10 ** rng.uniform(0, 6))) + return { + "age": int(rng.integers(70, 90)) + if aged_parent + else int(rng.integers(0, 18)), + "benefit": benefit, + "social_security": amount, + "items": {item: maybe(20_000) for item in NONTAXABLE_ITEMS}, + } + + units = [] + for _ in range(n): + joint = bool(rng.random() < 0.5) + head_age = int(rng.integers(25, 85)) + units.append( + { + "joint": joint, + "rent": maybe(24_000), + "head": filer(head_age), + "spouse": filer(int(rng.integers(25, 85))) if joint else None, + "dependents": [dependent() for _ in range(int(rng.integers(1, 4)))], + } + ) + return units + + +def _amounts(person, scale=1): + """A person's benefit and nontaxable items, each multiplied by scale.""" + amounts = {person["benefit"]: {YEAR: scale * person["social_security"]}} + for item, value in person["items"].items(): + amounts[item] = {YEAR: scale * value} + return amounts + + +def _moved_to_head(unit): + """The unit's head with every dependent's benefit and items added.""" + head = {**unit["head"], "items": dict(unit["head"]["items"])} + for dependent in unit["dependents"]: + head["social_security"] += dependent["social_security"] + for item, value in dependent["items"].items(): + head["items"][item] += value + return head + + +def _situation(units, scale=1, moved_to_head=False): + people, tax_units, marital_units, spm_units = {}, {}, {}, {} + families, households = {}, {} + for state in STATES: + for i, unit in enumerate(units): + key = f"{state}_{i}" + head = f"head_{key}" + members = [head] + couple = [head] + head_amounts = _moved_to_head(unit) if moved_to_head else unit["head"] + people[head] = { + "age": {YEAR: unit["head"]["age"]}, + "is_tax_unit_head": {YEAR: True}, + "is_tax_unit_spouse": {YEAR: False}, + "is_tax_unit_dependent": {YEAR: False}, + "employment_income": {YEAR: unit["head"]["employment_income"]}, + "rent": {YEAR: unit["rent"]}, + **_amounts(head_amounts), + } + if unit["spouse"] is not None: + spouse = f"spouse_{key}" + members.append(spouse) + couple.append(spouse) + people[spouse] = { + "age": {YEAR: unit["spouse"]["age"]}, + "is_tax_unit_head": {YEAR: False}, + "is_tax_unit_spouse": {YEAR: True}, + "is_tax_unit_dependent": {YEAR: False}, + "employment_income": {YEAR: unit["spouse"]["employment_income"]}, + **_amounts(unit["spouse"]), + } + marital_units[f"mu_{key}"] = {"members": couple} + for j, dependent in enumerate(unit["dependents"]): + person = f"dependent_{key}_{j}" + members.append(person) + marital_units[f"mu_{key}_{j}"] = {"members": [person]} + people[person] = { + "age": {YEAR: dependent["age"]}, + "is_tax_unit_head": {YEAR: False}, + "is_tax_unit_spouse": {YEAR: False}, + "is_tax_unit_dependent": {YEAR: True}, + **_amounts(dependent, scale=0 if moved_to_head else scale), + } + tax_units[f"tu_{key}"] = {"members": members} + spm_units[f"spm_{key}"] = {"members": members} + families[f"fam_{key}"] = {"members": members} + households[f"hh_{key}"] = { + "members": members, + "state_code": {YEAR: state}, + } + return { + "people": people, + "tax_units": tax_units, + "marital_units": marital_units, + "spm_units": spm_units, + "families": families, + "households": households, + } + + +def _state_slice(state): + start = STATES.index(state) * N_UNITS + return slice(start, start + N_UNITS) + + +def _child_dependents_only(units): + return np.array( + [all(d["age"] < CHILD_AGE_LIMIT for d in u["dependents"]) for u in units] + ) + + +@pytest.fixture(scope="module") +def population(): + rng = np.random.default_rng(SEED) + units = _draw_units(N_UNITS, rng) + simulations = { + name: Simulation(situation=_situation(units, scale=scale)) + for name, scale in SCENARIOS.items() + } + simulations["moved_to_head"] = Simulation( + situation=_situation(units, moved_to_head=True) + ) + return units, simulations + + +def test_population_draws_exact_whole_dollar_amounts(population): + units, _ = population + amounts = [ + value + for unit in units + for person in [unit["head"], unit["spouse"], *unit["dependents"]] + if person is not None + for value in [person["social_security"], *person["items"].values()] + ] + assert all(value == int(value) for value in amounts) + # Doubled, a unit's benefits and items still sum below 2**24. + totals = [ + sum( + SCENARIOS["doubled"] + * (person["social_security"] + sum(person["items"].values())) + for person in [unit["head"], unit["spouse"], *unit["dependents"]] + if person is not None + ) + for unit in units + ] + assert max(totals) < 2**24 + + +def test_population_has_units_with_child_and_parent_dependents(population): + units, _ = population + children_only = _child_dependents_only(units) + assert 10 <= children_only.sum() <= len(units) - 10 + + +@pytest.mark.parametrize("scenario", ["as_drawn", "doubled"]) +def test_dependents_benefits_leave_per_return_readers_unchanged(population, scenario): + units, simulations = population + assert any(d["social_security"] for u in units for d in u["dependents"]) + for state, readers in READERS.items(): + rows = _state_slice(state) + for reader in readers: + np.testing.assert_allclose( + simulations[scenario].calculate(reader, YEAR)[rows], + simulations["zero"].calculate(reader, YEAR)[rows], + atol=TOLERANCE, + err_msg=f"{state} {reader} {scenario}", + ) + + +@pytest.mark.parametrize("scenario", ["as_drawn", "doubled"]) +def test_child_dependents_benefits_leave_amounts_unchanged(population, scenario): + units, simulations = population + children_only = _child_dependents_only(units) + for state, amounts in AMOUNTS.items(): + rows = _state_slice(state) + for amount in amounts: + np.testing.assert_allclose( + simulations[scenario].calculate(amount, YEAR)[rows][children_only], + simulations["zero"].calculate(amount, YEAR)[rows][children_only], + atol=TOLERANCE, + err_msg=f"{state} {amount} {scenario}", + ) + + +def test_household_amount_carries_dependents_benefits(population): + units, simulations = population + dependents_benefits = np.array( + [sum(d["social_security"] for d in u["dependents"]) for u in units] + ) + for state in STATES: + rows = _state_slice(state) + # The household amount subtracts the filers' fractional taxable + # benefits from a total of up to a few million dollars, so it is + # exact only to float32's relative precision. + np.testing.assert_allclose( + simulations["as_drawn"].calculate("tax_exempt_social_security", YEAR)[rows] + - simulations["zero"].calculate("tax_exempt_social_security", YEAR)[rows], + dependents_benefits, + rtol=1e-6, + atol=TOLERANCE, + ) + + +def test_readers_and_amounts_count_the_filers_own_amounts(population): + units, simulations = population + children_only = _child_dependents_only(units) + for state in STATES: + rows = _state_slice(state) + checks = [(reader, slice(None)) for reader in READERS[state]] + checks += [(amount, children_only) for amount in AMOUNTS[state]] + for variable, units_checked in checks: + moved = simulations["moved_to_head"].calculate(variable, YEAR)[rows] + kept = simulations["as_drawn"].calculate(variable, YEAR)[rows] + changed = np.abs(moved - kept)[units_checked] > TOLERANCE + assert changed.any(), f"{state} {variable}" diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.yaml index 30d05487d03..80b7f7e3d1f 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_income_bracket.yaml @@ -168,11 +168,11 @@ is_tax_unit_spouse: true employment_income: 15_000 ssi: 2_400 - tax_exempt_pension_income: 6_000 + tax_exempt_pension_income: 11_000 grandchild: age: 12 is_tax_unit_dependent: true - social_security_survivors: 9_000 + social_security_survivors: 10_000 ssi: 7_200 tax_exempt_interest_income: 1_000 tax_units: @@ -188,9 +188,10 @@ # 29_000, below the $32,000 joint base amount, so no benefit is taxable. adjusted_gross_income: 15_000 filer_tax_exempt_social_security: 20_000 - # 15_000 + 4_000 + 3_000 + 20_000 + 2_400 + 6_000 = 50_400 is in the - # $50,000-$60,000 row. Counting the grandchild's 17_200 would give - # 67_600, the $60,000-$70,000 row. + # 15_000 + 4_000 + 3_000 + 20_000 + 2_400 + 11_000 = 55_400 is in the + # $50,000-$60,000 row. The grandchild's 10_000 of benefits alone would + # give 65_400, and the grandchild's 7_200 of SSI and 1_000 of interest + # alone 63_600, both in the $60,000-$70,000 row. state_sales_tax_income_bracket: 5 - name: A family's TANF counts in full, the children's shares included diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_modified_agi.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_modified_agi.yaml new file mode 100644 index 00000000000..78cbd79283f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/sales_tax_refund/co_modified_agi.yaml @@ -0,0 +1,68 @@ +# C.R.S. 39-22-2003(1.5)(b) adds to federal adjusted gross income the "Social +# security benefits excluded from federal adjusted gross income for the tax +# year", and subsection (3) measures the qualified individuals' own income. +# DR 0104 line 35 is the filers' Form 1040 line 6a less line 6b (2025 Book 104, +# page 13). A tax unit dependent's benefits are the dependent's own. Refund +# amounts are from the 2025 DR 0104 line 38 table (Book 104, page 42): $19 for +# single filers and $38 for joint filers with modified AGI of $52,000 or less, +# $25 and $50 from $52,001 to $105,000. + +- name: A dependent child's Social Security stays out of the parent's modified AGI + period: 2025 + input: + people: + parent: + age: 40 + employment_income: 50_000 + child: + age: 10 + is_tax_unit_dependent: true + social_security_survivors: 6_000 + tax_units: + tax_unit: + members: [parent, child] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [parent, child] + state_code: CO + output: + adjusted_gross_income: 50_000 + filer_tax_exempt_social_security: 0 + # 50_000, not 50_000 + 6_000 = 56_000. + co_modified_agi: 50_000 + # $19, not the $25 that 56_000 would give. + co_sales_tax_refund: 19 + +- name: Joint filers' own nontaxable Social Security counts, a dependent's does not + period: 2025 + input: + people: + head: + age: 70 + employment_income: 10_000 + social_security_retirement: 30_000 + spouse: + age: 68 + social_security_retirement: 10_000 + grandchild: + age: 16 + is_tax_unit_dependent: true + social_security_survivors: 9_000 + tax_units: + tax_unit: + members: [head, spouse, grandchild] + filing_status: JOINT + households: + household: + members: [head, spouse, grandchild] + state_code: CO + output: + # Combined income of 10_000 + (30_000 + 10_000) / 2 = 30_000 is below the + # $32,000 joint base amount, so none of the couple's benefits is taxable. + adjusted_gross_income: 10_000 + filer_tax_exempt_social_security: 40_000 + # 10_000 + 40_000 = 50_000, not 59_000 with the grandchild's 9_000. + co_modified_agi: 50_000 + # $38 for joint filers, not the $50 that 59_000 would give. + co_sales_tax_refund: 38 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.yaml new file mode 100644 index 00000000000..627a703307c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.yaml @@ -0,0 +1,71 @@ +# 36 M.R.S. 5213-A(1)(B) and 5219-KK(1)(D): income is federal adjusted gross +# income increased by interest and "Payments received under the federal Social +# Security Act ... to the extent not included in federal adjusted gross +# income". Rule 813 .03 makes it "the taxpayer's". Schedule PTFC/STFC line 2b +# is the filers' Form 1040 line 6a less line 6b. A tax unit dependent's +# benefits and interest are the dependent's own. Credit amounts follow the +# 2025 Schedule PTFC/STFC. + +- name: A dependent child's Social Security and interest stay out of the parent's income + period: 2025 + input: + people: + parent: + age: 40 + employment_income: 36_000 + rent: 12_000 + child: + age: 10 + is_tax_unit_dependent: true + social_security_survivors: 6_000 + tax_exempt_interest_income: 600 + tax_units: + tax_unit: + members: [parent, child] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [parent, child] + state_code: ME + output: + # Line 3: the parent's 36_000, not 36_000 + 6_000 + 600 = 42_600. + me_sales_and_property_tax_fairness_credit_income: 36_000 + # Line 17, head of household with one child: $215 up to $38,200 ($125 + # from $41,951 to $42,700). + me_sales_tax_fairness_credit: 215 + # Line 5e: 15% of 12_000 = 1_800; line 9: min(1_800, 3_300) = 1_800; + # line 10: 4% of 36_000 = 1_440; line 13: min(360, 1_000) = 360 (with + # 42_600: 1_800 - 1_704 = 96). + me_property_tax_fairness_credit: 360 + +- name: Joint filers' own nontaxable Social Security counts, a dependent's does not + period: 2025 + input: + people: + head: + age: 66 + social_security_retirement: 20_000 + real_estate_taxes: 2_500 + spouse: + age: 60 + child: + age: 12 + is_tax_unit_dependent: true + social_security_dependents: 5_000 + tax_units: + tax_unit: + members: [head, spouse, child] + filing_status: JOINT + households: + household: + members: [head, spouse, child] + state_code: ME + output: + # Combined income of 20_000 / 2 = 10_000 is below the $32,000 joint base + # amount, so line 2a is 0 and line 2b is the head's 20_000; the child's + # 5_000 would make it 25_000. + me_sales_and_property_tax_fairness_credit_income: 20_000 + # Line 8: $4,100 (a filer is 65 or older); line 9: min(2_500, 4_100) = + # 2_500; line 10: 4% of 20_000 = 800; line 13: min(1_700, 2_000) = 1_700 + # (with 25_000: 2_500 - 1_000 = 1_500). + me_property_tax_fairness_credit: 1_700 diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index 5635dff0c54..ae588fb38c0 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -407,6 +407,41 @@ watca_alternative_tax_magi: 112_000 watca_alternative_tax_eligible: true +- name: A dependent's Social Security from $0 to $1,000,000 leaves the MAGI unchanged + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + people: + parent: + age: 45 + employment_income: 100_000 + child: + age: 10 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child] + filing_status: HEAD_OF_HOUSEHOLD + axes: + - - name: social_security_survivors + # The child, the second person in each copy. + index: 1 + min: 0 + max: 1_000_000 + count: 11 + period: 2026 + output: + # The household amount carries the child's benefit in every copy. + tax_exempt_social_security: + [0, 100_000, 200_000, 300_000, 400_000, 500_000, 600_000, 700_000, 800_000, 900_000, 1_000_000] + # MAGI is the parent's AGI at every benefit amount, so the parent stays + # below the 64_400 * 1.75 = 112_700 limit, and the alternative maximum + # tax is (100_000 - 64_400) * 0.255 = 9_078. + watca_alternative_tax_magi: 100_000 + watca_alternative_tax_eligible: true + watca_alternative_max_tax: 9_078 + - name: Alternative tax MAGI includes specified possession income period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object diff --git a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.py b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.py index 7511a063e49..d6b8775fe2a 100644 --- a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.py +++ b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.py @@ -6,10 +6,24 @@ class me_sales_and_property_tax_fairness_credit_income(Variable): entity = TaxUnit unit = USD label = "Maine sales and property tax fairness credit total income" + documentation = ( + "The filers' income for the sales and property tax fairness credits: " + "their own income plus their own interest and Social Security left out " + "of federal adjusted gross income. A tax unit dependent's benefits and " + "interest are the dependent's own." + ) definition_period = YEAR reference = ( - "https://legislature.maine.gov/statutes/36/title36sec5219-KK.html" # 1. D. + "https://legislature.maine.gov/statutes/36/title36sec5219-KK.html", # 1. D. + "https://legislature.maine.gov/statutes/36/title36sec5213-A.html", # 1. B. + # Rule 813 .03: "The income of the taxpayer is equal to the + # taxpayer's federal adjusted gross income increased by" interest and + # Social Security left out of it. + "https://www.maine.gov/revenue/sites/maine.gov.revenue/files/inline-files/Rule_813_May_2023.pdf#page=4", ) defined_for = StateCode.ME - adds = "gov.states.me.tax.income.credits.fairness.income_sources" + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.me.tax.income.credits.fairness + # Person-level sources are summed over the head and spouse. + return tax_unit_non_dep_add(tax_unit, period, p.income_sources) From a658ba99247f413fc598873794ff578c43a88b4f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 00:49:19 +0000 Subject: [PATCH 3/3] Leave dependents' income out of Washington's combined disposable income RCW 84.36.383(2) combines the disposable income of the claimant, the spouse or domestic partner, and each cotenant (a resident with an ownership interest, (3)). WAC 458-16A-120(2)(b) adds nontaxable Social Security to the AGI of "the person receiving these benefits", and pensions, excluded military pay and tax-exempt interest to the recipient's. The model read tax_exempt_social_security, which carries a dependent's whole benefit, and summed every member's person-level add-backs with add(). - sources use filer_tax_exempt_social_security, summed with tax_unit_non_dep_add. A dependent who owns part of the residence is a cotenant and comes in once, through wa_pte_cotenant_disposable_income. - The medical deductions keep summing every member: the statute counts amounts the claimant or spouse paid, and expense inputs don't record the payer. - Rhode Island's property tax credit counts "all income received by all persons of a household" (R.I. Gen. Laws 44-33-3(6)), so it keeps the household amount; a new case pins a dependent's benefits there. - The property test adds Washington. It zeroes Medicare premiums, which the measure subtracts and which can turn on another member's income through Medicare Savings Program deeming. Co-Authored-By: Claude Opus 5.5 --- changelog.d/per-return-tax-exempt-ss.fixed.md | 2 +- .../senior_disabled/income/sources.yaml | 7 +- .../watca/working_americans_tax_cut_act.py | 17 +++-- ...t_social_security_per_return_invariants.py | 66 ++++++++++------- .../ri_property_tax_household_income.yaml | 31 ++++++++ .../wa_pte_combined_disposable_income.yaml | 71 ++++++++++++++++++- .../wa_pte_combined_disposable_income.py | 25 ++++--- .../wa_pte_cotenant_disposable_income.py | 11 +-- 8 files changed, 174 insertions(+), 56 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/credits/property_tax/ri_property_tax_household_income.yaml diff --git a/changelog.d/per-return-tax-exempt-ss.fixed.md b/changelog.d/per-return-tax-exempt-ss.fixed.md index d2647fa7425..5ec3f12b530 100644 --- a/changelog.d/per-return-tax-exempt-ss.fixed.md +++ b/changelog.d/per-return-tax-exempt-ss.fixed.md @@ -1 +1 @@ -Leave a tax unit dependent's Social Security benefits and other nontaxable income out of income measures that describe the filers' own return: the federal Optional State Sales Tax Table income, the Working Americans' Tax Cut Act alternative tax modified AGI, Colorado's sales tax refund modified AGI and Maine's sales and property tax fairness credit income. +Leave a tax unit dependent's Social Security benefits and other nontaxable income out of income measures that describe the filers' own return: the federal Optional State Sales Tax Table income, the Working Americans' Tax Cut Act alternative tax modified AGI, Colorado's sales tax refund modified AGI, Maine's sales and property tax fairness credit income and Washington's senior and disabled property tax exemption combined disposable income. diff --git a/policyengine_us/parameters/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/sources.yaml b/policyengine_us/parameters/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/sources.yaml index 11b1484dc78..3d51617152f 100644 --- a/policyengine_us/parameters/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/sources.yaml +++ b/policyengine_us/parameters/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/sources.yaml @@ -12,8 +12,11 @@ values: # dominated by those excluded categories, so adding it back would # systematically over-count CDI. We omit it conservatively at the moment # and don't track the included subcomponents separately. - # RCW 84.36.383(7)(g) — Social Security and Railroad Retirement not in AGI - - tax_exempt_social_security + # RCW 84.36.383(7)(g) — Social Security and Railroad Retirement not in AGI, + # added to the AGI of "the person receiving these benefits" (WAC + # 458-16A-120(2)(b)(vi)). The head's and spouse's benefits only: a tax + # unit dependent's benefits are the dependent's own disposable income. + - filer_tax_exempt_social_security # RCW 84.36.383(7)(i) — state/municipal bond interest not in AGI - tax_exempt_interest_income # RCW 84.36.383(7)(a) — capital gains add-back. AGI already includes diff --git a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py index bf3528f5d65..9cce3b893c0 100644 --- a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py +++ b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py @@ -6,6 +6,10 @@ "title": "Working Americans' Tax Cut Act", "href": "https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf", }, + { + "title": "S. 4083 (119th Congress), Working Americans' Tax Cut Act, as introduced", + "href": "https://www.govinfo.gov/content/pkg/BILLS-119s4083is/html/BILLS-119s4083is.htm", + }, ] @@ -16,15 +20,7 @@ class watca_alternative_tax_magi(Variable): label = "WATCA alternative tax modified AGI" definition_period = YEAR unit = USD - reference = WATCA_REFERENCES + [ - { - # Section 1A(d)(2): "the portion of the taxpayer's social - # security benefits (as defined in section 86(d)) which is - # not included in gross income under section 86". - "title": "S. 4083 (119th Congress), section 2(a), new section 1A(d)", - "href": "https://www.govinfo.gov/content/pkg/BILLS-119s4083is/html/BILLS-119s4083is.htm", - }, - ] + reference = WATCA_REFERENCES documentation = ( "Section 1A(d): AGI + foreign income exclusions (sec 911, 931, 933) " "+ the filers' non-taxable Social Security. A tax unit dependent's " @@ -36,6 +32,9 @@ class watca_alternative_tax_magi(Variable): "foreign_earned_income_exclusion", "specified_possession_income", "puerto_rico_income", + # Section 1A(d)(2): "the portion of the taxpayer's social security + # benefits (as defined in section 86(d)) which is not included in + # gross income under section 86". "filer_tax_exempt_social_security", ] diff --git a/policyengine_us/tests/core/test_dependent_social_security_per_return_invariants.py b/policyengine_us/tests/core/test_dependent_social_security_per_return_invariants.py index dcec787d462..7ae51d206cd 100644 --- a/policyengine_us/tests/core/test_dependent_social_security_per_return_invariants.py +++ b/policyengine_us/tests/core/test_dependent_social_security_per_return_invariants.py @@ -4,42 +4,53 @@ receive the benefits" (IRS Pub. 915, 2025, "Who is taxed"), so a tax unit dependent's benefits belong on the dependent's own return. `filer_tax_exempt_social_security` is the nontaxable part of the head's and -spouse's benefits, and the readers in READERS measure the filers' own return: +spouse's benefits. These measures count the filers' own return: -- `state_sales_tax_income_bracket` and `state_sales_tax`: the Optional State - Sales Tax Table row is read at "the amount shown on your Form 1040 or +- the Optional State Sales Tax Table income row, + `state_sales_tax_income_bracket`: "the amount shown on your Form 1040 or 1040-SR, line 11b, plus any nontaxable items" (2025 Schedule A instructions). A dependent's other nontaxable items (SSI, tax-exempt interest, veterans' benefits, workers' compensation, nontaxable pensions - and retirement distributions) are the dependent's own too. -- `co_modified_agi` and `co_sales_tax_refund`: C.R.S. 39-22-2003(1.5)(b) - adds the "Social security benefits excluded from federal adjusted gross - income", and subsection (3) measures the qualified individuals' own income. -- `me_sales_and_property_tax_fairness_credit_income` and the two credits - that read it: 36 M.R.S. 5213-A(1)(B) and 5219-KK(1)(D) add interest and - Social Security left out of federal adjusted gross income, and Rule 813 - .03 makes the income "the taxpayer's". + and retirement distributions) are the dependent's own too; +- Colorado's sales tax refund income, `co_modified_agi`: C.R.S. + 39-22-2003(1.5)(b) adds the "Social security benefits excluded from + federal adjusted gross income", and subsection (3) measures the qualified + individuals' own income; +- Maine's fairness credit income, + `me_sales_and_property_tax_fairness_credit_income`: 36 M.R.S. 5213-A(1)(B) + and 5219-KK(1)(D) add interest and Social Security left out of federal + adjusted gross income, and Rule 813 .03 makes the income "the + taxpayer's"; +- Washington's combined disposable income, + `wa_pte_combined_disposable_income`: RCW 84.36.383(2) combines the + claimant's, spouse's and cotenants' disposable income, and WAC + 458-16A-120(2)(b) adds each item left out of AGI to the AGI of "the person + receiving these benefits". The measure also subtracts members' medical + costs, and a member's net Medicare premium can turn on another member's + income through the Medicare Savings Program, so the population's Medicare + premiums are zero. + +READERS holds the income measures. AMOUNTS holds what they feed: the table +amount, the refund and the credits. These also read the filing status and +who counts as a dependent, which an aged parent's own income can move: a +parent must pass a gross income test to be a dependent, and a filer heads a +household for a parent only if the parent is the filer's dependent. So +AMOUNTS are checked for the units whose dependents are all children. `tax_exempt_social_security`, the household amount, still carries a dependent's whole benefit for the readers whose law counts a household's income. -AMOUNTS are what those readers feed: the table amount, the refund and the -two credits. They also read the filing status and who counts as a -dependent, and a parent's or other relative's own income can decide that: -such a dependent must pass a gross income test, and a filer heads a -household for a parent only if the parent is the filer's dependent. So the -amounts are checked for the units whose dependents are all children. - The tests draw a seeded random population of tax units: a single filer or a married couple filing jointly, whose head and spouse have wages, Social Security and the nontaxable items above, and whose head may pay rent, with -one to three dependents, each a child or an aged parent. A dependent draws benefits of $1 to $1,000,000, -log-uniformly, and the other nontaxable items, each of them zero about half -the time. Amounts are whole dollars and stay below 2**24, so their float32 -sums, and the subtraction that takes dependents' benefits out of the tax -unit's, are exact. Every unit is placed in each state in READERS, and each -scenario runs as one vectorized simulation. +one to three dependents, each a child or an aged parent. A dependent draws +benefits of $1 to $1,000,000, log-uniformly, and the other nontaxable items, +each of them zero about half the time. Amounts are whole dollars and stay +below 2**24, so their float32 sums, and the subtraction that takes +dependents' benefits out of the tax unit's, are exact. Every unit is placed +in each state in READERS, and each scenario runs as one vectorized +simulation. 1. Each reader is the same whether the dependents' benefits and nontaxable items are zero, as drawn, or doubled, and so is each amount for the units @@ -65,12 +76,14 @@ "TX": ["filer_tax_exempt_social_security", "state_sales_tax_income_bracket"], "CO": ["co_modified_agi"], "ME": ["me_sales_and_property_tax_fairness_credit_income"], + "WA": ["wa_pte_combined_disposable_income"], } # What the readers feed, checked for units whose dependents are all children. AMOUNTS = { "TX": ["state_sales_tax"], "CO": ["co_sales_tax_refund"], "ME": ["me_sales_tax_fairness_credit", "me_property_tax_fairness_credit"], + "WA": [], } STATES = list(READERS) CHILD_AGE_LIMIT = 18 @@ -142,7 +155,10 @@ def dependent(): def _amounts(person, scale=1): """A person's benefit and nontaxable items, each multiplied by scale.""" - amounts = {person["benefit"]: {YEAR: scale * person["social_security"]}} + amounts = { + person["benefit"]: {YEAR: scale * person["social_security"]}, + "medicare_part_b_premium": {YEAR: 0}, + } for item, value in person["items"].items(): amounts[item] = {YEAR: scale * value} return amounts diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/credits/property_tax/ri_property_tax_household_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/credits/property_tax/ri_property_tax_household_income.yaml new file mode 100644 index 00000000000..f16e184ca23 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/credits/property_tax/ri_property_tax_household_income.yaml @@ -0,0 +1,31 @@ +# R.I. Gen. Laws 44-33-3(6): "Household income" means all income received by +# all persons of a household, and 44-33-3(7) counts all payments received +# under the federal Social Security Act. Unlike the per-return readers, this +# household amount keeps a dependent's benefits. + +- name: A dependent grandchild's Social Security counts in household income + period: 2025 + input: + people: + grandparent: + age: 70 + employment_income: 5_000 + social_security_retirement: 10_000 + grandchild: + age: 12 + is_tax_unit_dependent: true + social_security_survivors: 6_000 + tax_units: + tax_unit: + members: [grandparent, grandchild] + households: + household: + members: [grandparent, grandchild] + state_code: RI + output: + # Combined income of 5_000 + 10_000 / 2 = 10_000 is below the $25,000 + # base amount, so none of the grandparent's benefit is taxable. + adjusted_gross_income: 5_000 + filer_tax_exempt_social_security: 10_000 + # 5_000 + 10_000 + the grandchild's 6_000. + ri_property_tax_household_income: 21_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/dor/property_tax_exemption/senior_disabled/wa_pte_combined_disposable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/dor/property_tax_exemption/senior_disabled/wa_pte_combined_disposable_income.yaml index 8d5b9246b16..cfe8365a790 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/dor/property_tax_exemption/senior_disabled/wa_pte_combined_disposable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/dor/property_tax_exemption/senior_disabled/wa_pte_combined_disposable_income.yaml @@ -29,7 +29,7 @@ tax_unit: members: [person1] adjusted_gross_income: 30_000 - tax_exempt_social_security: 12_000 + filer_tax_exempt_social_security: 12_000 spm_units: spm_unit: members: [person1] @@ -55,7 +55,7 @@ tax_unit: members: [person1] adjusted_gross_income: 20_000 - tax_exempt_social_security: 10_000 + filer_tax_exempt_social_security: 10_000 spm_units: spm_unit: members: [person1] @@ -107,7 +107,7 @@ tax_unit: members: [person1, person2] adjusted_gross_income: 30_000 - tax_exempt_social_security: 14_000 + filer_tax_exempt_social_security: 14_000 spm_units: spm_unit: members: [person1, person2] @@ -217,3 +217,68 @@ # Cotenant filed a separate return; statute folds their disposable income # into the combined total. 30,000 + 25,000 = 55,000. wa_pte_combined_disposable_income: 55_000 + +- name: Case 10, a dependent child's Social Security and interest stay out of combined disposable income. + period: 2024 + input: + people: + person1: + age: 64 + social_security_retirement: 20_000 + person2: + age: 10 + is_tax_unit_dependent: true + social_security_dependents: 6_000 + tax_exempt_interest_income: 500 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: WA + output: + # Combined income of 20_000 / 2 = 10_000 is below the $25,000 base + # amount, so none of the head's benefit is taxable and AGI is 0. The + # child's 6_000 and 500 are the child's own disposable income (WAC + # 458-16A-120(2)(b)(vi) and (x)), and the child owns no part of the + # residence. 0 + 20_000 = 20_000, not 26_500. + filer_tax_exempt_social_security: 20_000 + wa_pte_combined_disposable_income: 20_000 + +- name: Case 11, a dependent who co-owns the residence counts once, through the cotenant input. + period: 2024 + input: + people: + person1: + age: 64 + social_security_retirement: 18_000 + person2: + # A sibling claimed as a qualifying relative. Both are under 65, so + # no Medicare premium enters the deductions. + age: 63 + is_tax_unit_dependent: true + social_security_retirement: 14_000 + tax_exempt_pension_income: 2_000 + tax_units: + tax_unit: + members: [person1, person2] + # The sibling co-owns the residence, so is a cotenant (RCW + # 84.36.383(3)): AGI 0 (14_000 / 2 = 7_000 is below the $25,000 base + # amount) + 14_000 + 2_000 = 16_000. + wa_pte_cotenant_disposable_income: 16_000 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: WA + output: + # 18_000 / 2 = 9_000 is below the $25,000 base amount, so the head's AGI + # is 0. 0 + 18_000 + 16_000 = 34_000; the sibling's benefits and pension + # would otherwise count twice (50_000). + wa_pte_combined_disposable_income: 34_000 diff --git a/policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/wa_pte_combined_disposable_income.py b/policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/wa_pte_combined_disposable_income.py index 7c8d600c796..21e9ad484c6 100644 --- a/policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/wa_pte_combined_disposable_income.py +++ b/policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/wa_pte_combined_disposable_income.py @@ -20,19 +20,22 @@ def formula(tax_unit, period, parameters): p = parameters( period ).gov.states.wa.dor.property_tax_exemption.senior_disabled.income - # RCW 84.36.383(2) requires combining the claimant's, spouse's, and - # each occupying cotenant's disposable income. AGI (and the other - # tax-unit-level sources) already excludes dependents because - # irs_gross_income masks them out at the person level. Non-tax-unit - # cotenants come in through wa_pte_cotenant_disposable_income. - # Person-level income add-backs (pension, military pay, veterans - # benefits) and medical deductions are summed across all tax-unit - # members. The statute (RCW 84.36.383(2) and (7)) restricts these - # to amounts of the claimant and spouse/domestic partner only, but - # we don't filter dependents at the moment. - own_income = add(tax_unit, period, p.sources) + # RCW 84.36.383(2) combines the disposable income of the claimant, + # the spouse or domestic partner, and each cotenant occupying the + # residence. WAC 458-16A-120(2)(b) adds each item left out of AGI to + # the AGI of the person who received it, so a tax unit dependent's + # benefits, pensions, military pay and tax-exempt interest are the + # dependent's own. Person-level sources are summed over the head and + # spouse; AGI and filer_tax_exempt_social_security already describe + # their return. Other cotenants, including a tax unit dependent who + # owns part of the residence, come in through + # wa_pte_cotenant_disposable_income. + own_income = tax_unit_non_dep_add(tax_unit, period, p.sources) cotenant_income = tax_unit("wa_pte_cotenant_disposable_income", period) income = own_income + cotenant_income + # The deductions are amounts the claimant or spouse paid (RCW + # 84.36.383(2)). Expense inputs record whose expense it is, not who + # paid it, so they are summed over every tax unit member. itemized = add(tax_unit, period, p.deductions.sources) # ESSB 6162 (RCW 84.36.383(14)) lets the claimant elect the standard # deduction in place of the itemized medical basket. The per-claimant diff --git a/policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/wa_pte_cotenant_disposable_income.py b/policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/wa_pte_cotenant_disposable_income.py index 99261c59857..8e7a63f6d41 100644 --- a/policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/wa_pte_cotenant_disposable_income.py +++ b/policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/wa_pte_cotenant_disposable_income.py @@ -1,8 +1,9 @@ -# Disposable income of cotenants who occupy the residence but file -# outside this tax unit. Per RCW 84.36.383(2), combined disposable -# income includes each cotenant's disposable income. Default is zero; -# populate when the household has occupying co-owners filing separate -# federal returns. +# Disposable income of cotenants (residents with an ownership interest in +# the residence, RCW 84.36.383(3)) other than the tax unit head and spouse: +# co-owners who file their own federal returns, and a tax unit dependent +# who owns part of the residence. Per RCW 84.36.383(2), combined +# disposable income includes each cotenant's disposable income. Default is +# zero. from policyengine_us.model_api import *