From e5da8ea339cb1babbcab4e336b056607a25f31b6 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Wed, 23 Sep 2026 14:16:25 -0400 Subject: [PATCH 1/9] Fix AR, MT and NM income tax gaps found in TAXSIM comparison sweep - AR: count partnership/S-corp income in gross income (AR1000F Line 19); stop flooring each spouse's net income separately on joint returns (all income goes in Column A), flooring the unit total instead. - MT: floor the capital gain credit at zero on net capital losses; allow dependent exemptions for non-child dependents with gross income at or below the exemption amount, with the disabled extra exemption for children only. - NM: from TY2023, compute net capital gains per IRC 1222(11) (net long-term gain less net short-term loss), excluding short-term gains. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../taxsim-sweep-ar-mt-nm-fixes.fixed.md | 1 + .../gross_income/sources/individual.yaml | 3 + .../income/gross_income/sources/joint.yaml | 3 + .../excludes_short_term_gains.yaml | 19 +++++ .../states/ar/tax/income/ar_agi_joint.yaml | 32 +++++++- .../ar/tax/income/ar_gross_income_indiv.yaml | 13 +++ .../income/ar_net_taxable_income_joint.yaml | 23 ++++++ .../credits/mt_capital_gain_credit.yaml | 10 +++ .../mt_dependent_exemptions_person.yaml | 79 +++++++++++++++++++ .../nm_net_capital_gains_deduction.yaml | 77 ++++++++++++++++-- .../gov/states/ar/tax/income/ar_agi_joint.py | 10 ++- .../tax/income/ar_net_taxable_income_joint.py | 4 +- .../low_income/ar_low_income_tax_joint.py | 4 +- .../capital_gain/mt_capital_gain_credit.py | 5 +- .../mt_dependent_exemptions_person.py | 14 +++- .../nm_net_capital_gains_deduction.py | 12 ++- 16 files changed, 288 insertions(+), 21 deletions(-) create mode 100644 changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md create mode 100644 policyengine_us/parameters/gov/states/nm/tax/income/deductions/net_capital_gains/excludes_short_term_gains.yaml diff --git a/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md b/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md new file mode 100644 index 00000000000..00a03f140c9 --- /dev/null +++ b/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md @@ -0,0 +1 @@ +Fixed Arkansas gross income to include partnership and S-corporation income and to let one spouse's loss offset the other's income on a joint return; Montana's capital gain credit to be zero rather than negative on net losses and dependent exemptions to cover non-child dependents whose gross income is at or below the exemption amount; and New Mexico's net capital gains deduction to exclude short-term gains from tax year 2023. diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml index fda2ae79246..6a750777637 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml @@ -17,6 +17,7 @@ values: - taxable_pension_income # Line 18 - disability_benefits # Line 18b - rental_income # Line 19 + - partnership_s_corp_income # Line 19 - farm_rent_income # Line 20 - unemployment_compensation # Line 21 2020-01-01: @@ -33,6 +34,7 @@ values: - taxable_pension_income # Line 18 - disability_benefits # Line 18b - rental_income # Line 19 + - partnership_s_corp_income # Line 19 - farm_rent_income # Line 20 2022-01-01: - irs_employment_income # Line 8 @@ -52,6 +54,7 @@ values: - keogh_distributions # Line 18 - disability_benefits # Line 18b - rental_income # Line 19 + - partnership_s_corp_income # Line 19 - farm_rent_income # Line 20 - unemployment_compensation # Line 21 diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml index c9631d11ac3..8286eeb8d43 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml @@ -17,6 +17,7 @@ values: - taxable_pension_income # Line 18 - disability_benefits # Line 18b - rental_income # Line 19 + - partnership_s_corp_income # Line 19 - farm_rent_income # Line 20 - unemployment_compensation # Line 21 2020-01-01: @@ -33,6 +34,7 @@ values: - taxable_pension_income # Line 18 - disability_benefits # Line 18b - rental_income # Line 19 + - partnership_s_corp_income # Line 19 - farm_rent_income # Line 20 2022-01-01: - irs_employment_income # Line 8 @@ -52,6 +54,7 @@ values: - keogh_distributions # Line 18 - disability_benefits # Line 18b - rental_income # Line 19 + - partnership_s_corp_income # Line 19 - farm_rent_income # Line 20 - unemployment_compensation # Line 21 diff --git a/policyengine_us/parameters/gov/states/nm/tax/income/deductions/net_capital_gains/excludes_short_term_gains.yaml b/policyengine_us/parameters/gov/states/nm/tax/income/deductions/net_capital_gains/excludes_short_term_gains.yaml new file mode 100644 index 00000000000..44868a17bd9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/tax/income/deductions/net_capital_gains/excludes_short_term_gains.yaml @@ -0,0 +1,19 @@ +description: New Mexico excludes short-term capital gains from net capital gains under the net capital gains deduction if this is true. +values: + 2021-01-01: false + # From tax year 2023 the instructions define "net capital gains" by IRC + # Section 1222(11): net long-term capital gains less any net short-term + # capital losses. Earlier instructions use the net capital gains reported and + # claimed on the federal return. + 2023-01-01: true +metadata: + unit: bool + period: year + label: New Mexico net capital gains deduction excludes short-term capital gains + reference: + - title: 2022 New Mexico Personal Income Tax Instructions, PIT-ADJ Line 15 + href: https://klvg4oyd4j.execute-api.us-west-2.amazonaws.com/prod/PublicFiles/34821a9573ca43e7b06dfad20f5183fd/1afc56af-ea90-4d48-82e5-1f9aeb43255a/PITbook2022.pdf#page=51 + - title: 2023 New Mexico Personal Income Tax Instructions, PIT-ADJ ("net capital gains" defined by IRC Section 1222(11)) + href: https://klvg4oyd4j.execute-api.us-west-2.amazonaws.com/prod/PublicFiles/34821a9573ca43e7b06dfad20f5183fd/90560f4e-0ef0-4e52-a003-878b84f858bb/PITbook2023.pdf#page=32 + - title: 2024 New Mexico Personal Income Tax Instructions, PIT-ADJ ("net capital gains" defined by IRC Section 1222(11)) + href: https://klvg4oyd4j.execute-api.us-west-2.amazonaws.com/prod/PublicFiles/34821a9573ca43e7b06dfad20f5183fd/97f27f33-5e23-4a25-ad68-0980a22b802d/PITbook2024.pdf#page=33 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_agi_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_agi_joint.yaml index ce671aa83aa..b481feb47b3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_agi_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_agi_joint.yaml @@ -19,7 +19,7 @@ output: ar_agi_joint: [900, 1_800] -- name: Dependent AGI gets attributed to the head, AGI capped at 0 +- name: Dependent AGI gets attributed to the head; spouse net income is not floored on a joint return period: 2022 input: people: @@ -43,4 +43,32 @@ members: [person1, person2, person3] state_code: AR output: - ar_agi_joint: [1_200, 0, 0] + # Head: 900 + dependent's 300 (dependent income floored at 0, then + # attributed to the head). Spouse: 100 - 200 = -100. On a joint return all + # income goes in Column A, so the spouse's amount offsets the head's; the + # tax unit total is floored at 0 only when used as a tax base. + ar_agi_joint: [1_200, -100, 0] + +- name: A spouse's business loss offsets the other spouse's income on a joint return + period: 2024 + input: + people: + person1: + ar_gross_income_joint: 50_000 + ar_exemptions: 0 + is_tax_unit_dependent: false + is_tax_unit_head: true + person2: + ar_gross_income_joint: -10_000 + ar_exemptions: 0 + is_tax_unit_dependent: false + is_tax_unit_head: false + households: + household: + members: [person1, person2] + state_code: AR + output: + # AR1000F instructions: "If your filing status is Married Filing + # Separately on the Same Return, both Column A and Column B must be used. + # ... For all other filing statuses, write all income in Column A only." + ar_agi_joint: [50_000, -10_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gross_income_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gross_income_indiv.yaml index 7ca318c4d02..64f867182f7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gross_income_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gross_income_indiv.yaml @@ -130,3 +130,16 @@ ar_gross_income_indiv: 60_000 ar_gross_income_joint: 60_000 ar_income_tax: 1_730.56 + +- name: Arkansas gross income includes partnership and S-corporation income + period: 2024 + input: + irs_employment_income: 30_000 + partnership_s_corp_income: 20_000 + state_code: AR + output: + # AR1000F Line 19: "income from rents, royalties, estates or trusts, + # profits (whether received or not) from partnerships, fiduciaries, small + # business corporations, etc." + ar_gross_income_indiv: 50_000 + ar_gross_income_joint: 50_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_net_taxable_income_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_net_taxable_income_joint.yaml index 75a01bb0fee..0c8b2a264a0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_net_taxable_income_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_net_taxable_income_joint.yaml @@ -106,3 +106,26 @@ output: # Total AGI = 10k + 10k = 20k, all attributed to head ar_net_taxable_income_joint: [20_000, 0] + +- name: A negative joint total is floored at zero when the low income table is used + period: 2024 + input: + people: + person1: + ar_taxable_income_joint: 0 + ar_agi_joint: 5_000 + is_tax_unit_head: true + person2: + ar_taxable_income_joint: 0 + ar_agi_joint: -10_000 + tax_units: + tax_unit: + members: [person1, person2] + ar_uses_low_income_tax_tables: true + households: + household: + members: [person1, person2] + state_code: AR + output: + # 5,000 - 10,000 = -5,000 combined; the tax base cannot be negative. + ar_net_taxable_income_joint: [0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/mt_capital_gain_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/mt_capital_gain_credit.yaml index 370c349f925..6f28a48eb44 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/mt_capital_gain_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/mt_capital_gain_credit.yaml @@ -13,3 +13,13 @@ state_code: MT output: mt_capital_gain_credit: 60 + +- name: A net capital loss produces no credit rather than a negative one + period: 2022 + input: + capital_gains: -5_000 + state_code: MT + output: + # 2% of net capital gains (Form 2 instructions, Line 1); a net loss is not + # a net capital gain, so the credit is 0, not -100. + mt_capital_gain_credit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml index 406960f7e07..20a5ec1872f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml @@ -6,6 +6,10 @@ is_qualifying_child_dependent: false is_disabled: false person3: + # Not a qualifying child, and gross income above the $2,580 exemption + # amount, so not a Montana dependent. + is_tax_unit_dependent: true + irs_gross_income: 3_000 is_qualifying_child_dependent: false is_disabled: false person4: @@ -71,9 +75,15 @@ person1: is_tax_unit_head: true person3: + # Not qualifying children, and gross income above the $2,710 + # exemption amount, so neither is a Montana dependent. + is_tax_unit_dependent: true + irs_gross_income: 5_000 is_qualifying_child_dependent: false is_disabled: false person4: + is_tax_unit_dependent: true + irs_gross_income: 2_711 is_qualifying_child_dependent: false is_disabled: true tax_units: @@ -108,3 +118,72 @@ state_code: MT output: mt_dependent_exemptions_person: [0, 0, 0] + +- name: Adult dependent with no gross income is eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + person2: + # e.g. a parent who is not a qualifying child. Form 2 instructions: a + # dependent is an individual "who does not have gross income of more + # than $2,710, unless the dependent is a 'qualifying child'". + age: 70 + is_tax_unit_dependent: true + irs_gross_income: 0 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + mt_dependent_exemptions_person: [0, 2_710] + +- name: Adult dependent with gross income exactly at the exemption amount is eligible + period: 2023 + input: + people: + person1: + is_tax_unit_head: true + person2: + is_tax_unit_dependent: true + irs_gross_income: 2_960 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # "not ... more than" the $2,960 exemption amount + mt_dependent_exemptions_person: [0, 2_960] + +- name: Disabled adult dependent who is not a child gets one exemption, not two + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + person2: + # The additional exemption is "for a child who is disabled". + is_tax_unit_dependent: true + irs_gross_income: 1_000 + is_qualifying_child_dependent: false + is_disabled: true + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + mt_dependent_exemptions_person: [0, 2_710] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml index b7d6e6a7080..4fdbc59ae7e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml @@ -2,7 +2,7 @@ period: 2021 input: filing_status: SINGLE - capital_gains: 0 + long_term_capital_gains: 0 state_code: NM output: nm_net_capital_gains_deduction: 0 @@ -11,7 +11,7 @@ period: 2021 input: filing_status: SEPARATE - capital_gains: 10_000 + long_term_capital_gains: 10_000 state_code: NM output: nm_net_capital_gains_deduction: 2_000 @@ -20,7 +20,7 @@ period: 2021 input: filing_status: JOINT - capital_gains: 2_000 + long_term_capital_gains: 2_000 state_code: NM output: nm_net_capital_gains_deduction: 1_000 @@ -29,7 +29,7 @@ period: 2021 input: filing_status: SEPARATE - capital_gains: 1_000 + long_term_capital_gains: 1_000 state_code: NM output: nm_net_capital_gains_deduction: 500 @@ -42,7 +42,7 @@ period: 2025 input: filing_status: SINGLE - capital_gains: 500_000 + long_term_capital_gains: 500_000 state_code: NM output: nm_net_capital_gains_deduction: 2_500 @@ -51,7 +51,7 @@ period: 2025 input: filing_status: SINGLE - capital_gains: 3_000_000 + long_term_capital_gains: 3_000_000 state_code: NM output: nm_net_capital_gains_deduction: 2_500 @@ -60,7 +60,7 @@ period: 2025 input: filing_status: SEPARATE - capital_gains: 2_000_000 + long_term_capital_gains: 2_000_000 state_code: NM output: nm_net_capital_gains_deduction: 1_250 @@ -69,7 +69,68 @@ period: 2024 input: filing_status: SINGLE - capital_gains: 3_000_000 + long_term_capital_gains: 3_000_000 state_code: NM output: nm_net_capital_gains_deduction: 1_200_000 + +# "Net capital gains" follow IRC Section 1222(11): net long-term capital gain +# less any net short-term capital loss. PIT-ADJ instructions (2023 and 2024): +# "that section defines 'net capital gains' as only net long-term capital +# gains less any net short-term capital losses." + +- name: Short-term gains are excluded from net capital gains + period: 2023 + input: + filing_status: SINGLE + long_term_capital_gains: 10_000 + short_term_capital_gains: 5_000 + state_code: NM + output: + # max(40% x 10,000, min(10,000, 1,000)) = 4,000; the 5,000 short-term gain + # is not part of net capital gains. + nm_net_capital_gains_deduction: 4_000 + +- name: A net short-term loss reduces the long-term gain + period: 2024 + input: + filing_status: SINGLE + long_term_capital_gains: 10_000 + short_term_capital_gains: -4_000 + state_code: NM + output: + # Net capital gains = 10,000 - 4,000 = 6,000; 40% = 2,400. + nm_net_capital_gains_deduction: 2_400 + +- name: Only short-term gains means no net capital gains + period: 2023 + input: + filing_status: JOINT + long_term_capital_gains: 0 + short_term_capital_gains: 10_000 + state_code: NM + output: + nm_net_capital_gains_deduction: 0 + +- name: A net long-term loss means no net capital gains + period: 2023 + input: + filing_status: SINGLE + long_term_capital_gains: -2_000 + short_term_capital_gains: 5_000 + state_code: NM + output: + nm_net_capital_gains_deduction: 0 + +- name: Before 2023, short-term gains count toward net capital gains + period: 2022 + input: + filing_status: SINGLE + long_term_capital_gains: 10_000 + short_term_capital_gains: 5_000 + state_code: NM + output: + # 2022 PIT-ADJ Line 15: "net capital gains reported and claimed on your + # federal tax return" (Form 1040 line 7 nets short- and long-term gains). + # 40% x 15,000 = 6,000. + nm_net_capital_gains_deduction: 6_000 diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_agi_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_agi_joint.py index 5a0bf590db0..e0eee49f3af 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_agi_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_agi_joint.py @@ -13,9 +13,13 @@ class ar_agi_joint(Variable): def formula(person, period, parameters): gross_income = person("ar_gross_income_joint", period) income_exemptions = person("ar_exemptions", period) - net_income = max_(gross_income - income_exemptions, 0) - # allocate any dependent gross income to tax unit head + net_income = gross_income - income_exemptions + # On a return that is not married filing separately on the same + # return, all income goes in Column A, so one spouse's loss offsets + # the other's income; the tax unit total is floored at zero when it + # is used as a tax base. + # Allocate any dependent net income (floored at zero) to the head. is_dependent = person("is_tax_unit_dependent", period) - sum_dep_net_income = person.tax_unit.sum(is_dependent * net_income) + sum_dep_net_income = person.tax_unit.sum(is_dependent * max_(net_income, 0)) is_head = person("is_tax_unit_head", period) return ~is_dependent * net_income + is_head * sum_dep_net_income diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_net_taxable_income_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_net_taxable_income_joint.py index 7d4d4f43201..42fb4ac6456 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_net_taxable_income_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_net_taxable_income_joint.py @@ -22,7 +22,9 @@ def formula(person, period, parameters): # is built in, so use AGI instead of taxable income. agi = person("ar_agi_joint", period) is_head = person("is_tax_unit_head", period) - total_agi = is_head * person.tax_unit.sum(agi) + # A spouse's loss can make the joint total negative; the tax base + # cannot be. + total_agi = is_head * max_(person.tax_unit.sum(agi), 0) return where( uses_low_income_tax_tables, total_agi, diff --git a/policyengine_us/variables/gov/states/ar/tax/income/low_income/ar_low_income_tax_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/low_income/ar_low_income_tax_joint.py index 40f719f9a6b..d737a723bae 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/low_income/ar_low_income_tax_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/low_income/ar_low_income_tax_joint.py @@ -11,7 +11,9 @@ class ar_low_income_tax_joint(Variable): defined_for = StateCode.AR def formula(person, period, parameters): - agi = add(person.tax_unit, period, ["ar_agi_joint"]) + # A spouse's loss can make the joint total negative; the tax base + # cannot be. + agi = max_(add(person.tax_unit, period, ["ar_agi_joint"]), 0) head = person("is_tax_unit_head", period) agi_attributed_to_head = agi * head p = parameters(period).gov.states.ar.tax.income.rates.low_income_tax_tables diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/capital_gain/mt_capital_gain_credit.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/capital_gain/mt_capital_gain_credit.py index 8226eaf09cb..03982ead3d3 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/credits/capital_gain/mt_capital_gain_credit.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/capital_gain/mt_capital_gain_credit.py @@ -13,6 +13,7 @@ class mt_capital_gain_credit(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.mt.tax.income.credits.capital_gain - net_capital_gain = person("capital_gains", period) - # The net capital gain variable is capped at 0 + # The credit is 2% of net capital gains (Form 2 instructions, Line 1), + # so a net capital loss produces no credit rather than a negative one. + net_capital_gain = max_(person("capital_gains", period), 0) return p.percentage * net_capital_gain diff --git a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py index ae6696424d4..ef6618bd0f5 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py @@ -14,12 +14,20 @@ def formula(person, period, parameters): p = parameters(period).gov.states.mt.tax.income.exemptions if p.applies: - # Qualifying child under IRC 152(c), which defines for the EITC + # A qualifying child under IRC 152(c) is a dependent regardless of + # income. Any other dependent counts only if their gross income + # does not exceed the exemption amount (Form 2 instructions: a + # dependent is an individual "who does not have gross income of + # more than" the exemption amount "unless the dependent is a + # 'qualifying child' according to the federal rules"). qualifying_child = person("is_qualifying_child_dependent", period) - # Disabled dependents get an additional exemption. + dependent = person("is_tax_unit_dependent", period) + gross_income = person("irs_gross_income", period) + other_dependent = dependent & ~qualifying_child & (gross_income <= p.amount) + # Disabled children get an additional exemption. disabled = person("is_disabled", period) - eligible_dependent = qualifying_child * (1 + disabled) + eligible_dependent = qualifying_child * (1 + disabled) + other_dependent return eligible_dependent * p.amount return 0 diff --git a/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py b/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py index ec018ac189e..59fb0f87386 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py @@ -12,7 +12,17 @@ class nm_net_capital_gains_deduction(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.nm.tax.income.deductions.net_capital_gains - net_capital_gains = max_(0, add(tax_unit, period, ["capital_gains"])) + long_term_gain = add(tax_unit, period, ["long_term_capital_gains"]) + short_term_gain = add(tax_unit, period, ["short_term_capital_gains"]) + # From 2023 the instructions define "net capital gains" by IRC Section + # 1222(11): net long-term capital gain less any net short-term capital + # loss, so short-term gains are excluded. Earlier years use the net + # capital gains reported on the federal return. + net_capital_gains = where( + p.excludes_short_term_gains, + max_(0, long_term_gain - max_(0, -short_term_gain)), + max_(0, long_term_gain + short_term_gain), + ) uncapped_element = p.uncapped_element_percent * net_capital_gains filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE From 2ce9b1c03f5060804664b2bc27ece675b50fa594 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Thu, 24 Sep 2026 11:16:29 -0400 Subject: [PATCH 2/9] Address review: MT dependent income test and NM net capital gain definition Test Montana's non-child dependent exemption against the dependent's own gross income (dependent_gross_income) instead of irs_gross_income, which is zero for dependents, with earnings-derived below/at/above-limit cases. Apply New Mexico's IRC 1222(11) net capital gain definition in every year (NMSA 7-2-34(D), 2019 HB6) and drop the unsupported pre-2023 exception. Add Arkansas joint asymmetric profit/loss and New Mexico cross-spouse netting coverage. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LqCGPgxDukji8Bhs57c5rH --- .../taxsim-sweep-ar-mt-nm-fixes.fixed.md | 2 +- .../excludes_short_term_gains.yaml | 19 --- .../gov/states/ar/tax/income/integration.yaml | 149 +++++++++++++++++ .../mt_dependent_exemptions_person.yaml | 152 ++++++++++++++++-- .../nm_net_capital_gains_deduction.yaml | 106 +++++++++++- .../mt_dependent_exemptions_person.py | 9 +- .../nm_net_capital_gains_deduction.py | 21 +-- 7 files changed, 409 insertions(+), 49 deletions(-) delete mode 100644 policyengine_us/parameters/gov/states/nm/tax/income/deductions/net_capital_gains/excludes_short_term_gains.yaml diff --git a/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md b/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md index 00a03f140c9..4debb39eeb2 100644 --- a/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md +++ b/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md @@ -1 +1 @@ -Fixed Arkansas gross income to include partnership and S-corporation income and to let one spouse's loss offset the other's income on a joint return; Montana's capital gain credit to be zero rather than negative on net losses and dependent exemptions to cover non-child dependents whose gross income is at or below the exemption amount; and New Mexico's net capital gains deduction to exclude short-term gains from tax year 2023. +Fixed Arkansas gross income to include partnership and S-corporation income and to let one spouse's loss offset the other's income on a joint return; Montana's capital gain credit to be zero rather than negative on net losses and dependent exemptions to cover non-child dependents whose own gross income is at or below the exemption amount; and New Mexico's net capital gains deduction to exclude short-term gains in every year, following the IRC Section 1222(11) definition that New Mexico law has used since 2019. diff --git a/policyengine_us/parameters/gov/states/nm/tax/income/deductions/net_capital_gains/excludes_short_term_gains.yaml b/policyengine_us/parameters/gov/states/nm/tax/income/deductions/net_capital_gains/excludes_short_term_gains.yaml deleted file mode 100644 index 44868a17bd9..00000000000 --- a/policyengine_us/parameters/gov/states/nm/tax/income/deductions/net_capital_gains/excludes_short_term_gains.yaml +++ /dev/null @@ -1,19 +0,0 @@ -description: New Mexico excludes short-term capital gains from net capital gains under the net capital gains deduction if this is true. -values: - 2021-01-01: false - # From tax year 2023 the instructions define "net capital gains" by IRC - # Section 1222(11): net long-term capital gains less any net short-term - # capital losses. Earlier instructions use the net capital gains reported and - # claimed on the federal return. - 2023-01-01: true -metadata: - unit: bool - period: year - label: New Mexico net capital gains deduction excludes short-term capital gains - reference: - - title: 2022 New Mexico Personal Income Tax Instructions, PIT-ADJ Line 15 - href: https://klvg4oyd4j.execute-api.us-west-2.amazonaws.com/prod/PublicFiles/34821a9573ca43e7b06dfad20f5183fd/1afc56af-ea90-4d48-82e5-1f9aeb43255a/PITbook2022.pdf#page=51 - - title: 2023 New Mexico Personal Income Tax Instructions, PIT-ADJ ("net capital gains" defined by IRC Section 1222(11)) - href: https://klvg4oyd4j.execute-api.us-west-2.amazonaws.com/prod/PublicFiles/34821a9573ca43e7b06dfad20f5183fd/90560f4e-0ef0-4e52-a003-878b84f858bb/PITbook2023.pdf#page=32 - - title: 2024 New Mexico Personal Income Tax Instructions, PIT-ADJ ("net capital gains" defined by IRC Section 1222(11)) - href: https://klvg4oyd4j.execute-api.us-west-2.amazonaws.com/prod/PublicFiles/34821a9573ca43e7b06dfad20f5183fd/97f27f33-5e23-4a25-ad68-0980a22b802d/PITbook2024.pdf#page=33 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml index a925a721679..a8a733a2b3f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml @@ -389,3 +389,152 @@ # Tax: max($321.44 - $236, 0) = $85.44 # TaxAct reference: $86 (from TAXSIM issue #493) ar_income_tax: 85 + +# Married couples filing jointly: all income goes in Column A, so one spouse's +# partnership/S-corporation loss offsets the other spouse's wages (AR1000F +# instructions). 2024 values: joint standard deduction $4,820; personal credit +# $29 each; the joint low income table tops out at $28,300. + +- name: Married joint couple, spouse loss offsets wages, positive combined income on the regular table + absolute_error_margin: 0.01 + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 60_000 + person2: + age: 38 + partnership_s_corp_income: -20_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + ar_agi_joint: [60_000, -20_000] + # Combined AGI = 60,000 - 20,000 = 40,000; taxable income = + # 40,000 - 4,820 = 35,180, held by the head. + ar_taxable_income_joint: [35_180, 0] + # 40,000 is above the low income table, so the regular table applies. + ar_uses_low_income_tax_tables: false + ar_net_taxable_income_joint: [35_180, 0] + # 35,180 x 3.9% - 409.86 = 1,372.02 - 409.86 = 962.16 + ar_income_tax_before_non_refundable_credits_joint: [962.16, 0] + # Separately, person1 alone: (60,000 - 2,410) x 3.9% - 409.86 = 1,836.15. + ar_files_separately: false + ar_income_tax_before_non_refundable_credits_unit: 962.16 + # Personal credits 2 x 29 = 58; additional credit + # 120 - ceil((35,180 - 25,800) / 100) x 5 x 2 = 120 - 940 -> 0. + ar_non_refundable_credits: 58 + # 962.16 - 58 = 904.16 + ar_income_tax: 904.16 + +- name: Married joint couple, spouse loss offsets wages, positive combined income on the low income table + absolute_error_margin: 0.01 + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 47_050 + person2: + age: 38 + partnership_s_corp_income: -21_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + ar_agi_joint: [47_050, -21_000] + # Combined AGI = 47,050 - 21,000 = 26,050; taxable income = + # 26,050 - 4,820 = 21,230. + ar_taxable_income_joint: [21_230, 0] + # Low income table (joint, 0-1 dependents), 26,000 < 26,050 <= 26,100: 283. + ar_low_income_tax_joint: [283, 0] + # Regular table: 21,230 x 3.4% - 281.37 = 721.82 - 281.37 = 440.45 > 283. + ar_uses_low_income_tax_tables: true + # The low income table uses AGI rather than taxable income. + ar_net_taxable_income_joint: [26_050, 0] + ar_income_tax_before_non_refundable_credits_joint: [283, 0] + ar_files_separately: false + ar_income_tax_before_non_refundable_credits_unit: 283 + # Additional credit: 120 - ceil((26,050 - 25,800) / 100) x 5 x 2 = 120 - 30 = 90. + ar_additional_tax_credit_for_qualified_individuals: 90 + # Personal credits 2 x 29 = 58; 58 + 90 = 148. + ar_non_refundable_credits: 148 + # 283 - 148 = 135 + ar_income_tax: 135 + +- name: Married joint couple, spouse loss exactly cancels wages, zero combined income + absolute_error_margin: 0.01 + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 20_000 + person2: + age: 38 + partnership_s_corp_income: -20_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + ar_agi_joint: [20_000, -20_000] + # Combined AGI = 20,000 - 20,000 = 0; max(0, 0 - 4,820) = 0. + ar_taxable_income_joint: [0, 0] + ar_low_income_tax_joint: [0, 0] + # Both tables give 0 tax, so the low income table is not selected. + ar_uses_low_income_tax_tables: false + ar_net_taxable_income_joint: [0, 0] + ar_income_tax_before_non_refundable_credits_joint: [0, 0] + # Separately, person1 alone: (20,000 - 2,410) x 3.4% - 281.37 = 316.69. + ar_files_separately: false + ar_income_tax_before_non_refundable_credits_unit: 0 + ar_income_tax: 0 + +- name: Married joint couple, spouse loss exceeds wages, negative combined income + absolute_error_margin: 0.01 + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 10_000 + person2: + age: 38 + partnership_s_corp_income: -25_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + ar_agi_joint: [10_000, -25_000] + # Combined AGI = 10,000 - 25,000 = -15,000; max(0, -15,000 - 4,820) = 0. + ar_taxable_income_joint: [0, 0] + # The low income table is looked up at max(-15,000, 0) = 0, giving 0. + ar_low_income_tax_joint: [0, 0] + ar_uses_low_income_tax_tables: false + ar_net_taxable_income_joint: [0, 0] + ar_income_tax_before_non_refundable_credits_joint: [0, 0] + # Separately, person1 alone: (10,000 - 2,410) x 2% - 109.98 = 41.82. + ar_files_separately: false + ar_income_tax_before_non_refundable_credits_unit: 0 + ar_income_tax: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml index 20a5ec1872f..6c076a20ada 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml @@ -6,10 +6,11 @@ is_qualifying_child_dependent: false is_disabled: false person3: - # Not a qualifying child, and gross income above the $2,580 exemption - # amount, so not a Montana dependent. + # Not a qualifying child, and gross income of $3,000 (wages) is above + # the $2,580 exemption amount, so not a Montana dependent. is_tax_unit_dependent: true - irs_gross_income: 3_000 + is_tax_unit_spouse: false + employment_income: 3_000 is_qualifying_child_dependent: false is_disabled: false person4: @@ -75,15 +76,18 @@ person1: is_tax_unit_head: true person3: - # Not qualifying children, and gross income above the $2,710 - # exemption amount, so neither is a Montana dependent. + # Not qualifying children, and gross income (wages of $5,000 and + # $2,711) above the $2,710 exemption amount, so neither is a Montana + # dependent. is_tax_unit_dependent: true - irs_gross_income: 5_000 + is_tax_unit_spouse: false + employment_income: 5_000 is_qualifying_child_dependent: false is_disabled: false person4: is_tax_unit_dependent: true - irs_gross_income: 2_711 + is_tax_unit_spouse: false + employment_income: 2_711 is_qualifying_child_dependent: false is_disabled: true tax_units: @@ -131,7 +135,8 @@ # than $2,710, unless the dependent is a 'qualifying child'". age: 70 is_tax_unit_dependent: true - irs_gross_income: 0 + is_tax_unit_spouse: false + employment_income: 0 is_qualifying_child_dependent: false is_disabled: false tax_units: @@ -152,7 +157,8 @@ is_tax_unit_head: true person2: is_tax_unit_dependent: true - irs_gross_income: 2_960 + is_tax_unit_spouse: false + employment_income: 2_960 is_qualifying_child_dependent: false is_disabled: false tax_units: @@ -175,7 +181,8 @@ person2: # The additional exemption is "for a child who is disabled". is_tax_unit_dependent: true - irs_gross_income: 1_000 + is_tax_unit_spouse: false + employment_income: 1_000 is_qualifying_child_dependent: false is_disabled: true tax_units: @@ -187,3 +194,128 @@ state_code: MT output: mt_dependent_exemptions_person: [0, 2_710] + +- name: Adult dependent with wages just below the 2022 exemption amount is eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + person2: + age: 20 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + employment_income: 2_709 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 2,709 of wages <= 2,710 exemption amount, so one + # exemption of 2,710. + mt_dependent_exemptions_person: [0, 2_710] + +- name: Adult dependent with wages exactly at the 2022 exemption amount is eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + person2: + age: 20 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + employment_income: 2_710 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 2,710 of wages, which is not "more than" the 2,710 + # exemption amount, so one exemption of 2,710. + mt_dependent_exemptions_person: [0, 2_710] + +- name: Adult dependent with wages above the 2022 exemption amount is not eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + person2: + age: 20 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + employment_income: 3_000 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 3,000 of wages > 2,710 exemption amount, so not a + # Montana dependent. + mt_dependent_exemptions_person: [0, 0] + +- name: Adult dependent whose wages and interest together exceed the 2022 exemption amount is not eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + person2: + age: 20 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + employment_income: 2_000 + taxable_interest_income: 1_000 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 2,000 wages + 1,000 interest = 3,000 > 2,710, so not a + # Montana dependent. + mt_dependent_exemptions_person: [0, 0] + +- name: Qualifying child with wages above the 2022 exemption amount is still eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + person2: + age: 17 + is_tax_unit_dependent: true + employment_income: 5_000 + is_qualifying_child_dependent: true + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # The gross income test does not apply to a qualifying child, so 5,000 of + # wages above the 2,710 exemption amount still gives one exemption of 2,710. + mt_dependent_exemptions_person: [0, 2_710] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml index 4fdbc59ae7e..dbef165fe78 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml @@ -75,9 +75,11 @@ nm_net_capital_gains_deduction: 1_200_000 # "Net capital gains" follow IRC Section 1222(11): net long-term capital gain -# less any net short-term capital loss. PIT-ADJ instructions (2023 and 2024): -# "that section defines 'net capital gains' as only net long-term capital -# gains less any net short-term capital losses." +# less any net short-term capital loss. NMSA 7-2-34(D) has adopted that +# definition since 2019 HB6, section 14, so it applies in every supported year. +# PIT-ADJ instructions (2023 and 2024): "that section defines 'net capital +# gains' as only net long-term capital gains less any net short-term capital +# losses." - name: Short-term gains are excluded from net capital gains period: 2023 @@ -122,7 +124,7 @@ output: nm_net_capital_gains_deduction: 0 -- name: Before 2023, short-term gains count toward net capital gains +- name: Short-term gains are also excluded before 2023 period: 2022 input: filing_status: SINGLE @@ -130,7 +132,97 @@ short_term_capital_gains: 5_000 state_code: NM output: - # 2022 PIT-ADJ Line 15: "net capital gains reported and claimed on your - # federal tax return" (Form 1040 line 7 nets short- and long-term gains). - # 40% x 15,000 = 6,000. + # NMSA 7-2-34(D) (2019 HB6, section 14) already used IRC 1222(11) in 2022. + # Net capital gains = 10,000 - max(0, -5,000) = 10,000 (the 5,000 + # short-term gain is excluded). + # max(40% x 10,000, min(10,000, 1,000)) = max(4,000, 1,000) = 4,000. + nm_net_capital_gains_deduction: 4_000 + +# Cross-spouse netting on a joint return: net capital gain is computed on the +# couple's combined long-term and short-term amounts, so one spouse's net +# short-term loss reduces the other spouse's long-term gain. + +- name: Joint couple, one spouse's short-term loss partially offsets the other's long-term gain + period: 2023 + input: + people: + person1: + long_term_capital_gains: 20_000 + person2: + short_term_capital_gains: -5_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: NM + output: + # Net capital gains = 20,000 - 5,000 = 15,000. + # max(40% x 15,000, min(15,000, 1,000)) = max(6,000, 1,000) = 6,000. nm_net_capital_gains_deduction: 6_000 + +- name: Joint couple, one spouse's short-term loss exactly cancels the other's long-term gain + period: 2023 + input: + people: + person1: + long_term_capital_gains: 8_000 + person2: + short_term_capital_gains: -8_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: NM + output: + # Net capital gains = max(0, 8,000 - 8,000) = 0. + # max(40% x 0, min(0, 1,000)) = 0; the $1,000 element is limited to the + # net capital gain. + nm_net_capital_gains_deduction: 0 + +- name: Joint couple, one spouse's short-term loss exceeds the other's long-term gain + period: 2023 + input: + people: + person1: + long_term_capital_gains: 5_000 + person2: + short_term_capital_gains: -12_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: NM + output: + # Net capital gains = max(0, 5,000 - 12,000) = 0, so no deduction. + nm_net_capital_gains_deduction: 0 + +- name: Joint couple, short-term gain and loss net across spouses before offsetting the long-term gain + period: 2023 + input: + people: + person1: + long_term_capital_gains: 10_000 + short_term_capital_gains: 3_000 + person2: + short_term_capital_gains: -5_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: NM + output: + # Net short-term = 3,000 - 5,000 = -2,000, a net short-term loss of 2,000. + # Net capital gains = 10,000 - 2,000 = 8,000; 40% x 8,000 = 3,200. + nm_net_capital_gains_deduction: 3_200 diff --git a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py index ef6618bd0f5..13e52bc87f2 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py @@ -7,7 +7,10 @@ class mt_dependent_exemptions_person(Variable): label = "Montana dependent exemption for each dependent" unit = USD definition_period = YEAR - reference = "https://regulations.justia.com/states/montana/department-42/chapter-42-15/subchapter-42-15-4/rule-42-15-403/" + reference = ( + "https://regulations.justia.com/states/montana/department-42/chapter-42-15/subchapter-42-15-4/rule-42-15-403/", + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=14", + ) defined_for = StateCode.MT def formula(person, period, parameters): @@ -22,7 +25,9 @@ def formula(person, period, parameters): # 'qualifying child' according to the federal rules"). qualifying_child = person("is_qualifying_child_dependent", period) dependent = person("is_tax_unit_dependent", period) - gross_income = person("irs_gross_income", period) + # irs_gross_income is zero for dependents, so test the dependent's + # own gross income, as in the federal qualifying relative test. + gross_income = person("dependent_gross_income", period) other_dependent = dependent & ~qualifying_child & (gross_income <= p.amount) # Disabled children get an additional exemption. disabled = person("is_disabled", period) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py b/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py index 59fb0f87386..c83c316535d 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py @@ -7,22 +7,23 @@ class nm_net_capital_gains_deduction(Variable): label = "New Mexico net capital gain deduction" unit = USD definition_period = YEAR - reference = "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503882/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgAcwgEwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA" + reference = ( + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503882/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgAcwgEwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA", + # 2019 HB6, section 14: NMSA 7-2-34(D) adopts IRC 1222(11). + "https://www.nmlegis.gov/Sessions/19%20Regular/final/HB0006.pdf#page=41", + "https://www.law.cornell.edu/uscode/text/26/1222#11", + ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): p = parameters(period).gov.states.nm.tax.income.deductions.net_capital_gains long_term_gain = add(tax_unit, period, ["long_term_capital_gains"]) short_term_gain = add(tax_unit, period, ["short_term_capital_gains"]) - # From 2023 the instructions define "net capital gains" by IRC Section - # 1222(11): net long-term capital gain less any net short-term capital - # loss, so short-term gains are excluded. Earlier years use the net - # capital gains reported on the federal return. - net_capital_gains = where( - p.excludes_short_term_gains, - max_(0, long_term_gain - max_(0, -short_term_gain)), - max_(0, long_term_gain + short_term_gain), - ) + # NMSA 7-2-34(D) defines "net capital gain" by IRC Section 1222(11): + # net long-term capital gain less any net short-term capital loss, so + # short-term gains are excluded. + net_short_term_loss = max_(0, -short_term_gain) + net_capital_gains = max_(0, long_term_gain - net_short_term_loss) uncapped_element = p.uncapped_element_percent * net_capital_gains filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE From 57a635e7153c119e28568409ec86dc5c1ef52ca4 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 29 Sep 2026 12:50:20 -0400 Subject: [PATCH 3/9] Address review: Montana-specific dependent gross income Test Montana's non-child dependent exemption against a new mt_dependent_gross_income (MCA 15-30-2114(5)(a) and 15-30-2101(10)): federal gross income excluding unemployment compensation, counting only the IRC 86 taxable part of Social Security on the dependent's own income, and flooring long- and short-term gains separately instead of netting. Also count a permanently and totally disabled adult child as a qualifying child, count New Mexico capital gain distributions, and fix docs and tests from the review. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LqCGPgxDukji8Bhs57c5rH --- .../taxsim-sweep-ar-mt-nm-fixes.fixed.md | 2 +- .../gov/states/ar/tax/income/integration.yaml | 109 ++++- .../mt_dependent_exemptions_person.yaml | 419 +++++++++++++++++- .../exemptions/mt_dependent_gross_income.yaml | 188 ++++++++ .../nm_net_capital_gains_deduction.yaml | 64 ++- .../mt_dependent_exemptions_person.py | 22 +- .../dependent/mt_dependent_gross_income.py | 99 +++++ .../nm_net_capital_gains_deduction.py | 26 +- 8 files changed, 898 insertions(+), 31 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_gross_income.yaml create mode 100644 policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_gross_income.py diff --git a/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md b/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md index 4debb39eeb2..11eac934ed4 100644 --- a/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md +++ b/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md @@ -1 +1 @@ -Fixed Arkansas gross income to include partnership and S-corporation income and to let one spouse's loss offset the other's income on a joint return; Montana's capital gain credit to be zero rather than negative on net losses and dependent exemptions to cover non-child dependents whose own gross income is at or below the exemption amount; and New Mexico's net capital gains deduction to exclude short-term gains in every year, following the IRC Section 1222(11) definition that New Mexico law has used since 2019. +Fixed Arkansas gross income to include partnership and S-corporation income and to let one spouse's loss offset the other's income on a joint return; Montana's capital gain credit to be zero rather than negative on net losses, dependent exemptions to cover non-child dependents whose own Montana gross income (excluding unemployment compensation; only taxable Social Security) is at or below the exemption amount, and permanently and totally disabled adult children to count as qualifying children; and New Mexico's net capital gains deduction to exclude short-term gains in every year, following the IRC Section 1222(11) definition that New Mexico law has used since 2019, and to include capital gain distributions. diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml index a8a733a2b3f..aebfaf3328c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml @@ -391,9 +391,11 @@ ar_income_tax: 85 # Married couples filing jointly: all income goes in Column A, so one spouse's -# partnership/S-corporation loss offsets the other spouse's wages (AR1000F -# instructions). 2024 values: joint standard deduction $4,820; personal credit -# $29 each; the joint low income table tops out at $28,300. +# partnership/S-corporation loss offsets the other spouse's wages (2024 +# AR1000F/AR1000NR instructions, p.12: +# https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=12). +# 2024 values: joint standard deduction $4,820; personal credit $29 each; the +# joint low income table tops out at $28,300. - name: Married joint couple, spouse loss offsets wages, positive combined income on the regular table absolute_error_margin: 0.01 @@ -423,6 +425,9 @@ ar_uses_low_income_tax_tables: false ar_net_taxable_income_joint: [35_180, 0] # 35,180 x 3.9% - 409.86 = 1,372.02 - 409.86 = 962.16 + # The model uses the bracket formula. The 2024 tax table band + # 35,100-35,200 gives 961 (p.31), or 903 after credits; the gap between + # the formula and the table is at most about 2.45 below 100,000. ar_income_tax_before_non_refundable_credits_joint: [962.16, 0] # Separately, person1 alone: (60,000 - 2,410) x 3.9% - 409.86 = 1,836.15. ar_files_separately: false @@ -538,3 +543,101 @@ ar_files_separately: false ar_income_tax_before_non_refundable_credits_unit: 0 ar_income_tax: 0 + +- name: Married joint couple, spouse loss exceeds wages that would be taxed under a per-spouse floor + absolute_error_margin: 0.01 + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 30_000 + person2: + age: 38 + partnership_s_corp_income: -45_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + ar_agi_joint: [30_000, -45_000] + # Combined AGI = 30,000 - 45,000 = -15,000; max(0, -15,000 - 4,820) = 0. + ar_taxable_income_joint: [0, 0] + ar_low_income_tax_joint: [0, 0] + ar_net_taxable_income_joint: [0, 0] + ar_income_tax_before_non_refundable_credits_joint: [0, 0] + # Separately, person1 alone: (30,000 - 2,410) x 3.9% - 409.86 = 666.15. + ar_files_separately: false + ar_income_tax_before_non_refundable_credits_unit: 0 + # Flooring each spouse at zero would tax the 30,000 on its own: + # (30,000 - 4,820) x 3.4% - 281.37 = 574.75, less 58 personal and 120 + # additional credits = 396.75. + ar_income_tax: 0 + +- name: Married joint couple, head loss offsets spouse wages + absolute_error_margin: 0.01 + period: 2024 + input: + people: + person1: + age: 40 + partnership_s_corp_income: -20_000 + person2: + age: 38 + employment_income: 60_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + ar_agi_joint: [-20_000, 60_000] + # Combined AGI = -20,000 + 60,000 = 40,000; taxable income = + # 40,000 - 4,820 = 35,180, held by the head. + ar_taxable_income_joint: [35_180, 0] + ar_uses_low_income_tax_tables: false + ar_net_taxable_income_joint: [35_180, 0] + # 35,180 x 3.9% - 409.86 = 962.16 + ar_income_tax_before_non_refundable_credits_joint: [962.16, 0] + # Separately, person2 alone: (60,000 - 2,410) x 3.9% - 409.86 = 1,836.15. + ar_files_separately: false + ar_income_tax_before_non_refundable_credits_unit: 962.16 + # Personal credits 2 x 29 = 58; the additional credit phases out to 0. + ar_non_refundable_credits: 58 + # 962.16 - 58 = 904.16 + ar_income_tax: 904.16 + +- name: Married joint couple in 2021, partnership loss is in the earlier gross income source list + absolute_error_margin: 0.01 + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 60_000 + person2: + age: 38 + partnership_s_corp_income: -20_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + # The 2020-01-01 source list (which covers 2021) includes + # partnership_s_corp_income. + ar_gross_income_indiv: [60_000, -20_000] + ar_agi_joint: [60_000, -20_000] + # Combined AGI = 60,000 - 20,000 = 40,000; taxable income = + # 40,000 - 4,400 (2021 joint standard deduction) = 35,600. + ar_taxable_income_joint: [35_600, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml index 6c076a20ada..60f00abb70a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml @@ -9,7 +9,7 @@ # Not a qualifying child, and gross income of $3,000 (wages) is above # the $2,580 exemption amount, so not a Montana dependent. is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 3_000 is_qualifying_child_dependent: false is_disabled: false @@ -80,13 +80,13 @@ # $2,711) above the $2,710 exemption amount, so neither is a Montana # dependent. is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 5_000 is_qualifying_child_dependent: false is_disabled: false person4: is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 2_711 is_qualifying_child_dependent: false is_disabled: true @@ -135,7 +135,7 @@ # than $2,710, unless the dependent is a 'qualifying child'". age: 70 is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 0 is_qualifying_child_dependent: false is_disabled: false @@ -157,7 +157,7 @@ is_tax_unit_head: true person2: is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 2_960 is_qualifying_child_dependent: false is_disabled: false @@ -172,16 +172,18 @@ # "not ... more than" the $2,960 exemption amount mt_dependent_exemptions_person: [0, 2_960] -- name: Disabled adult dependent who is not a child gets one exemption, not two +- name: Disabled adult dependent who is not the taxpayer's child (for example, a parent) gets one exemption, not two period: 2022 input: people: person1: is_tax_unit_head: true person2: - # The additional exemption is "for a child who is disabled". + # The additional exemption is "for a child who is disabled". This + # dependent is not the taxpayer's child (for example, a parent), so + # is_qualifying_child_dependent is false. is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 1_000 is_qualifying_child_dependent: false is_disabled: true @@ -204,7 +206,7 @@ person2: age: 20 is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 2_709 is_qualifying_child_dependent: false is_disabled: false @@ -229,7 +231,7 @@ person2: age: 20 is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 2_710 is_qualifying_child_dependent: false is_disabled: false @@ -254,7 +256,7 @@ person2: age: 20 is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 3_000 is_qualifying_child_dependent: false is_disabled: false @@ -279,7 +281,7 @@ person2: age: 20 is_tax_unit_dependent: true - is_tax_unit_spouse: false + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. employment_income: 2_000 taxable_interest_income: 1_000 is_qualifying_child_dependent: false @@ -319,3 +321,396 @@ # The gross income test does not apply to a qualifying child, so 5,000 of # wages above the 2,710 exemption amount still gives one exemption of 2,710. mt_dependent_exemptions_person: [0, 2_710] + +# The income test uses Montana gross income (MCA 15-30-2101(10), see +# mt_dependent_gross_income): federal gross income excluding unemployment +# compensation, only taxable Social Security, and gains but not losses. In +# these 2022 cases the taxpayer has 50,000 of wages, so one 2,710 exemption +# changes mt_income_tax by 2,710 x 6.75% = 182.925 (182.92 or 182.93 after +# rounding). + +- name: Adult dependent with only unemployment compensation is eligible because Montana excludes it + absolute_error_margin: 0.01 + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + unemployment_compensation: 3_000 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Montana gross income = 3,000 - 3,000 unemployment compensation = 0 + # <= 2,710, so one exemption of 2,710. + mt_dependent_exemptions_person: [0, 2_710] + # Without the exemption the tax is 1,712.07; 1,712.07 - 182.92 = 1,529.15. + mt_income_tax: 1_529.15 + +- name: Adult dependent with unemployment compensation and wages exactly at the 2022 exemption amount is eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + unemployment_compensation: 3_000 + employment_income: 2_710 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Montana gross income = 2,710 wages (the 3,000 of unemployment + # compensation is excluded) <= 2,710, so one exemption of 2,710. + mt_dependent_exemptions_person: [0, 2_710] + +- name: Adult dependent with unemployment compensation and wages one dollar above the 2022 exemption amount is not eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + unemployment_compensation: 3_000 + employment_income: 2_711 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Montana gross income = 2,711 wages > 2,710, so not a Montana dependent. + mt_dependent_exemptions_person: [0, 0] + +- name: Dependent parent with Social Security below the taxable threshold is eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 70 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + social_security_retirement: 12_000 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Provisional income = 0.5 x 12,000 = 6,000 < 25,000, so no Social + # Security is taxable; Montana gross income = 0 <= 2,710. + mt_dependent_exemptions_person: [0, 2_710] + # mt_income_tax is not asserted: federal dependent_gross_income counts + # all 12,000 of Social Security, so the head files single rather than + # head of household. With that status the tax falls from 2,246.00 to + # 2,063.07 (-182.93). + +- name: Dependent parent with taxable Social Security exactly at the 2022 exemption amount is eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 70 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + social_security_retirement: 60_840 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Provisional income = 0.5 x 60,840 = 30,420; taxable Social Security = + # min(30,420, 0.5 x (30,420 - 25,000)) = 2,710 <= 2,710. + mt_dependent_exemptions_person: [0, 2_710] + +- name: Dependent parent with taxable Social Security one dollar above the 2022 exemption amount is not eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 70 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + social_security_retirement: 60_844 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Provisional income = 0.5 x 60,844 = 30,422; taxable Social Security = + # min(30,422, 0.5 x (30,422 - 25,000)) = 2,711 > 2,710. + mt_dependent_exemptions_person: [0, 0] + +- name: Adult dependent whose long-term gain exceeds the exemption amount is not eligible despite a short-term loss + absolute_error_margin: 0.01 + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + long_term_capital_gains: 3_000 + short_term_capital_gains: -2_000 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income counts gains but not losses: 3,000 + 0 = 3,000 > 2,710 + # (the net gain is only 1,000), so not a Montana dependent. + mt_dependent_exemptions_person: [0, 0] + # With the exemption on the net 1,000 the tax was 1,711.65; + # 1,711.65 + 182.92 = 1,894.57. + mt_income_tax: 1_894.57 + +- name: Adult dependent whose short-term gain exceeds the exemption amount is not eligible despite a long-term loss + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + short_term_capital_gains: 2_711 + long_term_capital_gains: -3_000 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 2,711 + 0 = 2,711 > 2,710 (the net is a 289 loss), so + # not a Montana dependent. + mt_dependent_exemptions_person: [0, 0] + +- name: Adult dependent with wages exactly at the 2021 exemption amount is eligible + period: 2021 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + employment_income: 2_580 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 2,580 <= 2,580 exemption amount (2021 Form 2 + # instructions, p.13), so one exemption of 2,580. + mt_dependent_exemptions_person: [0, 2_580] + +- name: Adult dependent with wages one dollar above the 2021 exemption amount is not eligible + period: 2021 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + employment_income: 2_581 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 2,581 > 2,580, so not a Montana dependent. + mt_dependent_exemptions_person: [0, 0] + +- name: Adult dependent with wages one dollar above the 2023 exemption amount is not eligible + period: 2023 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + employment_income: 2_961 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 2,961 > 2,960 exemption amount (2023 Form 2 + # instructions, p.16), so not a Montana dependent. + mt_dependent_exemptions_person: [0, 0] + +- name: Full-time student aged 22 is a qualifying child with no gross income test + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + # is_qualifying_child_dependent is derived from age and student status. + age: 22 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + is_full_time_student: true + employment_income: 5_000 + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Age 22 < 24 student age limit, so a qualifying child; the 5,000 of wages + # is not tested, so one exemption of 2,710. + mt_dependent_exemptions_person: [0, 2_710] + +- name: Non-student aged 22 is not a qualifying child and fails the gross income test + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + # is_qualifying_child_dependent is derived from age and student status. + age: 22 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + is_full_time_student: false + employment_income: 5_000 + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Age 22 >= 19 non-student age limit, so not a qualifying child; gross + # income = 5,000 > 2,710, so not a Montana dependent. + mt_dependent_exemptions_person: [0, 0] + +- name: Permanently and totally disabled adult child is a qualifying child and gets two exemptions + absolute_error_margin: 0.01 + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + # IRC 152(c)(3)(B): the age test does not apply to a child who is + # permanently and totally disabled. + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + is_permanently_and_totally_disabled: true + is_disabled: true + employment_income: 5_000 + is_qualifying_child_dependent: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # A qualifying child skips the gross income test, and a disabled child + # gets an additional exemption: 2 x 2,710 = 5,420. + mt_dependent_exemptions_person: [0, 5_420] + # Without the exemptions the tax is 1,914.57; 1,914.57 - 2 x 182.925 + # = 1,548.72. + mt_income_tax: 1_548.72 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_gross_income.yaml new file mode 100644 index 00000000000..d349bf5da95 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_gross_income.yaml @@ -0,0 +1,188 @@ +# Montana gross income (MCA 15-30-2101(10)) for the dependent income test in +# MCA 15-30-2114(5)(a): federal gross income excluding unemployment +# compensation, with only the IRC 86 taxable part of Social Security, and with +# gains but not losses (Pub. 501 (2022), p.2). Each case is 2022: the taxpayer +# (tax unit head) has $50,000 of wages, and the dependent is an adult who is +# not a qualifying child. is_tax_unit_spouse: false is required on each +# dependent; otherwise the adult is inferred as the spouse, whose income is +# not counted. 2022 IRC 86 single base amounts: $25,000 and $34,000. + +- name: Case 1, unemployment compensation is excluded. + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_qualifying_child_dependent: false + unemployment_compensation: 3_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 3,000 unemployment compensation - 3,000 excluded = 0. + mt_dependent_gross_income: [0, 0] + +- name: Case 2, wages still count when unemployment compensation is excluded. + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_qualifying_child_dependent: false + unemployment_compensation: 3_000 + employment_income: 2_711 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = 2,711 wages + 0 of the 3,000 unemployment compensation + # = 2,711. + mt_dependent_gross_income: [0, 2_711] + +- name: Case 3, Social Security below the base amount is not gross income. + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 70 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_qualifying_child_dependent: false + social_security_retirement: 12_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Provisional income = 0 other gross income + 0.5 x 12,000 = 6,000 + # < 25,000 base amount, so taxable Social Security = 0. + # Gross income = 0 + 0 = 0. + mt_dependent_gross_income: [0, 0] + +- name: Case 4, taxable Social Security exactly at the exemption amount. + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 70 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_qualifying_child_dependent: false + social_security_retirement: 60_840 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Provisional income = 0.5 x 60,840 = 30,420, between 25,000 and 34,000. + # Taxable = min(0.5 x 60,840, 0.5 x (30,420 - 25,000)) + # = min(30,420, 2,710) = 2,710. + mt_dependent_gross_income: [0, 2_710] + +- name: Case 5, taxable Social Security one dollar above the exemption amount. + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 70 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_qualifying_child_dependent: false + social_security_retirement: 60_844 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Provisional income = 0.5 x 60,844 = 30,422. + # Taxable = min(30,422, 0.5 x (30,422 - 25,000)) = min(30,422, 2,711) + # = 2,711. + mt_dependent_gross_income: [0, 2_711] + +- name: Case 6, a short-term loss does not offset a long-term gain. + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_qualifying_child_dependent: false + long_term_capital_gains: 3_000 + short_term_capital_gains: -2_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = max(0, 3,000) + max(0, -2,000) = 3,000 + 0 = 3,000, + # not the net gain of 1,000. + mt_dependent_gross_income: [0, 3_000] + +- name: Case 7, a long-term loss does not offset a short-term gain. + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_qualifying_child_dependent: false + short_term_capital_gains: 2_711 + long_term_capital_gains: -3_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Gross income = max(0, 2,711) + max(0, -3,000) = 2,711 + 0 = 2,711, + # not the net loss of 289. + mt_dependent_gross_income: [0, 2_711] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml index dbef165fe78..2ab1cd66842 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.yaml @@ -75,11 +75,13 @@ nm_net_capital_gains_deduction: 1_200_000 # "Net capital gains" follow IRC Section 1222(11): net long-term capital gain -# less any net short-term capital loss. NMSA 7-2-34(D) has adopted that -# definition since 2019 HB6, section 14, so it applies in every supported year. -# PIT-ADJ instructions (2023 and 2024): "that section defines 'net capital -# gains' as only net long-term capital gains less any net short-term capital -# losses." +# less any net short-term capital loss. NMSA 7-2-34 has adopted that +# definition since tax year 2019 (2019 HB6, section 14; applicability in +# section 59(A), p.105): in subsection (D) for 2019 through 2024 and in +# subsection (C) from 2025 (2024 HB252, section 8, p.30; section 42(B), +# p.147). PIT-1 line 15 instructions (2023 PIT instruction book, p.32): "that +# section defines 'net capital gains' as only net long-term capital gains +# less any net short-term capital losses." - name: Short-term gains are excluded from net capital gains period: 2023 @@ -132,7 +134,8 @@ short_term_capital_gains: 5_000 state_code: NM output: - # NMSA 7-2-34(D) (2019 HB6, section 14) already used IRC 1222(11) in 2022. + # NMSA 7-2-34(D) (2019 HB6, sections 14 and 59(A)) already used IRC + # 1222(11) in 2022. # Net capital gains = 10,000 - max(0, -5,000) = 10,000 (the 5,000 # short-term gain is excluded). # max(40% x 10,000, min(10,000, 1,000)) = max(4,000, 1,000) = 4,000. @@ -226,3 +229,52 @@ # Net short-term = 3,000 - 5,000 = -2,000, a net short-term loss of 2,000. # Net capital gains = 10,000 - 2,000 = 8,000; 40% x 8,000 = 3,200. nm_net_capital_gains_deduction: 3_200 + +- name: Short-term gains are also excluded in 2021 + period: 2021 + input: + filing_status: SINGLE + long_term_capital_gains: 10_000 + short_term_capital_gains: 5_000 + state_code: NM + output: + # 2019 HB6, section 59(A), p.105: the IRC 1222(11) definition applies + # from 2019. Net capital gains = 10,000 - max(0, -5,000) = 10,000. + # max(40% x 10,000, min(10,000, 1,000)) = max(4,000, 1,000) = 4,000 + # (counting the short-term gain would give 40% x 15,000 = 6,000). + nm_net_capital_gains_deduction: 4_000 + +- name: Only short-term gains in 2021 means no net capital gains + period: 2021 + input: + filing_status: SINGLE + short_term_capital_gains: 10_000 + state_code: NM + output: + # Net capital gains = max(0, 0 - max(0, -10,000)) = 0, so no deduction + # (counting the short-term gain would give 40% x 10,000 = 4,000). + nm_net_capital_gains_deduction: 0 + +- name: Short-term gains are excluded in 2024 + period: 2024 + input: + filing_status: SINGLE + long_term_capital_gains: 10_000 + short_term_capital_gains: 5_000 + state_code: NM + output: + # Net capital gains = 10,000 - max(0, -5,000) = 10,000. + # max(40% x 10,000, min(10,000, 1,000)) = max(4,000, 1,000) = 4,000. + nm_net_capital_gains_deduction: 4_000 + +- name: Capital gain distributions are long-term capital gains + period: 2023 + input: + filing_status: SINGLE + non_sch_d_capital_gains: 5_000 + state_code: NM + output: + # Capital gain distributions are long-term capital gains under IRC + # 852(b)(3)(B), so net capital gains = 5,000. + # max(40% x 5,000, min(5,000, 1,000)) = max(2,000, 1,000) = 2,000. + nm_net_capital_gains_deduction: 2_000 diff --git a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py index 13e52bc87f2..4da963d92c3 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_exemptions_person.py @@ -8,8 +8,16 @@ class mt_dependent_exemptions_person(Variable): unit = USD definition_period = YEAR reference = ( + # MCA 15-30-2114(5)(a) (2021): the dependent exemption. + "http://web.archive.org/web/20220818141612/https://leg.mt.gov/bills/mca/title_0150/chapter_0300/part_0210/section_0140/0150-0300-0210-0140.html", + # MCA 15-30-2101(10) (2021): the definition of gross income. + "http://web.archive.org/web/20220127192023/http://leg.mt.gov/bills/mca/title_0150/chapter_0300/part_0210/section_0010/0150-0300-0210-0010.html", "https://regulations.justia.com/states/montana/department-42/chapter-42-15/subchapter-42-15-4/rule-42-15-403/", + # Form 2 instructions, dependents: 2021 ($2,580), 2022 ($2,710) and + # 2023 ($2,960). + "https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2021_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=13", "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=14", + "https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2023_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=16", ) defined_for = StateCode.MT @@ -23,11 +31,17 @@ def formula(person, period, parameters): # dependent is an individual "who does not have gross income of # more than" the exemption amount "unless the dependent is a # 'qualifying child' according to the federal rules"). - qualifying_child = person("is_qualifying_child_dependent", period) dependent = person("is_tax_unit_dependent", period) - # irs_gross_income is zero for dependents, so test the dependent's - # own gross income, as in the federal qualifying relative test. - gross_income = person("dependent_gross_income", period) + # A permanently and totally disabled child is a qualifying child + # at any age (IRC 152(c)(3)(B)), as in + # is_qualifying_relative_dependent. + qualifying_child = person("is_qualifying_child_dependent", period) | ( + dependent & person("is_permanently_and_totally_disabled", period) + ) + # MCA 15-30-2114(5)(a) tests the dependent's own gross income as + # defined in MCA 15-30-2101(10): federal gross income excluding + # unemployment compensation, with only taxable Social Security. + gross_income = person("mt_dependent_gross_income", period) other_dependent = dependent & ~qualifying_child & (gross_income <= p.amount) # Disabled children get an additional exemption. disabled = person("is_disabled", period) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_gross_income.py b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_gross_income.py new file mode 100644 index 00000000000..9077d9690fe --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_gross_income.py @@ -0,0 +1,99 @@ +from policyengine_us.model_api import * + +# Montana "gross income" (MCA 15-30-2101(10)) starts from IRC 61 gross income +# but differs from the federal gross income sources in three places. +MT_GROSS_INCOME_SOURCE_OVERRIDES = { + # Unemployment compensation is excluded from Montana gross income. + "taxable_unemployment_compensation": [], + # Only the IRC 86 taxable part of Social Security is gross income. It is + # computed below on the dependent's own income. + "taxable_social_security": [], + # Gross income includes gains but not losses, so long-term and + # short-term gains are each floored at zero instead of being netted. + "capital_gains": ["long_term_capital_gains", "short_term_capital_gains"], +} + + +class mt_dependent_gross_income(Variable): + value_type = float + entity = Person + label = "Montana gross income of a dependent for the dependent exemption" + unit = USD + definition_period = YEAR + reference = ( + # MCA 15-30-2101(10) (2021): gross income is IRC 61 gross income + # excluding unemployment compensation. + "http://web.archive.org/web/20220127192023/http://leg.mt.gov/bills/mca/title_0150/chapter_0300/part_0210/section_0010/0150-0300-0210-0010.html", + # MCA 15-30-2114(5)(a) (2021): the dependent gross income test. + "http://web.archive.org/web/20220818141612/https://leg.mt.gov/bills/mca/title_0150/chapter_0300/part_0210/section_0140/0150-0300-0210-0140.html", + # ARM 42.15.403 restates the test; its subsection numbers are not + # verified because the rule text was not machine-readable. + "https://rules.mt.gov/gateway/ruleno.asp?RN=42.15.403", + # 2022 Form 2 instructions: "federal gross income, excluding + # unemployment compensation". + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=7", + # IRS Pub. 501 (2022): gross income includes gains but not losses, and + # Social Security counts only when taxable. + "https://www.irs.gov/pub/irs-prior/p501--2022.pdf#page=2", + "https://www.irs.gov/pub/irs-prior/p501--2022.pdf#page=19", + "https://www.law.cornell.edu/uscode/text/26/86", + ) + defined_for = StateCode.MT + + def formula(person, period, parameters): + sources = parameters(period).gov.irs.gross_income.sources + other_gross_income = 0 + for source in sources: + components = MT_GROSS_INCOME_SOURCE_OVERRIDES.get(source, [source]) + for component in components: + other_gross_income += max_(0, add(person, period, [component])) + + # Taxable Social Security under IRC 86, computed on the dependent's + # own income with the single base amounts. Pub. 501: benefits are not + # gross income unless "one-half of your social security benefits plus + # your other gross income and any tax-exempt interest" exceeds the + # base amount. Approximations: adjustments to income are ignored, and + # the $0 base for a dependent who is married filing separately and + # lives with their spouse is not modeled. + p = parameters(period).gov.irs.social_security.taxability + social_security = max_(0, person("social_security", period)) + # Unemployment compensation is federal gross income, so it counts + # toward provisional income even though Montana excludes it from + # gross income. + unemployment_compensation = max_(0, person("unemployment_compensation", period)) + provisional_income = ( + other_gross_income + + unemployment_compensation + + person("tax_exempt_interest_income", period) + + p.combined_income_ss_fraction * social_security + ) + base_amount = p.threshold.base.main["SINGLE"] + adjusted_base_amount = p.threshold.adjusted_base.main["SINGLE"] + # IRC 86(a)(1): the lesser of half the benefits or half the excess of + # provisional income over the base amount. + amount_under_paragraph_1 = min_( + p.rate.base.benefit_cap * social_security, + p.rate.base.excess * max_(0, provisional_income - base_amount), + ) + # IRC 86(a)(2): above the adjusted base amount. + bracket_amount = min_( + amount_under_paragraph_1, + p.rate.additional.bracket * (adjusted_base_amount - base_amount), + ) + amount_over_adjusted_base = min_( + p.rate.additional.excess + * max_(0, provisional_income - adjusted_base_amount) + + bracket_amount, + p.rate.additional.benefit_cap * social_security, + ) + taxable_social_security = select( + [ + provisional_income < base_amount, + provisional_income < adjusted_base_amount, + ], + [0, amount_under_paragraph_1], + default=amount_over_adjusted_base, + ) + # Only dependents' own income is tested, as in dependent_gross_income. + is_dependent = ~person("is_tax_unit_head_or_spouse", period) + return is_dependent * (other_gross_income + taxable_social_security) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py b/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py index c83c316535d..7a67fc05a63 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_net_capital_gains_deduction.py @@ -11,23 +11,39 @@ class nm_net_capital_gains_deduction(Variable): "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503882/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgAcwgEwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA", # 2019 HB6, section 14: NMSA 7-2-34(D) adopts IRC 1222(11). "https://www.nmlegis.gov/Sessions/19%20Regular/final/HB0006.pdf#page=41", + # 2019 HB6, section 59(A): section 14 applies from tax year 2019. + "https://www.nmlegis.gov/Sessions/19%20Regular/final/HB0006.pdf#page=105", + # 2024 HB252, section 8: from 2025 (section 42(B)) the same definition + # is in NMSA 7-2-34(C). + "https://www.nmlegis.gov/Sessions/24%20Regular/final/HB0252.pdf#page=30", "https://www.law.cornell.edu/uscode/text/26/1222#11", + # Capital gain distributions are long-term capital gains. + "https://www.law.cornell.edu/uscode/text/26/852#b_3_B", ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): p = parameters(period).gov.states.nm.tax.income.deductions.net_capital_gains - long_term_gain = add(tax_unit, period, ["long_term_capital_gains"]) + # Capital gain distributions reported without Schedule D are + # long-term capital gains under IRC 852(b)(3)(B). + long_term_gain = add( + tax_unit, period, ["long_term_capital_gains", "non_sch_d_capital_gains"] + ) short_term_gain = add(tax_unit, period, ["short_term_capital_gains"]) - # NMSA 7-2-34(D) defines "net capital gain" by IRC Section 1222(11): - # net long-term capital gain less any net short-term capital loss, so - # short-term gains are excluded. + # NMSA 7-2-34 defines "net capital gain" by IRC Section 1222(11): net + # long-term capital gain less any net short-term capital loss, so + # short-term gains are excluded. The definition is in subsection (D) + # for 2019 through 2024 (2019 HB6, sections 14 and 59(A)) and in + # subsection (C) from 2025 (2024 HB252, sections 8 and 42(B)). net_short_term_loss = max_(0, -short_term_gain) net_capital_gains = max_(0, long_term_gain - net_short_term_loss) uncapped_element = p.uncapped_element_percent * net_capital_gains filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE - # Halve the deduction if filing separately. + # Halve the deduction if filing separately. Limitation: the law + # (2019 HB6, section 14(B)) halves the deduction allowed on the joint + # return, which requires the couple's combined gains; each separate + # unit here uses only its own gains. denominator = where(separate, 2, 1) capped_element = p.capped_element.calc(net_capital_gains) numerator = max_(uncapped_element, capped_element) From b584e8b160839a1ecc526df1bdf7ee3a7746c6c0 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 29 Sep 2026 13:41:54 -0400 Subject: [PATCH 4/9] Floor the joint Arkansas AGI total in the itemized deduction floors With one spouse's loss now offsetting the other's income, the summed ar_agi_joint can be negative. Main's medical, casualty loss and miscellaneous joint deductions multiply that total by their AGI floor rate, so a negative total enlarged each deduction beyond the expense. Floor the total at zero, as the federal deductions do with positive_agi. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LqCGPgxDukji8Bhs57c5rH --- .../ar_casualty_loss_deduction_joint.yaml | 21 +++++++++++++++++ .../ar_medical_expense_deduction_joint.yaml | 23 +++++++++++++++++++ .../itemized/ar_misc_deduction_joint.yaml | 21 +++++++++++++++++ .../ar_casualty_loss_deduction_joint.py | 4 +++- .../ar_medical_expense_deduction_joint.py | 4 +++- .../itemized/ar_misc_deduction_joint.py | 4 +++- 6 files changed, 74 insertions(+), 3 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_casualty_loss_deduction_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_casualty_loss_deduction_joint.yaml index 86cca44a96e..baf51c21542 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_casualty_loss_deduction_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_casualty_loss_deduction_joint.yaml @@ -86,3 +86,24 @@ state_code: AR output: ar_casualty_loss_deduction_joint: 14_500 + +- name: A spouse's loss that makes the joint AGI negative does not raise the casualty loss deduction + period: 2024 + input: + people: + person1: + casualty_loss: 1_000 + ar_agi_joint: 5_000 + person2: + ar_agi_joint: -10_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: AR + output: + # Joint AGI 5,000 - 10,000 = -5,000, floored at 0: + # 1,000 - 100 - 10% * 0 = 900 (an unfloored AGI would give 900 + 500). + ar_casualty_loss_deduction_joint: 900 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.yaml index 142d394f993..33e6d550fa8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.yaml @@ -203,3 +203,26 @@ output: # 9_000 - 0.075 * 50_000; Form AR3 Line 3B: "if either you or your spouse were age 65 or over" ar_medical_expense_deduction_joint: [5_250, 0] + +- name: A spouse's loss that makes the joint AGI negative does not raise the medical expense deduction + period: 2024 + input: + people: + person1: + age: 40 + ar_agi_joint: 5_000 + person2: + age: 40 + ar_agi_joint: -10_000 + tax_units: + tax_unit: + members: [person1, person2] + itemized_medical_expenses: 1_000 + households: + household: + members: [person1, person2] + state_code: AR + output: + # Joint AGI 5,000 - 10,000 = -5,000, floored at 0, so the 10% floor is 0: + # 1,000 - 0.1 * 0 = 1,000 (an unfloored AGI would give 1,000 + 500). + ar_medical_expense_deduction_joint: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_misc_deduction_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_misc_deduction_joint.yaml index f862cd1d699..9054f751b53 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_misc_deduction_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_misc_deduction_joint.yaml @@ -63,3 +63,24 @@ state_code: AR output: ar_misc_deduction_joint: 5_500 + +- name: A spouse's loss that makes the joint AGI negative does not raise the miscellaneous deduction + period: 2024 + input: + people: + person1: + ar_agi_joint: 5_000 + person2: + ar_agi_joint: -10_000 + tax_units: + tax_unit: + members: [person1, person2] + total_misc_deductions: 1_000 + households: + household: + members: [person1, person2] + state_code: AR + output: + # Joint AGI 5,000 - 10,000 = -5,000, floored at 0: + # 1,000 - 2% * 0 = 1,000 (an unfloored AGI would give 1,000 + 100). + ar_misc_deduction_joint: 1_000 diff --git a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_casualty_loss_deduction_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_casualty_loss_deduction_joint.py index 3224ce6f093..5116e6cfe8b 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_casualty_loss_deduction_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_casualty_loss_deduction_joint.py @@ -26,5 +26,7 @@ def formula(tax_unit, period, parameters): # casualty and the per-loss exclusion applies once. loss = add(tax_unit, period, ["casualty_loss"]) loss_after_exclusion = max_(0, loss - p.exclusion) - agi = add(tax_unit, period, ["ar_agi_joint"]) + # A spouse's loss can make the joint AGI total negative; floor it at + # zero so a negative AGI cannot enlarge the deduction. + agi = max_(add(tax_unit, period, ["ar_agi_joint"]), 0) return max_(0, loss_after_exclusion - p.income_floor * agi) diff --git a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.py index 06be959a004..3057595a610 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.py @@ -11,7 +11,9 @@ class ar_medical_expense_deduction_joint(Variable): defined_for = StateCode.AR def formula(tax_unit, period, parameters): - agi = add(tax_unit, period, ["ar_agi_joint"]) + # A spouse's loss can make the joint AGI total negative; floor it at + # zero so a negative AGI cannot enlarge the deduction. + agi = max_(add(tax_unit, period, ["ar_agi_joint"]), 0) floor = tax_unit("ar_medical_expense_deduction_floor", period) medical_expenses = tax_unit("itemized_medical_expenses", period) return max_(0, medical_expenses - floor * agi) diff --git a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_misc_deduction_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_misc_deduction_joint.py index a1ef9f0e8bf..95a4f17395c 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_misc_deduction_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_misc_deduction_joint.py @@ -21,5 +21,7 @@ def formula(tax_unit, period, parameters): # itemized deductions, so start from the expenses, not misc_deduction. p = parameters(period).gov.states.ar.tax.income.deductions.itemized.misc expenses = tax_unit("total_misc_deductions", period) - agi = add(tax_unit, period, ["ar_agi_joint"]) + # A spouse's loss can make the joint AGI total negative; floor it at + # zero so a negative AGI cannot enlarge the deduction. + agi = max_(add(tax_unit, period, ["ar_agi_joint"]), 0) return max_(0, expenses - p.income_floor * agi) From 0f4406f1f6820ca5f0ce52ef6cfc93f385a0346b Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 29 Sep 2026 20:53:40 -0400 Subject: [PATCH 5/9] Require Arkansas joint filing with a negative spouse income and use tax table bands - ar_files_separately returns false when either spouse's total income is negative (AR1000F instructions, Filing Status 4) (#9680). - New ar_regular_income_tax helper evaluates the bracket formula at the midpoint of the $100 Regular Income Tax Table row below each year's table threshold and rounds to whole dollars (#9681). New tax_table parameters reproduce all 2014-2025 published rows. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LqCGPgxDukji8Bhs57c5rH --- ...r-joint-negative-income-tax-table.fixed.md | 1 + .../rates/main/tax_table/band_offset.yaml | 41 ++ .../rates/main/tax_table/band_width.yaml | 32 ++ .../rates/main/tax_table/threshold.yaml | 41 ++ .../main/tax_table/whole_dollar_midpoint.yaml | 25 + .../ar/tax/income/ar_files_separately.yaml | 204 +++++++++ .../ar/tax/income/ar_gross_income_indiv.yaml | 5 +- ...x_before_non_refundable_credits_indiv.yaml | 428 +++++++++++++----- ...x_before_non_refundable_credits_joint.yaml | 44 +- .../gov/states/ar/tax/income/integration.yaml | 61 ++- .../ar/dependent_credit_reform_test.yaml | 10 +- .../tests/test_ar_income_tax_schedule.py | 151 +++++- .../ar/tax/income/ar_files_separately.py | 13 +- ...tax_before_non_refundable_credits_indiv.py | 11 +- ...tax_before_non_refundable_credits_joint.py | 11 +- .../ar/tax/income/ar_income_tax_helpers.py | 28 ++ .../income/ar_uses_low_income_tax_tables.py | 4 +- 17 files changed, 933 insertions(+), 177 deletions(-) create mode 100644 changelog.d/ar-joint-negative-income-tax-table.fixed.md create mode 100644 policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_offset.yaml create mode 100644 policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_width.yaml create mode 100644 policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/threshold.yaml create mode 100644 policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/whole_dollar_midpoint.yaml diff --git a/changelog.d/ar-joint-negative-income-tax-table.fixed.md b/changelog.d/ar-joint-negative-income-tax-table.fixed.md new file mode 100644 index 00000000000..14bb3d73217 --- /dev/null +++ b/changelog.d/ar-joint-negative-income-tax-table.fixed.md @@ -0,0 +1 @@ +Require Arkansas married couples to file jointly when either spouse has a negative total income, and compute Arkansas regular income tax from the published tax table bands below the table threshold. diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_offset.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_offset.yaml new file mode 100644 index 00000000000..7154064f4a8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_offset.yaml @@ -0,0 +1,41 @@ +description: Arkansas sets the start of each regular income tax table income band this amount above a multiple of the band width. +# From $75,000 the table's rows run "As Much As $75,001 But Less Than $75,101", +# so each band starts $1 above a multiple of $100. The row below them spans +# $74,900 to $75,001, so the $1 offset starts at $75,000. The 2014 and 2015 +# tables end at $50,000, below this row. +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: currency-USD + period: year + label: Arkansas regular income tax table band offset + reference: + - title: 2016 AR1000F and AR1000NR Instructions, 2016 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2016.pdf#page=50 + - title: 2017 AR1000F and AR1000NR Instructions, 2017 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions.pdf#page=50 + - title: 2018 Arkansas Tax Tables, 2018 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2018_Final_TaxTable_with_Cover_Sheet.pdf#page=8 + - title: 2019 AR1000F and AR1000NR Instructions, 2019 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2019_AR1000F_and_AR1000NR_Instructions.pdf#page=31 + - title: 2020 AR1000F and AR1000NR Instructions, 2020 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2020_AR1000F_and_AR1000NR_Instructions.pdf#page=31 + - title: 2021 AR1000F and AR1000NR Instructions, 2021 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2021_AR1000F_and_AR1000NR_Instructions.pdf#page=31 + - title: 2022 AR1000F and AR1000NR Instructions, 2022 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=34 + - title: 2023 AR1000F and AR1000NR Instructions, 2023 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_and_AR1000NR_Instructions.pdf#page=34 + - title: 2024 AR1000F and AR1000NR Instructions, 2024 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=33 + - title: 2025 AR1000F and AR1000NR Instructions, 2025 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=33 +brackets: + - threshold: + 2014-01-01: 0 + amount: + 2014-01-01: 0 + - threshold: + 2014-01-01: 75_000 + amount: + 2014-01-01: 1 diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_width.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_width.yaml new file mode 100644 index 00000000000..d250ef5afad --- /dev/null +++ b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_width.yaml @@ -0,0 +1,32 @@ +description: Arkansas sets each income band of the regular income tax table to this width. +values: + 2014-01-01: 100 +metadata: + unit: currency-USD + period: year + label: Arkansas regular income tax table band width + reference: + - title: 2014 AR1000F and AR1000NR Instructions, 2014 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2014.pdf#page=28 + - title: 2015 Arkansas Tax Tables, 2015 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/TaxTables_2015.pdf#page=4 + - title: 2016 AR1000F and AR1000NR Instructions, 2016 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2016.pdf#page=46 + - title: 2017 AR1000F and AR1000NR Instructions, 2017 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions.pdf#page=46 + - title: 2018 Arkansas Tax Tables, 2018 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2018_Final_TaxTable_with_Cover_Sheet.pdf#page=4 + - title: 2019 AR1000F and AR1000NR Instructions, 2019 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2019_AR1000F_and_AR1000NR_Instructions.pdf#page=28 + - title: 2020 AR1000F and AR1000NR Instructions, 2020 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2020_AR1000F_and_AR1000NR_Instructions.pdf#page=28 + - title: 2021 AR1000F and AR1000NR Instructions, 2021 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2021_AR1000F_and_AR1000NR_Instructions.pdf#page=28 + - title: 2022 AR1000F and AR1000NR Instructions, 2022 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=31 + - title: 2023 AR1000F and AR1000NR Instructions, 2023 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_and_AR1000NR_Instructions.pdf#page=31 + - title: 2024 AR1000F and AR1000NR Instructions, 2024 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=30 + - title: 2025 AR1000F and AR1000NR Instructions, 2025 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=30 diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/threshold.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/threshold.yaml new file mode 100644 index 00000000000..51677f7f83a --- /dev/null +++ b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/threshold.yaml @@ -0,0 +1,41 @@ +description: Arkansas uses the regular income tax table for net taxable income below this amount. +# Each value is the "But Less Than" bound of the table's last row, where the +# table's note starts ("For $100,001 and over, your tax is ..."). At and above +# it, the model applies the bracket formula to the exact income. +values: + 2014-01-01: 50_000 + 2016-01-01: 86_001 + 2019-01-01: 87_001 + 2020-01-01: 88_001 + 2021-01-01: 91_001 + 2022-01-01: 94_001 + 2023-01-01: 100_001 +metadata: + unit: currency-USD + period: year + label: Arkansas regular income tax table threshold + reference: + - title: 2014 AR1000F and AR1000NR Instructions, 2014 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2014.pdf#page=30 + - title: 2015 Arkansas Tax Tables, 2015 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/TaxTables_2015.pdf#page=6 + - title: 2016 AR1000F and AR1000NR Instructions, 2016 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2016.pdf#page=50 + - title: 2017 AR1000F and AR1000NR Instructions, 2017 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions.pdf#page=50 + - title: 2018 Arkansas Tax Tables, 2018 Regular Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2018_Final_TaxTable_with_Cover_Sheet.pdf#page=8 + - title: 2019 AR1000F and AR1000NR Instructions, 2019 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2019_AR1000F_and_AR1000NR_Instructions.pdf#page=32 + - title: 2020 AR1000F and AR1000NR Instructions, 2020 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2020_AR1000F_and_AR1000NR_Instructions.pdf#page=32 + - title: 2021 AR1000F and AR1000NR Instructions, 2021 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2021_AR1000F_and_AR1000NR_Instructions.pdf#page=32 + - title: 2022 AR1000F and AR1000NR Instructions, 2022 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=35 + - title: 2023 AR1000F and AR1000NR Instructions, 2023 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_and_AR1000NR_Instructions.pdf#page=35 + - title: 2024 AR1000F and AR1000NR Instructions, 2024 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=34 + - title: 2025 AR1000F and AR1000NR Instructions, 2025 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=34 diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/whole_dollar_midpoint.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/whole_dollar_midpoint.yaml new file mode 100644 index 00000000000..e98aa3cf297 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/whole_dollar_midpoint.yaml @@ -0,0 +1,25 @@ +description: Arkansas uses the midpoint of the whole-dollar incomes in each regular income tax table band if this is true, and the midpoint of the band's bounds otherwise. +# DFA computes each row at the band midpoint and rounds to whole dollars. At +# the midpoint of the bounds ($14,850 for the $14,800-$14,900 row), some rows +# land exactly on 50 cents. Through 2021 the table rounds these up (2021: +# $40,250 x 5.9% - $797.25 = $1,577.50, printed as $1,578). From 2022 it +# rounds them down (2022: $24,750 x 4.9% - $628.25 = $584.50, printed as $584). +# That matches evaluating at the midpoint of the whole-dollar incomes the row +# covers ($24,700 to $24,799: $24,749.50 x 4.9% - $628.25 = $584.48, or $584). +# With these settings every 2014-2025 row matches the published table. +values: + 2014-01-01: false + 2022-01-01: true +metadata: + unit: bool + period: year + label: Arkansas regular income tax table whole-dollar midpoint + reference: + - title: 2021 Indexed Tax Brackets (the calculations in the table are made at the midpoint of each income level) + href: https://www.dfa.arkansas.gov/wp-content/uploads/TaxBrackets_2021.pdf#page=1 + - title: 2021 AR1000F and AR1000NR Instructions, 2021 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2021_AR1000F_and_AR1000NR_Instructions.pdf#page=30 + - title: 2022 AR1000F and AR1000NR Instructions, 2022 Regular Income Tax Table + href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=32 + - title: 2024 Indexed Tax Brackets (the calculations in the table are made at the midpoint of each income level) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=1 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_files_separately.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_files_separately.yaml index 7243c838026..9216824b1fb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_files_separately.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_files_separately.yaml @@ -231,3 +231,207 @@ state_code: AR output: ar_files_separately: false + +# A married couple must file jointly if either spouse had a total negative +# income: "IF ONE SPOUSE HAD A TOTAL NEGATIVE INCOME, YOU MUST FILE MARRIED +# FILING JOINTLY." (2024 AR1000F/AR1000NR instructions, Filing Status 4, p.12: +# https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=12; +# same wording in 2025, p.12). +- name: Separate tax lower, but the spouse's total income is negative, files jointly + period: 2024 + input: + people: + person1: + ar_income_tax_before_non_refundable_credits_indiv: 0 + ar_income_tax_before_non_refundable_credits_joint: 100 + ar_taxable_income_indiv: 50_000 + ar_net_taxable_income_joint: 50_000 + ar_gross_income_indiv: 50_000 + person2: + ar_income_tax_before_non_refundable_credits_indiv: 100 + ar_income_tax_before_non_refundable_credits_joint: 500 + ar_taxable_income_indiv: 0 + ar_net_taxable_income_joint: 0 + ar_gross_income_indiv: -1 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + # Separate tax 100 < joint tax 600, but person2's total income is -1 < 0. + ar_files_separately: false + +- name: Spouse partnership loss of $10 forces a joint return that costs more (issue 9680) + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 27_850 + person2: + age: 38 + partnership_s_corp_income: -10 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + ar_gross_income_indiv: [27_850, -10] + # Separate path: person1 27,850 - 2,410 = 25,440, 2024 Regular Income Tax + # Table row $25,400-$25,500: $584 (p.31); person2 has no taxable income. + ar_income_tax_before_non_refundable_credits_indiv: [584, 0] + # Separate additional credit: 60 each (25,440 and 0 are below 25,800) = 120, + # so the separate path nets 584 - 120 = 464. + # Joint path: AGI 27,850 - 10 = 27,840, joint low income table (0-1 + # dependents) row $27,801-$27,900: $470 (p.29); the joint additional credit + # 120 - ceil((27,840 - 25,800) / 100) x 10 phases out to 0, so it nets 470. + ar_low_income_tax_joint: [470, 0] + # Separate (464) < joint (470), but person2's total income is negative. + ar_files_separately: false + ar_income_tax_before_non_refundable_credits_unit: 470 + # 470 - 2 x 29 personal credits = 412 + ar_non_refundable_credits: 58 + ar_income_tax: 412 + +- name: Head partnership loss of $10 also forces a joint return + period: 2024 + input: + people: + person1: + age: 40 + partnership_s_corp_income: -10 + person2: + age: 38 + employment_income: 27_850 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + ar_gross_income_indiv: [-10, 27_850] + ar_income_tax_before_non_refundable_credits_indiv: [0, 584] + # Same as above with the spouses swapped: joint 470 - 58 = 412. + ar_files_separately: false + ar_income_tax: 412 + +- name: Spouse with zero total income can still file separately (regression) + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 27_850 + person2: + age: 38 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + # Zero is not negative. Separate 584 - 120 = 464 < joint 470 (AGI 27,850, + # row $27,801-$27,900), so the couple files separately. + ar_files_separately: true + ar_income_tax_before_non_refundable_credits_unit: 584 + # 584 - 58 personal credits - 120 additional credits = 406 + ar_income_tax: 406 + +- name: Both spouses with positive income keep the cheaper separate election (regression) + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 40_000 + person2: + age: 38 + employment_income: 40_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + # Separate: each 40,000 - 2,410 = 37,590, row $37,500-$37,600: $1,055 (p.31). + ar_income_tax_before_non_refundable_credits_indiv: [1_055, 1_055] + # Joint: 80,000 - 4,820 = 75,180, row $75,101-$75,201: $2,521 (p.33). + ar_income_tax_before_non_refundable_credits_joint: [2_521, 0] + # Separate 2,110 < joint 2,521; neither total income is negative. + ar_files_separately: true + ar_income_tax_before_non_refundable_credits_unit: 2_110 + # 2,110 - 58 = 2,052 + ar_income_tax: 2_052 + +- name: Spouse partnership loss of $300 files jointly (issue 9680) + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 28_450 + person2: + age: 38 + partnership_s_corp_income: -300 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + # Joint AGI 28,450 - 300 = 28,150, joint low income table row + # $28,101-$28,200: $501 (p.29). Separately, person1's 26,040 falls in the + # regular row $26,000-$26,100: $606 (p.31), less 45 + 60 of additional + # credits = 501, a tie; the negative total income requires the joint return. + ar_low_income_tax_joint: [501, 0] + ar_income_tax_before_non_refundable_credits_indiv: [606, 0] + ar_files_separately: false + # 501 - 58 = 443 + ar_income_tax: 443 + +- name: Spouse capital loss makes total income negative, files jointly + period: 2024 + input: + people: + person1: + age: 40 + employment_income: 27_850 + person2: + age: 38 + short_term_capital_gains: -500 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: AR + output: + # person2's total income is the -500 capital loss. + ar_gross_income_indiv: [27_850, -500] + ar_files_separately: false + # Joint AGI 27,850 - 500 = 27,350, joint low income table row + # $27,301-$27,400: $418 (p.29); 418 - 58 = 360. + ar_low_income_tax_joint: [418, 0] + ar_income_tax: 360 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gross_income_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gross_income_indiv.yaml index 29b8cdce07c..9a9dcbbad3b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gross_income_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gross_income_indiv.yaml @@ -129,7 +129,10 @@ output: ar_gross_income_indiv: 60_000 ar_gross_income_joint: 60_000 - ar_income_tax: 1_730.56 + # Net taxable income 60,000 - 2,520 = 57,480; table row $57,400-$57,499, + # midpoint 57,449.50: 609.60 + (57,449.50 - 26,400) x 3.7% = 1,758.43 -> 1,758; + # less the 29 personal credit = 1,729 + ar_income_tax: 1_729 - name: Arkansas unemployment compensation is exempt through 2017 (2016) period: 2016 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.yaml index 955f915ea9f..dfceeb4e2e1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.yaml @@ -25,8 +25,9 @@ ar_taxable_income_indiv: 11_000 state_code: AR output: - # 104 + round((11_000 - 10_301)* 0.03) - ar_income_tax_before_non_refundable_credits_indiv: 125 + # 2022 Regular Income Tax Table (p.31), row $11,000-$11,100: $127. + # Midpoint of $11,000-$11,099 = 11,049.50; 11,049.50 x 3% - 204.97 = 126.52 -> 127 + ar_income_tax_before_non_refundable_credits_indiv: 127 - name: AR income tax rates - taxable income from $14,701 to $24,300 period: 2022 @@ -35,8 +36,9 @@ ar_taxable_income_indiv: 20_000 state_code: AR output: - # 236 + round((20_000 - 14_701)* 0.034) - ar_income_tax_before_non_refundable_credits_indiv: 416 + # 2022 Regular Income Tax Table (p.32), row $20,000-$20,100: $418. + # Midpoint 20,049.50 x 3.4% - 263.77 = 417.91 -> 418 + ar_income_tax_before_non_refundable_credits_indiv: 418 - name: AR income tax rates - taxable income for income over $24,301 period: 2022 @@ -45,8 +47,9 @@ ar_taxable_income_indiv: 25_000 state_code: AR output: - # 562.366 + round((25_000-24_301) * 0.049) = 596 - ar_income_tax_before_non_refundable_credits_indiv: 596 + # 2022 Regular Income Tax Table (p.32), row $25,000-$25,100: $599. + # Midpoint 25,049.50 x 4.9% - 628.25 = 599.18 -> 599 + ar_income_tax_before_non_refundable_credits_indiv: 599 - name: AR income tax rates - taxable income from $15,100 to $25,000 period: 2023 @@ -55,8 +58,9 @@ ar_taxable_income_indiv: 20_000 state_code: AR output: - # 20_000 * 0.034 - 272.37 = 407.63 - ar_income_tax_before_non_refundable_credits_indiv: 407.63 + # 2023 Regular Income Tax Table (p.31), row $20,000-$20,100: $409. + # Midpoint 20,049.50 x 3.4% - 272.37 = 409.31 -> 409 + ar_income_tax_before_non_refundable_credits_indiv: 409 - name: AR income tax rates - taxable income from $25,000 to $89,600 period: 2023 @@ -65,8 +69,9 @@ ar_taxable_income_indiv: 30_000 state_code: AR output: - # 30_000 * 0.047 - 597.35 = 812.65 - ar_income_tax_before_non_refundable_credits_indiv: 812.65 + # 2023 Regular Income Tax Table (p.32), row $30,000-$30,100: $815. + # Midpoint 30,049.50 x 4.7% - 597.35 = 814.98 -> 815 + ar_income_tax_before_non_refundable_credits_indiv: 815 - name: AR income tax rates - taxable income over $100,000 period: 2023 @@ -75,8 +80,10 @@ ar_taxable_income_indiv: 100_000 state_code: AR output: - # 100_000 * 0.047 - 153.7 = 4546.3 - ar_income_tax_before_non_refundable_credits_indiv: 4546.3 + # $100,000 is in the 2023 table's last row, $99,901-$100,001: $4,544 (p.35). + # Midpoint of $99,901-$100,000 = 99,950.50; 99,950.50 x 4.7% - 153.70 = 4,543.97 -> 4,544 + # (the formula at the exact income, 100,000 x 4.7% - 153.70 = 4,546.30, applies from $100,001) + ar_income_tax_before_non_refundable_credits_indiv: 4_544 - name: Case 9, 2025 taxable income in 0% bracket. period: 2025 @@ -95,8 +102,9 @@ ar_taxable_income_indiv: 10_000 state_code: AR output: - # DFA 2025 bracket: 10_000 * 0.02 - 111.98 = 88.02 - ar_income_tax_before_non_refundable_credits_indiv: 88.02 + # 2025 Regular Income Tax Table (p.30), row $10,000-$10,100: $89. + # Midpoint 10,049.50 x 2% - 111.98 = 89.01 -> 89 + ar_income_tax_before_non_refundable_credits_indiv: 89 - name: Case 11, 2025 taxable income in 3.4% bracket. period: 2025 @@ -105,8 +113,9 @@ ar_taxable_income_indiv: 20_000 state_code: AR output: - # DFA 2025 bracket: 20_000 * 0.034 - 287.97 = 392.03 - ar_income_tax_before_non_refundable_credits_indiv: 392.03 + # 2025 Regular Income Tax Table (p.30), row $20,000-$20,100: $394. + # Midpoint 20,049.50 x 3.4% - 287.97 = 393.71 -> 394 + ar_income_tax_before_non_refundable_credits_indiv: 394 - name: Case 12, 2025 taxable income in 3.9% bracket. period: 2025 @@ -115,8 +124,9 @@ ar_taxable_income_indiv: 50_000 state_code: AR output: - # DFA 2025 bracket: 50_000 * 0.039 - 419.96 = 1_530.04 - ar_income_tax_before_non_refundable_credits_indiv: 1_530.04 + # 2025 Regular Income Tax Table (p.32), row $50,000-$50,100: $1,532. + # Midpoint 50,049.50 x 3.9% - 419.96 = 1,531.97 -> 1,532 + ar_income_tax_before_non_refundable_credits_indiv: 1_532 - name: Case 13, 2025 taxable income at $100,000. period: 2025 @@ -125,12 +135,16 @@ ar_taxable_income_indiv: 100_000 state_code: AR output: - # DFA 2025 bracket "$97,801 and over": 100_000 * 0.039 - 89.30 = 3_810.70 - ar_income_tax_before_non_refundable_credits_indiv: 3_810.70 + # $100,000 is in the 2025 table's last row, $99,901-$100,001: $3,809 (p.34). + # Midpoint 99,950.50 x 3.9% - 89.30 ("$97,801 and over" card row) = 3,808.77 -> 3,809 + ar_income_tax_before_non_refundable_credits_indiv: 3_809 # 2026 tests follow A.C.A. § 26-51-201(a)(4) as amended by HB1001 (2026 1st Extraordinary Session). # Bill schedules: (a)(4)(A) for net income <= $94,700, (a)(4)(B) for > $94,700, plus # the (a)(4)(C) bracket adjustment for $94,701 - $97,600. +# No 2026 Regular Income Tax Table is published yet, so below $100,001 the +# model applies DFA's table method: these schedules at the midpoint of the +# income's $100 table row, rounded to whole dollars. - name: AR HB1001 2026 - 0% bracket, no tax period: 2026 absolute_error_margin: 0.01 @@ -148,8 +162,8 @@ ar_taxable_income_indiv: 10_000 state_code: AR output: - # ($10,000 - $5,600) * 0.02 = $88 - ar_income_tax_before_non_refundable_credits_indiv: 88 + # Row $10,000-$10,099, midpoint 10,049.50: (10,049.50 - 5,600) x 2% = 88.99 -> 89 + ar_income_tax_before_non_refundable_credits_indiv: 89 - name: AR HB1001 2026 - 3% bracket period: 2026 @@ -158,8 +172,9 @@ ar_taxable_income_indiv: 15_000 state_code: AR output: - # $112 + ($15,000 - $11,200) * 0.03 = $112 + $114 = $226 - ar_income_tax_before_non_refundable_credits_indiv: 226 + # Row $15,000-$15,099, midpoint 15,049.50: + # 112 + (15,049.50 - 11,200) x 3% = 112 + 115.485 = 227.485 -> 227 + ar_income_tax_before_non_refundable_credits_indiv: 227 - name: AR HB1001 2026 - 3.4% bracket period: 2026 @@ -168,8 +183,9 @@ ar_taxable_income_indiv: 20_000 state_code: AR output: - # $112 + $144 + ($20,000 - $16,000) * 0.034 = $256 + $136 = $392 - ar_income_tax_before_non_refundable_credits_indiv: 392 + # Row $20,000-$20,099, midpoint 20,049.50: + # 112 + 144 + (20,049.50 - 16,000) x 3.4% = 256 + 137.683 = 393.683 -> 394 + ar_income_tax_before_non_refundable_credits_indiv: 394 - name: AR HB1001 2026 - top low-income bracket (3.7%) period: 2026 @@ -178,8 +194,9 @@ ar_taxable_income_indiv: 50_000 state_code: AR output: - # $609.60 + ($50,000 - $26,400) * 0.037 = $609.60 + $873.20 = $1,482.80 - ar_income_tax_before_non_refundable_credits_indiv: 1_482.80 + # Row $50,000-$50,099, midpoint 50,049.50: + # 609.60 + (50,049.50 - 26,400) x 3.7% = 609.60 + 875.03 = 1,484.63 -> 1,485 + ar_income_tax_before_non_refundable_credits_indiv: 1_485 - name: AR HB1001 2026 - boundary at $94,700 under (a)(4)(A) period: 2026 @@ -188,8 +205,9 @@ ar_taxable_income_indiv: 94_700 state_code: AR output: - # $609.60 + ($94,700 - $26,400) * 0.037 = $609.60 + $2,527.10 = $3,136.70 - ar_income_tax_before_non_refundable_credits_indiv: 3_136.70 + # Row $94,601-$94,700, midpoint 94,650.50 <= 94,700, so (a)(4)(A): + # 609.60 + (94,650.50 - 26,400) x 3.7% = 609.60 + 2,525.27 = 3,134.87 -> 3,135 + ar_income_tax_before_non_refundable_credits_indiv: 3_135 - name: AR HB1001 2026 - bracket adjustment top ($94,701, $290 adj) period: 2026 @@ -198,9 +216,10 @@ ar_taxable_income_indiv: 94_701 state_code: AR output: - # (a)(4)(B): $94 + ($94,701 - $4,700) * 0.037 = $3,424.04 - # (a)(4)(C) adjustment: -$290 -> $3,134.04 - ar_income_tax_before_non_refundable_credits_indiv: 3_134.04 + # Row $94,701-$94,800, midpoint 94,750.50 > 94,700, so (a)(4)(B) less (C): + # 94 + (94,750.50 - 4,700) x 3.7% = 3,425.87; - 290 = 3,135.87 -> 3,136 + # (the table removes the statute's $2.66 drop from $94,700 to $94,701) + ar_income_tax_before_non_refundable_credits_indiv: 3_136 - name: AR HB1001 2026 - bracket adjustment mid ($95,500, $220 adj) period: 2026 @@ -209,9 +228,9 @@ ar_taxable_income_indiv: 95_500 state_code: AR output: - # (a)(4)(B): $94 + ($95,500 - $4,700) * 0.037 = $3,453.60 - # (a)(4)(C) row "$95,401-$95,500" adjustment: -$220 -> $3,233.60 - ar_income_tax_before_non_refundable_credits_indiv: 3_233.60 + # Row $95,401-$95,500, midpoint 95,450.50: + # 94 + (95,450.50 - 4,700) x 3.7% = 3,451.77; (C) row "$95,401-$95,500": - 220 = 3,231.77 -> 3,232 + ar_income_tax_before_non_refundable_credits_indiv: 3_232 - name: AR HB1001 2026 - bracket adjustment mid ($96,500, $120 adj) period: 2026 @@ -220,9 +239,9 @@ ar_taxable_income_indiv: 96_500 state_code: AR output: - # (a)(4)(B): $94 + ($96,500 - $4,700) * 0.037 = $3,490.60 - # (a)(4)(C) row "$96,401-$96,500" adjustment: -$120 -> $3,370.60 - ar_income_tax_before_non_refundable_credits_indiv: 3_370.60 + # Row $96,401-$96,500, midpoint 96,450.50: + # 94 + (96,450.50 - 4,700) x 3.7% = 3,488.77; (C) row "$96,401-$96,500": - 120 = 3,368.77 -> 3,369 + ar_income_tax_before_non_refundable_credits_indiv: 3_369 - name: AR HB1001 2026 - bracket adjustment mid ($97,000, $70 adj) period: 2026 @@ -231,9 +250,9 @@ ar_taxable_income_indiv: 97_000 state_code: AR output: - # (a)(4)(B): $94 + ($97,000 - $4,700) * 0.037 = $3,509.10 - # (a)(4)(C) row "$96,901-$97,000" adjustment: -$70 -> $3,439.10 - ar_income_tax_before_non_refundable_credits_indiv: 3_439.10 + # Row $96,901-$97,000, midpoint 96,950.50: + # 94 + (96,950.50 - 4,700) x 3.7% = 3,507.27; (C) row "$96,901-$97,000": - 70 = 3,437.27 -> 3,437 + ar_income_tax_before_non_refundable_credits_indiv: 3_437 - name: AR HB1001 2026 - bracket adjustment bottom ($97,600, $10 adj) period: 2026 @@ -242,9 +261,9 @@ ar_taxable_income_indiv: 97_600 state_code: AR output: - # (a)(4)(B): $94 + ($97,600 - $4,700) * 0.037 = $3,531.30 - # (a)(4)(C) adjustment: -$10 -> $3,521.30 - ar_income_tax_before_non_refundable_credits_indiv: 3_521.30 + # Row $97,501-$97,600, midpoint 97,550.50: + # 94 + (97,550.50 - 4,700) x 3.7% = 3,529.47; (C) adjustment - 10 = 3,519.47 -> 3,519 + ar_income_tax_before_non_refundable_credits_indiv: 3_519 - name: AR HB1001 2026 - just past adjustment ($97,601, $0 adj) period: 2026 @@ -253,8 +272,9 @@ ar_taxable_income_indiv: 97_601 state_code: AR output: - # (a)(4)(B): $94 + ($97,601 - $4,700) * 0.037 = $3,531.34 - ar_income_tax_before_non_refundable_credits_indiv: 3_531.34 + # Row $97,601-$97,700, midpoint 97,650.50: + # 94 + (97,650.50 - 4,700) x 3.7% = 3,533.17 -> 3,533 + ar_income_tax_before_non_refundable_credits_indiv: 3_533 - name: AR HB1001 2026 - high income (a)(4)(B) period: 2026 @@ -285,8 +305,10 @@ state_code: AR output: # card row "*$90,001 *$90,200 4.70% $543.70" (ranges with * extend an - # extra $100 intentionally): 0.047 x 90,100 - 543.70 = 3,691.00 - ar_income_tax_before_non_refundable_credits_indiv: 3_691 + # extra $100 intentionally). 2023 Regular Income Tax Table (p.35), row + # $90,001-$90,101: $3,689. Midpoint of $90,001-$90,100 = 90,050.50; + # 0.047 x 90,050.50 - 543.70 = 3,688.67 -> 3,689 + ar_income_tax_before_non_refundable_credits_indiv: 3_689 - name: AR 2023 bracket adjustment - formerly duplicated 91,701/91,801 thresholds (pe-us 9026) period: 2023 @@ -301,8 +323,9 @@ ar_income_tax_before_non_refundable_credits_indiv: 3_918.55 # 2025 and 2024 bracket adjustments, keyed to the DFA Indexed Tax Brackets cards -# (2025_TaxBrackets.pdf and 2024_TaxBrackets.pdf, p1-2). Tax = 3.9% x net taxable -# income - the row's minus adjustment. +# (2025_TaxBrackets.pdf and 2024_TaxBrackets.pdf, p1-2). Below $100,001 the tax is +# the Regular Income Tax Table row: 3.9% x the row's midpoint - the minus +# adjustment, rounded to whole dollars. - name: AR 2025 bracket adjustment - top of the $26,400-$94,700 row period: 2025 absolute_error_margin: 0.01 @@ -310,9 +333,10 @@ ar_taxable_income_indiv: 94_700 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$26,400 $94,700 3.90% $419.96": - # 0.039 x 94,700 - 419.96 = 3,273.34 - ar_income_tax_before_non_refundable_credits_indiv: 3_273.34 + # DFA 2025 Indexed Tax Brackets row "$26,400 $94,700 3.90% $419.96". + # 2025 Regular Income Tax Table (p.34), row $94,601-$94,701: $3,271. + # Midpoint 94,650.50 x 3.9% - 419.96 = 3,271.41 -> 3,271 + ar_income_tax_before_non_refundable_credits_indiv: 3_271 - name: AR 2025 bracket adjustment - first $94,701-$94,800 row PE previously skipped period: 2025 @@ -321,10 +345,11 @@ ar_taxable_income_indiv: 94_701 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$94,701 $94,800 3.90% $399.30": - # 0.039 x 94,701 - 399.30 = 3,294.04 - # (PE previously applied the $419.96-row amount, 420, here -> 3,273.34) - ar_income_tax_before_non_refundable_credits_indiv: 3_294.04 + # DFA 2025 Indexed Tax Brackets row "$94,701 $94,800 3.90% $399.30". + # 2025 Regular Income Tax Table (p.34), row $94,701-$94,801: $3,296. + # Midpoint 94,750.50 x 3.9% - 399.30 = 3,295.97 -> 3,296 + # (PE previously applied the $419.96-row amount here) + ar_income_tax_before_non_refundable_credits_indiv: 3_296 - name: AR 2025 bracket adjustment - midpoint of the $94,701-$94,800 row period: 2025 @@ -333,10 +358,8 @@ ar_taxable_income_indiv: 94_751 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$94,701 $94,800 3.90% $399.30": - # 0.039 x 94,751 - 399.30 = 3,295.99 - # (2025 Regular Income Tax Table, $94,701-$94,801: $3,296) - ar_income_tax_before_non_refundable_credits_indiv: 3_295.99 + # Same table row as $94,701: $94,701-$94,801, $3,296 (p.34). + ar_income_tax_before_non_refundable_credits_indiv: 3_296 - name: AR 2025 bracket adjustment - top of the $94,701-$94,800 row period: 2025 @@ -345,9 +368,8 @@ ar_taxable_income_indiv: 94_800 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$94,701 $94,800 3.90% $399.30": - # 0.039 x 94,800 - 399.30 = 3,297.90 - ar_income_tax_before_non_refundable_credits_indiv: 3_297.90 + # $94,800 is still in the table row $94,701-$94,801: $3,296 (p.34). + ar_income_tax_before_non_refundable_credits_indiv: 3_296 - name: AR 2025 bracket adjustment - first income in the $94,801-$94,900 row period: 2025 @@ -356,9 +378,10 @@ ar_taxable_income_indiv: 94_801 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$94,801 $94,900 3.90% $389.30": - # 0.039 x 94,801 - 389.30 = 3,307.94 - ar_income_tax_before_non_refundable_credits_indiv: 3_307.94 + # DFA 2025 Indexed Tax Brackets row "$94,801 $94,900 3.90% $389.30". + # 2025 Regular Income Tax Table (p.34), row $94,801-$94,901: $3,310. + # Midpoint 94,850.50 x 3.9% - 389.30 = 3,309.87 -> 3,310 + ar_income_tax_before_non_refundable_credits_indiv: 3_310 - name: AR 2025 bracket adjustment - midpoint of the $94,801-$94,900 row period: 2025 @@ -367,10 +390,8 @@ ar_taxable_income_indiv: 94_851 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$94,801 $94,900 3.90% $389.30": - # 0.039 x 94,851 - 389.30 = 3,309.89 - # (2025 Regular Income Tax Table, $94,801-$94,901: $3,310) - ar_income_tax_before_non_refundable_credits_indiv: 3_309.89 + # Same table row: $94,801-$94,901, $3,310 (p.34). + ar_income_tax_before_non_refundable_credits_indiv: 3_310 - name: AR 2025 bracket adjustment - first income in the $94,901-$95,000 row period: 2025 @@ -379,9 +400,10 @@ ar_taxable_income_indiv: 94_901 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$94,901 $95,000 3.90% $379.30": - # 0.039 x 94,901 - 379.30 = 3,321.84 - ar_income_tax_before_non_refundable_credits_indiv: 3_321.84 + # DFA 2025 Indexed Tax Brackets row "$94,901 $95,000 3.90% $379.30". + # 2025 Regular Income Tax Table (p.34), row $94,901-$95,001: $3,324. + # Midpoint 94,950.50 x 3.9% - 379.30 = 3,323.77 -> 3,324 + ar_income_tax_before_non_refundable_credits_indiv: 3_324 - name: AR 2025 bracket adjustment - midpoint of the $94,901-$95,000 row period: 2025 @@ -390,10 +412,8 @@ ar_taxable_income_indiv: 94_951 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$94,901 $95,000 3.90% $379.30": - # 0.039 x 94,951 - 379.30 = 3,323.79 - # (2025 Regular Income Tax Table, $94,901-$95,001: $3,324) - ar_income_tax_before_non_refundable_credits_indiv: 3_323.79 + # Same table row: $94,901-$95,001, $3,324 (p.34). + ar_income_tax_before_non_refundable_credits_indiv: 3_324 - name: AR 2025 bracket adjustment - top of the $94,901-$95,000 row period: 2025 @@ -402,9 +422,8 @@ ar_taxable_income_indiv: 95_000 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$94,901 $95,000 3.90% $379.30": - # 0.039 x 95,000 - 379.30 = 3,325.70 - ar_income_tax_before_non_refundable_credits_indiv: 3_325.70 + # $95,000 is still in the table row $94,901-$95,001: $3,324 (p.34). + ar_income_tax_before_non_refundable_credits_indiv: 3_324 - name: AR 2025 bracket adjustment - first income in the $95,001-$95,100 row period: 2025 @@ -413,9 +432,10 @@ ar_taxable_income_indiv: 95_001 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$95,001 $95,100 3.90% $369.30": - # 0.039 x 95,001 - 369.30 = 3,335.74 - ar_income_tax_before_non_refundable_credits_indiv: 3_335.74 + # DFA 2025 Indexed Tax Brackets row "$95,001 $95,100 3.90% $369.30". + # 2025 Regular Income Tax Table (p.34), row $95,001-$95,101: $3,338. + # Midpoint 95,050.50 x 3.9% - 369.30 = 3,337.67 -> 3,338 + ar_income_tax_before_non_refundable_credits_indiv: 3_338 - name: AR 2025 bracket adjustment - top of the last $10-step row period: 2025 @@ -424,9 +444,10 @@ ar_taxable_income_indiv: 97_800 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$97,701 $97,800 3.90% $99.30": - # 0.039 x 97,800 - 99.30 = 3,714.90 - ar_income_tax_before_non_refundable_credits_indiv: 3_714.90 + # DFA 2025 Indexed Tax Brackets row "$97,701 $97,800 3.90% $99.30". + # 2025 Regular Income Tax Table (p.34), row $97,701-$97,801: $3,713. + # Midpoint 97,750.50 x 3.9% - 99.30 = 3,712.97 -> 3,713 + ar_income_tax_before_non_refundable_credits_indiv: 3_713 - name: AR 2025 bracket adjustment - first income in the and-over row period: 2025 @@ -435,9 +456,10 @@ ar_taxable_income_indiv: 97_801 state_code: AR output: - # DFA 2025 Indexed Tax Brackets row "$97,801 and over 3.90% $89.30": - # 0.039 x 97,801 - 89.30 = 3,724.94 - ar_income_tax_before_non_refundable_credits_indiv: 3_724.94 + # DFA 2025 Indexed Tax Brackets row "$97,801 and over 3.90% $89.30". + # 2025 Regular Income Tax Table (p.34), row $97,801-$97,901: $3,727. + # Midpoint 97,850.50 x 3.9% - 89.30 = 3,726.87 -> 3,727 + ar_income_tax_before_non_refundable_credits_indiv: 3_727 - name: AR 2024 bracket adjustment - inside the asterisked $94,301-$94,500 row period: 2024 @@ -446,10 +468,11 @@ ar_taxable_income_indiv: 94_450 state_code: AR output: - # DFA 2024 Indexed Tax Brackets row "*$94,301 *$94,500 3.90% $197.40" (asterisked, $200 wide): - # 0.039 x 94,450 - 197.40 = 3,486.15 - # (2024 Regular Income Tax Table, $94,401-$94,501: $3,486; PE previously applied $187.40 here -> 3,496.15) - ar_income_tax_before_non_refundable_credits_indiv: 3_486.15 + # DFA 2024 Indexed Tax Brackets row "*$94,301 *$94,500 3.90% $197.40" (asterisked, $200 wide). + # 2024 Regular Income Tax Table (p.34), row $94,401-$94,501: $3,486. + # Midpoint 94,450.50 x 3.9% - 197.40 = 3,486.17 -> 3,486 + # (PE previously applied $187.40 here -> 3,496.15) + ar_income_tax_before_non_refundable_credits_indiv: 3_486 - name: AR 2024 bracket adjustment - top of the asterisked $94,301-$94,500 row period: 2024 @@ -458,9 +481,8 @@ ar_taxable_income_indiv: 94_500 state_code: AR output: - # DFA 2024 Indexed Tax Brackets row "*$94,301 *$94,500 3.90% $197.40": - # 0.039 x 94,500 - 197.40 = 3,488.10 - ar_income_tax_before_non_refundable_credits_indiv: 3_488.10 + # $94,500 is still in the table row $94,401-$94,501: $3,486 (p.34). + ar_income_tax_before_non_refundable_credits_indiv: 3_486 - name: AR 2024 bracket adjustment - first income after the asterisked row period: 2024 @@ -469,9 +491,10 @@ ar_taxable_income_indiv: 94_501 state_code: AR output: - # DFA 2024 Indexed Tax Brackets row "$94,501 $94,600 3.90% $187.40": - # 0.039 x 94,501 - 187.40 = 3,498.14 - ar_income_tax_before_non_refundable_credits_indiv: 3_498.14 + # DFA 2024 Indexed Tax Brackets row "$94,501 $94,600 3.90% $187.40". + # 2024 Regular Income Tax Table (p.34), row $94,501-$94,601: $3,500. + # Midpoint 94,550.50 x 3.9% - 187.40 = 3,500.07 -> 3,500 + ar_income_tax_before_non_refundable_credits_indiv: 3_500 - name: AR 2024 bracket adjustment - top of the last $10-step row period: 2024 @@ -480,9 +503,10 @@ ar_taxable_income_indiv: 95_500 state_code: AR output: - # DFA 2024 Indexed Tax Brackets row "$95,401 $95,500 3.90% $97.40": - # 0.039 x 95,500 - 97.40 = 3,627.10 - ar_income_tax_before_non_refundable_credits_indiv: 3_627.10 + # DFA 2024 Indexed Tax Brackets row "$95,401 $95,500 3.90% $97.40". + # 2024 Regular Income Tax Table (p.34), row $95,401-$95,501: $3,625. + # Midpoint 95,450.50 x 3.9% - 97.40 = 3,625.17 -> 3,625 + ar_income_tax_before_non_refundable_credits_indiv: 3_625 - name: AR 2024 bracket adjustment - first income in the and-over row period: 2024 @@ -491,9 +515,10 @@ ar_taxable_income_indiv: 95_501 state_code: AR output: - # DFA 2024 Indexed Tax Brackets row "$95,501 and over 3.90% $87.40": - # 0.039 x 95,501 - 87.40 = 3,637.14 - ar_income_tax_before_non_refundable_credits_indiv: 3_637.14 + # DFA 2024 Indexed Tax Brackets row "$95,501 and over 3.90% $87.40". + # 2024 Regular Income Tax Table (p.34), row $95,501-$95,601: $3,639. + # Midpoint 95,550.50 x 3.9% - 87.40 = 3,639.07 -> 3,639 + ar_income_tax_before_non_refundable_credits_indiv: 3_639 # 2027 cases use incomes whose tax survives a one-step change in the projected # uprating factor. The (B)-(C) rows near the indexed threshold (about $97,500) @@ -516,8 +541,8 @@ ar_taxable_income_indiv: 5_900 state_code: AR output: - # (5,900 - 5,800) x 2% = 2.00 - ar_income_tax_before_non_refundable_credits_indiv: 2 + # Row $5,900-$5,999, midpoint 5,949.50: (5,949.50 - 5,800) x 2% = 2.99 -> 3 + ar_income_tax_before_non_refundable_credits_indiv: 3 - name: AR 2027 indexed Act 1 of 2026 tables - income the frozen 2026 reduction undertaxed by $108 period: 2027 @@ -526,8 +551,10 @@ ar_taxable_income_indiv: 11_300 state_code: AR output: - # (11,300 - 5,800) x 2% = 110.00; the frozen 2026 reduction schedule gave 2% x 11,300 - 224 = 2.00 - ar_income_tax_before_non_refundable_credits_indiv: 110 + # Row $11,300-$11,399, midpoint 11,349.50: (11,349.50 - 5,800) x 2% = 110.99 -> 111; + # at the exact income, (11,300 - 5,800) x 2% = 110.00, and the frozen 2026 + # reduction schedule gave 2% x 11,300 - 224 = 2.00 + ar_income_tax_before_non_refundable_credits_indiv: 111 - name: AR 2027 indexed Act 1 of 2026 tables - start of the 3% row period: 2027 @@ -536,8 +563,9 @@ ar_taxable_income_indiv: 11_500 state_code: AR output: - # (11,500 - 5,800) x 2% = 114.00 - ar_income_tax_before_non_refundable_credits_indiv: 114 + # Row $11,500-$11,599, midpoint 11,549.50: + # (11,500 - 5,800) x 2% + (11,549.50 - 11,500) x 3% = 114 + 1.485 = 115.485 -> 115 + ar_income_tax_before_non_refundable_credits_indiv: 115 - name: AR 2027 indexed Act 1 of 2026 tables - start of the 3.4% row period: 2027 @@ -546,8 +574,9 @@ ar_taxable_income_indiv: 16_500 state_code: AR output: - # 114 + (16,500 - 11,500) x 3% = 264.00 - ar_income_tax_before_non_refundable_credits_indiv: 264 + # Row $16,500-$16,599, midpoint 16,549.50: + # 114 + (16,500 - 11,500) x 3% + (16,549.50 - 16,500) x 3.4% = 264 + 1.683 = 265.683 -> 266 + ar_income_tax_before_non_refundable_credits_indiv: 266 - name: AR 2027 indexed Act 1 of 2026 tables - income the frozen 2026 reduction undertaxed by $70.20 period: 2027 @@ -556,8 +585,10 @@ ar_taxable_income_indiv: 26_500 state_code: AR output: - # 264 + (26,500 - 16,500) x 3.4% = 604.00; the frozen 2026 reduction schedule gave 3.4% x 26,500 - 367.20 = 533.80 - ar_income_tax_before_non_refundable_credits_indiv: 604 + # Row $26,500-$26,599, midpoint 26,549.50: + # 264 + (26,549.50 - 16,500) x 3.4% = 264 + 341.683 = 605.683 -> 606; + # the frozen 2026 reduction schedule gave 3.4% x 26,500 - 367.20 = 533.80 + ar_income_tax_before_non_refundable_credits_indiv: 606 - name: AR 2027 indexed Act 1 of 2026 tables - start of the 3.7% row period: 2027 @@ -566,8 +597,9 @@ ar_taxable_income_indiv: 27_200 state_code: AR output: - # 264 + (27,200 - 16,500) x 3.4% = 627.80 - ar_income_tax_before_non_refundable_credits_indiv: 627.80 + # Row $27,200-$27,299, midpoint 27,249.50: + # 264 + (27,200 - 16,500) x 3.4% + (27,249.50 - 27,200) x 3.7% = 627.80 + 1.8315 = 629.63 -> 630 + ar_income_tax_before_non_refundable_credits_indiv: 630 - name: AR 2027 indexed Act 1 of 2026 tables - top of table (A) at the indexed high-income threshold period: 2027 @@ -576,8 +608,10 @@ ar_taxable_income_indiv: 97_500 state_code: AR output: - # 627.80 + (97,500 - 27,200) x 3.7% = 3,228.90; the threshold is 94,700 x 1.0298 = 97,522, nearest $100 - ar_income_tax_before_non_refundable_credits_indiv: 3_228.90 + # The threshold is 94,700 x 1.0298 = 97,522, nearest $100 = 97,500. Row + # $97,401-$97,500, midpoint 97,450.50 <= 97,500, so table (A): + # 627.80 + (97,450.50 - 27,200) x 3.7% = 627.80 + 2,599.27 = 3,227.07 -> 3,227 + ar_income_tax_before_non_refundable_credits_indiv: 3_227 - name: AR 2027 indexed Act 1 of 2026 tables - high income period: 2027 @@ -588,3 +622,155 @@ output: # 96 + (150,000 - 4,800) x 3.7% = 5,468.40 ar_income_tax_before_non_refundable_credits_indiv: 5_468.40 + +# Regular Income Tax Table (issue 9681). Below the table threshold ($100,001 +# from 2023), filers look up net taxable income in $100 rows. DFA computes each +# row at its midpoint and rounds to whole dollars: "If you use a formula to +# calculate Arkansas income tax, the results must match the table exactly. The +# calculations in the table are made at the midpoint of each income level." +# (2024 Indexed Tax Brackets, https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=1). +# Pages below are file pages of that year's AR1000F/AR1000NR instructions. +- name: AR regular tax table 2024 - income inside the $35,100-$35,200 row + period: 2024 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 35_180 + state_code: AR + output: + # 2024 Regular Income Tax Table (p.31), row $35,100-$35,200: $961. + # Midpoint of $35,100-$35,199 = 35,149.50; 35,149.50 x 3.9% - 409.86 = 960.97 -> 961 + # (the bracket formula at the exact income gives 35,180 x 3.9% - 409.86 = 962.16) + ar_income_tax_before_non_refundable_credits_indiv: 961 + +- name: AR regular tax table 2024 - first income of the $35,100-$35,200 row + period: 2024 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 35_100 + state_code: AR + output: + # "As Much As" $35,100: same row, $961 (p.31). + ar_income_tax_before_non_refundable_credits_indiv: 961 + +- name: AR regular tax table 2024 - just below the $35,100-$35,200 row + period: 2024 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 35_099.99 + state_code: AR + output: + # "But Less Than" $35,100: row $35,000-$35,100, $957 (p.31). + # Midpoint 35,049.50 x 3.9% - 409.86 = 957.07 -> 957 + ar_income_tax_before_non_refundable_credits_indiv: 957 + +- name: AR regular tax table 2024 - round $80,000 falls in the $79,901-$80,001 row + period: 2024 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 80_000 + state_code: AR + output: + # From $75,000 rows start $1 above the hundred. Row $79,901-$80,001: $2,708 (p.33). + # Midpoint of $79,901-$80,000 = 79,950.50; 79,950.50 x 3.9% - 409.86 = 2,708.21 -> 2,708 + ar_income_tax_before_non_refundable_credits_indiv: 2_708 + +- name: AR regular tax table 2024 - $80,001 starts the next row + period: 2024 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 80_001 + state_code: AR + output: + # Row $80,001-$80,101: $2,712 (p.33). + # Midpoint 80,050.50 x 3.9% - 409.86 = 2,712.11 -> 2,712 + ar_income_tax_before_non_refundable_credits_indiv: 2_712 + +- name: AR regular tax table 2024 - $75,000 is in the $101-wide $74,900-$75,001 row + period: 2024 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 75_000 + state_code: AR + output: + # Row $74,900-$75,001: $2,513 (p.33). + # Midpoint of $74,901-$75,000 = 74,950.50; 74,950.50 x 3.9% - 409.86 = 2,513.21 -> 2,513 + ar_income_tax_before_non_refundable_credits_indiv: 2_513 + +- name: AR regular tax table 2024 - $100,000 is in the last table row + period: 2024 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 100_000 + state_code: AR + output: + # Row $99,901-$100,001: $3,811 (p.34). + # Midpoint 99,950.50 x 3.9% - 87.40 = 3,810.67 -> 3,811 + ar_income_tax_before_non_refundable_credits_indiv: 3_811 + +- name: AR regular tax table 2024 - the formula applies from $100,001 + period: 2024 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 100_001 + state_code: AR + output: + # "For $100,001 and over" (p.34) the model keeps the bracket formula at the + # exact income: 100,001 x 3.9% - 87.40 = 3,812.64 + ar_income_tax_before_non_refundable_credits_indiv: 3_812.64 + +- name: AR regular tax table 2025 - income inside the $35,100-$35,200 row + period: 2025 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 35_180 + state_code: AR + output: + # 2025 Regular Income Tax Table (p.31), row $35,100-$35,200: $951. + # Midpoint 35,149.50 x 3.9% - 419.96 = 950.87 -> 951 + ar_income_tax_before_non_refundable_credits_indiv: 951 + +- name: AR regular tax table 2021 - a 50-cent midpoint rounds up + period: 2021 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 40_250 + state_code: AR + output: + # 2021 Regular Income Tax Table (p.30), row $40,200-$40,300: $1,578. + # Through 2021 the midpoint is that of the row's bounds: + # (40,200 + 40,300) / 2 = 40,250; 40,250 x 5.9% - 797.25 = 1,577.50 -> 1,578 + ar_income_tax_before_non_refundable_credits_indiv: 1_578 + +- name: AR regular tax table 2022 - the matching 50-cent row rounds down + period: 2022 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 24_750 + state_code: AR + output: + # 2022 Regular Income Tax Table (p.32), row $24,700-$24,800: $584. + # From 2022 the midpoint is that of the whole-dollar incomes in the row: + # (24,700 + 24,799) / 2 = 24,749.50; 24,749.50 x 4.9% - 628.25 = 584.48 -> 584 + # (the bounds' midpoint would give 24,750 x 4.9% - 628.25 = 584.50) + ar_income_tax_before_non_refundable_credits_indiv: 584 + +- name: AR regular tax table 2015 - last row below the $50,000 threshold + period: 2015 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 49_999 + state_code: AR + output: + # 2015 Tax Tables (TaxTables_2015.pdf p.6), row $49,900-$50,000: $2,547. + # Midpoint 49,950 x 7% - 949.24 = 2,547.26 -> 2,547 + ar_income_tax_before_non_refundable_credits_indiv: 2_547 + +- name: AR regular tax table 2015 - the formula applies from $50,000 + period: 2015 + absolute_error_margin: 0.01 + input: + ar_taxable_income_indiv: 50_000 + state_code: AR + output: + # "For $50,000 and over" (p.6): 50,000 x 7% - 949.24 = 2,550.76 + ar_income_tax_before_non_refundable_credits_indiv: 2_550.76 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.yaml index 3429a1c3b37..973c835dbc3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.yaml @@ -28,8 +28,9 @@ ar_low_income_tax_joint: 500 state_code: AR output: - # 104 + round((11_000 - 10_301)* 0.03) - ar_income_tax_before_non_refundable_credits_joint: 125 + # 2022 Regular Income Tax Table (p.31), row $11,000-$11,100: $127 < 500. + # Midpoint of $11,000-$11,099 = 11,049.50; 11,049.50 x 3% - 204.97 = 126.52 -> 127 + ar_income_tax_before_non_refundable_credits_joint: 127 - name: AR income tax rates - taxable income from $14,701 to $24,300 period: 2022 @@ -39,8 +40,9 @@ ar_low_income_tax_joint: 500 state_code: AR output: - # 236 + round((20_000 - 14_701)* 0.034) - ar_income_tax_before_non_refundable_credits_joint: 416 + # 2022 Regular Income Tax Table (p.32), row $20,000-$20,100: $418 < 500. + # Midpoint 20,049.50 x 3.4% - 263.77 = 417.91 -> 418 + ar_income_tax_before_non_refundable_credits_joint: 418 - name: AR income tax rates - taxable income for income over $24,301 period: 2022 @@ -50,8 +52,9 @@ ar_low_income_tax_joint: 1_000 state_code: AR output: - # 562.366 + round((25_000-24_301) * 0.049) = 596 - ar_income_tax_before_non_refundable_credits_joint: 596 + # 2022 Regular Income Tax Table (p.32), row $25,000-$25,100: $599 < 1,000. + # Midpoint 25,049.50 x 4.9% - 628.25 = 599.18 -> 599 + ar_income_tax_before_non_refundable_credits_joint: 599 - name: AR income tax rates - taxable income for income over $25,000 period: 2023 @@ -61,7 +64,9 @@ ar_low_income_tax_joint: 1_000 state_code: AR output: - ar_income_tax_before_non_refundable_credits_joint: 591 + # 2023 Regular Income Tax Table (p.32), row $25,300-$25,400: $594 < 1,000. + # Midpoint 25,349.50 x 4.7% - 597.35 = 594.08 -> 594 + ar_income_tax_before_non_refundable_credits_joint: 594 - name: AR income tax rates - taxable income from $15,100 to $25,000 period: 2023 @@ -71,8 +76,9 @@ ar_low_income_tax_joint: 1_000 state_code: AR output: - # 20_000 * 0.034 - 272.37 = 407.63 < 1_000 - ar_income_tax_before_non_refundable_credits_joint: 407.63 + # 2023 Regular Income Tax Table (p.31), row $20,000-$20,100: $409 < 1,000. + # Midpoint 20,049.50 x 3.4% - 272.37 = 409.31 -> 409 + ar_income_tax_before_non_refundable_credits_joint: 409 - name: AR income tax rates - taxable income from $25,000 to $89,600 period: 2023 @@ -82,8 +88,9 @@ ar_low_income_tax_joint: 1_000 state_code: AR output: - # 30_000 * 0.047 - 597.35 = 812.65 < 1_000 - ar_income_tax_before_non_refundable_credits_joint: 812.65 + # 2023 Regular Income Tax Table (p.32), row $30,000-$30,100: $815 < 1,000. + # Midpoint 30,049.50 x 4.7% - 597.35 = 814.98 -> 815 + ar_income_tax_before_non_refundable_credits_joint: 815 - name: AR income tax rates - taxable income over $100,000 period: 2023 @@ -117,10 +124,11 @@ ar_low_income_tax_joint: 5_000 state_code: AR output: - # Regular: 50_000 * 0.039 - 419.96 = 1_530.04 + # Regular: 2025 Regular Income Tax Table (p.32), row $50,000-$50,100: $1,532 + # (midpoint 50,049.50 x 3.9% - 419.96 = 1,531.97 -> 1,532) # Low income cap: 5_000 - # min(1_530.04, 5_000) = 1_530.04 - ar_income_tax_before_non_refundable_credits_joint: 1_530.04 + # min(1_532, 5_000) = 1_532 + ar_income_tax_before_non_refundable_credits_joint: 1_532 - name: AR 2021 MFJ with two dependents just above the low-income table cutoff uses the regular table (pe-us 9024) period: 2021 @@ -208,6 +216,8 @@ ar_low_income_tax_joint: 5_000 state_code: AR output: - # Indexed 2027 rows start at 5,800 / 11,500 / 16,500 / 27,200 (nearest $100): - # 5,700 x 2% + 5,000 x 3% + 10,700 x 3.4% + (50,000 - 27,200) x 3.7% = 1,471.40 - ar_income_tax_before_non_refundable_credits_joint: 1_471.40 + # Indexed 2027 rows start at 5,800 / 11,500 / 16,500 / 27,200 (nearest $100). + # Table row $50,000-$50,099, midpoint 50,049.50: + # 5,700 x 2% + 5,000 x 3% + 10,700 x 3.4% + (50,049.50 - 27,200) x 3.7% + # = 627.80 + 845.43 = 1,473.23 -> 1,473 + ar_income_tax_before_non_refundable_credits_joint: 1_473 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml index aebfaf3328c..fea78f9589a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/integration.yaml @@ -91,8 +91,16 @@ household: members: [person1, person2] state_fips: 5 # AR - output: # expected results from patched TAXSIM35 2024-02-09 version - ar_income_tax: 4427.88 + output: + # Patched TAXSIM35 (2024-02-09) gives 4,427.88 from the bracket formula at + # the exact incomes. Filing separately, each spouse looks up the 2021 + # Regular Income Tax Table instead: + # person1: 77,010 + 5,505 - 2,200 = 80,315, row $80,301-$80,401: $3,943 (p.32) + # (midpoint 80,351 x 5.9% - 797.25 = 3,943.46 -> 3,943) + # person2: 20,010 + 5,505 - 2,200 = 23,315, row $23,300-$23,400: $546 (p.29) + # (midpoint 23,350 x 3.4% - 248.17 = 545.73 -> 546) + # 3,943 + 546 = 4,489, less 2 x 29 personal credits = 4,431 + ar_income_tax: 4_431 - name: Tax unit with taxsimid 77552 in e21.its.csv and e21.ots.csv # High error margin because of rounding in the tax tables @@ -196,7 +204,14 @@ members: [person1, person2, person3] state_fips: 5 output: - ar_income_tax: 1_085 + # Each spouse: 10,068.22 + 28,857.35 x 50% + 4,218.73 = 28,715.63 of + # gross income, less the 2,410 standard deduction = 26,305.63. Filing + # separately, each looks up the 2024 Regular Income Tax Table row + # $26,300-$26,400: $618 (p.31; midpoint 26,349.50 x 3.9% - 409.86 = 617.77). + # 2 x 618 = 1,236, less 3 x 29 personal credits and 2 x 30 additional + # credits (60 - ceil(505.63 / 100) x 5) = 1,236 - 147 = 1,089. The bracket + # formula at the exact incomes gave 2 x 616.06 - 147 = 1,085.12. + ar_income_tax: 1_089 - name: 3864-AR.yaml absolute_error_margin: 2 @@ -424,19 +439,19 @@ # 40,000 is above the low income table, so the regular table applies. ar_uses_low_income_tax_tables: false ar_net_taxable_income_joint: [35_180, 0] - # 35,180 x 3.9% - 409.86 = 1,372.02 - 409.86 = 962.16 - # The model uses the bracket formula. The 2024 tax table band - # 35,100-35,200 gives 961 (p.31), or 903 after credits; the gap between - # the formula and the table is at most about 2.45 below 100,000. - ar_income_tax_before_non_refundable_credits_joint: [962.16, 0] - # Separately, person1 alone: (60,000 - 2,410) x 3.9% - 409.86 = 1,836.15. + # 2024 Regular Income Tax Table row $35,100-$35,200: $961 (p.31). + # Midpoint 35,149.50 x 3.9% - 409.86 = 1,370.83 - 409.86 = 960.97 -> 961 + # (the bracket formula at the exact income gave 962.16) + ar_income_tax_before_non_refundable_credits_joint: [961, 0] + # Person2's total income is negative, so the couple must file jointly + # (2024 AR1000F instructions, p.12). ar_files_separately: false - ar_income_tax_before_non_refundable_credits_unit: 962.16 + ar_income_tax_before_non_refundable_credits_unit: 961 # Personal credits 2 x 29 = 58; additional credit # 120 - ceil((35,180 - 25,800) / 100) x 5 x 2 = 120 - 940 -> 0. ar_non_refundable_credits: 58 - # 962.16 - 58 = 904.16 - ar_income_tax: 904.16 + # 961 - 58 = 903 + ar_income_tax: 903 - name: Married joint couple, spouse loss offsets wages, positive combined income on the low income table absolute_error_margin: 0.01 @@ -506,7 +521,8 @@ ar_uses_low_income_tax_tables: false ar_net_taxable_income_joint: [0, 0] ar_income_tax_before_non_refundable_credits_joint: [0, 0] - # Separately, person1 alone: (20,000 - 2,410) x 3.4% - 281.37 = 316.69. + # Person2's total income is negative, so the couple must file jointly + # (2024 AR1000F instructions, p.12). ar_files_separately: false ar_income_tax_before_non_refundable_credits_unit: 0 ar_income_tax: 0 @@ -539,7 +555,8 @@ ar_uses_low_income_tax_tables: false ar_net_taxable_income_joint: [0, 0] ar_income_tax_before_non_refundable_credits_joint: [0, 0] - # Separately, person1 alone: (10,000 - 2,410) x 2% - 109.98 = 41.82. + # Person2's total income is negative, so the couple must file jointly + # (2024 AR1000F instructions, p.12). ar_files_separately: false ar_income_tax_before_non_refundable_credits_unit: 0 ar_income_tax: 0 @@ -570,7 +587,8 @@ ar_low_income_tax_joint: [0, 0] ar_net_taxable_income_joint: [0, 0] ar_income_tax_before_non_refundable_credits_joint: [0, 0] - # Separately, person1 alone: (30,000 - 2,410) x 3.9% - 409.86 = 666.15. + # Person2's total income is negative, so the couple must file jointly + # (2024 AR1000F instructions, p.12). ar_files_separately: false ar_income_tax_before_non_refundable_credits_unit: 0 # Flooring each spouse at zero would tax the 30,000 on its own: @@ -604,15 +622,16 @@ ar_taxable_income_joint: [35_180, 0] ar_uses_low_income_tax_tables: false ar_net_taxable_income_joint: [35_180, 0] - # 35,180 x 3.9% - 409.86 = 962.16 - ar_income_tax_before_non_refundable_credits_joint: [962.16, 0] - # Separately, person2 alone: (60,000 - 2,410) x 3.9% - 409.86 = 1,836.15. + # 2024 Regular Income Tax Table row $35,100-$35,200: $961 (p.31). + ar_income_tax_before_non_refundable_credits_joint: [961, 0] + # Person1 (the head) has a negative total income, so the couple must + # file jointly (2024 AR1000F instructions, p.12). ar_files_separately: false - ar_income_tax_before_non_refundable_credits_unit: 962.16 + ar_income_tax_before_non_refundable_credits_unit: 961 # Personal credits 2 x 29 = 58; the additional credit phases out to 0. ar_non_refundable_credits: 58 - # 962.16 - 58 = 904.16 - ar_income_tax: 904.16 + # 961 - 58 = 903 + ar_income_tax: 903 - name: Married joint couple in 2021, partnership loss is in the earlier gross income source list absolute_error_margin: 0.01 diff --git a/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml index 5f2473f69f6..f9800b94721 100644 --- a/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml @@ -116,8 +116,10 @@ # End-to-end: with the default (no-op) amount, the personal credit (head 29 + # 2 deps * 29 = 87) is consumed via `adds` into ar_personal_credits and reduces # AR income tax. This proves the shared-base-redirect dependent credit is wired - # through to the actual tax, not just the credit aggregate. - output: {ar_income_tax: 1346.67} + # through to the actual tax, not just the credit aggregate. Net taxable + # income 50,000 - 2,470 = 47,530 falls in the 2025 Regular Income Tax Table + # row $47,500-$47,600: $1,434 (AR1000F instructions p.32); 1,434 - 87 = 1,347. + output: {ar_income_tax: 1347} - name: AR dependent credit reform - amount zero raises income tax (downstream end-to-end) period: 2025 @@ -133,9 +135,9 @@ tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: AR}} # End-to-end: removing the dependent portion (amount 0) drops the personal - # credit from 87 to 29, raising AR income tax by exactly 58 (1346.67 -> 1404.67). + # credit from 87 to 29, raising AR income tax by exactly 58 (1347 -> 1405). # Confirms the dependent-credit -> tax wiring under the shared-base-redirect pattern. - output: {ar_income_tax: 1404.67} + output: {ar_income_tax: 1405} - name: AR dependent credit reform - age limit excludes dependents at or over the age period: 2025 diff --git a/policyengine_us/tests/test_ar_income_tax_schedule.py b/policyengine_us/tests/test_ar_income_tax_schedule.py index 4e865b8574d..4bcda918727 100644 --- a/policyengine_us/tests/test_ar_income_tax_schedule.py +++ b/policyengine_us/tests/test_ar_income_tax_schedule.py @@ -1,7 +1,8 @@ """Arkansas regular income tax schedule: differential and invariant tests. -`ar_main_income_tax` (shared by the indiv and joint tax variables and -`ar_uses_low_income_tax_tables`) has two paths: +`ar_main_income_tax` (the bracket formula that `ar_regular_income_tax` applies +at Regular Income Tax Table row midpoints for the indiv and joint tax variables +and `ar_uses_low_income_tax_tables`) has two paths: - Years with a published DFA Indexed Tax Brackets card (2021-2025) compute `rate.calc(ti) * ti - reduction.calc(ti)` from two single_amount scales @@ -46,6 +47,10 @@ - A reform to the rate table in a projected year keeps tax from falling anywhere except where (A) meets (B)-(C), because the reduction is derived rather than stored. + +`ar_regular_income_tax` reproduces published Regular Income Tax Table rows for +2014-2025, switches to the formula at each year's table threshold, and below +it gives whole dollars that change only at a row start. """ from types import SimpleNamespace @@ -57,6 +62,7 @@ from policyengine_us.system import system from policyengine_us.variables.gov.states.ar.tax.income.ar_income_tax_helpers import ( ar_main_income_tax, + ar_regular_income_tax, ) P = system.parameters.gov.states.ar.tax.income.rates.main @@ -387,3 +393,144 @@ def test_ar_rate_table_reform_keeps_projected_tax_from_falling(): falls = ti[:-1][change < -1e-9] seam = reformed.high_income.threshold("2027-01-01") assert set(falls) <= {seam}, falls + + +# Regular Income Tax Table: below the table threshold, filers look up their net +# taxable income in $100 rows ("As Much As" / "But Less Than"), each computed +# at the row midpoint and rounded to whole dollars. Rows start $1 above the +# hundred from $75,000. Transcribed from each year's DFA table; the last +# element of each row is the file page. +TAX_TABLE_SOURCES = { + 2014: "https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2014.pdf", + 2015: "https://www.dfa.arkansas.gov/wp-content/uploads/TaxTables_2015.pdf", + 2016: "https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2016.pdf", + 2017: "https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions.pdf", + 2018: "https://www.dfa.arkansas.gov/wp-content/uploads/2018_Final_TaxTable_with_Cover_Sheet.pdf", + **{ + year: f"https://www.dfa.arkansas.gov/wp-content/uploads/{year}_AR1000F_and_AR1000NR_Instructions.pdf" + for year in range(2019, 2026) + }, +} +# (As Much As, But Less Than, tax, file page). 2021 rounds its 50-cent +# midpoints up ($40,250: $1,577.50 -> $1,578); 2022 and 2023 round them down +# ($24,750: $584.50 -> $584; $34,550: $1,026.50 -> $1,026). +TAX_TABLE_ROWS = { + 2014: [(100, 200, 1, 28), (35_000, 35_100, 1_509, 30), (49_900, 50_000, 2_552, 30)], + 2015: [(100, 200, 1, 4), (35_000, 35_100, 1_507, 6), (49_900, 50_000, 2_547, 6)], + 2016: [ + (100, 200, 1, 46), + (35_000, 35_100, 1_369, 48), + (74_900, 75_001, 3_762, 50), + (75_001, 75_101, 3_829, 50), + (85_901, 86_001, 5_021, 50), + ], + 2017: [ + (100, 200, 1, 46), + (35_000, 35_100, 1_356, 48), + (74_900, 75_001, 3_737, 50), + (75_001, 75_101, 3_743, 50), + (85_901, 86_001, 4_990, 50), + ], + 2018: [ + (100, 200, 1, 4), + (35_000, 35_100, 1_347, 6), + (74_900, 75_001, 3_719, 8), + (75_001, 75_101, 3_725, 8), + (85_901, 86_001, 4_968, 8), + ], + 2019: [ + (4_600, 4_700, 1, 28), + (35_000, 35_100, 1_331, 29), + (74_900, 75_001, 3_697, 31), + (75_001, 75_101, 3_703, 31), + (86_901, 87_001, 5_018, 32), + ], + 2020: [ + (4_700, 4_800, 1, 28), + (35_000, 35_100, 1_325, 29), + (74_900, 75_001, 3_648, 31), + (75_001, 75_101, 3_654, 31), + (87_901, 88_001, 4_989, 32), + ], + 2021: [ + (4_800, 4_900, 1, 28), + (35_000, 35_100, 1_313, 29), + (40_200, 40_300, 1_578, 30), + (74_900, 75_001, 3_625, 31), + (75_001, 75_101, 3_631, 31), + (90_901, 91_001, 5_119, 32), + ], + 2022: [ + (5_100, 5_200, 1, 31), + (24_700, 24_800, 584, 32), + (35_000, 35_100, 1_089, 32), + (74_900, 75_001, 3_044, 34), + (75_001, 75_101, 3_049, 34), + (93_901, 94_001, 4_436, 35), + ], + 2023: [ + (5_300, 5_400, 1, 31), + (34_500, 34_600, 1_026, 32), + (35_000, 35_100, 1_050, 32), + (74_900, 75_001, 2_925, 34), + (75_001, 75_101, 2_930, 34), + (99_901, 100_001, 4_544, 35), + ], + 2024: [ + (5_500, 5_600, 1, 30), + (35_000, 35_100, 957, 31), + (35_100, 35_200, 961, 31), + (74_900, 75_001, 2_513, 33), + (75_001, 75_101, 2_517, 33), + (99_901, 100_001, 3_811, 34), + ], + 2025: [ + (5_600, 5_700, 1, 30), + (35_000, 35_100, 947, 31), + (74_900, 75_001, 2_503, 33), + (75_001, 75_101, 2_507, 33), + (99_901, 100_001, 3_809, 34), + ], +} + + +@pytest.mark.parametrize( + "year, low, high, tax, page", + [(year, *row) for year, rows in TAX_TABLE_ROWS.items() for row in rows], +) +def test_ar_regular_income_tax_matches_published_table_rows(year, low, high, tax, page): + incomes = np.array([low, (low + high) / 2, high - 1, high - 0.01], dtype=float) + model = ar_regular_income_tax(incomes, P(f"{year}-01-01")) + assert (model == tax).all(), ( + f"{TAX_TABLE_SOURCES[year]}#page={page}: row {low:,}-{high:,} is {tax:,}, " + f"model gives {model}" + ) + + +@pytest.mark.parametrize("year", range(2014, 2026)) +def test_ar_regular_income_tax_table_threshold_is_the_last_published_row(year): + # The last row's "But Less Than" bound is where the formula takes over. + p = P(f"{year}-01-01") + assert p.tax_table.threshold == TAX_TABLE_ROWS[year][-1][1] + at_and_above = np.array([0, 0.5, 10_000]) + p.tax_table.threshold + assert np.allclose( + ar_regular_income_tax(at_and_above, p), ar_main_income_tax(at_and_above, p) + ) + + +@pytest.mark.parametrize("year", list(range(2014, 2036))) +def test_ar_regular_income_tax_is_whole_dollars_constant_within_each_table_row(year): + p = P(f"{year}-01-01") + threshold = p.tax_table.threshold + ti = np.arange(0, threshold, 0.25) + tax = ar_regular_income_tax(ti, p) + assert (tax == np.round(tax)).all() + # Tax changes only at a row start: a multiple of $100, plus $1 from $75,000. + changes = ti[1:][np.diff(tax) != 0] + offset = p.tax_table.band_offset.calc(changes) + row_start = (changes - offset) % p.tax_table.band_width == 0 + # The one row wider than $100 ($74,900 to $75,001) is evaluated at + # midpoints a dollar apart on either side of $75,000, which can round + # differently. + offset_start = np.isin(changes, p.tax_table.band_offset.thresholds[1:]) + assert (row_start | offset_start).all(), changes diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_files_separately.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_files_separately.py index 239ac6ea49f..16187a590be 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_files_separately.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_files_separately.py @@ -9,6 +9,10 @@ class ar_files_separately(Variable): reference = ( "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_and_AR1000NR_Instructions.pdf", "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_FullYearResidentIndividualIncomeTaxReturn.pdf", + # Filing Status 4: "IF ONE SPOUSE HAD A TOTAL NEGATIVE INCOME, YOU + # MUST FILE MARRIED FILING JOINTLY." + "https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=12", + "https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=12", ) defined_for = StateCode.AR @@ -67,6 +71,13 @@ def formula(tax_unit, period, parameters): married = (filing_status == statuses.JOINT) | ( filing_status == statuses.SEPARATE ) + # A couple must file jointly if either spouse had a total negative + # income. Total income (line 23) is each spouse's column of gross + # income less the exemptions subtracted within it, before any floor. + total_income = person("ar_gross_income_indiv", period) - person( + "ar_exemptions", period + ) + negative_total_income = tax_unit.any(head_or_spouse & (total_income < 0)) # Compare after filing-method-sensitive credits. separate_cheaper = (itax_indiv - credit_sep) < (itax_joint - credit_joint) - return married & separate_cheaper + return married & ~negative_total_income & separate_cheaper diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.py index 07e8810ce18..d5b50bdc007 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.py @@ -1,6 +1,6 @@ from policyengine_us.model_api import * from policyengine_us.variables.gov.states.ar.tax.income.ar_income_tax_helpers import ( - ar_main_income_tax, + ar_regular_income_tax, ) @@ -11,12 +11,15 @@ class ar_income_tax_before_non_refundable_credits_indiv(Variable): unit = USD definition_period = YEAR reference = ( - "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_and_AR1000NR_Instructions.pdf" - "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_FullYearResidentIndividualIncomeTaxReturn.pdf" + "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_and_AR1000NR_Instructions.pdf", + "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_FullYearResidentIndividualIncomeTaxReturn.pdf", + # Regular Income Tax Table. + "https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=30", + "https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=1", ) defined_for = StateCode.AR def formula(person, period, parameters): p = parameters(period).gov.states.ar.tax.income.rates.main taxable_income = person("ar_taxable_income_indiv", period) - return max_(ar_main_income_tax(taxable_income, p), 0) + return max_(ar_regular_income_tax(taxable_income, p), 0) diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.py index de48f8af0f8..cbae67cb32f 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.py @@ -1,6 +1,6 @@ from policyengine_us.model_api import * from policyengine_us.variables.gov.states.ar.tax.income.ar_income_tax_helpers import ( - ar_main_income_tax, + ar_regular_income_tax, ) @@ -13,14 +13,17 @@ class ar_income_tax_before_non_refundable_credits_joint(Variable): unit = USD definition_period = YEAR reference = ( - "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_and_AR1000NR_Instructions.pdf" - "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_FullYearResidentIndividualIncomeTaxReturn.pdf" + "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_and_AR1000NR_Instructions.pdf", + "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_FullYearResidentIndividualIncomeTaxReturn.pdf", + # Regular Income Tax Table. + "https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=30", + "https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=1", ) defined_for = StateCode.AR def formula(person, period, parameters): p = parameters(period).gov.states.ar.tax.income.rates.main taxable_income = person("ar_taxable_income_joint", period) - total_main_rate = max_(ar_main_income_tax(taxable_income, p), 0) + total_main_rate = max_(ar_regular_income_tax(taxable_income, p), 0) low_income_tax = person("ar_low_income_tax_joint", period) return min_(total_main_rate, low_income_tax) diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_helpers.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_helpers.py index d03274be3c8..a49c68bd86c 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_helpers.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_helpers.py @@ -37,6 +37,34 @@ def ar_main_income_tax(taxable_income, p): ) +def ar_regular_income_tax(taxable_income, p): + """Arkansas regular income tax as the Regular Income Tax Table gives it. + + `p` is `parameters(period).gov.states.ar.tax.income.rates.main`. + + Below the table threshold, filers look up their net taxable income in the + Regular Income Tax Table. DFA computes each row by applying the bracket + formula at the midpoint of the row's income band and rounding to whole + dollars. At and above the threshold, the formula applies to the exact + income. The result is not floored at zero. + """ + table = p.tax_table + width = table.band_width + offset = table.band_offset.calc(taxable_income) + band_start = np.floor((taxable_income - offset) / width) * width + offset + # Rows cover whole-dollar incomes from band_start up to one dollar below + # the next row. + band_end = band_start + where(table.whole_dollar_midpoint, width - 1, width) + midpoint = (band_start + band_end) / 2 + # Round half up to whole dollars. + table_tax = np.floor(ar_main_income_tax(midpoint, p) + 0.5) + return where( + taxable_income < table.threshold, + table_tax, + ar_main_income_tax(taxable_income, p), + ) + + def marginal_tax(rate_scale, income): """Tax at the rates of a single_amount scale keyed by each row's lower bound, applied to the income within each row.""" diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_uses_low_income_tax_tables.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_uses_low_income_tax_tables.py index 8e7563c3cb2..aa07cbb681b 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_uses_low_income_tax_tables.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_uses_low_income_tax_tables.py @@ -1,6 +1,6 @@ from policyengine_us.model_api import * from policyengine_us.variables.gov.states.ar.tax.income.ar_income_tax_helpers import ( - ar_main_income_tax, + ar_regular_income_tax, ) @@ -20,7 +20,7 @@ def formula(tax_unit, period, parameters): p = parameters(period).gov.states.ar.tax.income.rates.main person = tax_unit.members taxable_income = person("ar_taxable_income_joint", period) - total_main_rate_person = max_(ar_main_income_tax(taxable_income, p), 0) + total_main_rate_person = max_(ar_regular_income_tax(taxable_income, p), 0) total_main_rate = tax_unit.sum(total_main_rate_person) low_income_tax = add(tax_unit, period, ["ar_low_income_tax_joint"]) return total_main_rate > low_income_tax From f76b110972ec12cb6b781cc87e7bb23cf3401434 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Fri, 2 Oct 2026 12:14:01 -0400 Subject: [PATCH 6/9] Address review: 2024 card page anchor, documented threshold choice, carry-forward note - Cite the 2024 bracket card's midpoint statement at #page=2. - Document the at/above-threshold treatment as a choice between the booklet note and the bracket-card formula, with each year's gap. - State the 2026+ carry-forward assumption; band_offset first threshold -inf; branch the scalar whole_dollar_midpoint parameter in Python. - Clarify the -$300/-$500 test cases and document the pre-2014 boundary. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LqCGPgxDukji8Bhs57c5rH --- .../rates/main/tax_table/band_offset.yaml | 5 ++-- .../rates/main/tax_table/threshold.yaml | 25 +++++++++++++++++-- .../main/tax_table/whole_dollar_midpoint.yaml | 4 ++- .../ar/tax/income/ar_files_separately.yaml | 24 +++++++++++++----- ...x_before_non_refundable_credits_indiv.yaml | 6 +++-- .../tests/test_ar_income_tax_schedule.py | 4 ++- ...tax_before_non_refundable_credits_indiv.py | 3 ++- ...tax_before_non_refundable_credits_joint.py | 3 ++- .../ar/tax/income/ar_income_tax_helpers.py | 15 ++++++++--- 9 files changed, 70 insertions(+), 19 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_offset.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_offset.yaml index 7154064f4a8..c52883e88a2 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_offset.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/band_offset.yaml @@ -2,7 +2,8 @@ description: Arkansas sets the start of each regular income tax table income ban # From $75,000 the table's rows run "As Much As $75,001 But Less Than $75,101", # so each band starts $1 above a multiple of $100. The row below them spans # $74,900 to $75,001, so the $1 offset starts at $75,000. The 2014 and 2015 -# tables end at $50,000, below this row. +# tables end at $50,000, below this row. Negative net taxable income takes the +# zero offset; the callers floor the resulting tax at zero. metadata: type: single_amount threshold_unit: currency-USD @@ -32,7 +33,7 @@ metadata: href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=33 brackets: - threshold: - 2014-01-01: 0 + 2014-01-01: -.inf amount: 2014-01-01: 0 - threshold: diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/threshold.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/threshold.yaml index 51677f7f83a..5f92e999726 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/threshold.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/threshold.yaml @@ -1,7 +1,21 @@ description: Arkansas uses the regular income tax table for net taxable income below this amount. # Each value is the "But Less Than" bound of the table's last row, where the -# table's note starts ("For $100,001 and over, your tax is ..."). At and above -# it, the model applies the bracket formula to the exact income. +# table's note starts ("For $100,001 and over, your tax is ..."). +# At and above it, two DFA instructions conflict, and the model follows the +# first: +# - The Indexed Tax Brackets card gives the bracket formula (rate x income +# minus the adjustment) and says a formula "must match the table exactly" +# (2021 card, file p.1; 2024 card, file p.2). The model applies this +# formula to the exact income. +# - The note under each table's last row adds the top rate to the last row's +# tax: "For $100,001 and over, your tax is $3,811 + 3.9% of the excess over +# $100,000" (2024 table, file p.34); "$3,809 + 3.9%" (2025 table, file p.34). +# The note gives less tax than the formula, by the same amount for every +# income at or above the threshold: $3.19 (2014), $3.69 (2015), $3.36 (2016), +# $3.46 (2017), $3.26 (2018), $3.36 (2019), $3.30 (2020), $2.50 (2021), +# $2.80 (2022), $2.30 (2023), $1.60 (2024) and $1.70 (2025). Neither source +# settles which applies above the table, where there are no rows for the +# card's formula to match, so keep the formula unless a DFA source does. values: 2014-01-01: 50_000 2016-01-01: 86_001 @@ -10,6 +24,9 @@ values: 2021-01-01: 91_001 2022-01-01: 94_001 2023-01-01: 100_001 + # No Regular Income Tax Table has been published for 2026 or later, so the + # model carries this value forward and applies the table method to the + # 2026+ statutory schedules below it. metadata: unit: currency-USD period: year @@ -39,3 +56,7 @@ metadata: href: https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=34 - title: 2025 AR1000F and AR1000NR Instructions, 2025 Regular Income Tax Table href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=34 + - title: 2021 Indexed Tax Brackets (a formula must match the table exactly) + href: https://www.dfa.arkansas.gov/wp-content/uploads/TaxBrackets_2021.pdf#page=1 + - title: 2024 Indexed Tax Brackets (a formula must match the table exactly) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=2 diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/whole_dollar_midpoint.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/whole_dollar_midpoint.yaml index e98aa3cf297..fe559e9664a 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/whole_dollar_midpoint.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/rates/main/tax_table/whole_dollar_midpoint.yaml @@ -10,6 +10,8 @@ description: Arkansas uses the midpoint of the whole-dollar incomes in each regu values: 2014-01-01: false 2022-01-01: true + # No Regular Income Tax Table has been published for 2026 or later, so the + # model carries this value forward. metadata: unit: bool period: year @@ -22,4 +24,4 @@ metadata: - title: 2022 AR1000F and AR1000NR Instructions, 2022 Regular Income Tax Table href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=32 - title: 2024 Indexed Tax Brackets (the calculations in the table are made at the midpoint of each income level) - href: https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=1 + href: https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=2 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_files_separately.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_files_separately.yaml index 9216824b1fb..cafabf4f4c4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_files_separately.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_files_separately.yaml @@ -380,7 +380,10 @@ # 2,110 - 58 = 2,052 ar_income_tax: 2_052 -- name: Spouse partnership loss of $300 files jointly (issue 9680) +- name: Spouse partnership loss of $300 files jointly, where the joint and separate paths tie (issue 9680) + # The tie already keeps this couple on the joint path (strict <), so this case + # passes without the negative-income rule. The $10 loss cases above isolate + # the rule. period: 2024 input: people: @@ -402,14 +405,17 @@ # Joint AGI 28,450 - 300 = 28,150, joint low income table row # $28,101-$28,200: $501 (p.29). Separately, person1's 26,040 falls in the # regular row $26,000-$26,100: $606 (p.31), less 45 + 60 of additional - # credits = 501, a tie; the negative total income requires the joint return. + # credits = 501, a tie, which keeps the joint return; the negative total + # income also requires it. ar_low_income_tax_joint: [501, 0] ar_income_tax_before_non_refundable_credits_indiv: [606, 0] ar_files_separately: false # 501 - 58 = 443 ar_income_tax: 443 -- name: Spouse capital loss makes total income negative, files jointly +- name: Spouse capital loss of $500 files jointly, where the joint path is already cheaper + # The joint path costs less even without the negative-income rule, so this + # case does not isolate the rule. The $10 loss cases above do. period: 2024 input: people: @@ -430,8 +436,14 @@ output: # person2's total income is the -500 capital loss. ar_gross_income_indiv: [27_850, -500] - ar_files_separately: false - # Joint AGI 27,850 - 500 = 27,350, joint low income table row - # $27,301-$27,400: $418 (p.29); 418 - 58 = 360. + # Separate path: person1 27,850 - 2,410 = 25,440, row $25,400-$25,500: + # $584 (p.31), less 60 + 60 of additional credits = 464. + ar_income_tax_before_non_refundable_credits_indiv: [584, 0] + # Joint path: AGI 27,850 - 500 = 27,350, joint low income table row + # $27,301-$27,400: $418 (p.29); the joint additional credit + # 120 - ceil((27,350 - 25,800) / 100) x 10 phases out to 0, so it nets 418. ar_low_income_tax_joint: [418, 0] + # Joint (418) < separate (464), so the couple files jointly either way. + ar_files_separately: false + # 418 - 58 = 360 ar_income_tax: 360 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.yaml index dfceeb4e2e1..aeacc605cbd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.yaml @@ -628,7 +628,7 @@ # row at its midpoint and rounds to whole dollars: "If you use a formula to # calculate Arkansas income tax, the results must match the table exactly. The # calculations in the table are made at the midpoint of each income level." -# (2024 Indexed Tax Brackets, https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=1). +# (2024 Indexed Tax Brackets, https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=2). # Pages below are file pages of that year's AR1000F/AR1000NR instructions. - name: AR regular tax table 2024 - income inside the $35,100-$35,200 row period: 2024 @@ -715,7 +715,9 @@ state_code: AR output: # "For $100,001 and over" (p.34) the model keeps the bracket formula at the - # exact income: 100,001 x 3.9% - 87.40 = 3,812.64 + # exact income: 100,001 x 3.9% - 87.40 = 3,812.64. The table's note, + # "$3,811 + 3.9% of the excess over $100,000", would give $1.60 less + # (threshold.yaml documents the choice). ar_income_tax_before_non_refundable_credits_indiv: 3_812.64 - name: AR regular tax table 2025 - income inside the $35,100-$35,200 row diff --git a/policyengine_us/tests/test_ar_income_tax_schedule.py b/policyengine_us/tests/test_ar_income_tax_schedule.py index 4bcda918727..1d1dbd7f9e6 100644 --- a/policyengine_us/tests/test_ar_income_tax_schedule.py +++ b/policyengine_us/tests/test_ar_income_tax_schedule.py @@ -249,7 +249,9 @@ def test_ar_published_reduction_applies_through_2025_only(): @pytest.mark.parametrize("year", [2000, 2013]) def test_ar_schedule_before_modeled_years_still_computes(year): # rate.yaml starts in 2014; earlier years must not hit the statutory path, - # whose parameters start in 2026. + # whose parameters start in 2026. This covers ar_main_income_tax only: + # ar_regular_income_tax reads the tax_table parameters, which start with + # the 2014 table, so it raises for earlier years (see its docstring). assert bool(P.use_published_reduction(f"{year}-01-01")) assert (model_tax(year, TAXABLE_INCOME) == 0).all() diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.py index d5b50bdc007..16718c93f7b 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_indiv.py @@ -15,7 +15,8 @@ class ar_income_tax_before_non_refundable_credits_indiv(Variable): "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_FullYearResidentIndividualIncomeTaxReturn.pdf", # Regular Income Tax Table. "https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=30", - "https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=1", + # Indexed Tax Brackets: the table is computed at each row's midpoint. + "https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=2", ) defined_for = StateCode.AR diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.py index cbae67cb32f..7241c6983a6 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_before_non_refundable_credits_joint.py @@ -17,7 +17,8 @@ class ar_income_tax_before_non_refundable_credits_joint(Variable): "https://www.dfa.arkansas.gov/wp-content/uploads/2023_AR1000F_FullYearResidentIndividualIncomeTaxReturn.pdf", # Regular Income Tax Table. "https://www.dfa.arkansas.gov/wp-content/uploads/2024_AR1000F_and_AR1000NR_Instructions.pdf#page=30", - "https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=1", + # Indexed Tax Brackets: the table is computed at each row's midpoint. + "https://www.dfa.arkansas.gov/wp-content/uploads/2024_TaxBrackets.pdf#page=2", ) defined_for = StateCode.AR diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_helpers.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_helpers.py index a49c68bd86c..d028bdaf3fb 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_helpers.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_income_tax_helpers.py @@ -47,14 +47,23 @@ def ar_regular_income_tax(taxable_income, p): formula at the midpoint of the row's income band and rounding to whole dollars. At and above the threshold, the formula applies to the exact income. The result is not floored at zero. + + The `tax_table` parameters start with the 2014 table, so unlike + `ar_main_income_tax` (which returns 0 there), this helper has no parameter + values before 2014 and raises `ParameterNotFoundError`. Computed from + household inputs, the Arkansas income tax never reaches those years: the + standard deduction has no value before 2015, the first year the model + computes. """ table = p.tax_table width = table.band_width offset = table.band_offset.calc(taxable_income) band_start = np.floor((taxable_income - offset) / width) * width + offset - # Rows cover whole-dollar incomes from band_start up to one dollar below - # the next row. - band_end = band_start + where(table.whole_dollar_midpoint, width - 1, width) + band_end = band_start + width + if table.whole_dollar_midpoint: + # Rows cover whole-dollar incomes from band_start up to one dollar + # below the next row. + band_end = band_end - 1 midpoint = (band_start + band_end) / 2 # Round half up to whole dollars. table_tax = np.floor(ar_main_income_tax(midpoint, p) + 0.5) From 216b329c7e0d4df5432b92b25942ac2d645ed6ce Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Mon, 12 Oct 2026 14:06:05 -0400 Subject: [PATCH 7/9] Keep a dependent's Montana additions and subtractions off the filer's return mt_agi_joint pooled every tax unit member's Montana additions and subtractions, but a dependent's federal AGI is not on the filer's return (adjusted_gross_income_person is zero for dependents). A dependent's subtraction, such as unemployment compensation, therefore lowered the filer's Montana AGI and, since main's #9903, the joint standard deduction. Pool additions and subtractions over the head and spouse only. The adult-dependent exemption case had pinned the leaked AGI of 47,000; under the law the filer's Montana AGI is 50,000 and the tax is 1,731.65. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LqCGPgxDukji8Bhs57c5rH --- .../taxsim-sweep-ar-mt-nm-fixes.fixed.md | 2 +- .../mt_dependent_exemptions_person.yaml | 8 +++- .../states/mt/tax/income/mt_agi_joint.yaml | 45 +++++++++++++++++++ .../states/mt/tax/income/base/mt_agi_joint.py | 15 ++++--- 4 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_joint.yaml diff --git a/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md b/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md index 11eac934ed4..cd827e1a467 100644 --- a/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md +++ b/changelog.d/taxsim-sweep-ar-mt-nm-fixes.fixed.md @@ -1 +1 @@ -Fixed Arkansas gross income to include partnership and S-corporation income and to let one spouse's loss offset the other's income on a joint return; Montana's capital gain credit to be zero rather than negative on net losses, dependent exemptions to cover non-child dependents whose own Montana gross income (excluding unemployment compensation; only taxable Social Security) is at or below the exemption amount, and permanently and totally disabled adult children to count as qualifying children; and New Mexico's net capital gains deduction to exclude short-term gains in every year, following the IRC Section 1222(11) definition that New Mexico law has used since 2019, and to include capital gain distributions. +Fixed Arkansas gross income to include partnership and S-corporation income and to let one spouse's loss offset the other's income on a joint return; Montana's capital gain credit to be zero rather than negative on net losses, dependent exemptions to cover non-child dependents whose own Montana gross income (excluding unemployment compensation; only taxable Social Security) is at or below the exemption amount, and permanently and totally disabled adult children to count as qualifying children, and Montana joint AGI to leave out a dependent's Montana additions and subtractions, which are not on the filer's return; and New Mexico's net capital gains deduction to exclude short-term gains in every year, following the IRC Section 1222(11) definition that New Mexico law has used since 2019, and to include capital gain distributions. diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml index 60f00abb70a..b3399810ebb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml @@ -355,8 +355,12 @@ # Montana gross income = 3,000 - 3,000 unemployment compensation = 0 # <= 2,710, so one exemption of 2,710. mt_dependent_exemptions_person: [0, 2_710] - # Without the exemption the tax is 1,712.07; 1,712.07 - 182.92 = 1,529.15. - mt_income_tax: 1_529.15 + # The dependent's unemployment compensation is not on the filer's return, + # so Montana AGI is 50,000 and the standard deduction is 20% of it, 10,000. + # Taxable income = 50,000 - 10,000 - 2 x 2,710 exemptions = 34,580. + # Without the dependent's exemption the tax is 1,914.58; + # 1,914.58 - 2,710 x 6.75% (182.93) = 1,731.65. + mt_income_tax: 1_731.65 - name: Adult dependent with unemployment compensation and wages exactly at the 2022 exemption amount is eligible period: 2022 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_joint.yaml new file mode 100644 index 00000000000..f1e3425ed94 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_joint.yaml @@ -0,0 +1,45 @@ +- name: A dependent's Montana subtraction stays off the filer's joint Montana AGI + period: 2022 + input: + people: + head: + is_tax_unit_head: true + employment_income: 50_000 + dependent: + age: 30 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + unemployment_compensation: 3_000 + tax_units: + tax_unit: + members: [head, dependent] + households: + household: + members: [head, dependent] + state_code: MT + output: + # The dependent's unemployment compensation is not in the filer's federal + # AGI, so its Montana subtraction does not reduce the filer's Montana AGI. + mt_agi_joint: 50_000 + +- name: A spouse's Montana subtraction still offsets the joint return's income + period: 2022 + input: + people: + head: + is_tax_unit_head: true + employment_income: 50_000 + spouse: + is_tax_unit_spouse: true + unemployment_compensation: 3_000 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MT + output: + # Federal AGI 53,000 includes the spouse's 3,000 of unemployment + # compensation, which Montana subtracts: 53,000 - 3,000 = 50,000. + mt_agi_joint: 50_000 diff --git a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py index 56cdef43d71..3a081d76218 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py @@ -14,12 +14,15 @@ def formula(tax_unit, period, parameters): # Pool federal AGI at tax unit level agi = add(tax_unit, period, ["adjusted_gross_income_person"]) - # Pool Montana additions at tax unit level - additions = add(tax_unit, period, ["mt_additions"]) - - # Pool Montana subtractions at tax unit level - # This allows spouse's subtractions to offset head's income - subtractions = add(tax_unit, period, ["mt_subtractions"]) + # Pool Montana additions and subtractions over the head and spouse. + # This allows the spouse's subtractions to offset the head's income. + # A dependent's income is not on the return (adjusted_gross_income_person + # is zero for dependents), so neither are the dependent's Montana + # additions and subtractions. + person = tax_unit.members + on_return = person("is_tax_unit_head_or_spouse", period) + additions = tax_unit.sum(person("mt_additions", period) * on_return) + subtractions = tax_unit.sum(person("mt_subtractions", period) * on_return) # Apply pooled subtractions to pooled income reduced_agi = max_(agi + additions - subtractions, 0) From a434b88b17d5b2e084de7e97005e56058448af3b Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Mon, 12 Oct 2026 22:22:50 -0400 Subject: [PATCH 8/9] List partnership and S-corporation income in the AR line-comment check Main's code-health test pins the AR1000F line for every Arkansas gross income source. Partnership and S-corporation income is reported with Schedule E income: line 18 for 2015-2017 and line 19 from 2018. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LqCGPgxDukji8Bhs57c5rH --- .../code_health/test_ar_gross_income_source_line_comments.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py b/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py index 9bcafacd5aa..7ab012c7ad6 100644 --- a/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py +++ b/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py @@ -11,7 +11,7 @@ 13 business income (Schedule C), 14 capital gains, 16 nonqualified IRA distributions and taxable annuities, 17A/17B employer pensions and qualified IRAs, where military retirement and disability retirement also go, 18 Schedule -E, 19 Schedule F, 20 other income (Form AR-OI, including gambling winnings). +E (rents, royalties, partnerships, S corporations, estates and trusts), 19 Schedule F, 20 other income (Form AR-OI, including gambling winnings). TY2018+ AR1000F (2018 booklet, form on #page=15; 2022 and 2025 AR1000F, #page=2): 13 business income, 14 capital gains, 16 nonqualified IRA @@ -49,6 +49,7 @@ "taxable_pension_income": "17A/17B", "disability_benefits": "17A/17B", "rental_income": "18", + "partnership_s_corp_income": "18", "estate_income": "18", "farm_rent_income": "18", "farm_operations_income": "19", @@ -73,6 +74,7 @@ "keogh_distributions": "18A/18B", "disability_benefits": "18A/18B", "rental_income": "19", + "partnership_s_corp_income": "19", "estate_income": "19", "farm_rent_income": "19", "farm_operations_income": "20", From 6fea3d287642bf1d62ad93ae47815b14ebc17f21 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 13 Oct 2026 10:22:49 -0400 Subject: [PATCH 9/9] Build Montana dependent gross income on the federal dependent gross income mt_dependent_gross_income recomputed taxable Social Security itself and floored every income component at zero before forming provisional income, so a dependent's capital loss never lowered taxable Social Security. A 2022 dependent with $26,000 of unemployment compensation, $10,000 of Social Security and a $3,000 long-term loss got $3,000 of Montana gross income and no exemption, instead of $1,500 and the $2,710 exemption. MCA 15-30-2101(10) defines Montana gross income as IRC 61 gross income excluding unemployment compensation, and the 2021-2023 Form 2 instructions name no other difference. Since #9691 the federal dependent_gross_income counts gains but not losses and only the IRC 86 taxable part of Social Security, figured on the dependent's own modified AGI (which nets capital losses, subtracts the dependent's adjustments and uses the $0 base for a dependent living with a spouse). Montana gross income is now that amount minus the dependent's unemployment compensation, which removes the duplicated IRC 86 code and its approximations. Adds the capital-loss case to both MT dependent test files and drops a test comment that described the pre-#9691 federal treatment of Social Security. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LqCGPgxDukji8Bhs57c5rH --- .../mt_dependent_exemptions_person.yaml | 35 +++++++- .../exemptions/mt_dependent_gross_income.yaml | 34 +++++++ .../dependent/mt_dependent_gross_income.py | 88 ++++--------------- 3 files changed, 81 insertions(+), 76 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml index b3399810ebb..9104def4ba4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml @@ -440,10 +440,6 @@ # Provisional income = 0.5 x 12,000 = 6,000 < 25,000, so no Social # Security is taxable; Montana gross income = 0 <= 2,710. mt_dependent_exemptions_person: [0, 2_710] - # mt_income_tax is not asserted: federal dependent_gross_income counts - # all 12,000 of Social Security, so the head files single rather than - # head of household. With that status the tax falls from 2,246.00 to - # 2,063.07 (-182.93). - name: Dependent parent with taxable Social Security exactly at the 2022 exemption amount is eligible period: 2022 @@ -718,3 +714,34 @@ # Without the exemptions the tax is 1,914.57; 1,914.57 - 2 x 182.925 # = 1,548.72. mt_income_tax: 1_548.72 + +- name: Adult dependent whose capital loss lowers taxable Social Security below the 2022 exemption amount is eligible + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 70 + is_tax_unit_dependent: true + is_tax_unit_spouse: false # Required, or the adult is inferred as the spouse and skips the income test. + unemployment_compensation: 26_000 + social_security_retirement: 10_000 + long_term_capital_gains: -3_000 + is_qualifying_child_dependent: false + is_disabled: false + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # IRC 86(b)(2) modified AGI = 26,000 unemployment compensation - 3,000 + # capital loss = 23,000; combined income = 23,000 + 0.5 x 10,000 = 28,000; + # taxable Social Security = min(5,000, 0.5 x (28,000 - 25,000)) = 1,500. + # Montana gross income = 1,500 (unemployment compensation is excluded and + # losses are not gross income) <= 2,710, so one exemption of 2,710. + mt_dependent_exemptions_person: [0, 2_710] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_gross_income.yaml index d349bf5da95..612f06f645e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_gross_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_gross_income.yaml @@ -186,3 +186,37 @@ # Gross income = max(0, 2,711) + max(0, -3,000) = 2,711 + 0 = 2,711, # not the net loss of 289. mt_dependent_gross_income: [0, 2_711] + +- name: Case 8, a capital loss reduces the modified AGI that sets taxable Social Security. + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + employment_income: 50_000 + person2: + age: 70 + is_tax_unit_dependent: true + is_tax_unit_spouse: false + is_qualifying_child_dependent: false + unemployment_compensation: 26_000 + social_security_retirement: 10_000 + long_term_capital_gains: -3_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # IRC 86(b)(2) modified AGI = 26,000 unemployment compensation - 3,000 + # capital loss (within the IRC 1211(b) limit) = 23,000. + dependent_taxable_ss_magi: [0, 23_000] + # Combined income = 23,000 + 0.5 x 10,000 = 28,000, between 25,000 and + # 34,000. Taxable = min(0.5 x 10,000, 0.5 x (28,000 - 25,000)) + # = min(5,000, 1,500) = 1,500. + dependent_taxable_social_security: [0, 1_500] + # Gross income = 0 unemployment compensation (excluded) + 0 capital gains + # (losses are not gross income) + 1,500 taxable Social Security = 1,500. + mt_dependent_gross_income: [0, 1_500] diff --git a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_gross_income.py b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_gross_income.py index 9077d9690fe..953cc44c33d 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_gross_income.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/dependent/mt_dependent_gross_income.py @@ -1,24 +1,19 @@ from policyengine_us.model_api import * -# Montana "gross income" (MCA 15-30-2101(10)) starts from IRC 61 gross income -# but differs from the federal gross income sources in three places. -MT_GROSS_INCOME_SOURCE_OVERRIDES = { - # Unemployment compensation is excluded from Montana gross income. - "taxable_unemployment_compensation": [], - # Only the IRC 86 taxable part of Social Security is gross income. It is - # computed below on the dependent's own income. - "taxable_social_security": [], - # Gross income includes gains but not losses, so long-term and - # short-term gains are each floored at zero instead of being netted. - "capital_gains": ["long_term_capital_gains", "short_term_capital_gains"], -} - class mt_dependent_gross_income(Variable): value_type = float entity = Person label = "Montana gross income of a dependent for the dependent exemption" unit = USD + documentation = """ + Montana gross income (MCA 15-30-2101(10)) is federal gross income under + IRC 61, excluding unemployment compensation. This starts from the federal + dependent_gross_income, which counts gains but not losses and only the + IRC 86 taxable part of Social Security (figured on the dependent's own + modified AGI, which includes unemployment compensation), and removes the + dependent's unemployment compensation. + """ definition_period = YEAR reference = ( # MCA 15-30-2101(10) (2021): gross income is IRC 61 gross income @@ -32,68 +27,17 @@ class mt_dependent_gross_income(Variable): # 2022 Form 2 instructions: "federal gross income, excluding # unemployment compensation". "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=7", - # IRS Pub. 501 (2022): gross income includes gains but not losses, and - # Social Security counts only when taxable. - "https://www.irs.gov/pub/irs-prior/p501--2022.pdf#page=2", - "https://www.irs.gov/pub/irs-prior/p501--2022.pdf#page=19", - "https://www.law.cornell.edu/uscode/text/26/86", ) defined_for = StateCode.MT def formula(person, period, parameters): + federal_gross_income = person("dependent_gross_income", period) + # dependent_gross_income counts a dependent's unemployment + # compensation in full whenever it is a federal gross income source. sources = parameters(period).gov.irs.gross_income.sources - other_gross_income = 0 - for source in sources: - components = MT_GROSS_INCOME_SOURCE_OVERRIDES.get(source, [source]) - for component in components: - other_gross_income += max_(0, add(person, period, [component])) - - # Taxable Social Security under IRC 86, computed on the dependent's - # own income with the single base amounts. Pub. 501: benefits are not - # gross income unless "one-half of your social security benefits plus - # your other gross income and any tax-exempt interest" exceeds the - # base amount. Approximations: adjustments to income are ignored, and - # the $0 base for a dependent who is married filing separately and - # lives with their spouse is not modeled. - p = parameters(period).gov.irs.social_security.taxability - social_security = max_(0, person("social_security", period)) - # Unemployment compensation is federal gross income, so it counts - # toward provisional income even though Montana excludes it from - # gross income. - unemployment_compensation = max_(0, person("unemployment_compensation", period)) - provisional_income = ( - other_gross_income - + unemployment_compensation - + person("tax_exempt_interest_income", period) - + p.combined_income_ss_fraction * social_security - ) - base_amount = p.threshold.base.main["SINGLE"] - adjusted_base_amount = p.threshold.adjusted_base.main["SINGLE"] - # IRC 86(a)(1): the lesser of half the benefits or half the excess of - # provisional income over the base amount. - amount_under_paragraph_1 = min_( - p.rate.base.benefit_cap * social_security, - p.rate.base.excess * max_(0, provisional_income - base_amount), - ) - # IRC 86(a)(2): above the adjusted base amount. - bracket_amount = min_( - amount_under_paragraph_1, - p.rate.additional.bracket * (adjusted_base_amount - base_amount), - ) - amount_over_adjusted_base = min_( - p.rate.additional.excess - * max_(0, provisional_income - adjusted_base_amount) - + bracket_amount, - p.rate.additional.benefit_cap * social_security, - ) - taxable_social_security = select( - [ - provisional_income < base_amount, - provisional_income < adjusted_base_amount, - ], - [0, amount_under_paragraph_1], - default=amount_over_adjusted_base, - ) - # Only dependents' own income is tested, as in dependent_gross_income. + unemployment_compensation_counted = ( + "taxable_unemployment_compensation" in sources + ) * max_(0, person("unemployment_compensation", period)) + # dependent_gross_income is zero for the head and spouse. is_dependent = ~person("is_tax_unit_head_or_spouse", period) - return is_dependent * (other_gross_income + taxable_social_security) + return federal_gross_income - is_dependent * unemployment_compensation_counted