From 63c349aff67f318128c30f280cb754b65894a537 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:56:28 +0000 Subject: [PATCH 01/10] Add failing tests for Vermont's 2017 taxable income and 2017 federal standard deduction Former 32 V.S.A. 5811(21) starts 2017 Vermont taxable income from federal taxable income with itemized addbacks; main starts every year from Vermont AGI less Vermont deductions, and the federal standard deduction has no 2017 values. Co-Authored-By: Claude Opus 5.5 --- .../additional_standard_deduction.yaml | 47 ++++++++++ .../standard/basic_standard_deduction.yaml | 35 ++++++++ .../vt_2017_itemized_deduction_addback.yaml | 86 +++++++++++++++++++ .../vt/tax/income/vt_taxable_income.yaml | 74 ++++++++++++++++ 4 files changed, 242 insertions(+) create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml new file mode 100644 index 00000000000..b50eb26788b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml @@ -0,0 +1,47 @@ +- name: 2017 aged single filer adds $1,550 + period: 2017 + input: + filing_status: SINGLE + aged_blind_count: 1 + output: + additional_standard_deduction: 1_550 + +- name: 2017 aged and blind head of household adds 2 x $1,550 + period: 2017 + input: + filing_status: HEAD_OF_HOUSEHOLD + aged_blind_count: 2 + output: + additional_standard_deduction: 3_100 + +- name: 2017 joint filers with one aged spouse add $1,250 + period: 2017 + input: + filing_status: JOINT + aged_blind_count: 1 + output: + additional_standard_deduction: 1_250 + +- name: 2017 separate filer aged adds $1,250 + period: 2017 + input: + filing_status: SEPARATE + aged_blind_count: 1 + output: + additional_standard_deduction: 1_250 + +- name: 2017 aged surviving spouse adds $1,250 + period: 2017 + input: + filing_status: SURVIVING_SPOUSE + aged_blind_count: 1 + output: + additional_standard_deduction: 1_250 + +- name: 2018 aged single filer adds $1,600, unchanged + period: 2018 + input: + filing_status: SINGLE + aged_blind_count: 1 + output: + additional_standard_deduction: 1_600 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml index a22f9f3c55d..87a848b759c 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml @@ -574,3 +574,38 @@ irs_gross_income: 15_000 basic_standard_deduction: 15_450 tax_unit_is_required_to_file: false + +- name: 2017 single filer standard deduction is $6,350 + period: 2017 + input: + filing_status: SINGLE + output: + basic_standard_deduction: 6_350 + +- name: 2017 separate filer standard deduction is $6,350 + period: 2017 + input: + filing_status: SEPARATE + output: + basic_standard_deduction: 6_350 + +- name: 2017 head of household standard deduction is $9,350 + period: 2017 + input: + filing_status: HEAD_OF_HOUSEHOLD + output: + basic_standard_deduction: 9_350 + +- name: 2017 joint filer standard deduction is $12,700 + period: 2017 + input: + filing_status: JOINT + output: + basic_standard_deduction: 12_700 + +- name: 2017 surviving spouse standard deduction is $12,700 + period: 2017 + input: + filing_status: SURVIVING_SPOUSE + output: + basic_standard_deduction: 12_700 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml new file mode 100644 index 00000000000..0f155d37605 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml @@ -0,0 +1,86 @@ +- name: A 2017 nonitemizer adds nothing back. + period: 2017 + input: + state_code: VT + tax_unit_itemizes: false + itemized_taxable_income_deductions: 30_000 + standard_deduction: 6_350 + salt_deduction: 2_000 + state_withheld_income_tax: 2_000 + output: + vt_2017_itemized_deduction_addback: 0 + +- name: 2017 adds back income taxes and other deductions above 2.5 times the standard deduction. + period: 2017 + input: + state_code: VT + tax_unit_itemizes: true + itemized_taxable_income_deductions: 30_000 + standard_deduction: 6_350 + salt_deduction: 2_000 + state_withheld_income_tax: 2_000 + medical_expense_deduction: 0 + charitable_deduction: 0 + output: + # min(2,000, 30,000 - 6,350) + max(30,000 - 2,000 - 2.5 x 6,350, 0) + # = 2,000 + 12,125 + vt_2017_itemized_deduction_addback: 14_125 + +- name: 2017 income tax addback is limited to itemized deductions above the standard deduction. + period: 2017 + input: + state_code: VT + tax_unit_itemizes: true + itemized_taxable_income_deductions: 7_000 + standard_deduction: 6_350 + salt_deduction: 3_000 + state_withheld_income_tax: 3_000 + medical_expense_deduction: 0 + charitable_deduction: 0 + output: + # min(3,000, 650) + max(7,000 - 3,000 - 15,875, 0) + vt_2017_itemized_deduction_addback: 650 + +- name: 2017 sales tax deducted instead of income tax is not added back. + period: 2017 + input: + state_code: VT + tax_unit_itemizes: true + itemized_taxable_income_deductions: 25_000 + standard_deduction: 6_350 + salt_deduction: 3_000 + state_withheld_income_tax: 1_000 + state_sales_tax: 3_000 + medical_expense_deduction: 2_000 + charitable_deduction: 3_000 + output: + # T = 0; max(25,000 - 0 - 2,000 - 3,000 - 15,875, 0) = 4,125 + vt_2017_itemized_deduction_addback: 4_125 + +- name: 2017 medical and charitable deductions are not in the other-deduction pool. + period: 2017 + input: + state_code: VT + filing_status: JOINT + tax_unit_itemizes: true + itemized_taxable_income_deductions: 40_000 + standard_deduction: 12_700 + salt_deduction: 5_000 + state_withheld_income_tax: 5_000 + medical_expense_deduction: 10_000 + charitable_deduction: 5_000 + output: + # min(5,000, 27,300) + max(40,000 - 5,000 - 10,000 - 5,000 - 31,750, 0) + vt_2017_itemized_deduction_addback: 5_000 + +- name: The addback only applies in 2017. + period: 2018 + input: + state_code: VT + tax_unit_itemizes: true + itemized_taxable_income_deductions: 30_000 + standard_deduction: 12_000 + salt_deduction: 2_000 + state_withheld_income_tax: 2_000 + output: + vt_2017_itemized_deduction_addback: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml index 548e36c989c..196794f2c60 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml @@ -34,3 +34,77 @@ state_code: VT output: vt_taxable_income: 0 + +- name: 2017 uses federal deductions and historical adjustments. + period: 2017 + input: + state_code: VT + adjusted_gross_income: 50_000 + exemptions: 4_050 + taxable_income_deductions: 20_000 + vt_2017_itemized_deduction_addback: 2_000 + vt_2017_other_additions: 1_000 + vt_2017_subtractions: 500 + # The 2018+ Vermont-base inputs below do not enter 2017 taxable income. + vt_agi: 49_500 + vt_standard_deduction: 6_350 + vt_personal_exemptions: 4_050 + output: + # 50,000 - 4,050 - 20,000 + 2,000 + 1,000 - 500 + vt_taxable_income: 28_450 + +- name: 2017 restores excess other itemized deductions. + period: 2017 + input: + state_code: VT + adjusted_gross_income: 70_000 + exemptions: 4_050 + taxable_income_deductions: 30_000 + vt_2017_itemized_deduction_addback: 14_125 + vt_2017_other_additions: 0 + vt_2017_subtractions: 0 + output: + # 70,000 - 4,050 - 30,000 + 14,125 + vt_taxable_income: 50_075 + +- name: 2017 keeps a negative federal base before Vermont additions. + period: 2017 + input: + state_code: VT + adjusted_gross_income: 5_000 + exemptions: 4_050 + taxable_income_deductions: 6_350 + taxable_income: 0 + vt_2017_itemized_deduction_addback: 0 + vt_2017_other_additions: 1_000 + vt_2017_subtractions: 0 + output: + # 5,000 - 4,050 - 6,350 + 1,000 = -4,400, floored at zero. Starting + # from floored federal taxable income would give $1,000. + vt_taxable_income: 0 + +- name: 2017 subtractions cannot push taxable income below zero. + period: 2017 + input: + state_code: VT + adjusted_gross_income: 20_000 + exemptions: 4_050 + taxable_income_deductions: 6_350 + vt_2017_itemized_deduction_addback: 0 + vt_2017_other_additions: 0 + vt_2017_subtractions: 15_000 + output: + vt_taxable_income: 0 + +- name: 2018 still starts from Vermont AGI. + period: 2018 + input: + state_code: VT + adjusted_gross_income: 50_000 + exemptions: 0 + taxable_income_deductions: 20_000 + vt_agi: 40_000 + vt_standard_deduction: 6_000 + vt_personal_exemptions: 4_000 + output: + vt_taxable_income: 30_000 From 5c25092d918bcde34cbaf0237d0c67893b94bc2a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 15:34:37 +0000 Subject: [PATCH 02/10] Compute Vermont's 2017 taxable income from the federal base Former 32 V.S.A. 5811(21) defines 2017 Vermont taxable income as federal taxable income with Vermont's additions and subtractions, including the itemized deduction addback; the Vermont-AGI formula now starts in 2018. The federal standard deduction and aged/blind amounts had no 2017 values, so 2017 fell back to the 2018 TCJA amounts. Co-Authored-By: Claude Opus 5.5 --- changelog.d/vt-2017-taxable-income.fixed.md | 1 + .../standard/aged_or_blind/amount.yaml | 7 + .../gov/irs/deductions/standard/amount.yaml | 7 + .../itemized_addback/other_multiple.yaml | 10 ++ .../deductions/vt_2017_other_additions.yaml | 8 + .../deductions/vt_2017_subtractions.yaml | 17 ++ .../vt/tax/income/vt_taxable_income.yaml | 37 ++++ .../test_vt_2017_taxable_income_properties.py | 160 ++++++++++++++++++ .../vt_2017_itemized_deduction_addback.py | 48 ++++++ .../deductions/vt_2017_other_additions.py | 23 +++ .../deductions/vt_2017_other_subtractions.py | 21 +++ .../income/deductions/vt_2017_subtractions.py | 22 +++ .../states/vt/tax/income/vt_taxable_income.py | 33 +++- 13 files changed, 391 insertions(+), 3 deletions(-) create mode 100644 changelog.d/vt-2017-taxable-income.fixed.md create mode 100644 policyengine_us/parameters/gov/states/vt/tax/income/deductions/itemized_addback/other_multiple.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_other_additions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_subtractions.yaml create mode 100644 policyengine_us/tests/test_vt_2017_taxable_income_properties.py create mode 100644 policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.py create mode 100644 policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_other_additions.py create mode 100644 policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_other_subtractions.py create mode 100644 policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_subtractions.py diff --git a/changelog.d/vt-2017-taxable-income.fixed.md b/changelog.d/vt-2017-taxable-income.fixed.md new file mode 100644 index 00000000000..773a65e7d9b --- /dev/null +++ b/changelog.d/vt-2017-taxable-income.fixed.md @@ -0,0 +1 @@ +Start Vermont's 2017 taxable income from federal taxable income, with the former 32 V.S.A. § 5811(21) itemized deduction addback, additions and subtractions, and add the 2017 federal standard deduction amounts. diff --git a/policyengine_us/parameters/gov/irs/deductions/standard/aged_or_blind/amount.yaml b/policyengine_us/parameters/gov/irs/deductions/standard/aged_or_blind/amount.yaml index d78354841f2..76a7882c4c0 100644 --- a/policyengine_us/parameters/gov/irs/deductions/standard/aged_or_blind/amount.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/standard/aged_or_blind/amount.yaml @@ -1,5 +1,6 @@ description: Additional standard deduction for blind and aged SINGLE: + 2017-01-01: 1_550 2018-01-01: 1_600 2019-01-01: 1_650 2021-01-01: 1_700 @@ -9,6 +10,7 @@ SINGLE: 2025-01-01: 2_000 2026-01-01: 2_050 JOINT: + 2017-01-01: 1_250 2018-01-01: 1_300 2019-01-01: 1_300 2021-01-01: 1_350 @@ -18,6 +20,7 @@ JOINT: 2025-01-01: 1_600 2026-01-01: 1_650 SEPARATE: + 2017-01-01: 1_250 2018-01-01: 1_300 2019-01-01: 1_300 2021-01-01: 1_350 @@ -27,6 +30,7 @@ SEPARATE: 2025-01-01: 1_600 2026-01-01: 1_650 HEAD_OF_HOUSEHOLD: + 2017-01-01: 1_550 2018-01-01: 1_600 2019-01-01: 1_650 2021-01-01: 1_700 @@ -36,6 +40,7 @@ HEAD_OF_HOUSEHOLD: 2025-01-01: 2_000 2026-01-01: 2_050 SURVIVING_SPOUSE: + 2017-01-01: 1_250 2018-01-01: 1_300 2019-01-01: 1_300 2021-01-01: 1_350 @@ -52,6 +57,8 @@ metadata: period: year propagate_metadata_to_children: true reference: + - title: 2017 IRS data release (Rev. Proc. 2016-55) + href: https://www.irs.gov/pub/irs-drop/rp-16-55.pdf#page=15 - title: 2026 IRS data release href: https://www.irs.gov/pub/irs-drop/rp-25-32.pdf#page=18 - title: 2025 IRS data release diff --git a/policyengine_us/parameters/gov/irs/deductions/standard/amount.yaml b/policyengine_us/parameters/gov/irs/deductions/standard/amount.yaml index cd56afc4fd0..b30f1d2a7bf 100644 --- a/policyengine_us/parameters/gov/irs/deductions/standard/amount.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/standard/amount.yaml @@ -1,6 +1,7 @@ description: Federal deduction from AGI if not itemizing. SINGLE: + 2017-01-01: 6_350 2018-01-01: 12_000 2019-01-01: 12_200 2020-01-01: 12_400 @@ -11,6 +12,7 @@ SINGLE: 2025-01-01: 15_750 2026-01-01: 16_100 JOINT: + 2017-01-01: 12_700 2018-01-01: 24_000 2019-01-01: 24_400 2020-01-01: 24_800 @@ -21,6 +23,7 @@ JOINT: 2025-01-01: 31_500 2026-01-01: 32_200 SEPARATE: + 2017-01-01: 6_350 2018-01-01: 12_000 2019-01-01: 12_200 2020-01-01: 12_400 @@ -31,6 +34,7 @@ SEPARATE: 2025-01-01: 15_750 2026-01-01: 16_100 HEAD_OF_HOUSEHOLD: + 2017-01-01: 9_350 2018-01-01: 18_000 2019-01-01: 18_350 2020-01-01: 18_650 @@ -41,6 +45,7 @@ HEAD_OF_HOUSEHOLD: 2025-01-01: 23_625 2026-01-01: 24_150 SURVIVING_SPOUSE: + 2017-01-01: 12_700 2018-01-01: 24_000 2019-01-01: 24_400 2020-01-01: 24_800 @@ -76,6 +81,8 @@ metadata: href: https://www.irs.gov/pub/irs-drop/rp-19-44.pdf - title: 2019 IRS data release href: https://www.irs.gov/pub/irs-drop/rp-18-57.pdf + - title: 2017 IRS data release (Rev. Proc. 2016-55) + href: https://www.irs.gov/pub/irs-drop/rp-16-55.pdf#page=15 - title: CBO Tax Parameters and Effective Marginal Tax Rates | Feb 2024 # 1. Tax Parameters. href: https://www.cbo.gov/system/files/2024-02/53724-2024-02-Tax-Parameters.xlsx diff --git a/policyengine_us/parameters/gov/states/vt/tax/income/deductions/itemized_addback/other_multiple.yaml b/policyengine_us/parameters/gov/states/vt/tax/income/deductions/itemized_addback/other_multiple.yaml new file mode 100644 index 00000000000..b3c982cb833 --- /dev/null +++ b/policyengine_us/parameters/gov/states/vt/tax/income/deductions/itemized_addback/other_multiple.yaml @@ -0,0 +1,10 @@ +description: Vermont adds back itemized deductions, other than income taxes, medical expenses and charitable contributions, in excess of this multiple of the federal standard deduction, under former 32 V.S.A. § 5811(21)(A)(iv). +values: + 2017-01-01: 2.5 +metadata: + unit: /1 + period: year + label: Vermont 2017 other itemized deduction addback standard deduction multiple + reference: + - title: 2017 Vt. Acts No. 73, § 13a (former 32 V.S.A. § 5811(21)(A)(iii)-(iv)) #page 21-22 + href: https://legislature.vermont.gov/Documents/2018/Docs/ACTS/ACT073/ACT073%20As%20Enacted.pdf#page=21 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_other_additions.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_other_additions.yaml new file mode 100644 index 00000000000..bddb1aa2f3e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_other_additions.yaml @@ -0,0 +1,8 @@ +- name: 2017 other additions total tax-exempt interest and other additions. + period: 2017 + input: + state_code: VT + tax_exempt_interest_income: 1_500 + vt_other_additions: 700 + output: + vt_2017_other_additions: 2_200 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_subtractions.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_subtractions.yaml new file mode 100644 index 00000000000..d42e347da8f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_subtractions.yaml @@ -0,0 +1,17 @@ +- name: 2017 subtractions total U.S. obligation interest, taxable state refunds and other subtractions. + period: 2017 + input: + state_code: VT + us_govt_interest: 1_000 + salt_refund_income: 300 + vt_2017_other_subtractions: 2_000 + output: + vt_2017_subtractions: 3_300 + +- name: No subtractions outside Vermont. + period: 2017 + input: + state_code: NH + us_govt_interest: 1_000 + output: + vt_2017_subtractions: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml index 196794f2c60..8364c4e30ea 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml @@ -108,3 +108,40 @@ vt_personal_exemptions: 4_000 output: vt_taxable_income: 30_000 + +- name: 2017 starts from the federal base, not Vermont AGI less Vermont deductions. + period: 2017 + input: + state_code: VT + tax_unit_itemizes: false + adjusted_gross_income: 50_000 + exemptions: 4_050 + taxable_income_deductions: 20_000 + vt_agi: 49_500 + vt_standard_deduction: 6_350 + vt_personal_exemptions: 4_050 + output: + # 50,000 - 4,050 - 20,000, with no addback, additions or subtractions. + # The Vermont-base formula would give 49,500 - 6,350 - 4,050 = 39,100. + vt_taxable_income: 25_950 + +- name: 2017 single wage earner, end to end from the federal base. + period: 2017 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: VT + output: + # 60,000 - 4,050 exemption - 6,350 standard deduction. + standard_deduction: 6_350 + exemptions: 4_050 + vt_taxable_income: 49_600 diff --git a/policyengine_us/tests/test_vt_2017_taxable_income_properties.py b/policyengine_us/tests/test_vt_2017_taxable_income_properties.py new file mode 100644 index 00000000000..66191512b50 --- /dev/null +++ b/policyengine_us/tests/test_vt_2017_taxable_income_properties.py @@ -0,0 +1,160 @@ +"""Property tests: Vermont's 2017 taxable income under former 32 V.S.A. +§ 5811(21). + +For 2017, Vermont taxable income is federal taxable income, before its zero +floor, plus the itemized deduction addback and other additions, less the +subtractions, floored at zero. For allowed federal itemized deductions I, +deducted income taxes T, medical M, charitable C and federal standard +deduction S, the addback is min(T, max(I - S, 0)) + max(I - T - M - C - +2.5 S, 0) for an itemizer and zero otherwise. + +Properties checked, each in one batched simulation per example: + +1. 0 <= addback <= max(I - S, 0) + max(I - 2.5 S, 0), and the addback is zero + for a nonitemizer. +2. The addback never falls when I rises with T, M, C and S held fixed. +3. vt_taxable_income >= 0, and does not depend on the 2018+ Vermont base + (vt_agi, vt_standard_deduction, vt_personal_exemptions). +4. Raising the other additions by d raises vt_taxable_income by between 0 + and d, and by exactly d when the original value is positive. + +Properties 3 and 4 fail if 2017 uses the 2018+ formula, which ignores the +additions and reads the Vermont base. The model computes in single +precision, so comparisons allow one cent or eight float32 spacings. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +YEAR = 2017 +MAX_AMOUNT = 100_000 +SETTINGS = settings( + max_examples=15, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) + +amount = st.integers(min_value=0, max_value=MAX_AMOUNT) + + +def tolerance(*amounts): + scale = np.float32(max(np.max(np.abs(a)) for a in amounts)) + return max(0.01, 8 * float(np.spacing(scale))) + + +def calculate(units, variable): + """One Vermont tax unit per household, each with its own inputs.""" + people, tax_units, households = {}, {}, {} + for i, unit in enumerate(units): + people[f"p{i}"] = {"age": {YEAR: 40}} + tax_units[f"t{i}"] = { + "members": [f"p{i}"], + **{name: {YEAR: value} for name, value in unit.items()}, + } + households[f"h{i}"] = { + "members": [f"p{i}"], + "state_code": {YEAR: "VT"}, + } + situation = { + "people": people, + "tax_units": tax_units, + "households": households, + } + return Simulation(situation=situation).calculate(variable, YEAR) + + +addback_unit = st.fixed_dictionaries( + { + "tax_unit_itemizes": st.booleans(), + "itemized_taxable_income_deductions": amount, + "standard_deduction": st.integers(min_value=0, max_value=20_000), + "salt_deduction": amount, + "state_withheld_income_tax": amount, + "local_income_tax": st.just(0), + "state_sales_tax": amount, + "local_sales_tax": st.just(0), + "medical_expense_deduction": amount, + "charitable_deduction": amount, + } +) + + +@SETTINGS +@given(units=st.lists(addback_unit, min_size=1, max_size=20), raise_by=amount) +def test_addback_bounds_and_monotonicity(units, raise_by): + addback = calculate(units, "vt_2017_itemized_deduction_addback") + itemized = np.array([u["itemized_taxable_income_deductions"] for u in units]) + standard = np.array([u["standard_deduction"] for u in units]) + itemizes = np.array([u["tax_unit_itemizes"] for u in units]) + upper = np.maximum(itemized - standard, 0) + np.maximum( + itemized - 2.5 * standard, 0 + ) + eps = tolerance(addback, upper) + assert np.all(addback >= -eps) + assert np.all(addback <= upper + eps) + assert np.all(np.abs(addback[~itemizes]) <= eps) + + raised = [ + { + **u, + "itemized_taxable_income_deductions": u[ + "itemized_taxable_income_deductions" + ] + + raise_by, + } + for u in units + ] + raised_addback = calculate(raised, "vt_2017_itemized_deduction_addback") + assert np.all(raised_addback >= addback - tolerance(addback, raised_addback)) + + +taxable_income_unit = st.fixed_dictionaries( + { + "adjusted_gross_income": st.integers(min_value=-20_000, max_value=MAX_AMOUNT), + "exemptions": st.integers(min_value=0, max_value=20_000), + "taxable_income_deductions": st.integers(min_value=0, max_value=50_000), + "vt_2017_itemized_deduction_addback": st.integers( + min_value=0, max_value=20_000 + ), + "vt_2017_other_additions": st.integers(min_value=0, max_value=20_000), + "vt_2017_subtractions": st.integers(min_value=0, max_value=20_000), + "vt_agi": amount, + "vt_standard_deduction": st.integers(min_value=0, max_value=20_000), + "vt_personal_exemptions": st.integers(min_value=0, max_value=20_000), + } +) + + +@SETTINGS +@given( + units=st.lists(taxable_income_unit, min_size=1, max_size=20), + vermont_base_shift=st.integers(min_value=1, max_value=50_000), + raise_by=st.integers(min_value=1, max_value=20_000), +) +def test_taxable_income_floor_base_and_additions(units, vermont_base_shift, raise_by): + taxable = calculate(units, "vt_taxable_income") + assert np.all(taxable >= 0) + + shifted = [{**u, "vt_agi": u["vt_agi"] + vermont_base_shift} for u in units] + shifted_taxable = calculate(shifted, "vt_taxable_income") + assert np.all( + np.abs(shifted_taxable - taxable) <= tolerance(taxable, shifted_taxable) + ) + + raised = [ + { + **u, + "vt_2017_other_additions": u["vt_2017_other_additions"] + raise_by, + } + for u in units + ] + raised_taxable = calculate(raised, "vt_taxable_income") + change = raised_taxable - taxable + eps = tolerance(taxable, raised_taxable) + assert np.all(change >= -eps) + assert np.all(change <= raise_by + eps) + positive = taxable > eps + assert np.all(np.abs(change[positive] - raise_by) <= eps) diff --git a/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.py b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.py new file mode 100644 index 00000000000..2035fe19239 --- /dev/null +++ b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.py @@ -0,0 +1,48 @@ +from policyengine_us.model_api import * + + +class vt_2017_itemized_deduction_addback(Variable): + value_type = float + entity = TaxUnit + label = "Vermont 2017 itemized deduction addback" + unit = USD + documentation = ( + "Federal itemized deductions Vermont adds back to 2017 federal " + "taxable income: deducted state and local income taxes, up to the " + "itemized deductions above the federal standard deduction, plus other " + "itemized deductions (excluding medical and charitable) above 2.5 " + "times the federal standard deduction." + ) + definition_period = YEAR + defined_for = StateCode.VT + reference = ( + # Former 32 V.S.A. § 5811(21)(A)(iii)-(iv), reproduced in 2017 Vt. + # Acts No. 73, § 13a. PDF pages 21-22. + "https://legislature.vermont.gov/Documents/2018/Docs/ACTS/ACT073/ACT073%20As%20Enacted.pdf#page=21", + ) + + def formula_2017(tax_unit, period, parameters): + p = parameters(period).gov.states.vt.tax.income.deductions.itemized_addback + itemizes = tax_unit("tax_unit_itemizes", period) + # Allowed federal itemized deductions, after the section 68 limitation. + itemized = tax_unit("itemized_taxable_income_deductions", period) + standard = tax_unit("standard_deduction", period) + # The SALT deduction holds income taxes only when they exceed sales + # taxes (state_and_local_sales_or_income_tax takes the larger). + income_taxes = add( + tax_unit, period, ["state_withheld_income_tax", "local_income_tax"] + ) + sales_taxes = add(tax_unit, period, ["state_sales_tax", "local_sales_tax"]) + salt = tax_unit("salt_deduction", period) + deducted_income_taxes = where( + income_taxes >= sales_taxes, min_(income_taxes, salt), 0 + ) + income_tax_addback = min_(deducted_income_taxes, max_(itemized - standard, 0)) + medical = tax_unit("medical_expense_deduction", period) + charitable = tax_unit("charitable_deduction", period) + other_deductions = itemized - deducted_income_taxes - medical - charitable + other_addback = max_(other_deductions - p.other_multiple * standard, 0) + return where(itemizes, income_tax_addback + other_addback, 0) + + def formula_2018(tax_unit, period, parameters): + return 0 diff --git a/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_other_additions.py b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_other_additions.py new file mode 100644 index 00000000000..ce2b2e8b263 --- /dev/null +++ b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_other_additions.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class vt_2017_other_additions(Variable): + value_type = float + entity = TaxUnit + label = "Vermont 2017 additions to federal taxable income" + unit = USD + documentation = ( + "Vermont's 2017 additions to federal taxable income other than the " + "itemized deduction addback: interest on non-Vermont state and local " + "obligations and the adjustment for federal bonus depreciation under " + "26 U.S.C. § 168(k). As in Vermont's AGI additions, all tax-exempt " + "interest is added; vt_other_additions carries the rest." + ) + definition_period = YEAR + defined_for = StateCode.VT + reference = ( + # Former 32 V.S.A. § 5811(21), as amended by 2015 Vt. Acts No. 57, § 64. + "https://legislature.vermont.gov/Documents/2016/Docs/ACTS/ACT057/ACT057%20As%20Enacted.pdf#page=89", + ) + + adds = ["tax_exempt_interest_income", "vt_other_additions"] diff --git a/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_other_subtractions.py b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_other_subtractions.py new file mode 100644 index 00000000000..8c6af95d4ac --- /dev/null +++ b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_other_subtractions.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class vt_2017_other_subtractions(Variable): + value_type = float + entity = TaxUnit + label = "Vermont 2017 other subtractions from federal taxable income" + unit = USD + documentation = ( + "Vermont's 2017 subtractions from federal taxable income that " + "PolicyEngine does not calculate, entered as a positive amount: the " + "capital gains exclusion under the law then in effect and the " + "depreciation adjustment for property whose federal bonus " + "depreciation was added back in an earlier year." + ) + definition_period = YEAR + defined_for = StateCode.VT + reference = ( + # Former 32 V.S.A. § 5811(21), as amended by 2015 Vt. Acts No. 57, § 64. + "https://legislature.vermont.gov/Documents/2016/Docs/ACTS/ACT057/ACT057%20As%20Enacted.pdf#page=89", + ) diff --git a/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_subtractions.py b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_subtractions.py new file mode 100644 index 00000000000..4ce44f8e6eb --- /dev/null +++ b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_subtractions.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class vt_2017_subtractions(Variable): + value_type = float + entity = TaxUnit + label = "Vermont 2017 subtractions from federal taxable income" + unit = USD + documentation = ( + "Vermont's 2017 subtractions from federal taxable income: income from " + "U.S. obligations, taxable state and local income tax refunds, and " + "other subtractions such as the capital gains exclusion and " + "depreciation adjustments for property placed in service earlier." + ) + definition_period = YEAR + defined_for = StateCode.VT + reference = ( + # Former 32 V.S.A. § 5811(21), as amended by 2015 Vt. Acts No. 57, § 64. + "https://legislature.vermont.gov/Documents/2016/Docs/ACTS/ACT057/ACT057%20As%20Enacted.pdf#page=89", + ) + + adds = ["us_govt_interest", "salt_refund_income", "vt_2017_other_subtractions"] diff --git a/policyengine_us/variables/gov/states/vt/tax/income/vt_taxable_income.py b/policyengine_us/variables/gov/states/vt/tax/income/vt_taxable_income.py index 771e9c3949e..bee7f534f3e 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/vt_taxable_income.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/vt_taxable_income.py @@ -6,12 +6,39 @@ class vt_taxable_income(Variable): entity = TaxUnit label = "Vermont taxable income" unit = USD - documentation = "VT AGI less taxable income deductions and exemptions" + documentation = ( + "From 2018, Vermont AGI less the Vermont standard deduction and " + "personal exemptions. In 2017, federal taxable income with Vermont's " + "additions and subtractions under former 32 V.S.A. § 5811(21)." + ) definition_period = YEAR - reference = "https://tax.vermont.gov/sites/tax/files/documents/IN-111-2022.pdf" + reference = ( + "https://tax.vermont.gov/sites/tax/files/documents/IN-111-2022.pdf", + # Former 32 V.S.A. § 5811(21), as amended by 2015 Vt. Acts No. 57, § 64. + "https://legislature.vermont.gov/Documents/2016/Docs/ACTS/ACT057/ACT057%20As%20Enacted.pdf#page=89", + # 2017 Vt. Acts No. 73, § 13a reproduces the former text; § 32(5) + # changes the base from 2018. PDF pages 21-22, 59. + "https://legislature.vermont.gov/Documents/2018/Docs/ACTS/ACT073/ACT073%20As%20Enacted.pdf#page=21", + "https://tax.vermont.gov/sites/tax/files/documents/IN-111%20Instr-2017.pdf#page=5", + ) defined_for = StateCode.VT - def formula(tax_unit, period, parameters): + def formula_2017(tax_unit, period, parameters): + # Federal taxable income before its zero floor, so that a negative + # federal base offsets Vermont's additions. + agi = tax_unit("adjusted_gross_income", period) + exemptions = tax_unit("exemptions", period) + deductions = tax_unit("taxable_income_deductions", period) + federal_base = agi - exemptions - deductions + additions = add( + tax_unit, + period, + ["vt_2017_itemized_deduction_addback", "vt_2017_other_additions"], + ) + subtractions = tax_unit("vt_2017_subtractions", period) + return max_(0, federal_base + additions - subtractions) + + def formula_2018(tax_unit, period, parameters): agi = tax_unit("vt_agi", period) deductions = tax_unit("vt_standard_deduction", period) exemptions = tax_unit("vt_personal_exemptions", period) From 8ae4b6bcf2f23f43fe18afdc2761e1ceb38c223d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 19:28:47 +0000 Subject: [PATCH 03/10] Keep Vermont's Vermont-base taxable income before 2017 formula_2017 was the earliest formula, so vt_taxable_income returned 0 for 2015 and 2016 and zeroed Vermont income tax. Restore main's formula for those years, add a 2016 case, and point the 2017 IN-111 instructions at an archived copy, since the Department removed the original. Co-Authored-By: Claude Opus 5.5 --- .../states/vt/tax/income/vt_taxable_income.yaml | 14 ++++++++++++++ .../gov/states/vt/tax/income/vt_taxable_income.py | 10 +++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml index 8364c4e30ea..ce0cf987070 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml @@ -109,6 +109,20 @@ output: vt_taxable_income: 30_000 +- name: 2016 keeps the Vermont-base formula. + period: 2016 + input: + state_code: VT + adjusted_gross_income: 50_000 + exemptions: 0 + taxable_income_deductions: 20_000 + vt_agi: 40_000 + vt_standard_deduction: 6_000 + vt_personal_exemptions: 4_000 + output: + # 40,000 - 6,000 - 4,000. Without a formula before 2017 this is 0. + vt_taxable_income: 30_000 + - name: 2017 starts from the federal base, not Vermont AGI less Vermont deductions. period: 2017 input: diff --git a/policyengine_us/variables/gov/states/vt/tax/income/vt_taxable_income.py b/policyengine_us/variables/gov/states/vt/tax/income/vt_taxable_income.py index bee7f534f3e..43da19a2ee7 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/vt_taxable_income.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/vt_taxable_income.py @@ -7,8 +7,8 @@ class vt_taxable_income(Variable): label = "Vermont taxable income" unit = USD documentation = ( - "From 2018, Vermont AGI less the Vermont standard deduction and " - "personal exemptions. In 2017, federal taxable income with Vermont's " + "Before 2017 and from 2018, Vermont AGI less the Vermont standard " + "deduction and personal exemptions. In 2017, federal taxable income with Vermont's " "additions and subtractions under former 32 V.S.A. § 5811(21)." ) definition_period = YEAR @@ -19,7 +19,7 @@ class vt_taxable_income(Variable): # 2017 Vt. Acts No. 73, § 13a reproduces the former text; § 32(5) # changes the base from 2018. PDF pages 21-22, 59. "https://legislature.vermont.gov/Documents/2018/Docs/ACTS/ACT073/ACT073%20As%20Enacted.pdf#page=21", - "https://tax.vermont.gov/sites/tax/files/documents/IN-111%20Instr-2017.pdf#page=5", + "https://web.archive.org/web/2018/https://tax.vermont.gov/sites/tax/files/documents/IN-111%20Instr-2017.pdf#page=5", ) defined_for = StateCode.VT @@ -43,3 +43,7 @@ def formula_2018(tax_unit, period, parameters): deductions = tax_unit("vt_standard_deduction", period) exemptions = tax_unit("vt_personal_exemptions", period) return max_(0, agi - deductions - exemptions) + + # Before 2017, keep the Vermont-base formula. Former § 5811(21) also set + # the federal base for 2015 and 2016, which this variable doesn't model. + formula = formula_2018 From 9b2c93c3e028e6f82eac8f519ac75ce866d47d2b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 19:28:47 +0000 Subject: [PATCH 04/10] Follow Schedule IN-155 and its worksheet in the 2017 Vermont addback IN-155 line 1 takes Schedule A line 29, which includes wagering losses, and the Department's worksheet scales income taxes and charity by the share of section 68-limited deductions the limitation leaves. Medical stays whole. Co-Authored-By: Claude Opus 5.5 --- .../vt_2017_itemized_deduction_addback.yaml | 100 ++++++++++++++++++ .../vt_2017_itemized_deduction_addback.py | 45 ++++++-- 2 files changed, 136 insertions(+), 9 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml index 0f155d37605..a949bd16273 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml @@ -84,3 +84,103 @@ state_withheld_income_tax: 2_000 output: vt_2017_itemized_deduction_addback: 0 + +- name: 2017 counts wagering losses among the other itemized deductions. + period: 2017 + input: + state_code: VT + tax_unit_itemizes: true + itemized_taxable_income_deductions: 12_850 + wagering_losses_deduction: 30_000 + standard_deduction: 6_350 + salt_deduction: 4_850 + state_withheld_income_tax: 4_850 + medical_expense_deduction: 0 + charitable_deduction: 0 + output: + # IN-155 line 1 takes Schedule A line 29, so I = 12,850 + 30,000 = 42,850. + # min(4,850, 42,850 - 6,350) + max(42,850 - 4,850 - 2.5 x 6,350, 0) + # = 4,850 + 22,125 + vt_2017_itemized_deduction_addback: 26_975 + +- name: 2017 scales deducted income taxes by the share section 68 leaves. + period: 2017 + input: + state_code: VT + tax_unit_itemizes: true + total_itemized_taxable_income_deductions: 44_000 + itemized_taxable_income_deductions_reduction: 4_000 + itemized_taxable_income_deductions: 40_000 + standard_deduction: 6_350 + salt_deduction: 20_000 + state_withheld_income_tax: 20_000 + medical_expense_deduction: 4_000 + charitable_deduction: 10_000 + output: + # Limited base 44,000 - 4,000 medical = 40,000; r = 1 - 4,000 / 40,000 = 0.9. + # T = 18,000, C = 9,000. + # min(18,000, 40,000 - 6,350) + max(40,000 - 18,000 - 4,000 - 9,000 - 15,875, 0) + # = 18,000 + 0 (unscaled: 20,000) + vt_2017_itemized_deduction_addback: 18_000 + +- name: 2017 scales charitable deductions by the share section 68 leaves. + period: 2017 + input: + state_code: VT + tax_unit_itemizes: true + total_itemized_taxable_income_deductions: 74_000 + itemized_taxable_income_deductions_reduction: 7_000 + itemized_taxable_income_deductions: 67_000 + standard_deduction: 6_350 + salt_deduction: 20_000 + state_withheld_income_tax: 20_000 + medical_expense_deduction: 4_000 + charitable_deduction: 10_000 + output: + # Limited base 74,000 - 4,000 = 70,000; r = 1 - 7,000 / 70,000 = 0.9. + # T = 18,000, C = 9,000. + # min(18,000, 67,000 - 6,350) + max(67,000 - 18,000 - 4,000 - 9,000 - 15,875, 0) + # = 18,000 + 20,125 (unscaled: 37,125) + vt_2017_itemized_deduction_addback: 38_125 + +- name: 2017 leaves income taxes whole when no deductions are subject to section 68. + period: 2017 + input: + state_code: VT + tax_unit_itemizes: true + total_itemized_taxable_income_deductions: 8_000 + itemized_taxable_income_deductions_reduction: 0 + itemized_taxable_income_deductions: 8_000 + standard_deduction: 6_350 + salt_deduction: 2_000 + state_withheld_income_tax: 2_000 + medical_expense_deduction: 8_000 + charitable_deduction: 0 + output: + # Limited base max(8,000 - 8,000, 0) = 0, so r = 1. + # min(2,000, 8,000 - 6,350) + max(8,000 - 2,000 - 8,000 - 15,875, 0) = 1,650 + vt_2017_itemized_deduction_addback: 1_650 + +- name: 2017 addback through the section 68 limitation, end to end. + period: 2017 + absolute_error_margin: 0.01 + input: + state_code: VT + tax_unit_itemizes: true + adjusted_gross_income: 500_000 + salt_deduction: 20_000 + state_withheld_income_tax: 20_000 + charitable_deduction: 10_000 + medical_expense_deduction: 4_000 + interest_deduction: 40_000 + standard_deduction: 6_350 + output: + # Total 20,000 + 10,000 + 4,000 + 40,000 = 74,000; limited base 70,000. + # Reduction min((500,000 - 261,500) x 3%, 80% x 70,000) = 7,155. + # I = 66,845; r = 62,845 / 70,000. + # T = 17,955.71, C = 8,977.86. + # min(17,955.71, 60,495) + max(66,845 - 17,955.71 - 4,000 - 8,977.86 - 15,875, 0) + # = 17,955.71 + 20,036.43 + itemized_taxable_income_deductions_reduction: 7_155 + itemized_taxable_income_deductions: 66_845 + vt_2017_itemized_deduction_addback: 37_992.14 diff --git a/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.py b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.py index 2035fe19239..7f4cf5fffbd 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.py @@ -8,10 +8,13 @@ class vt_2017_itemized_deduction_addback(Variable): unit = USD documentation = ( "Federal itemized deductions Vermont adds back to 2017 federal " - "taxable income: deducted state and local income taxes, up to the " - "itemized deductions above the federal standard deduction, plus other " - "itemized deductions (excluding medical and charitable) above 2.5 " - "times the federal standard deduction." + "taxable income, following Schedule IN-155: deducted state and local " + "income taxes, up to the itemized deductions above the federal " + "standard deduction, plus other itemized deductions (excluding " + "medical and charitable) above 2.5 times the federal standard " + "deduction. Itemized deductions include wagering losses and are after " + "the section 68 limitation, which also scales the income taxes and " + "charity." ) definition_period = YEAR defined_for = StateCode.VT @@ -19,13 +22,23 @@ class vt_2017_itemized_deduction_addback(Variable): # Former 32 V.S.A. § 5811(21)(A)(iii)-(iv), reproduced in 2017 Vt. # Acts No. 73, § 13a. PDF pages 21-22. "https://legislature.vermont.gov/Documents/2018/Docs/ACTS/ACT073/ACT073%20As%20Enacted.pdf#page=21", + # 2017 Schedule IN-155 and the Department's 2017 worksheet for limited + # itemized deductions. + "https://web.archive.org/web/20250629013817/https://tax.vermont.gov/sites/tax/files/documents/IN-155-2017.pdf", + "https://web.archive.org/web/20201019064237/https://tax.vermont.gov/sites/tax/files/documents/2017%20IN-155%20Worksheet.xlsx", ) def formula_2017(tax_unit, period, parameters): p = parameters(period).gov.states.vt.tax.income.deductions.itemized_addback itemizes = tax_unit("tax_unit_itemizes", period) - # Allowed federal itemized deductions, after the section 68 limitation. - itemized = tax_unit("itemized_taxable_income_deductions", period) + # IN-155 line 1 takes Schedule A line 29: allowed itemized deductions + # after the section 68 limitation, including wagering losses, which + # itemized_taxable_income_deductions leaves out. + itemized = add( + tax_unit, + period, + ["itemized_taxable_income_deductions", "wagering_losses_deduction"], + ) standard = tax_unit("standard_deduction", period) # The SALT deduction holds income taxes only when they exceed sales # taxes (state_and_local_sales_or_income_tax takes the larger). @@ -37,10 +50,24 @@ def formula_2017(tax_unit, period, parameters): deducted_income_taxes = where( income_taxes >= sales_taxes, min_(income_taxes, salt), 0 ) - income_tax_addback = min_(deducted_income_taxes, max_(itemized - standard, 0)) + # The Department's worksheet for limited itemized deductions scales + # income taxes and charity by the share of the limited deductions that + # section 68 leaves. Section 68(c) doesn't limit medical expenses. + limitation = parameters(period).gov.irs.deductions.itemized.limitation + total = tax_unit("total_itemized_taxable_income_deductions", period) + excluded = add(tax_unit, period, limitation.excluded_deductions) + limited = max_(total - excluded, 0) + reduction = tax_unit("itemized_taxable_income_deductions_reduction", period) + remaining_share = 1 - np.divide( + reduction, limited, out=np.zeros_like(limited), where=limited > 0 + ) + allowed_income_taxes = deducted_income_taxes * remaining_share + allowed_charitable = tax_unit("charitable_deduction", period) * remaining_share medical = tax_unit("medical_expense_deduction", period) - charitable = tax_unit("charitable_deduction", period) - other_deductions = itemized - deducted_income_taxes - medical - charitable + income_tax_addback = min_(allowed_income_taxes, max_(itemized - standard, 0)) + other_deductions = ( + itemized - allowed_income_taxes - medical - allowed_charitable + ) other_addback = max_(other_deductions - p.other_multiple * standard, 0) return where(itemizes, income_tax_addback + other_addback, 0) From 54def4f846cdb9b5f8c7170ab43598c46384e410 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 19:28:48 +0000 Subject: [PATCH 05/10] Add the 2015 and 2016 federal standard deduction amounts The earliest values were dated 2017, so 2015 and 2016 took 2017's. Add the amounts from Rev. Procs. 2014-61 and 2015-53, with cases for both years. Co-Authored-By: Claude Opus 5.5 --- .../standard/aged_or_blind/amount.yaml | 14 +++++++++++++ .../gov/irs/deductions/standard/amount.yaml | 14 +++++++++++++ .../additional_standard_deduction.yaml | 13 ++++++++++++ .../standard/basic_standard_deduction.yaml | 21 +++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/policyengine_us/parameters/gov/irs/deductions/standard/aged_or_blind/amount.yaml b/policyengine_us/parameters/gov/irs/deductions/standard/aged_or_blind/amount.yaml index 76a7882c4c0..c73dc7f2759 100644 --- a/policyengine_us/parameters/gov/irs/deductions/standard/aged_or_blind/amount.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/standard/aged_or_blind/amount.yaml @@ -1,5 +1,7 @@ description: Additional standard deduction for blind and aged SINGLE: + 2015-01-01: 1_550 + 2016-01-01: 1_550 2017-01-01: 1_550 2018-01-01: 1_600 2019-01-01: 1_650 @@ -10,6 +12,8 @@ SINGLE: 2025-01-01: 2_000 2026-01-01: 2_050 JOINT: + 2015-01-01: 1_250 + 2016-01-01: 1_250 2017-01-01: 1_250 2018-01-01: 1_300 2019-01-01: 1_300 @@ -20,6 +24,8 @@ JOINT: 2025-01-01: 1_600 2026-01-01: 1_650 SEPARATE: + 2015-01-01: 1_250 + 2016-01-01: 1_250 2017-01-01: 1_250 2018-01-01: 1_300 2019-01-01: 1_300 @@ -30,6 +36,8 @@ SEPARATE: 2025-01-01: 1_600 2026-01-01: 1_650 HEAD_OF_HOUSEHOLD: + 2015-01-01: 1_550 + 2016-01-01: 1_550 2017-01-01: 1_550 2018-01-01: 1_600 2019-01-01: 1_650 @@ -40,6 +48,8 @@ HEAD_OF_HOUSEHOLD: 2025-01-01: 2_000 2026-01-01: 2_050 SURVIVING_SPOUSE: + 2015-01-01: 1_250 + 2016-01-01: 1_250 2017-01-01: 1_250 2018-01-01: 1_300 2019-01-01: 1_300 @@ -59,6 +69,10 @@ metadata: reference: - title: 2017 IRS data release (Rev. Proc. 2016-55) href: https://www.irs.gov/pub/irs-drop/rp-16-55.pdf#page=15 + - title: 2016 IRS data release (Rev. Proc. 2015-53) + href: https://www.irs.gov/pub/irs-drop/rp-15-53.pdf#page=14 + - title: 2015 IRS data release (Rev. Proc. 2014-61) + href: https://www.irs.gov/pub/irs-drop/rp-14-61.pdf#page=13 - title: 2026 IRS data release href: https://www.irs.gov/pub/irs-drop/rp-25-32.pdf#page=18 - title: 2025 IRS data release diff --git a/policyengine_us/parameters/gov/irs/deductions/standard/amount.yaml b/policyengine_us/parameters/gov/irs/deductions/standard/amount.yaml index b30f1d2a7bf..75562208a5a 100644 --- a/policyengine_us/parameters/gov/irs/deductions/standard/amount.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/standard/amount.yaml @@ -1,6 +1,8 @@ description: Federal deduction from AGI if not itemizing. SINGLE: + 2015-01-01: 6_300 + 2016-01-01: 6_300 2017-01-01: 6_350 2018-01-01: 12_000 2019-01-01: 12_200 @@ -12,6 +14,8 @@ SINGLE: 2025-01-01: 15_750 2026-01-01: 16_100 JOINT: + 2015-01-01: 12_600 + 2016-01-01: 12_600 2017-01-01: 12_700 2018-01-01: 24_000 2019-01-01: 24_400 @@ -23,6 +27,8 @@ JOINT: 2025-01-01: 31_500 2026-01-01: 32_200 SEPARATE: + 2015-01-01: 6_300 + 2016-01-01: 6_300 2017-01-01: 6_350 2018-01-01: 12_000 2019-01-01: 12_200 @@ -34,6 +40,8 @@ SEPARATE: 2025-01-01: 15_750 2026-01-01: 16_100 HEAD_OF_HOUSEHOLD: + 2015-01-01: 9_250 + 2016-01-01: 9_300 2017-01-01: 9_350 2018-01-01: 18_000 2019-01-01: 18_350 @@ -45,6 +53,8 @@ HEAD_OF_HOUSEHOLD: 2025-01-01: 23_625 2026-01-01: 24_150 SURVIVING_SPOUSE: + 2015-01-01: 12_600 + 2016-01-01: 12_600 2017-01-01: 12_700 2018-01-01: 24_000 2019-01-01: 24_400 @@ -83,6 +93,10 @@ metadata: href: https://www.irs.gov/pub/irs-drop/rp-18-57.pdf - title: 2017 IRS data release (Rev. Proc. 2016-55) href: https://www.irs.gov/pub/irs-drop/rp-16-55.pdf#page=15 + - title: 2016 IRS data release (Rev. Proc. 2015-53) + href: https://www.irs.gov/pub/irs-drop/rp-15-53.pdf#page=13 + - title: 2015 IRS data release (Rev. Proc. 2014-61) + href: https://www.irs.gov/pub/irs-drop/rp-14-61.pdf#page=13 - title: CBO Tax Parameters and Effective Marginal Tax Rates | Feb 2024 # 1. Tax Parameters. href: https://www.cbo.gov/system/files/2024-02/53724-2024-02-Tax-Parameters.xlsx diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml index b50eb26788b..036188e194f 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml @@ -38,6 +38,19 @@ output: additional_standard_deduction: 1_250 +- name: 2016 single filer aged 65 adds $1,550 + period: 2016 + input: + people: + head: + age: 65 + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + output: + additional_standard_deduction: 1_550 + - name: 2018 aged single filer adds $1,600, unchanged period: 2018 input: diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml index 87a848b759c..ba6d15de01f 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml @@ -609,3 +609,24 @@ filing_status: SURVIVING_SPOUSE output: basic_standard_deduction: 12_700 + +- name: 2015 head of household standard deduction is $9,250 + period: 2015 + input: + filing_status: HEAD_OF_HOUSEHOLD + output: + basic_standard_deduction: 9_250 + +- name: 2016 head of household standard deduction is $9,300 + period: 2016 + input: + filing_status: HEAD_OF_HOUSEHOLD + output: + basic_standard_deduction: 9_300 + +- name: 2016 joint filer standard deduction is $12,600 + period: 2016 + input: + filing_status: JOINT + output: + basic_standard_deduction: 12_600 From 04fccb986f9cc83256a18e412f7c9d3c64da0bfb Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 19:28:48 +0000 Subject: [PATCH 06/10] Check the 2017 Vermont addback against a Schedule IN-155 reference Add a lower bound, draw wagering losses and a section 68 reduction within its ceiling, and compare the addback with a line-by-line IN-155 reference, so dropping either addback term fails the property file. Co-Authored-By: Claude Opus 5.5 --- .../test_vt_2017_taxable_income_properties.py | 109 +++++++++++++++--- 1 file changed, 96 insertions(+), 13 deletions(-) diff --git a/policyengine_us/tests/test_vt_2017_taxable_income_properties.py b/policyengine_us/tests/test_vt_2017_taxable_income_properties.py index 66191512b50..1cceb17a680 100644 --- a/policyengine_us/tests/test_vt_2017_taxable_income_properties.py +++ b/policyengine_us/tests/test_vt_2017_taxable_income_properties.py @@ -3,22 +3,28 @@ For 2017, Vermont taxable income is federal taxable income, before its zero floor, plus the itemized deduction addback and other additions, less the -subtractions, floored at zero. For allowed federal itemized deductions I, -deducted income taxes T, medical M, charitable C and federal standard -deduction S, the addback is min(T, max(I - S, 0)) + max(I - T - M - C - -2.5 S, 0) for an itemizer and zero otherwise. +subtractions, floored at zero. Schedule IN-155 computes the addback. For +allowed federal itemized deductions I (Schedule A line 29, after section 68 +and including wagering losses), deducted income taxes T, medical M, +charitable C and federal standard deduction S, it is min(rT, max(I - S, 0)) ++ max(I - rT - M - rC - 2.5 S, 0) for an itemizer and zero otherwise. r is +the Department's worksheet ratio: the share of the deductions section 68 +limits that remains after the limitation (1 when none are limited). Properties checked, each in one batched simulation per example: -1. 0 <= addback <= max(I - S, 0) + max(I - 2.5 S, 0), and the addback is zero - for a nonitemizer. -2. The addback never falls when I rises with T, M, C and S held fixed. -3. vt_taxable_income >= 0, and does not depend on the 2018+ Vermont base +1. max(I - SALT - M - C - 2.5 S, 0) <= addback <= max(I - S, 0) + + max(I - 2.5 S, 0) for an itemizer, and the addback is zero for a + nonitemizer. +2. The addback never falls when I rises with T, M, C, S and r held fixed. +3. The addback matches in155_line_11, a reference that follows Schedule + IN-155 lines 1 to 11 and the worksheet's cells L8 to L10. +4. vt_taxable_income >= 0, and does not depend on the 2018+ Vermont base (vt_agi, vt_standard_deduction, vt_personal_exemptions). -4. Raising the other additions by d raises vt_taxable_income by between 0 +5. Raising the other additions by d raises vt_taxable_income by between 0 and d, and by exactly d when the original value is positive. -Properties 3 and 4 fail if 2017 uses the 2018+ formula, which ignores the +Properties 4 and 5 fail if 2017 uses the 2018+ formula, which ignores the additions and reads the Vermont base. The model computes in single precision, so comparisons allow one cent or eight float32 spacings. """ @@ -66,10 +72,29 @@ def calculate(units, variable): return Simulation(situation=situation).calculate(variable, YEAR) +def complete_addback_unit(draw): + """Build total itemized deductions from their parts, and draw the section + 68 reduction within its ceiling: 80% of the deductions it limits.""" + unit = dict(draw) + unit["total_itemized_taxable_income_deductions"] = total = ( + unit["salt_deduction"] + + unit["medical_expense_deduction"] + + unit["charitable_deduction"] + + unit.pop("other_itemized") + ) + ceiling = 0.8 * max(total - unit["medical_expense_deduction"], 0) + reduction = round(unit.pop("reduction_share") * ceiling) + unit["itemized_taxable_income_deductions_reduction"] = reduction + unit["itemized_taxable_income_deductions"] = total - reduction + return unit + + addback_unit = st.fixed_dictionaries( { "tax_unit_itemizes": st.booleans(), - "itemized_taxable_income_deductions": amount, + "other_itemized": amount, + "reduction_share": st.sampled_from([0, 0.25, 0.5, 1]), + "wagering_losses_deduction": st.one_of(st.just(0), amount), "standard_deduction": st.integers(min_value=0, max_value=20_000), "salt_deduction": amount, "state_withheld_income_tax": amount, @@ -79,14 +104,57 @@ def calculate(units, variable): "medical_expense_deduction": amount, "charitable_deduction": amount, } -) +).map(complete_addback_unit) + + +def itemized_total(unit): + return ( + unit["itemized_taxable_income_deductions"] + unit["wagering_losses_deduction"] + ) + + +def in155_line_11(unit): + """The 2017 Schedule IN-155 addback, line by line, with the Department's + worksheet for limited itemized deductions on lines 4 and 6.""" + if not unit["tax_unit_itemizes"]: + return 0.0 + # Worksheet: L8 total deductions, L9 deductions not limited at the + # federal level (medical and wagering losses here), L10 total deductions + # after limitations. + l8 = ( + unit["total_itemized_taxable_income_deductions"] + + unit["wagering_losses_deduction"] + ) + l9 = unit["medical_expense_deduction"] + unit["wagering_losses_deduction"] + l10 = itemized_total(unit) + ratio = (l10 - l9) / (l8 - l9) if l8 > l9 else 1.0 + # Schedule A line 5a holds income taxes only when the filer elects them + # over sales taxes. + income_taxes = unit["state_withheld_income_tax"] + unit["local_income_tax"] + sales_taxes = unit["state_sales_tax"] + unit["local_sales_tax"] + schedule_a_5a = ( + min(income_taxes, unit["salt_deduction"]) if income_taxes >= sales_taxes else 0 + ) + line_1 = itemized_total(unit) # Schedule A line 29 + line_2 = unit["standard_deduction"] + # A filer whose itemized deductions are below the standard deduction has + # nothing to add back on line 3. + line_3 = max(line_1 - line_2, 0) + line_4 = schedule_a_5a * ratio + line_5 = unit["medical_expense_deduction"] + line_6 = unit["charitable_deduction"] * ratio + line_7 = line_4 + line_5 + line_6 + line_8 = line_1 - line_7 + line_9 = line_2 * 2.5 + line_10 = max(line_8 - line_9, 0) + return min(line_3, line_4) + line_10 @SETTINGS @given(units=st.lists(addback_unit, min_size=1, max_size=20), raise_by=amount) def test_addback_bounds_and_monotonicity(units, raise_by): addback = calculate(units, "vt_2017_itemized_deduction_addback") - itemized = np.array([u["itemized_taxable_income_deductions"] for u in units]) + itemized = np.array([itemized_total(u) for u in units]) standard = np.array([u["standard_deduction"] for u in units]) itemizes = np.array([u["tax_unit_itemizes"] for u in units]) upper = np.maximum(itemized - standard, 0) + np.maximum( @@ -96,6 +164,13 @@ def test_addback_bounds_and_monotonicity(units, raise_by): assert np.all(addback >= -eps) assert np.all(addback <= upper + eps) assert np.all(np.abs(addback[~itemizes]) <= eps) + # Scaled income taxes are at most the SALT deduction, so the other + # deductions are at least I - SALT - M - C. + salt = np.array([u["salt_deduction"] for u in units]) + medical = np.array([u["medical_expense_deduction"] for u in units]) + charitable = np.array([u["charitable_deduction"] for u in units]) + lower = np.maximum(itemized - salt - medical - charitable - 2.5 * standard, 0) + assert np.all(addback[itemizes] >= lower[itemizes] - eps) raised = [ { @@ -111,6 +186,14 @@ def test_addback_bounds_and_monotonicity(units, raise_by): assert np.all(raised_addback >= addback - tolerance(addback, raised_addback)) +@SETTINGS +@given(units=st.lists(addback_unit, min_size=1, max_size=20)) +def test_addback_matches_schedule_in155(units): + addback = calculate(units, "vt_2017_itemized_deduction_addback") + expected = np.array([in155_line_11(u) for u in units]) + assert np.all(np.abs(addback - expected) <= tolerance(addback, expected)) + + taxable_income_unit = st.fixed_dictionaries( { "adjusted_gross_income": st.integers(min_value=-20_000, max_value=MAX_AMOUNT), From d06aca425eabac0fb627fe8a6aeef081368920ba Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 16:05:25 +0000 Subject: [PATCH 07/10] Make the 2016 Vermont case tell the Vermont base from the federal one Both bases gave 30,000, so pointing the years before 2017 at formula_2017 passed every test. With Vermont AGI at 45,000 the Vermont base is 35,000 while the 2017 federal base stays 30,000, so that mutant now fails the case, as does deleting the formula (0). Claude-Session: https://claude.ai/code/session_01QpcqEDZvsBp213aZFaj3ty Co-Authored-By: Claude Opus 5.5 --- .../gov/states/vt/tax/income/vt_taxable_income.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml index ce0cf987070..2a82cd9db15 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_taxable_income.yaml @@ -116,12 +116,13 @@ adjusted_gross_income: 50_000 exemptions: 0 taxable_income_deductions: 20_000 - vt_agi: 40_000 + vt_agi: 45_000 vt_standard_deduction: 6_000 vt_personal_exemptions: 4_000 output: - # 40,000 - 6,000 - 4,000. Without a formula before 2017 this is 0. - vt_taxable_income: 30_000 + # 45,000 - 6,000 - 4,000. The 2017 federal base would give + # 50,000 - 0 - 20,000 = 30,000, and no formula would give 0. + vt_taxable_income: 35_000 - name: 2017 starts from the federal base, not Vermont AGI less Vermont deductions. period: 2017 From 67b5b494ef5b46ce2917c5541e33e6210995ec3a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 16:05:25 +0000 Subject: [PATCH 08/10] Test the 2015 aged amount for a single filer No case reached the new 2015 aged-or-blind rows, so changing the 2015 single amount from 1,550 to 1,600 passed. Rev. Proc. 2014-61 gives 1,550 for an unmarried filer who is not a surviving spouse. Claude-Session: https://claude.ai/code/session_01QpcqEDZvsBp213aZFaj3ty Co-Authored-By: Claude Opus 5.5 --- .../standard/additional_standard_deduction.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml index 036188e194f..a5dd34ab9e8 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/additional_standard_deduction.yaml @@ -38,6 +38,19 @@ output: additional_standard_deduction: 1_250 +- name: 2015 single filer aged 65 adds $1,550 + period: 2015 + input: + people: + head: + age: 65 + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + output: + additional_standard_deduction: 1_550 + - name: 2016 single filer aged 65 adds $1,550 period: 2016 input: From a1b8c3286f89d4c7e34168cddd942818e228f2e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 16:05:25 +0000 Subject: [PATCH 09/10] Pin the 2017 Vermont addback for a separate filer whose spouse itemizes IN-155 line 2 takes the standard deduction allowable to the filer, and 26 U.S.C. 63(c)(6)(A) makes that 0 once either spouse itemizes, so line 9 is 0 too and every itemized deduction except medical and charity comes back. A pair of 2017 separate filers ($100,000 wages, $8,000 real estate tax) gives addbacks of 4,850.02 and 12,850.03. Measuring line 2 by the filing status's amount instead fails the second case. Claude-Session: https://claude.ai/code/session_01QpcqEDZvsBp213aZFaj3ty Co-Authored-By: Claude Opus 5.5 --- .../vt_2017_itemized_deduction_addback.yaml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml index a949bd16273..db7bc8f7b32 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml @@ -184,3 +184,49 @@ itemized_taxable_income_deductions_reduction: 7_155 itemized_taxable_income_deductions: 66_845 vt_2017_itemized_deduction_addback: 37_992.14 + +- name: 2017 separate filer whose spouse doesn't itemize measures the addback against the standard deduction. + period: 2017 + absolute_error_margin: 0.01 + input: + state_code: VT + filing_status: SEPARATE + tax_unit_itemizes: true + separate_filer_itemizes: false + employment_income: 100_000 + real_estate_taxes: 8_000 + output: + # Vermont withholding, deducted as income tax: 3.35% x 40,950 + + # 6.6% x (100,000 - 6,350 - 40,950) = 4,850.025. Schedule A line 29: + # 4,850.025 + 8,000 real estate tax = 12,850.025, with no section 68 cut. + # Line 1 12,850.025; line 2 6,350; line 3 12,850.025 - 6,350 = 6,500.025. + # Line 4 4,850.025; lines 5 and 6 0; line 7 4,850.025. + # Line 8 12,850.025 - 4,850.025 = 8,000; line 9 2.5 x 6,350 = 15,875; + # line 10 max(8,000 - 15,875, 0) = 0. + # Line 11 min(6,500.025, 4,850.025) + 0 = 4,850.025. + standard_deduction: 6_350 + vt_2017_itemized_deduction_addback: 4_850.02 + # 100,000 - 4,050 exemption - 12,850.025 itemized + 4,850.025 addback + vt_taxable_income: 87_950 + +- name: 2017 separate filer whose spouse itemizes has no standard deduction on line 2. + period: 2017 + absolute_error_margin: 0.01 + input: + state_code: VT + filing_status: SEPARATE + tax_unit_itemizes: true + separate_filer_itemizes: true + employment_income: 100_000 + real_estate_taxes: 8_000 + output: + # Once either spouse itemizes, the standard deduction is 0 (26 U.S.C. + # 63(c)(6)(A)). Schedule A line 29 is 12,850.025, as in the case above. + # Line 1 12,850.025; line 2 0; line 3 12,850.025. + # Line 4 4,850.025; lines 5 and 6 0; line 7 4,850.025. + # Line 8 12,850.025 - 4,850.025 = 8,000; line 9 0; line 10 8,000. + # Line 11 min(12,850.025, 4,850.025) + 8,000 = 12,850.025. + standard_deduction: 0 + vt_2017_itemized_deduction_addback: 12_850.03 + # 100,000 - 4,050 exemption - 12,850.025 itemized + 12,850.025 addback + vt_taxable_income: 95_950 From 892a9f00c03b18d89f3cc2db22ad038df31c28fe Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 00:55:43 +0000 Subject: [PATCH 10/10] Set the separate filers' withholding and pin the aged and 2015 amounts The separate-filer pair left state_withheld_income_tax to the model, which computes 2017 Vermont withholding with 2021-2022 brackets, so the expected addbacks would move when 2017 brackets land. Setting it to 4,850 makes the addbacks whole dollars that depend only on the addback rule. Add a 2017 aged single filer, so a line 2 that drops the aged amount fails, and 2015 joint and single basic amounts, which no case pinned. Co-Authored-By: Claude Opus 5.5 --- .../standard/basic_standard_deduction.yaml | 14 ++++ .../vt_2017_itemized_deduction_addback.yaml | 67 ++++++++++++++----- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml index ba6d15de01f..5e63d8f9df0 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml @@ -617,6 +617,20 @@ output: basic_standard_deduction: 9_250 +- name: 2015 joint filers get a $12,600 standard deduction + period: 2015 + input: + filing_status: JOINT + output: + basic_standard_deduction: 12_600 + +- name: 2015 single filers get a $6,300 standard deduction + period: 2015 + input: + filing_status: SINGLE + output: + basic_standard_deduction: 6_300 + - name: 2016 head of household standard deduction is $9,300 period: 2016 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml index db7bc8f7b32..671bdc0aa63 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/deductions/vt_2017_itemized_deduction_addback.yaml @@ -195,18 +195,17 @@ separate_filer_itemizes: false employment_income: 100_000 real_estate_taxes: 8_000 + state_withheld_income_tax: 4_850 output: - # Vermont withholding, deducted as income tax: 3.35% x 40,950 + - # 6.6% x (100,000 - 6,350 - 40,950) = 4,850.025. Schedule A line 29: - # 4,850.025 + 8,000 real estate tax = 12,850.025, with no section 68 cut. - # Line 1 12,850.025; line 2 6,350; line 3 12,850.025 - 6,350 = 6,500.025. - # Line 4 4,850.025; lines 5 and 6 0; line 7 4,850.025. - # Line 8 12,850.025 - 4,850.025 = 8,000; line 9 2.5 x 6,350 = 15,875; - # line 10 max(8,000 - 15,875, 0) = 0. - # Line 11 min(6,500.025, 4,850.025) + 0 = 4,850.025. + # Schedule A line 29: 4,850 state income tax + 8,000 real estate tax + # = 12,850, with no section 68 cut. + # Line 1 12,850; line 2 6,350; line 3 6,500. + # Line 4 4,850; lines 5 and 6 0; line 7 4,850. + # Line 8 8,000; line 9 2.5 x 6,350 = 15,875; line 10 0. + # Line 11 min(6,500, 4,850) + 0 = 4,850. standard_deduction: 6_350 - vt_2017_itemized_deduction_addback: 4_850.02 - # 100,000 - 4,050 exemption - 12,850.025 itemized + 4,850.025 addback + vt_2017_itemized_deduction_addback: 4_850 + # 100,000 - 4,050 exemption - 12,850 itemized + 4,850 addback vt_taxable_income: 87_950 - name: 2017 separate filer whose spouse itemizes has no standard deduction on line 2. @@ -219,14 +218,46 @@ separate_filer_itemizes: true employment_income: 100_000 real_estate_taxes: 8_000 + state_withheld_income_tax: 4_850 output: - # Once either spouse itemizes, the standard deduction is 0 (26 U.S.C. - # 63(c)(6)(A)). Schedule A line 29 is 12,850.025, as in the case above. - # Line 1 12,850.025; line 2 0; line 3 12,850.025. - # Line 4 4,850.025; lines 5 and 6 0; line 7 4,850.025. - # Line 8 12,850.025 - 4,850.025 = 8,000; line 9 0; line 10 8,000. - # Line 11 min(12,850.025, 4,850.025) + 8,000 = 12,850.025. + # The filer's spouse itemizes, so the filer's standard deduction falls + # to 0 (26 U.S.C. 63(c)(6)(A)). Schedule A line 29: 12,850, as in the + # case above. + # Line 1 12,850; line 2 0; line 3 12,850. + # Line 4 4,850; lines 5 and 6 0; line 7 4,850. + # Line 8 8,000; line 9 0; line 10 8,000. + # Line 11 min(12,850, 4,850) + 8,000 = 12,850. standard_deduction: 0 - vt_2017_itemized_deduction_addback: 12_850.03 - # 100,000 - 4,050 exemption - 12,850.025 itemized + 12,850.025 addback + vt_2017_itemized_deduction_addback: 12_850 + # 100,000 - 4,050 exemption - 12,850 itemized + 12,850 addback vt_taxable_income: 95_950 + +- name: 2017 single filer aged 65 adds the aged amount to line 2. + period: 2017 + absolute_error_margin: 0.01 + input: + people: + head: + age: 65 + employment_income: 100_000 + real_estate_taxes: 20_000 + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + tax_unit_itemizes: true + state_withheld_income_tax: 4_850 + households: + household: + members: [head] + state_code: VT + output: + # Line 1 4,850 + 20,000 = 24,850; line 2 6,350 + 1,550 aged = 7,900; + # line 3 16,950. Line 4 4,850; line 8 20,000; line 9 2.5 x 7,900 = + # 19,750; line 10 250. Line 11 min(16,950, 4,850) + 250 = 5,100. + # Without the aged amount, line 9 would come to 15,875 and the + # addback to 8,975. + standard_deduction: 7_900 + vt_2017_itemized_deduction_addback: 5_100 + # 100,000 - 4,050 - 24,850 + 5,100 + vt_taxable_income: 76_200