From 86dbece26bef41688150392a89b1588b3b7f954d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 26 Sep 2026 09:14:54 -0400 Subject: [PATCH 1/4] Count gambling winnings in Arkansas gross income and add the Arkansas gambling loss deduction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arkansas taxes gambling winnings as other income (Ark. Code § 26-51-404(a)(1)(E); DFA Form AR-OI line 4, carried to AR1000F Line 20 through 2017 and Line 22 from 2018). Ark. Code § 26-51-424(a)(2)(B), as amended by Act 155 of 2017, § 21, makes gambling losses deductible to the extent of gambling winnings and not subject to the 2% floor on miscellaneous itemized deductions, for tax years from 2015 (§ 25); DFA Subject 208 has stated the same rule since at least 2009. policyengine-us had neither: gambling_winnings was missing from the Arkansas gross income sources, and the Arkansas itemized sources had no gambling loss deduction. - Add gambling_winnings to ar gross income sources (joint and individual). - Add ar_gambling_loss_deduction = min(losses, winnings) over the tax unit and add it to both itemized source lists, outside ar_misc_deduction_*. - YAML tests for 2014, 2015, 2019, 2020 and 2025, and grid property tests for the cap, bounds, monotonicity, year and AGI independence, agreement with the federal pre-2026 wagering rule, and the effect on Arkansas taxable income. Co-Authored-By: Claude Opus 5.5 --- .../fix-ar-gambling-loss-deduction.fixed.md | 1 + .../itemized/sources/individual.yaml | 20 +++ .../deductions/itemized/sources/joint.yaml | 20 +++ .../gross_income/sources/individual.yaml | 17 ++ .../income/gross_income/sources/joint.yaml | 17 ++ .../ar_gambling_winnings_and_losses.yaml | 128 +++++++++++++++ .../itemized/ar_gambling_loss_deduction.yaml | 94 +++++++++++ .../ar_itemized_deductions_indiv.yaml | 25 +++ .../ar_itemized_deductions_joint.yaml | 44 +++++ .../tests/unit/test_ar_gambling_properties.py | 153 ++++++++++++++++++ .../itemized/ar_gambling_loss_deduction.py | 31 ++++ 11 files changed, 550 insertions(+) create mode 100644 changelog.d/fix-ar-gambling-loss-deduction.fixed.md create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gambling_winnings_and_losses.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_gambling_loss_deduction.yaml create mode 100644 policyengine_us/tests/unit/test_ar_gambling_properties.py create mode 100644 policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_gambling_loss_deduction.py diff --git a/changelog.d/fix-ar-gambling-loss-deduction.fixed.md b/changelog.d/fix-ar-gambling-loss-deduction.fixed.md new file mode 100644 index 00000000000..7aff9b21f85 --- /dev/null +++ b/changelog.d/fix-ar-gambling-loss-deduction.fixed.md @@ -0,0 +1 @@ +Count gambling winnings in Arkansas gross income and deduct Arkansas gambling losses up to those winnings, outside the 2% floor on miscellaneous itemized deductions (Ark. Code § 26-51-424(a)(2)(B), Act 155 of 2017). diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/sources/individual.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/sources/individual.yaml index 2fe78fc8ce5..1988ff310e0 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/sources/individual.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/sources/individual.yaml @@ -4,6 +4,8 @@ values: - ar_post_secondary_education_tuition_deduction - ar_medical_expense_deduction_indiv - ar_misc_deduction_indiv + # Gambling losses are outside the 2% floor on miscellaneous deductions. + - ar_gambling_loss_deduction - real_estate_taxes - charitable_deduction - interest_deduction @@ -23,3 +25,21 @@ metadata: href: https://advance.lexis.com/documentpage/?pdmfid=1000516&crid=3e1a3c6b-c5c0-44fe-bf07-af695b4a53cc&nodeid=ABAAAFAACAAFABK&nodepath=%2FROOT%2FABA%2FABAAAF%2FABAAAFAAC%2FABAAAFAACAAF%2FABAAAFAACAAFABK&level=5&haschildren=&populated=false&title=26-51-437.+Miscellaneous+itemized+deductions+%E2%80%94+Definition.&config=00JAA2ZjZiM2VhNS0wNTVlLTQ3NzUtYjQzYy0yYWZmODJiODRmMDYKAFBvZENhdGFsb2fXiYCnsel0plIgqpYkw9PK&pddocfullpath=%2Fshared%2Fdocument%2Fstatutes-legislation%2Furn%3AcontentItem%3A4WVP-0VS0-R03J-S4P7-00008-00&ecomp=bgf5kkk&prid=0e852b49-2c9b-4c19-b9ae-e52267babbe1 - title: Code of Arkansas-26-51-423. Deductions — Expenses.(a).(2) Medical and Dental Expense href: https://advance.lexis.com/documentpage/?pdmfid=1000516&crid=0c84f906-26e9-4165-a0dd-735544d09e0b&nodeid=ABAAAFAACAAFAAY&nodepath=%2FROOT%2FABA%2FABAAAF%2FABAAAFAAC%2FABAAAFAACAAF%2FABAAAFAACAAFAAY&level=5&haschildren=&populated=false&title=26-51-423.+Deductions+%E2%80%94+Expenses.&config=00JAA2ZjZiM2VhNS0wNTVlLTQ3NzUtYjQzYy0yYWZmODJiODRmMDYKAFBvZENhdGFsb2fXiYCnsel0plIgqpYkw9PK&pddocfullpath=%2Fshared%2Fdocument%2Fstatutes-legislation%2Furn%3AcontentItem%3A5W1G-MPN0-R03J-W33K-00008-00&ecomp=bgf5kkk&prid=0e852b49-2c9b-4c19-b9ae-e52267babbe1 + - title: Arkansas Act 155 of 2017, § 21, amending Ark. Code § 26-51-424(a)(2)(B) (gambling losses are deductible to the extent of gambling winnings and not subject to the 2% limitation on miscellaneous itemized deductions) + href: https://www.arkleg.state.ar.us/Acts/FTPDocument?path=%2FACTS%2F2017R%2FPublic%2F&file=155.pdf&ddBienniumSession=2017%2F2017R#page=9 + - title: Arkansas Act 155 of 2017, § 25 (effective for tax years beginning on and after January 1, 2015) + href: https://www.arkleg.state.ar.us/Acts/FTPDocument?path=%2FACTS%2F2017R%2FPublic%2F&file=155.pdf&ddBienniumSession=2017%2F2017R#page=10 + - title: Arkansas DFA Subject 208, Gambling Income and Expenses (gambling losses up to gambling winnings, a miscellaneous deduction not subject to the 2% limit) + href: https://www.dfa.arkansas.gov/wp-content/uploads/208-GamblingIncomeExpenses.pdf#page=1 + - title: Arkansas DFA Subject 208, 2009 version (the same rule before Act 155 of 2017) + href: http://web.archive.org/web/20090815162830/http://www.arkansas.gov/dfa/income_tax/documents/208-GamblingIncomeExpenses.pdf#page=1 + - title: Arkansas 2025 Individual Income Tax Forms and Instructions (AR3 Line 27, gambling losses cannot exceed gambling winnings) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=22 + - title: Arkansas 2025 Form AR3 (Line 27, gambling losses, among the deductions not subject to the 2% AGI limitation) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR3_ItemizedDeduction.pdf#page=1 + - title: Arkansas 2020 Individual Income Tax Forms and Instructions (AR3 Line 27, gambling losses, first year with a separate line) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2020_AR1000F_and_AR1000NR_Instructions.pdf#page=21 + - title: Arkansas 2019 Individual Income Tax Forms and Instructions (AR3 Line 27, other miscellaneous deductions not subject to the 2% AGI limit) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2019_AR1000F_and_AR1000NR_Instructions.pdf#page=21 + - title: Arkansas 2015 Individual Income Tax Instructions (AR3 Line 27, other miscellaneous deductions not subject to the 2% AGI limit) + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=43 diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/sources/joint.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/sources/joint.yaml index c1148965070..b20db099981 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/sources/joint.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/deductions/itemized/sources/joint.yaml @@ -4,6 +4,8 @@ values: - ar_post_secondary_education_tuition_deduction - ar_medical_expense_deduction_joint - ar_misc_deduction_joint + # Gambling losses are outside the 2% floor on miscellaneous deductions. + - ar_gambling_loss_deduction - real_estate_taxes - charitable_deduction - interest_deduction @@ -23,3 +25,21 @@ metadata: href: https://advance.lexis.com/documentpage/?pdmfid=1000516&crid=3e1a3c6b-c5c0-44fe-bf07-af695b4a53cc&nodeid=ABAAAFAACAAFABK&nodepath=%2FROOT%2FABA%2FABAAAF%2FABAAAFAAC%2FABAAAFAACAAF%2FABAAAFAACAAFABK&level=5&haschildren=&populated=false&title=26-51-437.+Miscellaneous+itemized+deductions+%E2%80%94+Definition.&config=00JAA2ZjZiM2VhNS0wNTVlLTQ3NzUtYjQzYy0yYWZmODJiODRmMDYKAFBvZENhdGFsb2fXiYCnsel0plIgqpYkw9PK&pddocfullpath=%2Fshared%2Fdocument%2Fstatutes-legislation%2Furn%3AcontentItem%3A4WVP-0VS0-R03J-S4P7-00008-00&ecomp=bgf5kkk&prid=0e852b49-2c9b-4c19-b9ae-e52267babbe1 - title: Code of Arkansas-26-51-423. Deductions — Expenses.(a).(2) Medical and Dental Expense href: https://advance.lexis.com/documentpage/?pdmfid=1000516&crid=0c84f906-26e9-4165-a0dd-735544d09e0b&nodeid=ABAAAFAACAAFAAY&nodepath=%2FROOT%2FABA%2FABAAAF%2FABAAAFAAC%2FABAAAFAACAAF%2FABAAAFAACAAFAAY&level=5&haschildren=&populated=false&title=26-51-423.+Deductions+%E2%80%94+Expenses.&config=00JAA2ZjZiM2VhNS0wNTVlLTQ3NzUtYjQzYy0yYWZmODJiODRmMDYKAFBvZENhdGFsb2fXiYCnsel0plIgqpYkw9PK&pddocfullpath=%2Fshared%2Fdocument%2Fstatutes-legislation%2Furn%3AcontentItem%3A5W1G-MPN0-R03J-W33K-00008-00&ecomp=bgf5kkk&prid=0e852b49-2c9b-4c19-b9ae-e52267babbe1 + - title: Arkansas Act 155 of 2017, § 21, amending Ark. Code § 26-51-424(a)(2)(B) (gambling losses are deductible to the extent of gambling winnings and not subject to the 2% limitation on miscellaneous itemized deductions) + href: https://www.arkleg.state.ar.us/Acts/FTPDocument?path=%2FACTS%2F2017R%2FPublic%2F&file=155.pdf&ddBienniumSession=2017%2F2017R#page=9 + - title: Arkansas Act 155 of 2017, § 25 (effective for tax years beginning on and after January 1, 2015) + href: https://www.arkleg.state.ar.us/Acts/FTPDocument?path=%2FACTS%2F2017R%2FPublic%2F&file=155.pdf&ddBienniumSession=2017%2F2017R#page=10 + - title: Arkansas DFA Subject 208, Gambling Income and Expenses (gambling losses up to gambling winnings, a miscellaneous deduction not subject to the 2% limit) + href: https://www.dfa.arkansas.gov/wp-content/uploads/208-GamblingIncomeExpenses.pdf#page=1 + - title: Arkansas DFA Subject 208, 2009 version (the same rule before Act 155 of 2017) + href: http://web.archive.org/web/20090815162830/http://www.arkansas.gov/dfa/income_tax/documents/208-GamblingIncomeExpenses.pdf#page=1 + - title: Arkansas 2025 Individual Income Tax Forms and Instructions (AR3 Line 27, gambling losses cannot exceed gambling winnings) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=22 + - title: Arkansas 2025 Form AR3 (Line 27, gambling losses, among the deductions not subject to the 2% AGI limitation) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR3_ItemizedDeduction.pdf#page=1 + - title: Arkansas 2020 Individual Income Tax Forms and Instructions (AR3 Line 27, gambling losses, first year with a separate line) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2020_AR1000F_and_AR1000NR_Instructions.pdf#page=21 + - title: Arkansas 2019 Individual Income Tax Forms and Instructions (AR3 Line 27, other miscellaneous deductions not subject to the 2% AGI limit) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2019_AR1000F_and_AR1000NR_Instructions.pdf#page=21 + - title: Arkansas 2015 Individual Income Tax Instructions (AR3 Line 27, other miscellaneous deductions not subject to the 2% AGI limit) + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=43 diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml index fda2ae79246..6a87cdd2ef3 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml @@ -19,6 +19,11 @@ values: - rental_income # Line 19 - farm_rent_income # Line 20 - unemployment_compensation # Line 21 + # Winnings from Arkansas electronic games of skill are not income + # (Ark. Code § 26-51-1310(f), Act 732 of 2007); DFA applies this to + # winnings before July 1, 2019. The model has a single gambling_winnings + # input and does not separate them. + - gambling_winnings # Line 22, other income (Form AR-OI) 2020-01-01: - irs_employment_income # Line 8 - self_employment_income # Line 8 @@ -34,6 +39,7 @@ values: - disability_benefits # Line 18b - rental_income # Line 19 - farm_rent_income # Line 20 + - gambling_winnings # Line 22, other income (Form AR-OI) 2022-01-01: - irs_employment_income # Line 8 - self_employment_income # Line 8 @@ -54,6 +60,7 @@ values: - rental_income # Line 19 - farm_rent_income # Line 20 - unemployment_compensation # Line 21 + - gambling_winnings # Line 22, other income (Form AR-OI) metadata: unit: list @@ -62,6 +69,16 @@ metadata: reference: - title: Arkansas 2025 Individual Income Tax Forms and Instructions href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=13 + - title: Arkansas 2025 Individual Income Tax Forms and Instructions (gambling winnings are subject to tax, reported on Form AR-OI line 4 and carried to Line 22) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=14 + - title: Arkansas 2025 Form AR-OI (line 4, gambling winnings, an addition to income) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR-OI_OtherIncomeLoss_and_DepreciationDifferences.pdf#page=1 + - title: Arkansas 2019 Individual Income Tax Forms and Instructions (gambling winnings are subject to tax) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2019_AR1000F_and_AR1000NR_Instructions.pdf#page=13 + - title: Arkansas 2018 Individual Income Tax Instructions (gambling winnings of any type, except from Arkansas electronic games of skill; other income on Line 22) + href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=13 + - title: Arkansas 2015 Individual Income Tax Instructions (gambling winnings of any type, except from Arkansas electronic games of skill; other income on Line 20) + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=15 - title: Arkansas 2022 AR1000F Instructions href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=13 - title: 2022 Arkansas AR1000F diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml index c9631d11ac3..e10cd436d02 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml @@ -19,6 +19,11 @@ values: - rental_income # Line 19 - farm_rent_income # Line 20 - unemployment_compensation # Line 21 + # Winnings from Arkansas electronic games of skill are not income + # (Ark. Code § 26-51-1310(f), Act 732 of 2007); DFA applies this to + # winnings before July 1, 2019. The model has a single gambling_winnings + # input and does not separate them. + - gambling_winnings # Line 22, other income (Form AR-OI) 2020-01-01: - irs_employment_income # Line 8 - self_employment_income # Line 8 @@ -34,6 +39,7 @@ values: - disability_benefits # Line 18b - rental_income # Line 19 - farm_rent_income # Line 20 + - gambling_winnings # Line 22, other income (Form AR-OI) 2022-01-01: - irs_employment_income # Line 8 - self_employment_income # Line 8 @@ -54,6 +60,7 @@ values: - rental_income # Line 19 - farm_rent_income # Line 20 - unemployment_compensation # Line 21 + - gambling_winnings # Line 22, other income (Form AR-OI) metadata: unit: list @@ -62,6 +69,16 @@ metadata: reference: - title: Arkansas 2025 Individual Income Tax Forms and Instructions href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=13 + - title: Arkansas 2025 Individual Income Tax Forms and Instructions (gambling winnings are subject to tax, reported on Form AR-OI line 4 and carried to Line 22) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=14 + - title: Arkansas 2025 Form AR-OI (line 4, gambling winnings, an addition to income) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR-OI_OtherIncomeLoss_and_DepreciationDifferences.pdf#page=1 + - title: Arkansas 2019 Individual Income Tax Forms and Instructions (gambling winnings are subject to tax) + href: https://www.dfa.arkansas.gov/wp-content/uploads/2019_AR1000F_and_AR1000NR_Instructions.pdf#page=13 + - title: Arkansas 2018 Individual Income Tax Instructions (gambling winnings of any type, except from Arkansas electronic games of skill; other income on Line 22) + href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=13 + - title: Arkansas 2015 Individual Income Tax Instructions (gambling winnings of any type, except from Arkansas electronic games of skill; other income on Line 20) + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=15 - title: Arkansas 2022 AR1000F Instructions href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=13 - title: 2022 Arkansas AR1000F diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gambling_winnings_and_losses.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gambling_winnings_and_losses.yaml new file mode 100644 index 00000000000..2dc500e7140 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/ar_gambling_winnings_and_losses.yaml @@ -0,0 +1,128 @@ +# Gambling winnings are Arkansas gross income (reported as other income), and +# gambling losses are an itemized deduction up to those winnings, outside the +# 2% floor (Ark. Code § 26-51-424(a)(2)(B), Act 155 of 2017, from 2015). + +- name: 2015 gambling winnings are Arkansas gross income + period: 2015 + input: + irs_employment_income: 50_000 + gambling_winnings: 10_000 + state_code: AR + output: + ar_gross_income_joint: 60_000 + ar_gross_income_indiv: 60_000 + +- name: 2019 gambling winnings are Arkansas gross income + period: 2019 + input: + irs_employment_income: 50_000 + gambling_winnings: 10_000 + state_code: AR + output: + ar_gross_income_joint: 60_000 + ar_gross_income_indiv: 60_000 + +- name: 2020 gambling winnings are Arkansas gross income + period: 2020 + input: + irs_employment_income: 50_000 + gambling_winnings: 10_000 + state_code: AR + output: + ar_gross_income_joint: 60_000 + ar_gross_income_indiv: 60_000 + +- name: 2025 gambling winnings are Arkansas gross income + period: 2025 + input: + irs_employment_income: 50_000 + gambling_winnings: 10_000 + state_code: AR + output: + ar_gross_income_joint: 60_000 + ar_gross_income_indiv: 60_000 + +- name: A dependent's gambling winnings go to the head's Arkansas AGI + period: 2025 + input: + people: + head: + age: 45 + irs_employment_income: 50_000 + child: + age: 17 + gambling_winnings: 1_000 + tax_units: + tax_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: AR + output: + ar_gross_income_joint: [50_000, 1_000] + ar_agi_joint: [51_000, 0] + +- name: 2015 itemizer with losses below winnings is taxed on net winnings + period: 2015 + input: + irs_employment_income: 60_000 + gambling_winnings: 10_000 + gambling_losses: 8_000 + state_code: AR + output: + ar_agi_joint: 70_000 + ar_itemized_deductions_joint: 8_000 + ar_deduction_joint: 8_000 + ar_taxable_income_joint: 62_000 + +- name: 2019 itemizer with losses above winnings nets gambling to zero + period: 2019 + input: + irs_employment_income: 60_000 + gambling_winnings: 3_000 + gambling_losses: 10_000 + state_code: AR + output: + ar_agi_joint: 63_000 + ar_itemized_deductions_joint: 3_000 + ar_deduction_joint: 3_000 + ar_taxable_income_joint: 60_000 + +- name: 2019 losses below the standard deduction leave the winnings taxable + period: 2019 + input: + irs_employment_income: 60_000 + gambling_winnings: 1_000 + gambling_losses: 1_000 + state_code: AR + output: + ar_agi_joint: 61_000 + ar_itemized_deductions_joint: 1_000 + # The $2,200 standard deduction exceeds the $1,000 of losses. + ar_deduction_joint: 2_200 + ar_taxable_income_joint: 58_800 + +- name: 2020 itemizer with losses below winnings is taxed on net winnings + period: 2020 + input: + irs_employment_income: 60_000 + gambling_winnings: 10_000 + gambling_losses: 8_000 + state_code: AR + output: + ar_agi_joint: 70_000 + ar_itemized_deductions_joint: 8_000 + ar_taxable_income_joint: 62_000 + +- name: 2025 itemizer with losses below winnings is taxed on net winnings + period: 2025 + input: + irs_employment_income: 60_000 + gambling_winnings: 10_000 + gambling_losses: 8_000 + state_code: AR + output: + ar_agi_joint: 70_000 + ar_itemized_deductions_joint: 8_000 + ar_taxable_income_joint: 62_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_gambling_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_gambling_loss_deduction.yaml new file mode 100644 index 00000000000..d5dc7fc6163 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_gambling_loss_deduction.yaml @@ -0,0 +1,94 @@ +# DFA Subject 208 applied the same cap and 2% exemption before Act 155 of 2017 +# codified it from 2015, so the deduction is not limited to 2015 and later. +- name: 2014 gambling losses are deductible only to the extent of winnings + period: 2014 + input: + gambling_winnings: 4_000 + gambling_losses: 6_000 + state_code: AR + output: + ar_gambling_loss_deduction: 4_000 + +- name: 2015 gambling losses below winnings are deductible in full + period: 2015 + input: + gambling_winnings: 10_000 + gambling_losses: 8_000 + state_code: AR + output: + ar_gambling_loss_deduction: 8_000 + +- name: 2015 gambling losses are deductible only to the extent of winnings + period: 2015 + input: + gambling_winnings: 10_000 + gambling_losses: 12_000 + state_code: AR + output: + ar_gambling_loss_deduction: 10_000 + +- name: 2019 gambling losses without winnings are not deductible + period: 2019 + input: + gambling_winnings: 0 + gambling_losses: 5_000 + state_code: AR + output: + ar_gambling_loss_deduction: 0 + +- name: 2019 gambling losses are deductible only to the extent of winnings + period: 2019 + input: + gambling_winnings: 3_000 + gambling_losses: 4_500 + state_code: AR + output: + ar_gambling_loss_deduction: 3_000 + +- name: 2020 joint return caps combined losses at combined winnings + period: 2020 + input: + people: + person1: + gambling_winnings: 3_000 + gambling_losses: 1_000 + person2: + gambling_winnings: 0 + gambling_losses: 4_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: AR + output: + # min(1,000 + 4,000, 3,000 + 0) + ar_gambling_loss_deduction: 3_000 + +- name: 2025 gambling losses below winnings are deductible in full + period: 2025 + input: + gambling_winnings: 10_000 + gambling_losses: 8_000 + state_code: AR + output: + ar_gambling_loss_deduction: 8_000 + +- name: 2025 gambling losses equal to winnings are deductible in full + period: 2025 + input: + gambling_winnings: 7_500 + gambling_losses: 7_500 + state_code: AR + output: + ar_gambling_loss_deduction: 7_500 + +- name: No Arkansas gambling loss deduction outside Arkansas + period: 2025 + input: + gambling_winnings: 10_000 + gambling_losses: 8_000 + state_code: TX + output: + ar_gambling_loss_deduction: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.yaml index 5b9be691a8e..965971f0fdc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_indiv.yaml @@ -80,3 +80,28 @@ state_code: AR output: ar_itemized_deductions_indiv: [590, 410] + +- name: 2020 gambling loss deduction is prorated by AGI when married filing separately + period: 2020 + input: + people: + person1: + is_tax_unit_head: true + ar_agi_indiv: 60_000 + gambling_winnings: 10_000 + gambling_losses: 6_000 + person2: + is_tax_unit_head: false + ar_agi_indiv: 40_000 + gambling_losses: 4_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: AR + output: + # min(6,000 + 4,000, 10,000) split 60% / 40% + ar_gambling_loss_deduction: 10_000 + ar_itemized_deductions_indiv: [6_000, 4_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.yaml index 5e3132465bc..43cce786adb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_itemized_deductions_joint.yaml @@ -16,3 +16,47 @@ state_code: AR output: ar_itemized_deductions_joint: [1_000, 0] + +- name: 2019 gambling losses are not reduced by the 2% floor on miscellaneous deductions + period: 2019 + input: + people: + person1: + is_tax_unit_head: true + # 2% of this AGI (20,000) exceeds the losses, so a 2% floor would erase them. + ar_agi_joint: 1_000_000 + gambling_winnings: 20_000 + gambling_losses: 5_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: AR + output: + ar_misc_deduction_joint: 0 + ar_gambling_loss_deduction: 5_000 + ar_itemized_deductions_joint: 5_000 + +- name: 2025 gambling losses add to other itemized deductions, capped at winnings + period: 2025 + input: + people: + person1: + is_tax_unit_head: true + gambling_winnings: 4_000 + person2: + is_tax_unit_head: false + gambling_losses: 9_000 + tax_units: + tax_unit: + members: [person1, person2] + ar_medical_expense_deduction_joint: 1_000 + households: + household: + members: [person1, person2] + state_code: AR + output: + ar_gambling_loss_deduction: 4_000 + ar_itemized_deductions_joint: [5_000, 0] diff --git a/policyengine_us/tests/unit/test_ar_gambling_properties.py b/policyengine_us/tests/unit/test_ar_gambling_properties.py new file mode 100644 index 00000000000..5be6e44a3a9 --- /dev/null +++ b/policyengine_us/tests/unit/test_ar_gambling_properties.py @@ -0,0 +1,153 @@ +"""Properties of Arkansas's treatment of gambling winnings and losses. + +Ark. Code § 26-51-424(a)(2)(B), as amended by Act 155 of 2017, § 21, from +2015: gambling losses are deductible to the extent of gambling winnings and +are not subject to the 2% floor on miscellaneous itemized deductions. The +winnings are Arkansas gross income (other income, Form AR-OI). + +Each test evaluates every point of a winnings x losses grid in one vectorized +simulation, so the properties hold for all grid inputs rather than for a few +examples: + +- the deduction equals min(losses, winnings) summed over the tax unit; +- it is non-negative, never exceeds winnings or losses, and rises with each; +- it does not depend on Arkansas AGI (no 2% floor) or on the year; +- it agrees with the federal wagering losses deduction, which applies the + same rule under 26 U.S.C. § 165(d) through 2025; +- gambling never lowers Arkansas taxable income, and raises it by at most + the winnings; for a filer who itemizes either way, it raises it by exactly + max(0, winnings - losses). +""" + +import itertools + +import numpy as np +import pytest + +from policyengine_us import Simulation + +AMOUNTS = [0, 1, 999, 1_000, 2_200, 5_000, 10_000, 250_000] +GRID = list(itertools.product(AMOUNTS, AMOUNTS)) +WINNINGS = np.array([g[0] for g in GRID], dtype=float) +LOSSES = np.array([g[1] for g in GRID], dtype=float) +YEARS = [2015, 2017, 2018, 2019, 2020, 2022, 2025] + + +def grid_simulation(year, person_inputs, tax_unit_inputs=None): + """One single-person Arkansas tax unit per grid point.""" + situation = {"people": {}, "tax_units": {}, "households": {}} + for i in range(len(GRID)): + person = f"p{i}" + situation["people"][person] = { + "age": {year: 40}, + **{ + name: {year: float(values[i])} for name, values in person_inputs.items() + }, + } + situation["tax_units"][f"t{i}"] = { + "members": [person], + **{ + name: {year: float(values[i])} + for name, values in (tax_unit_inputs or {}).items() + }, + } + situation["households"][f"h{i}"] = { + "members": [person], + "state_code": {year: "AR"}, + } + return Simulation(situation=situation) + + +def constant(value): + return np.full(len(GRID), value, dtype=float) + + +def as_table(values): + """Rows index winnings, columns index losses.""" + return np.asarray(values).reshape(len(AMOUNTS), len(AMOUNTS)) + + +@pytest.mark.parametrize("year", YEARS) +def test_deduction_is_losses_capped_at_winnings(year): + sim = grid_simulation( + year, {"gambling_winnings": WINNINGS, "gambling_losses": LOSSES} + ) + deduction = sim.calculate("ar_gambling_loss_deduction", year) + np.testing.assert_allclose(deduction, np.minimum(LOSSES, WINNINGS)) + assert np.all(deduction >= 0) + assert np.all(deduction <= WINNINGS) + assert np.all(deduction <= LOSSES) + table = as_table(deduction) + assert np.all(np.diff(table, axis=0) >= 0), "falls as winnings rise" + assert np.all(np.diff(table, axis=1) >= 0), "falls as losses rise" + + +def test_deduction_does_not_depend_on_the_year(): + results = [] + for year in YEARS: + sim = grid_simulation( + year, {"gambling_winnings": WINNINGS, "gambling_losses": LOSSES} + ) + results.append(sim.calculate("ar_gambling_loss_deduction", year)) + for result in results[1:]: + np.testing.assert_array_equal(result, results[0]) + + +@pytest.mark.parametrize("agi", [0, 50_000, 1_000_000]) +def test_deduction_is_not_subject_to_the_two_percent_floor(agi): + year = 2019 + sim = grid_simulation( + year, + { + "gambling_winnings": WINNINGS, + "gambling_losses": LOSSES, + "ar_agi_joint": constant(agi), + "ar_agi_indiv": constant(agi), + }, + ) + deduction = sim.calculate("ar_gambling_loss_deduction", year) + np.testing.assert_allclose(deduction, np.minimum(LOSSES, WINNINGS)) + # No other itemized deduction has inputs, so the itemized total is the + # full gambling deduction whatever the AGI. + itemized = sim.calculate("ar_itemized_deductions_joint", year) + np.testing.assert_allclose(itemized, deduction) + + +@pytest.mark.parametrize("year", YEARS) +def test_deduction_matches_the_federal_wagering_losses_deduction(year): + # 26 U.S.C. § 165(d) allows wagering losses only to the extent of wagering + # gains through 2025, the same cap Arkansas applies. + sim = grid_simulation( + year, {"gambling_winnings": WINNINGS, "gambling_losses": LOSSES} + ) + np.testing.assert_allclose( + sim.calculate("ar_gambling_loss_deduction", year), + sim.calculate("wagering_losses_deduction", year), + ) + + +def taxable_income(year, winnings, losses, other_itemized): + sim = grid_simulation( + year, + { + "irs_employment_income": constant(60_000), + "gambling_winnings": winnings, + "gambling_losses": losses, + }, + {"ar_medical_expense_deduction_joint": constant(other_itemized)}, + ) + return sim.calculate("ar_taxable_income_joint", year) + + +@pytest.mark.parametrize("year", YEARS) +@pytest.mark.parametrize("other_itemized", [0, 20_000]) +def test_gambling_never_lowers_arkansas_taxable_income(year, other_itemized): + with_gambling = taxable_income(year, WINNINGS, LOSSES, other_itemized) + without_gambling = taxable_income(year, constant(0), constant(0), other_itemized) + change = with_gambling - without_gambling + assert np.all(change >= -1e-6), "gambling lowered taxable income" + assert np.all(change <= WINNINGS + 1e-6), "taxed more than the winnings" + if other_itemized: + # Itemized deductions exceed the standard deduction with or without + # gambling, so only net winnings are taxed. + np.testing.assert_allclose(change, np.maximum(0, WINNINGS - LOSSES)) diff --git a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_gambling_loss_deduction.py b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_gambling_loss_deduction.py new file mode 100644 index 00000000000..89aacf81693 --- /dev/null +++ b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_gambling_loss_deduction.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class ar_gambling_loss_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Arkansas gambling loss deduction" + unit = USD + documentation = ( + "Gambling losses, deductible to the extent of gambling winnings and " + "not subject to the 2% limitation on miscellaneous itemized deductions." + ) + definition_period = YEAR + reference = ( + "https://www.arkleg.state.ar.us/Acts/FTPDocument?path=%2FACTS%2F2017R%2FPublic%2F&file=155.pdf&ddBienniumSession=2017%2F2017R#page=9", + "https://www.arkleg.state.ar.us/Acts/FTPDocument?path=%2FACTS%2F2017R%2FPublic%2F&file=155.pdf&ddBienniumSession=2017%2F2017R#page=10", + "https://www.dfa.arkansas.gov/wp-content/uploads/208-GamblingIncomeExpenses.pdf#page=1", + "https://www.dfa.arkansas.gov/wp-content/uploads/2025_AR1000F_and_AR1000NR_Instructions.pdf#page=22", + ) + defined_for = StateCode.AR + + def formula(tax_unit, period, parameters): + # Ark. Code § 26-51-424(a)(2)(B), as amended by Act 155 of 2017, § 21, + # for tax years beginning on or after January 1, 2015 (§ 25): gambling + # losses are deductible to the extent of gambling winnings and are not + # subject to the 2% floor on miscellaneous itemized deductions, so the + # deduction sits outside ar_misc_deduction_{joint,indiv}. DFA Subject + # 208 has stated the same rule since at least 2009. + losses = add(tax_unit, period, ["gambling_losses"]) + winnings = add(tax_unit, period, ["gambling_winnings"]) + return min_(losses, winnings) From 5e9c13217d784d8370d57fe7ee2435155d20b004 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 05:05:11 -0400 Subject: [PATCH 2/4] Match Arkansas gross income line comments to each year's AR1000F The 2015 block (TY2015-2017) said gambling winnings were on Line 22, but the 2015-2017 AR1000F reports other income on Line 20 (2015 booklet, #page=15 and the form on #page=17). The block's other comments copied the 2018 numbering too: on the 2015-2017 form, pensions are Lines 17A/17B, Schedule E is Line 18 and Schedule F is Line 19. Every block also had Schedule C income on Line 8 (it is Line 13), capital gains on Line 13 (Line 14) and disability benefits on "Line 18b" (pensions and disability retirement go on Lines 17A/17B or 18A/18B). Comments and references only; no parameter value changes. A code-health test pins each source's line comment to the form for its period block. Co-Authored-By: Claude Opus 5.5 --- .../gross_income/sources/individual.yaml | 68 +++++---- .../income/gross_income/sources/joint.yaml | 68 +++++---- ...st_ar_gross_income_source_line_comments.py | 137 ++++++++++++++++++ 3 files changed, 213 insertions(+), 60 deletions(-) create mode 100644 policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml index 80101089cb3..9305561dfb1 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml @@ -1,44 +1,48 @@ description: Arkansas accounts for these sources when calculating the gross income. values: # Unemployment compensation was exempt income through tax year 2017. + # Line numbers in this block follow the TY2015-2017 AR1000F, which + # has no unemployment or military retirement line: pensions are on + # Lines 17A/17B, Schedule E on Line 18, Schedule F on Line 19 and + # other income on Line 20. 2015-01-01: - irs_employment_income # Line 8 - - self_employment_income # Line 8 - - sstb_self_employment_income + - self_employment_income # Line 13 + - sstb_self_employment_income # Line 13 - interest_income # Line 10 - dividend_income # Line 11 - alimony_income # Line 12 - - ar_taxable_capital_gains_indiv # Line 13 + - ar_taxable_capital_gains_indiv # Line 14 - taxable_ira_distributions # Line 16 - taxable_roth_conversions # Line 16 - - military_retirement_pay # Line 17 - - taxable_pension_income # Line 18 - - disability_benefits # Line 18b - - rental_income # Line 19 - - estate_income # Line 19 - - farm_rent_income # Line 19, Form 4835 rent is reported on Schedule E - - farm_operations_income # Line 20, Schedule F + - military_retirement_pay # Line 17A/17B + - taxable_pension_income # Line 17A/17B + - disability_benefits # Line 17A/17B + - rental_income # Line 18 + - estate_income # Line 18 + - farm_rent_income # Line 18, Form 4835 rent is reported on Schedule E + - farm_operations_income # Line 19, Schedule F # Winnings from Arkansas electronic games of skill are not income # (Ark. Code § 26-51-1310(f), Act 732 of 2007); DFA applies this to # winnings before July 1, 2019. The model has a single gambling_winnings # input and does not separate them. - - gambling_winnings # Line 22, other income (Form AR-OI) + - gambling_winnings # Line 20, other income (Form AR-OI) # Act 141 of 2017 added unemployment compensation to gross income for tax # years beginning on or after January 1, 2018. Act 154 of 2021 then exempted # it for calendar years 2020 and 2021, after which it is taxable again. 2018-01-01: - irs_employment_income # Line 8 - - self_employment_income # Line 8 - - sstb_self_employment_income + - self_employment_income # Line 13 + - sstb_self_employment_income # Line 13 - interest_income # Line 10 - dividend_income # Line 11 - alimony_income # Line 12 - - ar_taxable_capital_gains_indiv # Line 13 + - ar_taxable_capital_gains_indiv # Line 14 - taxable_ira_distributions # Line 16 - taxable_roth_conversions # Line 16 - military_retirement_pay # Line 17 - - taxable_pension_income # Line 18 - - disability_benefits # Line 18b + - taxable_pension_income # Line 18A/18B + - disability_benefits # Line 18A/18B - rental_income # Line 19 - estate_income # Line 19 - farm_rent_income # Line 19, Form 4835 rent is reported on Schedule E @@ -47,17 +51,17 @@ values: - gambling_winnings # Line 22, other income (Form AR-OI) 2020-01-01: - irs_employment_income # Line 8 - - self_employment_income # Line 8 - - sstb_self_employment_income + - self_employment_income # Line 13 + - sstb_self_employment_income # Line 13 - interest_income # Line 10 - dividend_income # Line 11 - alimony_income # Line 12 - - ar_taxable_capital_gains_indiv # Line 13 + - ar_taxable_capital_gains_indiv # Line 14 - taxable_ira_distributions # Line 16 - taxable_roth_conversions # Line 16 - military_retirement_pay # Line 17 - - taxable_pension_income # Line 18 - - disability_benefits # Line 18b + - taxable_pension_income # Line 18A/18B + - disability_benefits # Line 18A/18B - rental_income # Line 19 - estate_income # Line 19 - farm_rent_income # Line 19, Form 4835 rent is reported on Schedule E @@ -65,21 +69,21 @@ values: - gambling_winnings # Line 22, other income (Form AR-OI) 2022-01-01: - irs_employment_income # Line 8 - - self_employment_income # Line 8 - - sstb_self_employment_income + - self_employment_income # Line 13 + - sstb_self_employment_income # Line 13 - interest_income # Line 10 - dividend_income # Line 11 - alimony_income # Line 12 - - ar_taxable_capital_gains_indiv # Line 13 + - ar_taxable_capital_gains_indiv # Line 14 - taxable_ira_distributions # Line 16 - taxable_roth_conversions # Line 16 - military_retirement_pay # Line 17 - - taxable_pension_income # Line 18 - - taxable_401k_distributions # Line 18 - - taxable_403b_distributions # Line 18 - - taxable_sep_distributions # Line 18 - - keogh_distributions # Line 18 - - disability_benefits # Line 18b + - taxable_pension_income # Line 18A/18B + - taxable_401k_distributions # Line 18A/18B + - taxable_403b_distributions # Line 18A/18B + - taxable_sep_distributions # Line 18A/18B + - keogh_distributions # Line 18A/18B + - disability_benefits # Line 18A/18B - rental_income # Line 19 - estate_income # Line 19 - farm_rent_income # Line 19, Form 4835 rent is reported on Schedule E @@ -112,6 +116,10 @@ metadata: href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=13 - title: Arkansas 2015 Individual Income Tax Instructions (gambling winnings of any type, except from Arkansas electronic games of skill; other income on Line 20) href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=15 + - title: Arkansas 2015 Form AR1000F (TY2015-2017 income lines; Schedule C on Line 13, pensions on Lines 17A/17B, Schedule E on Line 18, Schedule F on Line 19, other income on Line 20) + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=17 + - title: Arkansas 2018 Form AR1000F (TY2018 and later income lines; Schedule C on Line 13, pensions on Lines 18A/18B, Schedule E on Line 19, other income on Line 22) + href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=19 - title: Arkansas 2022 AR1000F Instructions href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=13 - title: 2022 Arkansas AR1000F diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml index 9034c7a1f28..431ee108ebe 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml @@ -1,44 +1,48 @@ description: Arkansas accounts for these sources when calculating the gross income. values: # Unemployment compensation was exempt income through tax year 2017. + # Line numbers in this block follow the TY2015-2017 AR1000F, which + # has no unemployment or military retirement line: pensions are on + # Lines 17A/17B, Schedule E on Line 18, Schedule F on Line 19 and + # other income on Line 20. 2015-01-01: - irs_employment_income # Line 8 - - self_employment_income # Line 8 - - sstb_self_employment_income + - self_employment_income # Line 13 + - sstb_self_employment_income # Line 13 - interest_income # Line 10 - dividend_income # Line 11 - alimony_income # Line 12 - - ar_taxable_capital_gains_joint # Line 13 + - ar_taxable_capital_gains_joint # Line 14 - taxable_ira_distributions # Line 16 - taxable_roth_conversions # Line 16 - - military_retirement_pay # Line 17 - - taxable_pension_income # Line 18 - - disability_benefits # Line 18b - - rental_income # Line 19 - - estate_income # Line 19 - - farm_rent_income # Line 19, Form 4835 rent is reported on Schedule E - - farm_operations_income # Line 20, Schedule F + - military_retirement_pay # Line 17A/17B + - taxable_pension_income # Line 17A/17B + - disability_benefits # Line 17A/17B + - rental_income # Line 18 + - estate_income # Line 18 + - farm_rent_income # Line 18, Form 4835 rent is reported on Schedule E + - farm_operations_income # Line 19, Schedule F # Winnings from Arkansas electronic games of skill are not income # (Ark. Code § 26-51-1310(f), Act 732 of 2007); DFA applies this to # winnings before July 1, 2019. The model has a single gambling_winnings # input and does not separate them. - - gambling_winnings # Line 22, other income (Form AR-OI) + - gambling_winnings # Line 20, other income (Form AR-OI) # Act 141 of 2017 added unemployment compensation to gross income for tax # years beginning on or after January 1, 2018. Act 154 of 2021 then exempted # it for calendar years 2020 and 2021, after which it is taxable again. 2018-01-01: - irs_employment_income # Line 8 - - self_employment_income # Line 8 - - sstb_self_employment_income + - self_employment_income # Line 13 + - sstb_self_employment_income # Line 13 - interest_income # Line 10 - dividend_income # Line 11 - alimony_income # Line 12 - - ar_taxable_capital_gains_joint # Line 13 + - ar_taxable_capital_gains_joint # Line 14 - taxable_ira_distributions # Line 16 - taxable_roth_conversions # Line 16 - military_retirement_pay # Line 17 - - taxable_pension_income # Line 18 - - disability_benefits # Line 18b + - taxable_pension_income # Line 18A/18B + - disability_benefits # Line 18A/18B - rental_income # Line 19 - estate_income # Line 19 - farm_rent_income # Line 19, Form 4835 rent is reported on Schedule E @@ -47,17 +51,17 @@ values: - gambling_winnings # Line 22, other income (Form AR-OI) 2020-01-01: - irs_employment_income # Line 8 - - self_employment_income # Line 8 - - sstb_self_employment_income + - self_employment_income # Line 13 + - sstb_self_employment_income # Line 13 - interest_income # Line 10 - dividend_income # Line 11 - alimony_income # Line 12 - - ar_taxable_capital_gains_joint # Line 13 + - ar_taxable_capital_gains_joint # Line 14 - taxable_ira_distributions # Line 16 - taxable_roth_conversions # Line 16 - military_retirement_pay # Line 17 - - taxable_pension_income # Line 18 - - disability_benefits # Line 18b + - taxable_pension_income # Line 18A/18B + - disability_benefits # Line 18A/18B - rental_income # Line 19 - estate_income # Line 19 - farm_rent_income # Line 19, Form 4835 rent is reported on Schedule E @@ -65,21 +69,21 @@ values: - gambling_winnings # Line 22, other income (Form AR-OI) 2022-01-01: - irs_employment_income # Line 8 - - self_employment_income # Line 8 - - sstb_self_employment_income + - self_employment_income # Line 13 + - sstb_self_employment_income # Line 13 - interest_income # Line 10 - dividend_income # Line 11 - alimony_income # Line 12 - - ar_taxable_capital_gains_joint # Line 13 + - ar_taxable_capital_gains_joint # Line 14 - taxable_ira_distributions # Line 16 - taxable_roth_conversions # Line 16 - military_retirement_pay # Line 17 - - taxable_pension_income # Line 18 - - taxable_401k_distributions # Line 18 - - taxable_403b_distributions # Line 18 - - taxable_sep_distributions # Line 18 - - keogh_distributions # Line 18 - - disability_benefits # Line 18b + - taxable_pension_income # Line 18A/18B + - taxable_401k_distributions # Line 18A/18B + - taxable_403b_distributions # Line 18A/18B + - taxable_sep_distributions # Line 18A/18B + - keogh_distributions # Line 18A/18B + - disability_benefits # Line 18A/18B - rental_income # Line 19 - estate_income # Line 19 - farm_rent_income # Line 19, Form 4835 rent is reported on Schedule E @@ -112,6 +116,10 @@ metadata: href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=13 - title: Arkansas 2015 Individual Income Tax Instructions (gambling winnings of any type, except from Arkansas electronic games of skill; other income on Line 20) href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=15 + - title: Arkansas 2015 Form AR1000F (TY2015-2017 income lines; Schedule C on Line 13, pensions on Lines 17A/17B, Schedule E on Line 18, Schedule F on Line 19, other income on Line 20) + href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=17 + - title: Arkansas 2018 Form AR1000F (TY2018 and later income lines; Schedule C on Line 13, pensions on Lines 18A/18B, Schedule E on Line 19, other income on Line 22) + href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=19 - title: Arkansas 2022 AR1000F Instructions href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=13 - title: 2022 Arkansas AR1000F diff --git a/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py b/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py new file mode 100644 index 00000000000..d57247cc398 --- /dev/null +++ b/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py @@ -0,0 +1,137 @@ +"""Pin the AR1000F line comments in the Arkansas gross income sources lists. + +Each entry in gov.states.ar.tax.income.gross_income.sources.{joint,individual} +carries a "# Line N" comment naming the AR1000F income line it is reported on. +The form's numbering changed in tax year 2018, when Act 141 of 2017 added an +unemployment line and the uniformed services retirement exemption added a +military retirement line, so the 2015 block cannot reuse the 2018 numbers. + +TY2015-2017 AR1000F (2015 booklet, form on #page=17, instructions on +#page=13 to #page=15; the 2016 and 2017 booklets number the lines the same way): +13 business income (Schedule C), 14 capital gains, 16 nonqualified IRA +distributions and taxable annuities, 17A/17B employer pensions and qualified +IRAs, where military retirement and disability retirement also go, 18 Schedule +E, 19 Schedule F, 20 other income (Form AR-OI, including gambling winnings). + +TY2018+ AR1000F (2018 booklet form on #page=19; 2022 and 2025 AR1000F page 1): +13 business income, 14 capital gains, 16 nonqualified IRA distributions, +17 military retirement, 18A/18B employer pensions and qualified IRAs, 19 +Schedule E, 20 Schedule F, 21 unemployment, 22 other income. + +The check reads the YAML text without importing the model, so it costs nothing. +A new source must be added to EXPECTED_LINES after checking the form. +""" + +import re +from pathlib import Path + +import pytest + + +SOURCES = ( + Path(__file__).resolve().parents[2] + / "parameters/gov/states/ar/tax/income/gross_income/sources" +) + +TY2015_2017 = { + "irs_employment_income": "8", + "self_employment_income": "13", + "sstb_self_employment_income": "13", + "interest_income": "10", + "dividend_income": "11", + "alimony_income": "12", + "ar_taxable_capital_gains": "14", + "taxable_ira_distributions": "16", + "taxable_roth_conversions": "16", + "military_retirement_pay": "17A", + "taxable_pension_income": "17A", + "disability_benefits": "17A", + "rental_income": "18", + "estate_income": "18", + "farm_rent_income": "18", + "farm_operations_income": "19", + "gambling_winnings": "20", +} + +TY2018_ON = { + "irs_employment_income": "8", + "self_employment_income": "13", + "sstb_self_employment_income": "13", + "interest_income": "10", + "dividend_income": "11", + "alimony_income": "12", + "ar_taxable_capital_gains": "14", + "taxable_ira_distributions": "16", + "taxable_roth_conversions": "16", + "military_retirement_pay": "17", + "taxable_pension_income": "18A", + "taxable_401k_distributions": "18A", + "taxable_403b_distributions": "18A", + "taxable_sep_distributions": "18A", + "keogh_distributions": "18A", + "disability_benefits": "18A", + "rental_income": "19", + "estate_income": "19", + "farm_rent_income": "19", + "farm_operations_income": "20", + "unemployment_compensation": "21", + "gambling_winnings": "22", +} + +EXPECTED_LINES = { + "2015-01-01": TY2015_2017, + "2018-01-01": TY2018_ON, + "2020-01-01": TY2018_ON, + "2022-01-01": TY2018_ON, +} + +PERIOD = re.compile(r"^ (\d{4}-\d{2}-\d{2}):\s*$") +ENTRY = re.compile(r"^ - (\w+)\s*(?:#\s*(.*))?$") +LINE = re.compile(r"^Line (\d+[A-Za-z]?)\b") + + +def line_comments(path: Path) -> dict[str, list[tuple[str, str | None]]]: + """Map each period in `path` to its (source, line label) entries.""" + blocks = {} + period = None + for text in path.read_text().splitlines(): + if text.startswith("metadata:"): + break + match = PERIOD.match(text) + if match: + period = match.group(1) + blocks[period] = [] + continue + match = ENTRY.match(text) + if match and period is not None: + source, comment = match.groups() + label = LINE.match(comment or "") + blocks[period].append((source, label.group(1) if label else None)) + return blocks + + +@pytest.mark.parametrize("file_name", ["joint.yaml", "individual.yaml"]) +def test_ar_gross_income_sources_cite_the_form_line_for_their_year(file_name): + blocks = line_comments(SOURCES / file_name) + assert sorted(blocks) == sorted(EXPECTED_LINES) + errors = [] + for period, entries in blocks.items(): + expected = EXPECTED_LINES[period] + for source, label in entries: + key = re.sub(r"_(joint|indiv)$", "", source) + if key not in expected: + errors.append( + f"{period} {source}: not in EXPECTED_LINES; check the " + "AR1000F for that year and add it" + ) + elif label is None: + errors.append( + f"{period} {source}: no '# Line N' comment; the form has " + f"Line {expected[key]}" + ) + elif label != expected[key]: + errors.append( + f"{period} {source}: comment says Line {label}, the " + f"form has Line {expected[key]}" + ) + assert not errors, "\n".join(errors) From 304757c2fab2bdd19b4ee059b063a0c15cc905cb Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 05:32:57 -0400 Subject: [PATCH 3/4] Point the 2018 AR1000F reference at the resident form and tighten the line test AR1000FandAR1000NRInstructions_2018.pdf#page=19 is the AR1000NR; the full-year resident AR1000F income lines are on #page=15. Also note that qualified IRA distributions (after age 59 1/2) go on the pension lines and that TY2018 splits military retirement into Lines 17A/17B. The line-comment test now checks the full label (17A/17B, not just 17A) and asserts that its text parse sees exactly the lists yaml.safe_load sees, so a re-indented or quoted file cannot pass with nothing checked. Co-Authored-By: Claude Opus 5.5 --- .../gross_income/sources/individual.yaml | 20 ++++----- .../income/gross_income/sources/joint.yaml | 20 ++++----- ...st_ar_gross_income_source_line_comments.py | 43 +++++++++++-------- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml index 9305561dfb1..6c709962bc8 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml @@ -13,8 +13,8 @@ values: - dividend_income # Line 11 - alimony_income # Line 12 - ar_taxable_capital_gains_indiv # Line 14 - - taxable_ira_distributions # Line 16 - - taxable_roth_conversions # Line 16 + - taxable_ira_distributions # Line 16, or Lines 17A/17B after age 59 1/2 + - taxable_roth_conversions # Line 16, or Lines 17A/17B after age 59 1/2 - military_retirement_pay # Line 17A/17B - taxable_pension_income # Line 17A/17B - disability_benefits # Line 17A/17B @@ -38,9 +38,9 @@ values: - dividend_income # Line 11 - alimony_income # Line 12 - ar_taxable_capital_gains_indiv # Line 14 - - taxable_ira_distributions # Line 16 - - taxable_roth_conversions # Line 16 - - military_retirement_pay # Line 17 + - taxable_ira_distributions # Line 16, or Lines 18A/18B after age 59 1/2 + - taxable_roth_conversions # Line 16, or Lines 18A/18B after age 59 1/2 + - military_retirement_pay # Line 17 (Lines 17A/17B on the 2018 form) - taxable_pension_income # Line 18A/18B - disability_benefits # Line 18A/18B - rental_income # Line 19 @@ -57,8 +57,8 @@ values: - dividend_income # Line 11 - alimony_income # Line 12 - ar_taxable_capital_gains_indiv # Line 14 - - taxable_ira_distributions # Line 16 - - taxable_roth_conversions # Line 16 + - taxable_ira_distributions # Line 16, or Lines 18A/18B after age 59 1/2 + - taxable_roth_conversions # Line 16, or Lines 18A/18B after age 59 1/2 - military_retirement_pay # Line 17 - taxable_pension_income # Line 18A/18B - disability_benefits # Line 18A/18B @@ -75,8 +75,8 @@ values: - dividend_income # Line 11 - alimony_income # Line 12 - ar_taxable_capital_gains_indiv # Line 14 - - taxable_ira_distributions # Line 16 - - taxable_roth_conversions # Line 16 + - taxable_ira_distributions # Line 16, or Lines 18A/18B after age 59 1/2 + - taxable_roth_conversions # Line 16, or Lines 18A/18B after age 59 1/2 - military_retirement_pay # Line 17 - taxable_pension_income # Line 18A/18B - taxable_401k_distributions # Line 18A/18B @@ -119,7 +119,7 @@ metadata: - title: Arkansas 2015 Form AR1000F (TY2015-2017 income lines; Schedule C on Line 13, pensions on Lines 17A/17B, Schedule E on Line 18, Schedule F on Line 19, other income on Line 20) href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=17 - title: Arkansas 2018 Form AR1000F (TY2018 and later income lines; Schedule C on Line 13, pensions on Lines 18A/18B, Schedule E on Line 19, other income on Line 22) - href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=19 + href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=15 - title: Arkansas 2022 AR1000F Instructions href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=13 - title: 2022 Arkansas AR1000F diff --git a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml index 431ee108ebe..f5e8bf42c71 100644 --- a/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml +++ b/policyengine_us/parameters/gov/states/ar/tax/income/gross_income/sources/joint.yaml @@ -13,8 +13,8 @@ values: - dividend_income # Line 11 - alimony_income # Line 12 - ar_taxable_capital_gains_joint # Line 14 - - taxable_ira_distributions # Line 16 - - taxable_roth_conversions # Line 16 + - taxable_ira_distributions # Line 16, or Lines 17A/17B after age 59 1/2 + - taxable_roth_conversions # Line 16, or Lines 17A/17B after age 59 1/2 - military_retirement_pay # Line 17A/17B - taxable_pension_income # Line 17A/17B - disability_benefits # Line 17A/17B @@ -38,9 +38,9 @@ values: - dividend_income # Line 11 - alimony_income # Line 12 - ar_taxable_capital_gains_joint # Line 14 - - taxable_ira_distributions # Line 16 - - taxable_roth_conversions # Line 16 - - military_retirement_pay # Line 17 + - taxable_ira_distributions # Line 16, or Lines 18A/18B after age 59 1/2 + - taxable_roth_conversions # Line 16, or Lines 18A/18B after age 59 1/2 + - military_retirement_pay # Line 17 (Lines 17A/17B on the 2018 form) - taxable_pension_income # Line 18A/18B - disability_benefits # Line 18A/18B - rental_income # Line 19 @@ -57,8 +57,8 @@ values: - dividend_income # Line 11 - alimony_income # Line 12 - ar_taxable_capital_gains_joint # Line 14 - - taxable_ira_distributions # Line 16 - - taxable_roth_conversions # Line 16 + - taxable_ira_distributions # Line 16, or Lines 18A/18B after age 59 1/2 + - taxable_roth_conversions # Line 16, or Lines 18A/18B after age 59 1/2 - military_retirement_pay # Line 17 - taxable_pension_income # Line 18A/18B - disability_benefits # Line 18A/18B @@ -75,8 +75,8 @@ values: - dividend_income # Line 11 - alimony_income # Line 12 - ar_taxable_capital_gains_joint # Line 14 - - taxable_ira_distributions # Line 16 - - taxable_roth_conversions # Line 16 + - taxable_ira_distributions # Line 16, or Lines 18A/18B after age 59 1/2 + - taxable_roth_conversions # Line 16, or Lines 18A/18B after age 59 1/2 - military_retirement_pay # Line 17 - taxable_pension_income # Line 18A/18B - taxable_401k_distributions # Line 18A/18B @@ -119,7 +119,7 @@ metadata: - title: Arkansas 2015 Form AR1000F (TY2015-2017 income lines; Schedule C on Line 13, pensions on Lines 17A/17B, Schedule E on Line 18, Schedule F on Line 19, other income on Line 20) href: https://www.dfa.arkansas.gov/wp-content/uploads/LongBookwTaxTables_2015.pdf#page=17 - title: Arkansas 2018 Form AR1000F (TY2018 and later income lines; Schedule C on Line 13, pensions on Lines 18A/18B, Schedule E on Line 19, other income on Line 22) - href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=19 + href: https://www.dfa.arkansas.gov/wp-content/uploads/AR1000FandAR1000NRInstructions_2018.pdf#page=15 - title: Arkansas 2022 AR1000F Instructions href: https://www.dfa.arkansas.gov/wp-content/uploads/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=13 - title: 2022 Arkansas AR1000F diff --git a/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py b/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py index d57247cc398..4601837a6c6 100644 --- a/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py +++ b/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py @@ -13,10 +13,11 @@ IRAs, where military retirement and disability retirement also go, 18 Schedule E, 19 Schedule F, 20 other income (Form AR-OI, including gambling winnings). -TY2018+ AR1000F (2018 booklet form on #page=19; 2022 and 2025 AR1000F page 1): -13 business income, 14 capital gains, 16 nonqualified IRA distributions, -17 military retirement, 18A/18B employer pensions and qualified IRAs, 19 -Schedule E, 20 Schedule F, 21 unemployment, 22 other income. +TY2018+ AR1000F (2018 booklet, form on #page=15; 2022 and 2025 AR1000F, +#page=2): 13 business income, 14 capital gains, 16 nonqualified IRA +distributions, 17 military retirement (17A/17B on the 2018 form), 18A/18B +employer pensions and qualified IRAs, 19 Schedule E, 20 Schedule F, +21 unemployment, 22 other income. The check reads the YAML text without importing the model, so it costs nothing. A new source must be added to EXPECTED_LINES after checking the form. @@ -26,6 +27,7 @@ from pathlib import Path import pytest +import yaml SOURCES = ( @@ -43,9 +45,9 @@ "ar_taxable_capital_gains": "14", "taxable_ira_distributions": "16", "taxable_roth_conversions": "16", - "military_retirement_pay": "17A", - "taxable_pension_income": "17A", - "disability_benefits": "17A", + "military_retirement_pay": "17A/17B", + "taxable_pension_income": "17A/17B", + "disability_benefits": "17A/17B", "rental_income": "18", "estate_income": "18", "farm_rent_income": "18", @@ -64,12 +66,12 @@ "taxable_ira_distributions": "16", "taxable_roth_conversions": "16", "military_retirement_pay": "17", - "taxable_pension_income": "18A", - "taxable_401k_distributions": "18A", - "taxable_403b_distributions": "18A", - "taxable_sep_distributions": "18A", - "keogh_distributions": "18A", - "disability_benefits": "18A", + "taxable_pension_income": "18A/18B", + "taxable_401k_distributions": "18A/18B", + "taxable_403b_distributions": "18A/18B", + "taxable_sep_distributions": "18A/18B", + "keogh_distributions": "18A/18B", + "disability_benefits": "18A/18B", "rental_income": "19", "estate_income": "19", "farm_rent_income": "19", @@ -85,9 +87,9 @@ "2022-01-01": TY2018_ON, } -PERIOD = re.compile(r"^ (\d{4}-\d{2}-\d{2}):\s*$") -ENTRY = re.compile(r"^ - (\w+)\s*(?:#\s*(.*))?$") -LINE = re.compile(r"^Line (\d+[A-Za-z]?)\b") +PERIOD = re.compile(r"^\s*(\d{4}-\d{2}-\d{2}):\s*$") +ENTRY = re.compile(r"^\s*- (\w+)\s*(?:#\s*(.*))?$") +LINE = re.compile(r"^Line ([0-9A-Za-z/]+)") def line_comments(path: Path) -> dict[str, list[tuple[str, str | None]]]: @@ -112,7 +114,14 @@ def line_comments(path: Path) -> dict[str, list[tuple[str, str | None]]]: @pytest.mark.parametrize("file_name", ["joint.yaml", "individual.yaml"]) def test_ar_gross_income_sources_cite_the_form_line_for_their_year(file_name): - blocks = line_comments(SOURCES / file_name) + path = SOURCES / file_name + blocks = line_comments(path) + # The text parse must see exactly the lists the parameter loader sees, so a + # reformatted file cannot make this check pass with nothing to check. + values = yaml.safe_load(path.read_text())["values"] + assert {str(period): sources for period, sources in values.items()} == { + period: [source for source, _ in entries] for period, entries in blocks.items() + } assert sorted(blocks) == sorted(EXPECTED_LINES) errors = [] for period, entries in blocks.items(): From 4406b92fa9859b932a12175d17d5c4a990a8f431 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 7 Oct 2026 09:36:38 -0400 Subject: [PATCH 4/4] Require nonempty Arkansas gross-income source lists in form-line guard --- .../code_health/test_ar_gross_income_source_line_comments.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py b/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py index 4601837a6c6..9bcafacd5aa 100644 --- a/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py +++ b/policyengine_us/tests/code_health/test_ar_gross_income_source_line_comments.py @@ -117,7 +117,7 @@ def test_ar_gross_income_sources_cite_the_form_line_for_their_year(file_name): path = SOURCES / file_name blocks = line_comments(path) # The text parse must see exactly the lists the parameter loader sees, so a - # reformatted file cannot make this check pass with nothing to check. + # reformatted file cannot silently skip entries. values = yaml.safe_load(path.read_text())["values"] assert {str(period): sources for period, sources in values.items()} == { period: [source for source, _ in entries] for period, entries in blocks.items() @@ -125,6 +125,9 @@ def test_ar_gross_income_sources_cite_the_form_line_for_their_year(file_name): assert sorted(blocks) == sorted(EXPECTED_LINES) errors = [] for period, entries in blocks.items(): + assert entries, ( + f"{file_name} {period}: gross income source list must not be empty" + ) expected = EXPECTED_LINES[period] for source, label in entries: key = re.sub(r"_(joint|indiv)$", "", source)