From 2c127a689af58d70e2d52620ec165dad2d490703 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 11:46:08 -0400 Subject: [PATCH 1/5] Base Alabama's miscellaneous deduction on the expenses, not the federal deduction Ala. Code 40-18-15(a)(23) allows unreimbursed employee business expenses and section 212 expenses above 2% of Alabama AGI (Schedule A lines 20-24). Main started from the federal misc_deduction, which is zero from 2018 (26 U.S.C. 67(g)) and, before 2018, already net of the federal 2% floor, so Alabama allowed nothing from 2018 and applied two floors before. Co-Authored-By: Claude Opus 5.5 --- .../al-misc-deduction-own-floor.fixed.md | 1 + .../itemized/al_misc_deduction.yaml | 34 +++++++++++++++++-- .../deductions/itemized/al_misc_deduction.py | 9 ++++- 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 changelog.d/al-misc-deduction-own-floor.fixed.md diff --git a/changelog.d/al-misc-deduction-own-floor.fixed.md b/changelog.d/al-misc-deduction-own-floor.fixed.md new file mode 100644 index 00000000000..6fe02c6b15a --- /dev/null +++ b/changelog.d/al-misc-deduction-own-floor.fixed.md @@ -0,0 +1 @@ +Compute Alabama's miscellaneous itemized deduction from unreimbursed employee and other section 212 expenses above 2% of Alabama AGI, which Alabama kept after the federal suspension, instead of from the federal deduction. diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml index a2de34c3f83..153c9b5355b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml @@ -1,20 +1,48 @@ - name: Alabama work related itemized deductions unit test 1 period: 2021 input: - misc_deduction: 2_000 + total_misc_deductions: 2_000 al_agi: 70_000 state_code: AL output: al_misc_deduction: 600 -#2000-70000*0.2=600 +#2000-70000*0.02=600 - name: Alabama work related itemized deductions unit test 2 period: 2021 input: - misc_deduction: 7_000 + total_misc_deductions: 7_000 al_agi: 30_000 state_code: AL output: al_misc_deduction: 6_400 #7000-30000*0.02=6400 + +# Ala. Code 40-18-15(a)(23); 2024 Form 40 instructions, Schedule A lines 20 to +# 24: unreimbursed employee expenses (line 20) and other expenses such as tax +# preparation fees (line 21), less 2% of Alabama AGI (line 23). The federal +# miscellaneous deduction is zero from 2018. +- name: Case 3, 2025 Alabama keeps the miscellaneous deduction the federal return suspends. + period: 2025 + input: + unreimbursed_business_employee_expenses: 3_000 + tax_preparation_fees: 500 + al_agi: 50_000 + state_code: AL + output: + misc_deduction: 0 + # 3,500 - 2% * 50,000 + al_misc_deduction: 2_500 + +- name: Case 4, 2017 only Alabama's 2% floor applies, not the federal floor as well. + period: 2017 + input: + adjusted_gross_income: 100_000 + total_misc_deductions: 5_000 + al_agi: 90_000 + state_code: AL + output: + # Federal deduction 5,000 - 2,000 = 3,000; Alabama 5,000 - 1,800. + misc_deduction: 3_000 + al_misc_deduction: 3_200 diff --git a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py index a671f82d69c..bc1b6369e94 100644 --- a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py +++ b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py @@ -11,11 +11,18 @@ class al_misc_deduction(Variable): "https://law.justia.com/codes/alabama/2022/title-40/chapter-18/article-1/section-40-18-15/", # Code of Alabama Section 40-18-15, h(23) "https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2023/01/22f40schabdc_blk.pdf#page=1", # 2022 Schedule A (Form 1040) "https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2022/06/21f40schabdc_blk.pdf#page=1", # 2021 Schedule A (Form 1040) + # 2024 Form 40 instructions, Schedule A lines 20 to 24. + "https://www.revenue.alabama.gov/wp-content/uploads/2025/01/24f40bk.pdf#page=20", ) defined_for = StateCode.AL def formula(tax_unit, period, parameters): - misc_expense = add(tax_unit, period, ["misc_deduction"]) + # Ala. Code 40-18-15(a)(23) allows unreimbursed employee business + # expenses and section 212 expenses above 2% of Alabama AGI. Alabama + # kept this deduction after the federal suspension (26 U.S.C. 67(g)), + # so start from the expenses, not the federal misc_deduction, which is + # zero from 2018 and already net of the federal 2% floor. + misc_expense = tax_unit("total_misc_deductions", period) p = parameters(period).gov.states.al.tax.income.deductions.itemized misc_floor = p.work_related_expense_rate * tax_unit("al_agi", period) return max_(0, misc_expense - misc_floor) From 46e4c52451faf94a91ab5ea2b50590fd5c8be50a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 16:25:12 +0000 Subject: [PATCH 2/5] Give Alabama its own miscellaneous expense list and date its 2% floor from 2017 The draft read total_misc_deductions, the federal source list, so any category later added to the federal list would flow into Alabama without a reading of Ala. Code 40-18-15. Alabama now sums its own list (misc_sources.yaml) and dates the 2% floor from 2017, the first year the fix covers. Tests supply the person-level expenses instead of an already-summed federal total, add the spec's witnesses, and a Hypothesis property test checks bounds, monotonicity, year-invariance across the federal suspension, and equality with the federal deduction in 2017. Co-Authored-By: Claude Opus 5.5 --- .../al-misc-deduction-own-floor.fixed.md | 2 +- .../deductions/itemized/misc_sources.yaml | 20 ++ .../itemized/work_related_expense_rate.yaml | 14 +- .../itemized/al_misc_deduction.yaml | 98 ++++++-- .../unit/test_al_misc_deduction_properties.py | 223 ++++++++++++++++++ .../deductions/itemized/al_misc_deduction.py | 20 +- 6 files changed, 344 insertions(+), 33 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/misc_sources.yaml create mode 100644 policyengine_us/tests/unit/test_al_misc_deduction_properties.py diff --git a/changelog.d/al-misc-deduction-own-floor.fixed.md b/changelog.d/al-misc-deduction-own-floor.fixed.md index 6fe02c6b15a..59f6fa0779d 100644 --- a/changelog.d/al-misc-deduction-own-floor.fixed.md +++ b/changelog.d/al-misc-deduction-own-floor.fixed.md @@ -1 +1 @@ -Compute Alabama's miscellaneous itemized deduction from unreimbursed employee and other section 212 expenses above 2% of Alabama AGI, which Alabama kept after the federal suspension, instead of from the federal deduction. +Compute Alabama's miscellaneous itemized deduction from its own list of expenses (unreimbursed employee business expenses and tax preparation fees) above 2% of Alabama AGI, from 2017, instead of applying a second floor to the federal deduction, which is zero from 2018. diff --git a/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/misc_sources.yaml b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/misc_sources.yaml new file mode 100644 index 00000000000..1d44a14c257 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/misc_sources.yaml @@ -0,0 +1,20 @@ +description: Alabama counts these expenses toward the miscellaneous itemized deduction, which applies only above a percentage of Alabama adjusted gross income. +values: + 2017-01-01: + # Section (a)(1): unreimbursed employee business expenses. + - unreimbursed_business_employee_expenses + # Section (a)(14) deduction. + - tax_preparation_fees +metadata: + unit: list + period: year + label: Alabama miscellaneous itemized deduction sources + reference: + - title: Code of Alabama Section 40-18-15(a)(1), (14), (23) (2017 edition) + href: https://law.justia.com/codes/alabama/2017/title-40/chapter-18/article-1/section-40-18-15/ + - title: Act 2026-604, Section 1, amending Code of Alabama Section 40-18-15 + href: https://alison.legislature.state.al.us/files/pdf/SearchableInstruments/2026RS/HB527-enr.pdf#page=7 + - title: 2024 Alabama Form 40 instructions, Schedule A lines 20-24 + href: https://www.revenue.alabama.gov/wp-content/uploads/2025/01/24f40bk.pdf#page=20 + - title: 2025 Alabama Form 40 instructions, Schedule A lines 20-24 + href: https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=21 diff --git a/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/work_related_expense_rate.yaml b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/work_related_expense_rate.yaml index e8cbbbd7ac7..5b47b77c3f3 100644 --- a/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/work_related_expense_rate.yaml +++ b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/work_related_expense_rate.yaml @@ -1,14 +1,18 @@ description: Alabama deducts work-related expense over this percentage of adjusted gross income. values: - 2021-01-01: 0.02 + 2017-01-01: 0.02 metadata: label: Alabama work-related expense deduction rate period: year unit: /1 reference: - - title: Code of Alabama Section 40-18-15, (a) (3) (h) (23) - href: https://law.justia.com/codes/alabama/2022/title-40/chapter-18/article-1/section-40-18-15/ + - title: Code of Alabama Section 40-18-15(a)(23) (2017 edition) + href: https://law.justia.com/codes/alabama/2017/title-40/chapter-18/article-1/section-40-18-15/ + - title: Code of Alabama Section 40-18-15(a)(23) (2021 edition) + href: https://law.justia.com/codes/alabama/2021/title-40/chapter-18/article-1/section-40-18-15/ + - title: Act 2026-604, Section 1, amending Code of Alabama Section 40-18-15 + href: https://alison.legislature.state.al.us/files/pdf/SearchableInstruments/2026RS/HB527-enr.pdf#page=7 - title: 2021 Schedule A (Form 1040) href: https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2022/06/21f40schabdc_blk.pdf#page=1 - title: 2022 Schedule A (Form 1040) @@ -17,3 +21,7 @@ metadata: href: https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2024/01/23f40schabdc_blk.pdf#page=1 - title: 2024 Schedule A (Form 1040) href: https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2025/01/24f40schabdc_blk.pdf#page=20 + - title: 2024 Alabama Form 40 instructions, Schedule A lines 20-24 + href: https://www.revenue.alabama.gov/wp-content/uploads/2025/01/24f40bk.pdf#page=20 + - title: 2025 Alabama Form 40 instructions, Schedule A lines 20-24 + href: https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=21 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml index 153c9b5355b..6f7aaaa1f6f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml @@ -1,48 +1,102 @@ -- name: Alabama work related itemized deductions unit test 1 +# Ala. Code 40-18-15(a)(23) allows the subdivision (1) unreimbursed employee +# business expenses and the subdivision (14) deduction only to the extent they +# exceed 2% of Alabama AGI. Alabama applies that one floor to the expenses in +# every year; it does not start from the federal miscellaneous deduction, which +# is already net of the federal floor and is zero from 2018. + +- name: Case 1, 2021 expenses above 2% of Alabama AGI. period: 2021 input: - total_misc_deductions: 2_000 + unreimbursed_business_employee_expenses: 2_000 al_agi: 70_000 state_code: AL output: + # 2,000 - 0.02 * 70,000 al_misc_deduction: 600 -#2000-70000*0.02=600 - -- name: Alabama work related itemized deductions unit test 2 +- name: Case 2, 2021 expenses well above the floor. period: 2021 input: - total_misc_deductions: 7_000 + unreimbursed_business_employee_expenses: 7_000 al_agi: 30_000 state_code: AL output: + # 7,000 - 0.02 * 30,000 al_misc_deduction: 6_400 -#7000-30000*0.02=6400 -# Ala. Code 40-18-15(a)(23); 2024 Form 40 instructions, Schedule A lines 20 to -# 24: unreimbursed employee expenses (line 20) and other expenses such as tax -# preparation fees (line 21), less 2% of Alabama AGI (line 23). The federal -# miscellaneous deduction is zero from 2018. -- name: Case 3, 2025 Alabama keeps the miscellaneous deduction the federal return suspends. - period: 2025 +- name: Case 3, 2017 Alabama applies one floor to the expenses, not a second floor to the federal deduction. + period: 2017 input: - unreimbursed_business_employee_expenses: 3_000 - tax_preparation_fees: 500 + unreimbursed_business_employee_expenses: 5_000 + tax_preparation_fees: 0 + positive_agi: 50_000 al_agi: 50_000 state_code: AL output: - misc_deduction: 0 - # 3,500 - 2% * 50,000 - al_misc_deduction: 2_500 + # Federal and Alabama each allow 5,000 - 0.02 * 50,000 = 4,000. Before the + # fix Alabama took a further 1,000 off the federal 4,000. + misc_deduction: 4_000 + al_misc_deduction: 4_000 -- name: Case 4, 2017 only Alabama's 2% floor applies, not the federal floor as well. +- name: Case 4, 2017 Alabama floors on Alabama AGI, the federal deduction on federal AGI. period: 2017 input: - adjusted_gross_income: 100_000 - total_misc_deductions: 5_000 + unreimbursed_business_employee_expenses: 5_000 + positive_agi: 100_000 al_agi: 90_000 state_code: AL output: - # Federal deduction 5,000 - 2,000 = 3,000; Alabama 5,000 - 1,800. + # Federal 5,000 - 0.02 * 100,000 = 3,000; Alabama 5,000 - 0.02 * 90,000. misc_deduction: 3_000 al_misc_deduction: 3_200 + +- name: Case 5, 2025 Alabama keeps the deduction the federal return suspends. + period: 2025 + input: + unreimbursed_business_employee_expenses: 5_000 + tax_preparation_fees: 1_000 + al_agi: 50_000 + state_code: AL + output: + misc_deduction: 0 + # 5,000 + 1,000 - 0.02 * 50,000 + al_misc_deduction: 5_000 + +- name: Case 6, 2025 expenses equal to the floor yield no deduction. + period: 2025 + input: + unreimbursed_business_employee_expenses: 1_000 + al_agi: 50_000 + state_code: AL + output: + # max(1,000 - 0.02 * 50,000, 0) + al_misc_deduction: 0 + +- name: Case 7, 2025 Alabama adds both spouses' expenses before one floor. + period: 2025 + input: + people: + head: + unreimbursed_business_employee_expenses: 1_500 + spouse: + tax_preparation_fees: 700 + tax_units: + tax_unit: + members: [head, spouse] + al_agi: 60_000 + households: + household: + members: [head, spouse] + state_code: AL + output: + # 1,500 + 700 - 0.02 * 60,000 + al_misc_deduction: 1_000 + +- name: Case 8, 2025 Alabama reads its own expense list, not the federal total. + period: 2025 + input: + total_misc_deductions: 5_000 + al_agi: 50_000 + state_code: AL + output: + al_misc_deduction: 0 diff --git a/policyengine_us/tests/unit/test_al_misc_deduction_properties.py b/policyengine_us/tests/unit/test_al_misc_deduction_properties.py new file mode 100644 index 00000000000..1d5a0d2e555 --- /dev/null +++ b/policyengine_us/tests/unit/test_al_misc_deduction_properties.py @@ -0,0 +1,223 @@ +"""Properties of the Alabama miscellaneous itemized deduction. + +Ala. Code 40-18-15(a)(23) allows unreimbursed employee business expenses and +the subdivision (14) deduction only above 2% of Alabama AGI. Alabama applies +that one floor to the expenses themselves, whether or not the federal +deduction is suspended. For every generated tax unit (one or two filers): + +1. The deduction is never negative, never exceeds the unit's Alabama + expenses, and is at least the expenses less 2% of Alabama AGI. +2. It does not fall when expenses rise and does not rise when Alabama AGI + rises. +3. It is the same in 2017 and 2025: the federal suspension from 2018 + (26 U.S.C. 67(g)) does not reach it. +4. In 2017, when Alabama AGI equals federal AGI, it equals the federal + miscellaneous deduction, since each applies one 2% floor to the same + expenses. +5. A federal total supplied directly, with no Alabama expenses, yields no + Alabama deduction: Alabama reads its own list of expenses. + +Properties 3 and 4 fail whenever expenses exceed the floor if the deduction +starts from the federal misc_deduction (zero in 2025, already floored in +2017); the strategy draws expenses up to $40,000 against floors of at most +$4,000, so most generated units have a positive deduction, and +test_strategy_reaches_positive_deductions checks that the worked cases do. +""" + +import hypothesis +import hypothesis.strategies as st +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEARS = (2017, 2025) +FLOOR = 0.02 +EXTRA_EXPENSE = 1_000 +EXTRA_AGI = 10_000 +ARRANGEMENTS = ("base", "more_expenses", "more_agi") +TOLERANCE = 0.01 + + +def unit_inputs(case, arrangement): + head = { + "unreimbursed_business_employee_expenses": case["head_employee"], + "tax_preparation_fees": case["head_prep"], + } + spouse = { + "unreimbursed_business_employee_expenses": case["spouse_employee"], + "tax_preparation_fees": case["spouse_prep"], + } + agi = case["al_agi"] + if arrangement == "more_expenses": + head["unreimbursed_business_employee_expenses"] += EXTRA_EXPENSE + elif arrangement == "more_agi": + agi += EXTRA_AGI + elif arrangement == "federal_only": + head = {name: 0 for name in head} + spouse = {name: 0 for name in spouse} + return head, spouse, agi + + +def build_situation(cases, arrangements): + # A variable supplied for any tax unit is an input for every tax unit, so + # the federal-only units, which supply total_misc_deductions, get their + # own simulation. + people, tax_units, households = {}, {}, {} + for i, case in enumerate(cases): + for arrangement in arrangements: + key = f"{arrangement}_{i}" + head_inputs, spouse_inputs, agi = unit_inputs(case, arrangement) + members = [f"head_{key}"] + people[f"head_{key}"] = { + name: {year: value for year in YEARS} + for name, value in head_inputs.items() + } + people[f"head_{key}"]["age"] = {year: 40 for year in YEARS} + if case["couple"]: + members.append(f"spouse_{key}") + people[f"spouse_{key}"] = { + name: {year: value for year in YEARS} + for name, value in spouse_inputs.items() + } + people[f"spouse_{key}"]["age"] = {year: 40 for year in YEARS} + tax_unit = { + "members": members, + "al_agi": {year: agi for year in YEARS}, + "positive_agi": {year: agi for year in YEARS}, + } + if arrangement == "federal_only": + expenses = case["head_employee"] + case["head_prep"] + tax_unit["total_misc_deductions"] = {year: expenses for year in YEARS} + tax_units[f"tax_unit_{key}"] = tax_unit + households[f"household_{key}"] = { + "members": members, + "state_code": {year: "AL" for year in YEARS}, + } + return {"people": people, "tax_units": tax_units, "households": households} + + +def calculate(cases): + simulation = Simulation(situation=build_situation(cases, ARRANGEMENTS)) + shape = (len(cases), len(ARRANGEMENTS)) + al = { + year: np.asarray(simulation.calculate("al_misc_deduction", year)).reshape(shape) + for year in YEARS + } + federal_2017 = np.asarray(simulation.calculate("misc_deduction", 2017)).reshape( + shape + ) + federal_only = Simulation(situation=build_situation(cases, ("federal_only",))) + al_federal_only = { + year: np.asarray(federal_only.calculate("al_misc_deduction", year)) + for year in YEARS + } + return al, federal_2017, al_federal_only + + +def expenses_of(case, arrangement): + head, spouse, _ = unit_inputs(case, arrangement) + total = sum(head.values()) + if case["couple"]: + total += sum(spouse.values()) + return total + + +def assert_properties(cases): + al, federal_2017, al_federal_only = calculate(cases) + for i, case in enumerate(cases): + for j, arrangement in enumerate(ARRANGEMENTS): + expenses = expenses_of(case, arrangement) + _, _, agi = unit_inputs(case, arrangement) + for year in YEARS: + value = al[year][i, j] + assert value >= 0, (case, arrangement, year) + assert value <= expenses + TOLERANCE, (case, arrangement, year) + assert value >= expenses - FLOOR * agi - TOLERANCE, ( + case, + arrangement, + year, + ) + # The federal suspension does not reach the Alabama deduction. + assert al[2017][i, j] == pytest.approx(al[2025][i, j], abs=TOLERANCE), ( + case, + arrangement, + ) + base, more_expenses, more_agi = range(len(ARRANGEMENTS)) + for year in YEARS: + assert al[year][i, more_expenses] >= al[year][i, base] - TOLERANCE, case + assert al[year][i, more_agi] <= al[year][i, base] + TOLERANCE, case + assert al_federal_only[year][i] == 0, case + # One floor each on the same expenses and the same AGI. + for j in range(len(ARRANGEMENTS)): + assert al[2017][i, j] == pytest.approx(federal_2017[i, j], abs=TOLERANCE), ( + case, + ARRANGEMENTS[j], + ) + + +WORKED_CASES = [ + # 5,000 - 0.02 * 50,000 = 4,000 in both years. + { + "couple": False, + "head_employee": 5_000, + "head_prep": 0, + "spouse_employee": 0, + "spouse_prep": 0, + "al_agi": 50_000, + }, + # 1,500 + 700 - 0.02 * 60,000 = 1,000. + { + "couple": True, + "head_employee": 1_500, + "head_prep": 0, + "spouse_employee": 0, + "spouse_prep": 700, + "al_agi": 60_000, + }, + # Expenses equal to the floor: no deduction. + { + "couple": False, + "head_employee": 1_000, + "head_prep": 0, + "spouse_employee": 0, + "spouse_prep": 0, + "al_agi": 50_000, + }, +] + + +def test_strategy_reaches_positive_deductions(): + al, federal_2017, _ = calculate(WORKED_CASES) + assert federal_2017[:, 0] == pytest.approx([4_000, 1_000, 0], abs=TOLERANCE) + assert al[2025][:, 0] == pytest.approx([4_000, 1_000, 0], abs=TOLERANCE) + assert al[2017][:, 0] == pytest.approx([4_000, 1_000, 0], abs=TOLERANCE) + assert_properties(WORKED_CASES) + + +AMOUNTS = st.integers(min_value=0, max_value=10_000) + + +@hypothesis.settings( + max_examples=10, + deadline=None, + suppress_health_check=[hypothesis.HealthCheck.too_slow], +) +@hypothesis.given( + st.lists( + st.fixed_dictionaries( + { + "couple": st.booleans(), + "head_employee": AMOUNTS, + "head_prep": AMOUNTS, + "spouse_employee": AMOUNTS, + "spouse_prep": AMOUNTS, + "al_agi": st.integers(min_value=0, max_value=200_000), + } + ), + min_size=1, + max_size=8, + ) +) +def test_al_misc_deduction_properties(cases): + assert_properties(cases) diff --git a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py index bc1b6369e94..f07183340df 100644 --- a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py +++ b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py @@ -9,20 +9,26 @@ class al_misc_deduction(Variable): definition_period = YEAR reference = ( "https://law.justia.com/codes/alabama/2022/title-40/chapter-18/article-1/section-40-18-15/", # Code of Alabama Section 40-18-15, h(23) + "https://alison.legislature.state.al.us/files/pdf/SearchableInstruments/2026RS/HB527-enr.pdf#page=7", # Act 2026-604, Section 1 "https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2023/01/22f40schabdc_blk.pdf#page=1", # 2022 Schedule A (Form 1040) "https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2022/06/21f40schabdc_blk.pdf#page=1", # 2021 Schedule A (Form 1040) # 2024 Form 40 instructions, Schedule A lines 20 to 24. "https://www.revenue.alabama.gov/wp-content/uploads/2025/01/24f40bk.pdf#page=20", + # 2025 Form 40 instructions, Schedule A lines 20 to 24. + "https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=21", ) defined_for = StateCode.AL def formula(tax_unit, period, parameters): - # Ala. Code 40-18-15(a)(23) allows unreimbursed employee business - # expenses and section 212 expenses above 2% of Alabama AGI. Alabama - # kept this deduction after the federal suspension (26 U.S.C. 67(g)), - # so start from the expenses, not the federal misc_deduction, which is - # zero from 2018 and already net of the federal 2% floor. - misc_expense = tax_unit("total_misc_deductions", period) + # Ala. Code 40-18-15(a)(23) allows the subdivision (1) unreimbursed + # employee business expenses and the subdivision (14) deduction only + # above 2% of Alabama AGI. Alabama applies this floor to the expenses + # themselves, in every year, so the formula does not start from the + # federal misc_deduction: that is already net of the federal 2% floor + # and is zero once 26 U.S.C. 67(g) suspends it from 2018. Alabama keeps + # its own list of expenses so that federal categories added later do + # not flow in without a reading of the Alabama statute. p = parameters(period).gov.states.al.tax.income.deductions.itemized + expenses = add(tax_unit, period, p.misc_sources) misc_floor = p.work_related_expense_rate * tax_unit("al_agi", period) - return max_(0, misc_expense - misc_floor) + return max_(0, expenses - misc_floor) From 22c7f45d80c8212eb3b1d9d681c2b28be634aec9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 23:46:11 +0000 Subject: [PATCH 3/5] Leave dependents' expenses off Alabama's miscellaneous deduction al_agi sums only the head and spouse, since a tax unit dependent's items belong on the dependent's own Alabama return; the expense sum now does the same. The property test draws federal AGI independently, so a floor at federal AGI fails it, and the dependent invariants test now draws both expenses and checks the deduction is unchanged by them. Co-Authored-By: Claude Opus 5.5 --- .../al-misc-deduction-own-floor.fixed.md | 2 +- .../core/test_al_agi_dependent_invariants.py | 18 ++++++++--- .../itemized/al_misc_deduction.yaml | 23 ++++++++++++++ .../unit/test_al_misc_deduction_properties.py | 30 +++++++++++++++---- .../deductions/itemized/al_misc_deduction.py | 6 ++-- 5 files changed, 66 insertions(+), 13 deletions(-) diff --git a/changelog.d/al-misc-deduction-own-floor.fixed.md b/changelog.d/al-misc-deduction-own-floor.fixed.md index 59f6fa0779d..501169035c9 100644 --- a/changelog.d/al-misc-deduction-own-floor.fixed.md +++ b/changelog.d/al-misc-deduction-own-floor.fixed.md @@ -1 +1 @@ -Compute Alabama's miscellaneous itemized deduction from its own list of expenses (unreimbursed employee business expenses and tax preparation fees) above 2% of Alabama AGI, from 2017, instead of applying a second floor to the federal deduction, which is zero from 2018. +Compute Alabama's miscellaneous itemized deduction from its own list of expenses (unreimbursed employee business expenses and tax preparation fees) above 2% of Alabama AGI in every modeled year, counting only the head's and spouse's expenses, instead of applying a second floor to the federal deduction, which is zero from 2018. diff --git a/policyengine_us/tests/core/test_al_agi_dependent_invariants.py b/policyengine_us/tests/core/test_al_agi_dependent_invariants.py index 4715b562e27..cf2a7cd94f4 100644 --- a/policyengine_us/tests/core/test_al_agi_dependent_invariants.py +++ b/policyengine_us/tests/core/test_al_agi_dependent_invariants.py @@ -14,8 +14,9 @@ with the dependents' income and deduction inputs as drawn and with them set to zero. For every tax unit: -1. The dependents' inputs never change Alabama AGI, the standard deduction - or the personal and dependent exemptions. Alabama income tax is unchanged +1. The dependents' inputs never change Alabama AGI, the standard deduction, + the personal and dependent exemptions or the miscellaneous itemized + deduction. Alabama income tax is unchanged too wherever the itemized deductions and the federal income tax deduction are. Two leaks outside AGI remain, and are left out here: Alabama itemized deductions still count a dependent's FICA and self-employment @@ -57,24 +58,29 @@ "rental_income", "long_term_capital_gains", "taxable_public_pension_income", + # Alabama miscellaneous itemized deduction sources. + "unreimbursed_business_employee_expenses", + "tax_preparation_fees", ] UNIT_OUTPUTS = [ "al_agi", "al_standard_deduction", "al_personal_exemption", "al_dependent_exemption", + "al_misc_deduction", "al_itemized_deductions", "al_federal_income_tax_deduction", "al_income_tax", "tax_unit_dependents", ] -# Outputs that depend on Alabama AGI but not on federal tax or itemized -# deductions. +# Outputs that depend on Alabama AGI but not on federal tax, and on no +# itemized deduction that still leaks a dependent's items (#9941). INVARIANT_OUTPUTS = [ "al_agi", "al_standard_deduction", "al_personal_exemption", "al_dependent_exemption", + "al_misc_deduction", ] amount = st.integers(1, 40_000).map(float) @@ -110,6 +116,8 @@ def person_amounts(draw): # A public pension is both in pension income and subtracted from # Alabama gross income. "taxable_public_pension_income": public_pension, + "unreimbursed_business_employee_expenses": draw(small), + "tax_preparation_fees": draw(st.integers(0, 1_000).map(float)), } @@ -156,6 +164,8 @@ def some(low, high, p=0.5): "rental_income": some(-5_000, 20_000, 0.3), "long_term_capital_gains": some(1, 5_000, 0.3), "taxable_public_pension_income": public_pension, + "unreimbursed_business_employee_expenses": some(1, 5_000, 0.3), + "tax_preparation_fees": some(1, 1_000, 0.3), } diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml index 6f7aaaa1f6f..81429410400 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml @@ -100,3 +100,26 @@ state_code: AL output: al_misc_deduction: 0 + +- name: Case 9, 2025 a tax unit dependent's expenses stay off the filer's Alabama return. + period: 2025 + input: + people: + head: + unreimbursed_business_employee_expenses: 1_500 + child: + age: 17 + is_tax_unit_dependent: true + unreimbursed_business_employee_expenses: 2_000 + tax_units: + tax_unit: + members: [head, child] + al_agi: 50_000 + households: + household: + members: [head, child] + state_code: AL + output: + # The child's 2,000 belongs on the child's own return. + # 1,500 - 0.02 * 50,000 + al_misc_deduction: 500 diff --git a/policyengine_us/tests/unit/test_al_misc_deduction_properties.py b/policyengine_us/tests/unit/test_al_misc_deduction_properties.py index 1d5a0d2e555..fe6d4907655 100644 --- a/policyengine_us/tests/unit/test_al_misc_deduction_properties.py +++ b/policyengine_us/tests/unit/test_al_misc_deduction_properties.py @@ -6,14 +6,16 @@ deduction is suspended. For every generated tax unit (one or two filers): 1. The deduction is never negative, never exceeds the unit's Alabama - expenses, and is at least the expenses less 2% of Alabama AGI. + expenses, and is at least the expenses less 2% of Alabama AGI. Federal + AGI is drawn independently of Alabama AGI, so a floor at federal AGI + breaks these bounds whenever the two differ. 2. It does not fall when expenses rise and does not rise when Alabama AGI rises. 3. It is the same in 2017 and 2025: the federal suspension from 2018 (26 U.S.C. 67(g)) does not reach it. 4. In 2017, when Alabama AGI equals federal AGI, it equals the federal - miscellaneous deduction, since each applies one 2% floor to the same - expenses. + miscellaneous deduction for units whose only miscellaneous expenses are + on both lists, since each then applies one 2% floor to the same expenses. 5. A federal total supplied directly, with no Alabama expenses, yields no Alabama deduction: Alabama reads its own list of expenses. @@ -39,6 +41,13 @@ TOLERANCE = 0.01 +def federal_agi_of(case): + # None draws federal AGI equal to the base Alabama AGI. + if case["federal_agi"] is None: + return case["al_agi"] + return case["federal_agi"] + + def unit_inputs(case, arrangement): head = { "unreimbursed_business_employee_expenses": case["head_employee"], @@ -84,7 +93,7 @@ def build_situation(cases, arrangements): tax_unit = { "members": members, "al_agi": {year: agi for year in YEARS}, - "positive_agi": {year: agi for year in YEARS}, + "positive_agi": {year: federal_agi_of(case) for year in YEARS}, } if arrangement == "federal_only": expenses = case["head_employee"] + case["head_prep"] @@ -148,8 +157,11 @@ def assert_properties(cases): assert al[year][i, more_expenses] >= al[year][i, base] - TOLERANCE, case assert al[year][i, more_agi] <= al[year][i, base] + TOLERANCE, case assert al_federal_only[year][i] == 0, case - # One floor each on the same expenses and the same AGI. - for j in range(len(ARRANGEMENTS)): + # One floor each on the same expenses, where the two AGIs agree. + for j, arrangement in enumerate(ARRANGEMENTS): + _, _, agi = unit_inputs(case, arrangement) + if agi != federal_agi_of(case): + continue assert al[2017][i, j] == pytest.approx(federal_2017[i, j], abs=TOLERANCE), ( case, ARRANGEMENTS[j], @@ -165,6 +177,7 @@ def assert_properties(cases): "spouse_employee": 0, "spouse_prep": 0, "al_agi": 50_000, + "federal_agi": 50_000, }, # 1,500 + 700 - 0.02 * 60,000 = 1,000. { @@ -174,6 +187,7 @@ def assert_properties(cases): "spouse_employee": 0, "spouse_prep": 700, "al_agi": 60_000, + "federal_agi": 60_000, }, # Expenses equal to the floor: no deduction. { @@ -183,6 +197,7 @@ def assert_properties(cases): "spouse_employee": 0, "spouse_prep": 0, "al_agi": 50_000, + "federal_agi": 50_000, }, ] @@ -213,6 +228,9 @@ def test_strategy_reaches_positive_deductions(): "spouse_employee": AMOUNTS, "spouse_prep": AMOUNTS, "al_agi": st.integers(min_value=0, max_value=200_000), + "federal_agi": st.one_of( + st.none(), st.integers(min_value=0, max_value=200_000) + ), } ), min_size=1, diff --git a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py index f07183340df..4c63a580a97 100644 --- a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py +++ b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py @@ -27,8 +27,10 @@ def formula(tax_unit, period, parameters): # federal misc_deduction: that is already net of the federal 2% floor # and is zero once 26 U.S.C. 67(g) suspends it from 2018. Alabama keeps # its own list of expenses so that federal categories added later do - # not flow in without a reading of the Alabama statute. + # not flow in without a reading of the Alabama statute. Like al_agi, + # it sums the head's and spouse's expenses only: a tax unit + # dependent's expenses belong on the dependent's own Alabama return. p = parameters(period).gov.states.al.tax.income.deductions.itemized - expenses = add(tax_unit, period, p.misc_sources) + expenses = tax_unit_non_dep_add(tax_unit, period, p.misc_sources) misc_floor = p.work_related_expense_rate * tax_unit("al_agi", period) return max_(0, expenses - misc_floor) From 1fca67a67b9fe1f38bb84d695a69d47d5bef1d5f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 07:20:12 +0000 Subject: [PATCH 4/5] Cite the (a)(14) clause and follow the page-list rule in AL misc references Names the 26 U.S.C. 212(3) clause of subdivision (14) that covers tax preparation fees, gives multi-page references a first-page href and a '#page' title, adds the 2017 Form 40 booklet that backs the 2017 start dates, and relabels the variable and rate for the (1) plus (14) total. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6H28mCRxTXCRraPQpH --- .../tax/income/deductions/itemized/misc_sources.yaml | 10 ++++++---- .../deductions/itemized/work_related_expense_rate.yaml | 10 ++++++---- .../income/deductions/itemized/al_misc_deduction.py | 9 +++++---- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/misc_sources.yaml b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/misc_sources.yaml index 1d44a14c257..b334c572882 100644 --- a/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/misc_sources.yaml +++ b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/misc_sources.yaml @@ -3,7 +3,7 @@ values: 2017-01-01: # Section (a)(1): unreimbursed employee business expenses. - unreimbursed_business_employee_expenses - # Section (a)(14) deduction. + # Section (a)(14): expenses in connection with the determination, collection, or refund of any tax (26 U.S.C. 212(3)). - tax_preparation_fees metadata: unit: list @@ -12,9 +12,11 @@ metadata: reference: - title: Code of Alabama Section 40-18-15(a)(1), (14), (23) (2017 edition) href: https://law.justia.com/codes/alabama/2017/title-40/chapter-18/article-1/section-40-18-15/ - - title: Act 2026-604, Section 1, amending Code of Alabama Section 40-18-15 - href: https://alison.legislature.state.al.us/files/pdf/SearchableInstruments/2026RS/HB527-enr.pdf#page=7 - - title: 2024 Alabama Form 40 instructions, Schedule A lines 20-24 + - title: "Act 2026-604, Section 1, amending Code of Alabama Section 40-18-15 #page 2,6-8" + href: https://alison.legislature.state.al.us/files/pdf/SearchableInstruments/2026RS/HB527-enr.pdf#page=2 + - title: "2017 Alabama Form 40 instructions, Schedule A lines 20-24 #page 20-21" + href: https://www.revenue.alabama.gov/wp-content/uploads/2021/10/17f40bk.pdf#page=20 + - title: "2024 Alabama Form 40 instructions, Schedule A lines 20-24 #page 20-21" href: https://www.revenue.alabama.gov/wp-content/uploads/2025/01/24f40bk.pdf#page=20 - title: 2025 Alabama Form 40 instructions, Schedule A lines 20-24 href: https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=21 diff --git a/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/work_related_expense_rate.yaml b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/work_related_expense_rate.yaml index 5b47b77c3f3..4fd35adee44 100644 --- a/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/work_related_expense_rate.yaml +++ b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/work_related_expense_rate.yaml @@ -1,9 +1,9 @@ -description: Alabama deducts work-related expense over this percentage of adjusted gross income. +description: Alabama allows the total of the subdivision (1) unreimbursed employee business expenses and the subdivision (14) expenses only above this percentage of adjusted gross income. values: 2017-01-01: 0.02 metadata: - label: Alabama work-related expense deduction rate + label: Alabama miscellaneous itemized deduction floor rate period: year unit: /1 reference: @@ -11,7 +11,7 @@ metadata: href: https://law.justia.com/codes/alabama/2017/title-40/chapter-18/article-1/section-40-18-15/ - title: Code of Alabama Section 40-18-15(a)(23) (2021 edition) href: https://law.justia.com/codes/alabama/2021/title-40/chapter-18/article-1/section-40-18-15/ - - title: Act 2026-604, Section 1, amending Code of Alabama Section 40-18-15 + - title: "Act 2026-604, Section 1, amending Code of Alabama Section 40-18-15 #page 7-8" href: https://alison.legislature.state.al.us/files/pdf/SearchableInstruments/2026RS/HB527-enr.pdf#page=7 - title: 2021 Schedule A (Form 1040) href: https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2022/06/21f40schabdc_blk.pdf#page=1 @@ -21,7 +21,9 @@ metadata: href: https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2024/01/23f40schabdc_blk.pdf#page=1 - title: 2024 Schedule A (Form 1040) href: https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2025/01/24f40schabdc_blk.pdf#page=20 - - title: 2024 Alabama Form 40 instructions, Schedule A lines 20-24 + - title: "2017 Alabama Form 40 instructions, Schedule A lines 20-24 #page 20-21" + href: https://www.revenue.alabama.gov/wp-content/uploads/2021/10/17f40bk.pdf#page=20 + - title: "2024 Alabama Form 40 instructions, Schedule A lines 20-24 #page 20-21" href: https://www.revenue.alabama.gov/wp-content/uploads/2025/01/24f40bk.pdf#page=20 - title: 2025 Alabama Form 40 instructions, Schedule A lines 20-24 href: https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=21 diff --git a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py index 4c63a580a97..e063778f165 100644 --- a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py +++ b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.py @@ -4,15 +4,16 @@ class al_misc_deduction(Variable): value_type = float entity = TaxUnit - label = "Alabama Work Related Expense" + label = "Alabama miscellaneous itemized deduction" unit = USD definition_period = YEAR reference = ( - "https://law.justia.com/codes/alabama/2022/title-40/chapter-18/article-1/section-40-18-15/", # Code of Alabama Section 40-18-15, h(23) - "https://alison.legislature.state.al.us/files/pdf/SearchableInstruments/2026RS/HB527-enr.pdf#page=7", # Act 2026-604, Section 1 + "https://law.justia.com/codes/alabama/2022/title-40/chapter-18/article-1/section-40-18-15/", # Code of Alabama Section 40-18-15(a)(23) + # Act 2026-604, Section 1. PDF pages 7-8 + "https://alison.legislature.state.al.us/files/pdf/SearchableInstruments/2026RS/HB527-enr.pdf#page=7", "https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2023/01/22f40schabdc_blk.pdf#page=1", # 2022 Schedule A (Form 1040) "https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2022/06/21f40schabdc_blk.pdf#page=1", # 2021 Schedule A (Form 1040) - # 2024 Form 40 instructions, Schedule A lines 20 to 24. + # 2024 Form 40 instructions, Schedule A lines 20 to 24. PDF pages 20-21 "https://www.revenue.alabama.gov/wp-content/uploads/2025/01/24f40bk.pdf#page=20", # 2025 Form 40 instructions, Schedule A lines 20 to 24. "https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=21", From a7d8fc46fa8fb9946077139136ec82c185ffccfd Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 07:20:12 +0000 Subject: [PATCH 5/5] Tighten the AL misc property docstring and pin the Alabama AGI base The docstring now limits the bounds to nonnegative Alabama AGI, states which bound a federal-AGI floor breaks and which property a federal-start formula hits first, and gives the ranges the strategy actually draws. The worked-cases test is renamed for what it does. Case 10 puts federal AGI below Alabama AGI, so a floor at the smaller of the two AGIs fails. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6H28mCRxTXCRraPQpH --- .../itemized/al_misc_deduction.yaml | 13 +++++++++ .../unit/test_al_misc_deduction_properties.py | 28 ++++++++++++------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml index 81429410400..dd94d1e9f53 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_misc_deduction.yaml @@ -123,3 +123,16 @@ # The child's 2,000 belongs on the child's own return. # 1,500 - 0.02 * 50,000 al_misc_deduction: 500 + +- name: Case 10, 2017 Alabama floors on Alabama AGI even when federal AGI is lower. + period: 2017 + input: + unreimbursed_business_employee_expenses: 5_000 + positive_agi: 40_000 + al_agi: 50_000 + state_code: AL + output: + # Federal: 5,000 - 0.02 * 40,000 + misc_deduction: 4_200 + # Alabama: 5,000 - 0.02 * 50,000 + al_misc_deduction: 4_000 diff --git a/policyengine_us/tests/unit/test_al_misc_deduction_properties.py b/policyengine_us/tests/unit/test_al_misc_deduction_properties.py index fe6d4907655..70ad16768c5 100644 --- a/policyengine_us/tests/unit/test_al_misc_deduction_properties.py +++ b/policyengine_us/tests/unit/test_al_misc_deduction_properties.py @@ -5,10 +5,11 @@ that one floor to the expenses themselves, whether or not the federal deduction is suspended. For every generated tax unit (one or two filers): -1. The deduction is never negative, never exceeds the unit's Alabama - expenses, and is at least the expenses less 2% of Alabama AGI. Federal - AGI is drawn independently of Alabama AGI, so a floor at federal AGI - breaks these bounds whenever the two differ. +1. For Alabama AGI of zero or more, the deduction is never negative, never + exceeds the unit's Alabama expenses, and is at least the expenses less 2% + of Alabama AGI. Federal AGI is drawn independently of Alabama AGI, so a + floor at federal AGI breaks the lower bound whenever federal AGI exceeds + Alabama AGI and the expenses exceed 2% of Alabama AGI. 2. It does not fall when expenses rise and does not rise when Alabama AGI rises. 3. It is the same in 2017 and 2025: the federal suspension from 2018 @@ -19,11 +20,18 @@ 5. A federal total supplied directly, with no Alabama expenses, yields no Alabama deduction: Alabama reads its own list of expenses. -Properties 3 and 4 fail whenever expenses exceed the floor if the deduction -starts from the federal misc_deduction (zero in 2025, already floored in -2017); the strategy draws expenses up to $40,000 against floors of at most -$4,000, so most generated units have a positive deduction, and -test_strategy_reaches_positive_deductions checks that the worked cases do. +A deduction that starts from the federal misc_deduction fails property 1 in +2025 whenever the expenses exceed the Alabama floor, because the federal +deduction is zero. In 2017 it also fails property 4 wherever the two AGIs +agree and the expenses exceed the floor, and property 3 wherever the expenses +exceed the two floors combined. + +The strategy draws Alabama AGI from 0 to $200,000, so floors of up to $4,000 +($4,200 in the arrangement with $10,000 more AGI). It draws expenses of up to +$20,000 for single filers ($21,000 with the extra $1,000) and up to $40,000 +for couples ($41,000), so most generated units have a positive deduction. +test_worked_cases checks the exact values of three worked cases and runs the +properties on them; it does not draw from the strategy. """ import hypothesis @@ -202,7 +210,7 @@ def assert_properties(cases): ] -def test_strategy_reaches_positive_deductions(): +def test_worked_cases(): al, federal_2017, _ = calculate(WORKED_CASES) assert federal_2017[:, 0] == pytest.approx([4_000, 1_000, 0], abs=TOLERANCE) assert al[2025][:, 0] == pytest.approx([4_000, 1_000, 0], abs=TOLERANCE)