diff --git a/changelog.d/fix-gambling-winnings-gross-income.fixed.md b/changelog.d/fix-gambling-winnings-gross-income.fixed.md new file mode 100644 index 00000000000..02ddb809178 --- /dev/null +++ b/changelog.d/fix-gambling-winnings-gross-income.fixed.md @@ -0,0 +1 @@ +Count gambling winnings in federal gross income, limit the wagering losses deduction to 90% of losses from 2026 and to the head's and spouse's gambling, treat it as a Schedule A itemized deduction, and align the states that tax gambling winnings or build on federal itemized deductions. diff --git a/changelog.d/nc-claim-of-right-deduction.added.md b/changelog.d/nc-claim-of-right-deduction.added.md new file mode 100644 index 00000000000..d9a675b5026 --- /dev/null +++ b/changelog.d/nc-claim-of-right-deduction.added.md @@ -0,0 +1 @@ +Add North Carolina's itemized deduction for repaying income received under a claim of right (G.S. 105-153.5(a)(2)d), with a new claim_of_right_repayment input. diff --git a/changelog.d/nc-wagering-losses-deduction.added.md b/changelog.d/nc-wagering-losses-deduction.added.md new file mode 100644 index 00000000000..355c2b449b2 --- /dev/null +++ b/changelog.d/nc-wagering-losses-deduction.added.md @@ -0,0 +1 @@ +Add North Carolina's itemized deduction for wagering losses allowed under 26 U.S.C. 165(d), from 2025 (G.S. 105-153.5(a)(2)e, S.L. 2026-41 section 44.2). diff --git a/policyengine_us/parameters/gov/irs/deductions/deductions_if_itemizing.yaml b/policyengine_us/parameters/gov/irs/deductions/deductions_if_itemizing.yaml index 9d055329b32..8283c8ab53e 100644 --- a/policyengine_us/parameters/gov/irs/deductions/deductions_if_itemizing.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/deductions_if_itemizing.yaml @@ -2,12 +2,10 @@ description: Deductions from taxable income if itemizing. values: 2018-01-01: - qualified_business_income_deduction - - wagering_losses_deduction - itemized_taxable_income_deductions 2025-01-01: - qualified_business_income_deduction - - wagering_losses_deduction - itemized_taxable_income_deductions - tip_income_deduction - overtime_income_deduction @@ -16,7 +14,6 @@ values: 2029-01-01: - qualified_business_income_deduction - - wagering_losses_deduction - itemized_taxable_income_deductions metadata: diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/limitation/excluded_deductions.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/limitation/excluded_deductions.yaml index 96e642fd5db..b37a9d2fb0a 100644 --- a/policyengine_us/parameters/gov/irs/deductions/itemized/limitation/excluded_deductions.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/limitation/excluded_deductions.yaml @@ -5,8 +5,6 @@ values: # and wagering losses (section 165(c)(2), (c)(3) and (d)). # investment_interest_expense is the investment interest that # interest_deduction includes through non_mortgage_interest. - # wagering_losses_deduction is deducted outside - # total_itemized_taxable_income_deductions, so it never enters the base. # The 2017 worksheet also removes qualified disaster-relief contributions # (P.L. 115-63 sec. 504(a)(3), P.L. 115-123 sec. 20104(a)(3)), which are # not modeled. @@ -14,6 +12,7 @@ values: - medical_expense_deduction - investment_interest_expense - casualty_loss_deduction + - wagering_losses_deduction metadata: unit: list period: year diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/wagering_losses/rate.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/wagering_losses/rate.yaml new file mode 100644 index 00000000000..86e3b6d8a1a --- /dev/null +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/wagering_losses/rate.yaml @@ -0,0 +1,17 @@ +description: The US allows this share of wagering losses as an itemized deduction, up to wagering gains. +values: + 0000-01-01: 1 + # P.L. 119-21 sec. 70114 rewrote section 165(d)(1) for taxable years + # beginning after December 31, 2025. + 2026-01-01: 0.9 +metadata: + unit: /1 + period: year + label: Wagering losses deduction rate + reference: + - title: 26 U.S. Code § 165(d) - Wagering losses + href: https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title26-section165&num=0&edition=prelim + - title: P.L. 119-21, sec. 70114 - Extension and modification of limitation on wagering losses + href: https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=96 + - title: 2024 Instructions for Schedule A (Form 1040), line 16 - Gambling losses + href: https://www.irs.gov/pub/irs-prior/i1040sca--2024.pdf#page=11 diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml index 14d369f6999..2353367d41d 100644 --- a/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml @@ -7,6 +7,8 @@ values: - medical_expense_deduction - casualty_loss_deduction - misc_deduction + # Gambling losses, Schedule A line 16 (other itemized deductions). + - wagering_losses_deduction 2020-01-01: - charitable_deduction - charitable_deduction_for_non_itemizers @@ -15,6 +17,8 @@ values: - medical_expense_deduction - casualty_loss_deduction - misc_deduction + # Gambling losses, Schedule A line 16 (other itemized deductions). + - wagering_losses_deduction 2022-01-01: - charitable_deduction - interest_deduction @@ -22,6 +26,13 @@ values: - medical_expense_deduction - casualty_loss_deduction - misc_deduction + # Gambling losses, Schedule A line 16 (other itemized deductions). + - wagering_losses_deduction metadata: unit: list label: itemized deductions + reference: + - title: 2024 Schedule A (Form 1040), lines 4-17 + href: https://www.irs.gov/pub/irs-prior/f1040sa--2024.pdf#page=1 + - title: 2024 Instructions for Schedule A (Form 1040), line 16 - Other itemized deductions + href: https://www.irs.gov/pub/irs-prior/i1040sca--2024.pdf#page=11 diff --git a/policyengine_us/parameters/gov/irs/gross_income/sources.yaml b/policyengine_us/parameters/gov/irs/gross_income/sources.yaml index af8391cacdf..8e93dd5262b 100644 --- a/policyengine_us/parameters/gov/irs/gross_income/sources.yaml +++ b/policyengine_us/parameters/gov/irs/gross_income/sources.yaml @@ -33,6 +33,9 @@ values: - taxable_retirement_distributions - taxable_roth_conversions - miscellaneous_income + # Gambling winnings, including lotteries and raffles (Schedule 1 line + # 8b). Only itemizers deduct the losses (wagering_losses_deduction). + - gambling_winnings # Listed separately on 1040 line 8g. - ak_permanent_fund_dividend # Alimony from divorces executed before the TCJA cutoff (Schedule 1 line 2a). @@ -51,7 +54,11 @@ metadata: href: https://www.law.cornell.edu/uscode/text/26/111 - title: 26 U.S. Code § 61(a)(14) - Income from an interest in an estate or trust href: https://www.law.cornell.edu/uscode/text/26/61#a_14 - - title: 2024 Schedule 1 (Form 1040), line 5 - Rental real estate, royalties, partnerships, S corporations, trusts, etc. + - title: 2024 Schedule 1 (Form 1040), line 5 - Rental real estate, royalties, partnerships, S corporations, trusts, etc., and line 8b - Gambling href: https://www.irs.gov/pub/irs-prior/f1040s1--2024.pdf#page=1 - title: 2024 Instructions for Schedule E (Form 1040), Part III - Income or Loss From Estates and Trusts href: https://www.irs.gov/pub/irs-prior/i1040se--2024.pdf#page=11 + - title: 2024 Instructions for Form 1040, Schedule 1 line 8b - Gambling + href: https://www.irs.gov/pub/irs-prior/i1040gi--2024.pdf#page=85 + - title: 2025 Instructions for Form 1040, Schedule 1 line 8b - Gambling + href: https://www.irs.gov/pub/irs-prior/i1040gi--2025.pdf#page=91 diff --git a/policyengine_us/parameters/gov/states/al/tax/income/agi/gross_income_sources.yaml b/policyengine_us/parameters/gov/states/al/tax/income/agi/gross_income_sources.yaml index c21a24968de..8705dc2a6e8 100644 --- a/policyengine_us/parameters/gov/states/al/tax/income/agi/gross_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/al/tax/income/agi/gross_income_sources.yaml @@ -19,6 +19,9 @@ values: - partnership_s_corp_income - farm_operations_income - farm_rent_income + # Prizes and awards: contests, lotteries and gambling winnings (Form 40 + # page 2, Part I, other income), of the head and spouse. + - al_gambling_winnings metadata: unit: list diff --git a/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/sources.yaml b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/sources.yaml index bad60cdd74a..4197a465dc5 100644 --- a/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/sources.yaml +++ b/policyengine_us/parameters/gov/states/al/tax/income/deductions/itemized/sources.yaml @@ -12,6 +12,9 @@ values: - al_casualty_loss_deduction # Section (a), (6) - al_misc_deduction # Section (a), (23) - al_interest_deduction # Section (a), (2) + # Schedule A line 25: gambling losses to the extent of gambling winnings, + # not subject to the 2% limit. + - al_gambling_loss_deduction metadata: unit: list @@ -28,3 +31,5 @@ metadata: href: https://www.revenue.alabama.gov/ultraviewer/viewer/basic_viewer/index.html?form=2024/01/23f40schabdc_blk.pdf#page=1 - title: 2024 Alabama Income Tax Instructions href: https://www.revenue.alabama.gov/wp-content/uploads/2025/01/24f40bk.pdf#page=18 + - title: 2025 Alabama Income Tax Instructions, Schedule A line 25 - gambling losses + href: https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=21 diff --git a/policyengine_us/parameters/gov/states/ca/tax/income/deductions/itemized/limit/excluded_deductions.yaml b/policyengine_us/parameters/gov/states/ca/tax/income/deductions/itemized/limit/excluded_deductions.yaml index d3d5fd934b8..120fb6de409 100644 --- a/policyengine_us/parameters/gov/states/ca/tax/income/deductions/itemized/limit/excluded_deductions.yaml +++ b/policyengine_us/parameters/gov/states/ca/tax/income/deductions/itemized/limit/excluded_deductions.yaml @@ -4,12 +4,17 @@ values: - medical_expense_deduction - casualty_loss_deduction - non_mortgage_interest + # Itemized Deductions Worksheet line 2: "plus any gambling losses + # included on line 16". + - ca_wagering_losses_deduction metadata: unit: list period: year label: itemized deductions excluded from CA high-AGI limitation reference: + - title: 2025 Form 540 booklet, Schedule CA (540) Itemized Deductions Worksheet, line 2 + href: https://www.ftb.ca.gov/forms/2025/2025-540-booklet.pdf#page=68 - title: 2021 Form 540 Schedule CA instructions href: https://www.ftb.ca.gov/forms/2021/2021-540-ca-instructions.html - title: 2022 Form 540 Schedule CA instructions diff --git a/policyengine_us/parameters/gov/states/de/tax/income/deductions/itemized/sources.yaml b/policyengine_us/parameters/gov/states/de/tax/income/deductions/itemized/sources.yaml index ea8e0ca8dbe..d3f59d8c69e 100644 --- a/policyengine_us/parameters/gov/states/de/tax/income/deductions/itemized/sources.yaml +++ b/policyengine_us/parameters/gov/states/de/tax/income/deductions/itemized/sources.yaml @@ -5,6 +5,8 @@ values: - charitable_deduction - interest_deduction - casualty_loss_deduction + # PIT-RSA line 16, other itemized deductions permitted under federal law. + - wagering_losses_deduction metadata: unit: list period: year @@ -14,6 +16,6 @@ metadata: href: https://revenuefiles.delaware.gov/2022/TY22_PIT-RSA_2022-02_PaperInteractive.pdf - title: Code of Delaware. Title30. CHAPTER 11. Personal Income Tax, § 1109 href: https://delcode.delaware.gov/title30/c011/sc02/index.html - - title: 2025 Delaware Individual Income Tax Return Instructions + - title: "2025 Delaware Individual Income Tax Return Instructions #page 7,12" href: https://revenuefiles.delaware.gov/2025/PITForms_Instructions/Instructions/PIT-RES_Instructions_2025-01.pdf#page=7 diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/sources.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/sources.yaml index 20406023cc9..77bd496ee52 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/sources.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/sources.yaml @@ -6,6 +6,8 @@ values: - hi_interest_deduction - hi_casualty_loss_deduction - hi_salt_deduction + # Worksheet A-6, line 30: gambling losses, up to gambling winnings. + - wagering_losses_deduction metadata: unit: list period: year @@ -23,5 +25,5 @@ metadata: href: https://files.hawaii.gov/tax/forms/2018/n11ins.pdf#page=19 - title: Hawaii Income Tax Law, Chapter 235, Section 235-2.4 href: https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=10 - - title: 2025 Hawaii N-11 Instructions Itemized Deductions - href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=17 + - title: "2025 Hawaii N-11 Instructions Itemized Deductions #page 17,19" + href: https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/gross_income/sources.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/gross_income/sources.yaml index 1abc47597d6..7739e4c5f7e 100644 --- a/policyengine_us/parameters/gov/states/ia/tax/income/gross_income/sources.yaml +++ b/policyengine_us/parameters/gov/states/ia/tax/income/gross_income/sources.yaml @@ -16,6 +16,8 @@ values: - farm_operations_income - taxable_unemployment_compensation - miscellaneous_income + # IA 1040 line 13, gambling winnings, of the head and spouse. + - ia_gambling_winnings metadata: unit: list diff --git a/policyengine_us/parameters/gov/states/ky/tax/income/deductions/itemized/wagering_losses_allowed.yaml b/policyengine_us/parameters/gov/states/ky/tax/income/deductions/itemized/wagering_losses_allowed.yaml new file mode 100644 index 00000000000..1c9c01f00e6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/tax/income/deductions/itemized/wagering_losses_allowed.yaml @@ -0,0 +1,18 @@ +description: Kentucky allows the federal wagering losses deduction as an itemized deduction if this is true. +values: + 2000-01-01: true + # For 2018 Kentucky disallowed every section 165 loss. + 2018-01-01: false + # 2019 Ky. Acts ch. 151, sec. 37 restored wagering losses allowed under + # section 165(d), for taxable years beginning on or after January 1, 2019 + # (sec. 83). + 2019-01-01: true +metadata: + unit: bool + period: year + label: Kentucky wagering losses itemized deduction allowed + reference: + - title: "2019 Ky. Acts ch. 151, sec. 37 (KRS 141.019(2)(b)) and sec. 83 #page 60,112" + href: https://apps.legislature.ky.gov/law/acts/19RS/documents/0151.pdf#page=60 + - title: TY2018 Kentucky Publication 1345, itemized deductions obsolete for tax year 2018 + href: https://revenue.ky.gov/Software-Developer/Software%20Development%20Documents/TY2018%20KY%20Publication%201345-Handbook%20for%20Electronic%20Filing.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml b/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml index 62d9a981878..f7c8883e128 100644 --- a/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml @@ -11,6 +11,20 @@ values: - mi_interest_dividends_capital_gains_deduction # Line 27 - mi_529_deduction - salt_refund_income # Line 16 + # 2021 PA 168 added a deduction for the wagering losses a casual gambler + # deducts on the federal return, for tax years beginning in 2021. + 2021-01-01: + - us_govt_interest # Line 10 + - military_retirement_pay # Line 11 + - taxable_social_security # Line 14 + - military_service_income # Line 14 + - section_22_income # Line 22 + - mi_standard_deduction # Line 24 & 25 + - mi_pension_benefit # Line 26 + - mi_interest_dividends_capital_gains_deduction # Line 27 + - mi_529_deduction + - salt_refund_income # Line 16 + - mi_wagering_losses_deduction # Line 23 (2025), miscellaneous subtractions 2026-01-01: - us_govt_interest # Line 10 - military_retirement_pay # Line 11 @@ -24,6 +38,7 @@ values: - mi_qualified_tips_deduction # Qualified tips (PA 24 of 2025), TY2026-2028 - mi_qualified_overtime_deduction # Qualified overtime compensation (PA 24 of 2025), TY2026-2028 - salt_refund_income # Line 16 + - mi_wagering_losses_deduction # Line 23 (2025), miscellaneous subtractions metadata: unit: list label: Michigan taxable income subtraction sources @@ -35,6 +50,10 @@ metadata: href: https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Forms/IIT/TY2023/BOOK_MI-1040.pdf#page=47 - title: Michigan Legal Code Section 206.30 href: https://www.legislature.mi.gov/Laws/MCL?objectName=mcl-206-30 + - title: Michigan Treasury notice - New Tax Treatment of Wagering Losses for Casual Gamblers (2021 PA 168) + href: https://www.michigan.gov/taxes/rep-legal/notices/notice-new-tax-treatment-of-wagering-losses-for-casual-gamblers-under-the-michigan-income-tax-act + - title: 2025 MI-1040 booklet, Schedule 1 line 23 - Miscellaneous subtractions (wagering losses) + href: https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Forms/IIT/TY2025/MI-1040-Book.pdf#page=16 - title: Michigan Standard Deduction href: https://web.archive.org/web/20250202140014/https://www.michigan.gov/taxes/iit/retirement-and-pension-benefits/michigan-standard-deduction - title: Michigan 2022 MI-1040 Individual Income Tax FORMS AND INSTRUCTIONS diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml index c4744269768..7a9409ab7cb 100644 --- a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml +++ b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml @@ -6,13 +6,22 @@ description: Montana counts these sources as miscellaneous deductions. values: 2021-01-01: - casualty_loss_deduction - 2024-01-01: [] + # Gambling losses, up to gambling winnings (2023 Form 2 instructions). + - mt_wagering_losses_deduction + # From 2024 Montana starts from federal taxable income, which carries the + # federal itemized deductions. + 2024-01-01: + - mt_wagering_losses_deduction metadata: unit: list label: Montana miscellaneous deductions period: year reference: + - title: 2023 Montana Form 2 instructions, itemized deductions - gambling losses + href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2023_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=39 + - title: 2025 Montana Form 2 instructions, line 2 - federal standard deduction or federal itemized deductions + href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2025_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=13 - title: Montana Individual Income Tax Return (Form 2) 2024 href: https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2024_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=8 - title: Montana Individual Income Tax Return (Form 2) 2022 diff --git a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/nc_modified_income_basis.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/nc_modified_income_basis.yaml new file mode 100644 index 00000000000..ce8a8a199ec --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/nc_modified_income_basis.yaml @@ -0,0 +1,13 @@ +description: North Carolina limits the claim of right repayment deduction to repayments of amounts included in adjusted gross income, as modified for North Carolina, in the earlier year, if this is true. +values: + 2014-01-01: false + 2026-01-01: true +metadata: + unit: bool + period: year + label: North Carolina claim of right repayment deduction uses North Carolina modified income + reference: + - title: N.C. Gen. Stat. § 105-153.5(a)(2)d + href: https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html + - title: "S.L. 2026-31, Section 1.8.(a) and (b) (effective for taxable years beginning on or after January 1, 2026) #page 5-6" + href: https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-31.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/threshold.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/threshold.yaml new file mode 100644 index 00000000000..941fc92e523 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/threshold.yaml @@ -0,0 +1,16 @@ +description: North Carolina allows the whole repayment of claim of right income as an itemized deduction when the repayment exceeds this amount; smaller repayments are reduced by the federal two percent floor on miscellaneous itemized deductions. +values: + 2014-01-01: 3_000 +metadata: + unit: currency-USD + period: year + label: North Carolina claim of right repayment threshold + reference: + - title: N.C. Gen. Stat. § 105-153.5(a)(2)d + href: https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html + - title: S.L. 2016-5, Section 2.1.(a) and (d) (effective for taxable years beginning on or after January 1, 2014) + href: https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2015-2016/SL2016-5.pdf#page=4 + - title: 2021 Form D-401 instructions, Form D-400 Schedule A line 8, Repayment of Claim of Right Worksheet + href: https://www.ncdor.gov/2021-d-401-individual-income-tax-instructions/open#page=20 + - title: 2025 Form D-401 instructions, Form D-400 Schedule A line 8, Repayment of Claim of Right Worksheet + href: https://www.ncdor.gov/2025-d-401-individual-income-tax-instructions/open#page=20 diff --git a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/other_sources.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/other_sources.yaml new file mode 100644 index 00000000000..fc268a584e3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/other_sources.yaml @@ -0,0 +1,18 @@ +description: North Carolina adds these deductions to the capped mortgage interest and property tax deduction to compute itemized deductions. +values: + 2021-01-01: + - charitable_deduction + - medical_expense_deduction + - nc_claim_of_right_deduction + - nc_wagering_losses_deduction +metadata: + unit: list + period: year + label: North Carolina itemized deductions other than mortgage interest and property taxes + reference: + - title: N.C. Gen. Stat. § 105-153.5(a)(2) + href: https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html + - title: 2021 Form D-401 instructions, Form D-400 Schedule A lines 6 through 10 + href: https://www.ncdor.gov/2021-d-401-individual-income-tax-instructions/open#page=20 + - title: "2025 Form D-401 instructions, Form D-400 Schedule A lines 6 through 10 #page 20-21" + href: https://www.ncdor.gov/2025-d-401-individual-income-tax-instructions/open#page=20 diff --git a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/wagering_losses/in_effect.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/wagering_losses/in_effect.yaml new file mode 100644 index 00000000000..013c828c9a8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/wagering_losses/in_effect.yaml @@ -0,0 +1,17 @@ +description: North Carolina allows wagering losses as an itemized deduction if this is true. +values: + 2014-01-01: false + 2025-01-01: true +metadata: + unit: bool + period: year + label: North Carolina wagering losses itemized deduction in effect + reference: + - title: N.C. Gen. Stat. § 105-153.5(a)(2)e + href: https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html + - title: "S.L. 2026-41, Section 44.2.(a) and (b) (effective for taxable years beginning on or after January 1, 2025) #page 607-608" + href: https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-41.pdf#page=607 + - title: NCDOR, Important Notice, Impact of Recently Enacted Laws on North Carolina Individual and Corporate Income Tax Returns (July 23, 2026, updated July 29, 2026), New Itemized Deduction for Gambling Losses + href: https://www.ncdor.gov/taxes-forms/information-tax-professionals/tax-bulletins-directives-and-other-important-notices/important-notices-and-frequently-asked-questions-personal-taxes/important-notice-impact-recently-enacted-laws-north-carolina-individual-and-corporate-income + - title: "NCDOR, FAQs Regarding Recent Session Law Changes (September 11, 2026), Q36-Q40 #page 11-12" + href: https://www.ncdor.gov/faqs-recent-session-law-changes/open#page=11 diff --git a/policyengine_us/parameters/gov/states/pa/tax/income/nontaxable_income_sources.yaml b/policyengine_us/parameters/gov/states/pa/tax/income/nontaxable_income_sources.yaml index e5270142c66..7a3dd5c276b 100644 --- a/policyengine_us/parameters/gov/states/pa/tax/income/nontaxable_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/pa/tax/income/nontaxable_income_sources.yaml @@ -8,9 +8,13 @@ values: - taxable_social_security - us_govt_interest - salt_refund_income + # PA-40 Schedule T line 4: the cost of bets comes off the winnings. + - pa_gambling_loss_offset metadata: unit: currency-USD label: PA nontaxable income sources reference: - title: 2021 Pennsylvania PIT Return Instructions (PA-40 IN) href: https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2021/2021_pa-40in.pdf#page=8 + - title: 2025 PA-40 Schedule T - Gambling and Lottery Winnings + href: https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2025/2025_pa-40t.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/va/tax/income/deductions/itemized/reduction/applicable.yaml b/policyengine_us/parameters/gov/states/va/tax/income/deductions/itemized/reduction/applicable.yaml index 2c9e4102ca5..b80e5a27698 100644 --- a/policyengine_us/parameters/gov/states/va/tax/income/deductions/itemized/reduction/applicable.yaml +++ b/policyengine_us/parameters/gov/states/va/tax/income/deductions/itemized/reduction/applicable.yaml @@ -7,11 +7,15 @@ values: - real_estate_taxes - casualty_loss_deduction - va_capped_state_and_local_sales_or_income_tax + # Schedule A line 16a, gambling losses. + - wagering_losses_deduction metadata: unit: list period: year label: Virginia itemized deductions reduction applicable deduction sources reference: + - title: "2025 Virginia Schedule A Itemized Deductions, line 16a and Limited Itemized Deduction Worksheet line 1 #page 1-2" + href: https://www.tax.virginia.gov/sites/default/files/taxforms/individual-income-tax/2025/schedule-2025.pdf#page=1 - title: 2024 Virginia Schedule A Itemized Deductions href: https://www.tax.virginia.gov/sites/default/files/taxforms/individual-income-tax/2024/schedule-2024.pdf#page=2 - title: 2023 Virginia Schedule A Itemized Deductions diff --git a/policyengine_us/parameters/gov/states/va/tax/income/deductions/itemized/reduction/reducible.yaml b/policyengine_us/parameters/gov/states/va/tax/income/deductions/itemized/reduction/reducible.yaml index 8518c31559b..4775e39d0a3 100644 --- a/policyengine_us/parameters/gov/states/va/tax/income/deductions/itemized/reduction/reducible.yaml +++ b/policyengine_us/parameters/gov/states/va/tax/income/deductions/itemized/reduction/reducible.yaml @@ -3,13 +3,17 @@ values: 2013-01-01: - medical_expense_deduction - investment_interest_expense - - gambling_losses + # Worksheet line 2: "any gambling losses included on Line 16a", which + # is the deduction allowed, not the losses incurred. + - wagering_losses_deduction - casualty_loss_deduction metadata: unit: list period: year label: Virginia itemized deductions reduction reducible deduction sources reference: + - title: 2025 Virginia Schedule A, Limited Itemized Deduction Worksheet line 2 + href: https://www.tax.virginia.gov/sites/default/files/taxforms/individual-income-tax/2025/schedule-2025.pdf#page=2 - title: 2024 Virginia Schedule A Itemized Deductions href: https://www.tax.virginia.gov/sites/default/files/taxforms/individual-income-tax/2024/schedule-2024.pdf#page=2 - title: 2023 Virginia Schedule A Itemized Deductions diff --git a/policyengine_us/reforms/crfb/senior_deduction_extension.py b/policyengine_us/reforms/crfb/senior_deduction_extension.py index 56bd3d19c4f..5b38a9ae641 100644 --- a/policyengine_us/reforms/crfb/senior_deduction_extension.py +++ b/policyengine_us/reforms/crfb/senior_deduction_extension.py @@ -10,7 +10,6 @@ def modify_parameters(parameters): start=instant("2029-01-01"), value=[ "qualified_business_income_deduction", - "wagering_losses_deduction", "itemized_taxable_income_deductions", "additional_senior_deduction", ], diff --git a/policyengine_us/reforms/states/mn/walz/mn_walz_hf1938.py b/policyengine_us/reforms/states/mn/walz/mn_walz_hf1938.py index 78aed52cd19..412c8f636b1 100644 --- a/policyengine_us/reforms/states/mn/walz/mn_walz_hf1938.py +++ b/policyengine_us/reforms/states/mn/walz/mn_walz_hf1938.py @@ -115,10 +115,17 @@ def formula(tax_unit, period, parameters): mn_itm_deds = itm_deds_less_salt + capped_property_taxes # ... calculate itemized deductions offset p = parameters(period).gov.states.mn.tax.income.deductions.itemized + # As in the baseline: Minn. Stat. 290.0122, subd. 2(d)(3) leaves + # the subd. 8 losses, including wagering losses, out of the + # limitation. exempt_deds = add( tax_unit, period, - ["medical_expense_deduction", "casualty_loss_deduction"], + [ + "medical_expense_deduction", + "casualty_loss_deduction", + "wagering_losses_deduction", + ], ) net_deds = max_(0, mn_itm_deds - exempt_deds) net_deds_offset = p.reduction.alternate.rate * net_deds diff --git a/policyengine_us/tests/core/test_itemized_deduction_limitation_properties.py b/policyengine_us/tests/core/test_itemized_deduction_limitation_properties.py index 36c428c7094..4d6fb96038b 100644 --- a/policyengine_us/tests/core/test_itemized_deduction_limitation_properties.py +++ b/policyengine_us/tests/core/test_itemized_deduction_limitation_properties.py @@ -29,9 +29,9 @@ 7. With the OBBB structure switched off in 2026 (the prior-law counterfactual), the pre-2026 worksheet logic applies with the 2026 applicable amounts. -Gambling losses are deducted outside total_itemized_taxable_income_deductions -(wagering_losses_deduction), so they never enter the limited base; they are -not generated here. +Gambling losses (wagering_losses_deduction, Schedule A line 16) are part of +total_itemized_taxable_income_deductions and are one of the section 68(c) +exclusions, so they are generated here as an excluded deduction. """ from functools import cache @@ -59,7 +59,11 @@ # Components of total_itemized_taxable_income_deductions, split by whether # section 68(c) excludes them. Mortgage and investment interest are person # inputs that interest_deduction adds up. -EXCLUDED = ("medical_expense_deduction", "casualty_loss_deduction") +EXCLUDED = ( + "medical_expense_deduction", + "casualty_loss_deduction", + "wagering_losses_deduction", +) EXCLUDED_PERSON = ("investment_interest_expense",) OTHER = ("charitable_deduction", "salt_deduction", "misc_deduction") OTHER_PERSON = ("deductible_mortgage_interest",) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_taxable_income_deductions_reduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_taxable_income_deductions_reduction.yaml index 965f6ed655c..28bbca5664c 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_taxable_income_deductions_reduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_taxable_income_deductions_reduction.yaml @@ -164,3 +164,50 @@ # Excess over the 37 percent bracket: 900_000 - 640_600 = 259_400 # Lesser: 200_000; reduction: 200_000 * 2 / 37 = 10_810.81 itemized_taxable_income_deductions_reduction: 10_810.81 + +# Wagering losses (section 165(d)) are itemized deductions. Before 2018 +# section 68(c) kept them out of the 80 percent base; from 2026 the reduction +# counts them like any other itemized deduction. +- name: 2017 single filer - wagering losses are outside the 80 percent base + period: 2017 + absolute_error_margin: 0.01 + input: + adjusted_gross_income: 400_000 + filing_status: SINGLE + gambling_winnings: 20_000 + gambling_losses: 20_000 + charitable_deduction: 10_000 + interest_deduction: 0 + salt_deduction: 0 + casualty_loss_deduction: 0 + misc_deduction: 0 + medical_expense_deduction: 0 + output: + wagering_losses_deduction: 20_000 + # Line 1: 30_000. Line 2: 20_000. Line 3: 10_000. Line 4: 8_000. + # Line 7: 400_000 - 261_500 = 138_500. Line 8: 4_155. + # Line 9: smaller of 8_000 or 4_155. + itemized_taxable_income_deductions_reduction: 4_155 + itemized_taxable_income_deductions: 25_845 + +- name: 2026 single filer - the reduction counts wagering losses + period: 2026 + absolute_error_margin: 0.01 + input: + adjusted_gross_income: 1_000_000 + filing_status: SINGLE + gambling_winnings: 30_000 + gambling_losses: 30_000 + charitable_deduction: 0 + interest_deduction: 0 + salt_deduction: 0 + casualty_loss_deduction: 0 + misc_deduction: 0 + medical_expense_deduction: 0 + output: + # 90% of 30_000 + wagering_losses_deduction: 27_000 + # Excess over the 37 percent bracket: 1_000_000 - 640_600 = 359_400 + # Lesser: 27_000; reduction: 27_000 * 2 / 37 = 1_459.46 + itemized_taxable_income_deductions_reduction: 1_459.46 + itemized_taxable_income_deductions: 25_540.54 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/taxable_income_deductions_if_itemizing.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/taxable_income_deductions_if_itemizing.yaml index fb8dfc3a11a..225f1a94b32 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/taxable_income_deductions_if_itemizing.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/taxable_income_deductions_if_itemizing.yaml @@ -1,12 +1,11 @@ -- name: Integration test, reduction of itemized deductions but not QBID or wagering losses +- name: Integration test, reduction of itemized deductions but not QBID period: 2024 input: total_itemized_taxable_income_deductions: 1_000 itemized_taxable_income_deductions_reduction: 2_000 qualified_business_income_deduction: 1_000 - wagering_losses_deduction: 500 output: - taxable_income_deductions_if_itemizing: 1_500 + taxable_income_deductions_if_itemizing: 1_000 - name: Integration test, some itemized deductions over reduction threshold period: 2024 @@ -14,6 +13,18 @@ total_itemized_taxable_income_deductions: 3_000 itemized_taxable_income_deductions_reduction: 2_000 qualified_business_income_deduction: 1_000 - wagering_losses_deduction: 500 output: - taxable_income_deductions_if_itemizing: 2_500 + taxable_income_deductions_if_itemizing: 2_000 + +- name: Wagering losses are part of the itemized deductions total, not a separate deduction. + period: 2024 + input: + wagering_losses_deduction: 500 + charitable_deduction: 3_000 + qualified_business_income_deduction: 1_000 + output: + total_itemized_taxable_income_deductions: 3_500 + itemized_taxable_income_deductions: 3_500 + # 3,500 of itemized deductions and 1,000 of QBID; the wagering losses + # are not added a second time. + taxable_income_deductions_if_itemizing: 4_500 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/wagering_losses_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/wagering_losses_deduction.yaml index 6ded087918c..ae37d552290 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/wagering_losses_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/wagering_losses_deduction.yaml @@ -69,3 +69,228 @@ members: [person1, person2] output: wagering_losses_deduction: 5 + +- name: Through 2025 all losses count, up to winnings. + absolute_error_margin: 0 + period: 2025 + input: + gambling_winnings: 10_000 + gambling_losses: 8_000 + output: + wagering_losses_deduction: 8_000 + +- name: Through 2025 losses above winnings are not deductible. + absolute_error_margin: 0 + period: 2025 + input: + gambling_winnings: 10_000 + gambling_losses: 12_000 + output: + wagering_losses_deduction: 10_000 + +- name: From 2026 only 90% of losses count. + absolute_error_margin: 0 + period: 2026 + input: + gambling_winnings: 10_000 + gambling_losses: 8_000 + output: + # 90% of 8,000 + wagering_losses_deduction: 7_200 + +- name: From 2026 the winnings cap applies after the 90% share. + absolute_error_margin: 0 + period: 2026 + input: + gambling_winnings: 10_000 + gambling_losses: 12_000 + output: + # min(90% of 12,000, 10,000) + wagering_losses_deduction: 10_000 + +- name: From 2026 equal winnings and losses leave 10% of the winnings taxed. + absolute_error_margin: 0 + period: 2026 + input: + gambling_winnings: 30_000 + gambling_losses: 30_000 + output: + wagering_losses_deduction: 27_000 + +- name: A dependent's gambling is not on the parent's return. + absolute_error_margin: 0 + period: 2024 + input: + people: + parent: + age: 40 + child: + age: 17 + is_tax_unit_dependent: true + gambling_winnings: 10_000 + gambling_losses: 10_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + output: + wagering_losses_deduction: 0 + +- name: A dependent's losses cannot use up the parent's winnings. + absolute_error_margin: 0 + period: 2024 + input: + people: + parent: + age: 40 + gambling_winnings: 10_000 + gambling_losses: 2_000 + child: + age: 17 + is_tax_unit_dependent: true + gambling_winnings: 5_000 + gambling_losses: 9_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + output: + # The parent's own 2,000, not min(11,000, 15,000). + wagering_losses_deduction: 2_000 + +# Integration with gross income, itemizing and income tax. Gambling winnings +# are gross income (26 U.S.C. 61(a); Schedule 1 line 8b), and the losses are +# an itemized deduction up to the winnings (26 U.S.C. 165(d); Schedule A line +# 16), 90% of the losses from 2026 (P.L. 119-21 sec. 70114). The cases follow +# PolicyEngine/policyengine-us#9637: a single filer aged 40 in Texas with +# 60,000 of wages. The state and local tax deduction is set to zero so each +# figure can be computed by hand. + +- name: 2024, no gambling. + absolute_error_margin: 0.01 + period: 2024 + input: + age: 40 + employment_income: 60_000 + salt_deduction: 0 + state_code: TX + output: + adjusted_gross_income: 60_000 + # 60,000 - 14,600 standard deduction + taxable_income: 45_400 + # 10% x 11,600 + 12% x 33,800 + income_tax: 5_216 + +- name: 2024, winnings are income and losses below the standard deduction do not help. + absolute_error_margin: 0.01 + period: 2024 + input: + age: 40 + employment_income: 60_000 + gambling_winnings: 10_000 + gambling_losses: 8_000 + salt_deduction: 0 + state_code: TX + output: + irs_gross_income: 70_000 + adjusted_gross_income: 70_000 + wagering_losses_deduction: 8_000 + itemized_taxable_income_deductions: 8_000 + tax_unit_itemizes: false + # 70,000 - 14,600 + taxable_income: 55_400 + # 1,160 + 12% x 35,550 + 22% x 8,250 + income_tax: 7_241 + +- name: 2024, an itemizer deducts losses against the winnings in income. + absolute_error_margin: 0.01 + period: 2024 + input: + age: 40 + employment_income: 60_000 + gambling_winnings: 30_000 + gambling_losses: 25_000 + salt_deduction: 0 + state_code: TX + output: + adjusted_gross_income: 90_000 + wagering_losses_deduction: 25_000 + itemized_taxable_income_deductions: 25_000 + tax_unit_itemizes: true + # 90,000 - 25,000 + taxable_income: 65_000 + # 1,160 + 4,266 + 22% x 17,850 + income_tax: 9_353 + +- name: 2024, equal winnings and losses still raise tax by replacing the standard deduction. + absolute_error_margin: 0.01 + period: 2024 + input: + age: 40 + employment_income: 60_000 + gambling_winnings: 20_000 + gambling_losses: 20_000 + salt_deduction: 0 + state_code: TX + output: + adjusted_gross_income: 80_000 + wagering_losses_deduction: 20_000 + tax_unit_itemizes: true + # 80,000 - 20,000, against 45,400 with no gambling + taxable_income: 60_000 + # 1,160 + 4,266 + 22% x 12,850 + income_tax: 8_253 + +- name: 2026, the itemizer deducts 90% of losses. + absolute_error_margin: 0.01 + period: 2026 + input: + age: 40 + employment_income: 60_000 + gambling_winnings: 30_000 + gambling_losses: 25_000 + salt_deduction: 0 + state_code: TX + output: + adjusted_gross_income: 90_000 + # 90% of 25,000 + wagering_losses_deduction: 22_500 + itemized_taxable_income_deductions: 22_500 + tax_unit_itemizes: true + # 90,000 - 22,500 + taxable_income: 67_500 + # 10% x 12,400 + 12% x 38,000 + 22% x 17,100 + income_tax: 9_562 + +- name: A dependent's winnings and losses do not change the parent's return. + absolute_error_margin: 0.01 + period: 2024 + input: + people: + parent: + age: 40 + employment_income: 60_000 + child: + age: 17 + is_tax_unit_dependent: true + gambling_winnings: 30_000 + gambling_losses: 25_000 + tax_units: + tax_unit: + members: [parent, child] + salt_deduction: 0 + households: + household: + members: [parent, child] + state_code: TX + output: + irs_gross_income: [60_000, 0] + adjusted_gross_income: 60_000 + wagering_losses_deduction: 0 + tax_unit_itemizes: false + # 60,000 - 21,900 head of household standard deduction + taxable_income: 38_100 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/irs_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/irs/irs_gross_income.yaml index 5e241bbd3ee..ce70da66e59 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/irs_gross_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/irs_gross_income.yaml @@ -94,3 +94,21 @@ members: [parent, child] output: irs_gross_income: [0, 0] + +- name: Gambling winnings are gross income for the filer, not a dependent's on this return. + period: 2024 + input: + people: + parent: + is_tax_unit_head: true + gambling_winnings: 10_000 + gambling_losses: 4_000 + child: + is_tax_unit_dependent: true + gambling_winnings: 5_000 + tax_units: + tax_unit: + members: [parent, child] + output: + # The full winnings, not reduced by losses. + irs_gross_income: [10_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_agi.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_agi.yaml index d1b72912602..ac6be51d557 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_agi.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_agi.yaml @@ -68,3 +68,37 @@ # AGI leaves all of them on the child's return. adjusted_gross_income: 40_000 al_agi: 48_500 + +- name: Alabama taxes gambling winnings. + absolute_error_margin: 0.01 + period: 2026 + input: + irs_employment_income: 50_000 + gambling_winnings: 20_000 + gambling_losses: 12_000 + state_code: AL + output: + al_agi: 70_000 + +- name: A dependent's gambling winnings are not in the parent's Alabama AGI. + absolute_error_margin: 0.01 + period: 2025 + input: + people: + parent: + age: 40 + gambling_winnings: 2_000 + child: + age: 17 + is_tax_unit_dependent: true + gambling_winnings: 20_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: AL + output: + al_gambling_winnings: [2_000, 0] + al_agi: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_gambling_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_gambling_loss_deduction.yaml new file mode 100644 index 00000000000..9f2a5792722 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_gambling_loss_deduction.yaml @@ -0,0 +1,44 @@ +- name: Alabama deducts all gambling losses up to winnings in 2026. + absolute_error_margin: 0.01 + period: 2026 + input: + gambling_winnings: 20_000 + gambling_losses: 12_000 + state_code: AL + output: + # The federal deduction is 90% of the losses from 2026; Alabama's is not. + wagering_losses_deduction: 10_800 + al_gambling_loss_deduction: 12_000 + +- name: Alabama gambling losses are capped at winnings. + absolute_error_margin: 0.01 + period: 2025 + input: + gambling_winnings: 5_000 + gambling_losses: 12_000 + state_code: AL + output: + al_gambling_loss_deduction: 5_000 + +- name: A dependent's gambling is not on the parent's Alabama return. + absolute_error_margin: 0.01 + period: 2025 + input: + people: + parent: + age: 40 + gambling_winnings: 1_000 + gambling_losses: 10_000 + child: + age: 17 + is_tax_unit_dependent: true + gambling_winnings: 20_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: AL + output: + al_gambling_loss_deduction: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions.yaml index 13ce42b7d1c..548ce7f38f5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions.yaml @@ -51,3 +51,44 @@ ca_itemized_deductions_pre_limitation: 10_000 output: ca_itemized_deductions: 7_000 # charity subject to limitation + +- name: California allows all gambling losses up to winnings in 2026 and does not limit them. + absolute_error_margin: 0.01 + period: 2026 + input: + filing_status: SINGLE + adjusted_gross_income: 1_000_000 + gambling_winnings: 10_000 + gambling_losses: 10_000 + state_code: CA + output: + # The federal deduction is 90% of the losses from 2026. + wagering_losses_deduction: 9_000 + ca_wagering_losses_deduction: 10_000 + ca_itemized_deductions_pre_limitation: 10_000 + # Gambling losses are outside the high-income limitation. + ca_itemized_deductions: 10_000 + +- name: California gambling losses are capped at winnings and exclude a dependent's. + absolute_error_margin: 0.01 + period: 2025 + input: + people: + parent: + age: 40 + gambling_winnings: 5_000 + gambling_losses: 8_000 + child: + age: 17 + is_tax_unit_dependent: true + gambling_winnings: 10_000 + gambling_losses: 10_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: CA + output: + ca_wagering_losses_deduction: 5_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_itemized_deductions.yaml index b4bc41434fc..5d01c6cfcd4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_itemized_deductions.yaml @@ -53,3 +53,36 @@ state_code: DC output: dc_itemized_deductions: 8_000 # = 1_000 + (12_000 - 100_000 * 0.05) + +- name: Wagering losses are outside the DC itemized deduction phase-out. + absolute_error_margin: 0.01 + period: 2026 + input: + age: 40 + employment_income: 190_000 + gambling_winnings: 20_000 + gambling_losses: 20_000 + state_code: DC + output: + dc_agi: 210_000 + # 90% of 20,000 from 2026 + wagering_losses_deduction: 18_000 + # The phase-out, 5% x (210,000 - 200,000) = 500, applies only to + # deductions other than medical, casualty and wagering losses. + dc_itemized_deductions: 18_000 + +- name: The DC phase-out still reduces other deductions held with wagering losses. + absolute_error_margin: 0.01 + period: 2025 + input: + age: 40 + employment_income: 190_000 + gambling_winnings: 20_000 + gambling_losses: 20_000 + charitable_deduction: 10_000 + state_code: DC + output: + dc_agi: 210_000 + wagering_losses_deduction: 20_000 + # 20,000 + (10,000 - 500) + dc_itemized_deductions: 29_500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.yaml b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.yaml index 6c405d41d19..c460bfcd3f0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.yaml @@ -198,3 +198,66 @@ state_code: DE output: de_itemized_deductions_indv: [0, 5_000, 0] + +- name: Wagering losses go to the spouse who had them. + absolute_error_margin: 0.01 + period: 2025 + input: + people: + head: + age: 40 + employment_income: 50_000 + gambling_winnings: 10_000 + gambling_losses: 6_000 + spouse: + age: 40 + employment_income: 50_000 + gambling_losses: 2_000 + tax_units: + tax_unit: + members: [head, spouse] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: DE + output: + de_itemized_deductions_unit: 8_000 + de_itemized_deductions_indv: [6_000, 2_000] + +- name: A dependent's gambling losses do not change the spouses' wagering allocation. + absolute_error_margin: 0.01 + period: 2025 + input: + people: + head: + age: 40 + employment_income: 40_000 + gambling_winnings: 10_000 + gambling_losses: 6_000 + spouse: + age: 40 + employment_income: 50_000 + gambling_losses: 2_000 + child: + age: 17 + is_tax_unit_dependent: true + gambling_winnings: 10_000 + gambling_losses: 8_000 + tax_units: + tax_unit: + members: [head, spouse, child] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse, child] + state_code: DE + output: + de_itemized_deductions_unit: 8_000 + # The head's 6,000 and the spouse's 2,000; the child's 8,000 is on the + # child's own return. + de_itemized_deductions_indv: [6_000, 2_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_unit.yaml b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_unit.yaml new file mode 100644 index 00000000000..ff28767425d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_unit.yaml @@ -0,0 +1,13 @@ +- name: Delaware carries the federal wagering losses deduction. + absolute_error_margin: 0.01 + period: 2026 + input: + age: 40 + employment_income: 50_000 + gambling_winnings: 10_000 + gambling_losses: 8_000 + state_code: DE + output: + adjusted_gross_income: 60_000 + # 90% of 8,000 from 2026 + de_itemized_deductions_unit: 7_200 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_itemized_deductions.yaml index 5a44ba7f696..1ef26ddbdaf 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_itemized_deductions.yaml @@ -31,3 +31,36 @@ hi_reduced_itemized_deductions: 4_500 output: hi_itemized_deductions: 4_500 + +- name: Hawaii allows gambling losses and does not reduce them at high income. + absolute_error_margin: 0.01 + period: 2026 + input: + age: 40 + employment_income: 200_000 + gambling_winnings: 10_000 + gambling_losses: 10_000 + state_code: HI + output: + hi_agi: 210_000 + # 90% of 10,000 from 2026 + hi_total_itemized_deductions: 9_000 + # Total Itemized Deductions Worksheet line 4: line 3 (gambling losses) + # is not less than line 1, so the deduction is not limited. + hi_itemized_deductions: 9_000 + +- name: The Hawaii limitation reduces other deductions held with gambling losses. + absolute_error_margin: 0.01 + period: 2025 + input: + age: 40 + employment_income: 200_000 + gambling_winnings: 10_000 + gambling_losses: 10_000 + charitable_deduction: 20_000 + state_code: HI + output: + hi_agi: 210_000 + hi_total_itemized_deductions: 30_000 + # Smaller of 80% x 20,000 and 3% x (210,000 - 166,800) = 1,296. + hi_itemized_deductions: 28_704 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml index 912c803624b..05bb4f44a11 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.yaml @@ -43,7 +43,9 @@ members: [person1, person2] state_code: HI output: - hi_reduced_itemized_deductions: 0 + # Worksheet line 8: income is not over 166,800, so the deduction is not + # limited and line 1 is entered. + hi_reduced_itemized_deductions: 10_000 - name: Reduced itemized deductions same as total period: 2022 @@ -66,4 +68,28 @@ members: [person1, person2] state_code: HI output: - hi_reduced_itemized_deductions: 0 + # Worksheet line 4: the medical, investment interest and casualty + # deductions (300 + 200 + 400) are not less than the total, so the + # deduction is not limited and line 1 is entered. + hi_reduced_itemized_deductions: 900 + +- name: Gambling losses are outside the limitation (worksheet line 2d). + period: 2025 + input: + tax_units: + tax_unit: + filing_status: SINGLE + wagering_losses_deduction: 4_000 + hi_agi: 266_800 + members: [person1] + hi_total_itemized_deductions: 10_000 + people: + person1: {} + households: + household: + members: [person1] + state_code: HI + output: + # Smaller of 80% x (10,000 - 4,000) = 4,800 and 3% x (266,800 - 166,800) + # = 3,000. + hi_reduced_itemized_deductions: 7_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_gross_income.yaml index 7337123adcd..b2c25a9d95a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_gross_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_gross_income.yaml @@ -5,3 +5,28 @@ state_code: IA output: ia_gross_income: 100_000 + +- name: Gambling winnings are Iowa gross income on the 2021 return (IA 1040 line 13). + absolute_error_margin: 0.01 + period: 2021 + input: + people: + parent: + age: 40 + irs_employment_income: 60_000 + gambling_winnings: 10_000 + child: + age: 17 + is_tax_unit_dependent: true + gambling_winnings: 5_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: IA + output: + ia_gambling_winnings: [10_000, 0] + # The child's winnings are on the child's own return. + ia_gross_income: [70_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ks/tax/income/ks_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/ks/tax/income/ks_itemized_deductions.yaml index 63a6603f62b..17eb7b38c4c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ks/tax/income/ks_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ks/tax/income/ks_itemized_deductions.yaml @@ -19,3 +19,33 @@ state_code: KS output: ks_itemized_deductions: 12 + 9 + 2 + +- name: Kansas taxes gambling winnings but its itemized deductions leave out wagering losses. + absolute_error_margin: 0.01 + period: 2025 + input: + age: 40 + employment_income: 40_000 + gambling_winnings: 10_000 + gambling_losses: 8_000 + state_code: KS + output: + wagering_losses_deduction: 8_000 + ks_agi: 50_000 + # K.S.A. 79-32,120(a) lists only charity, medical care, home mortgage + # interest and property taxes. + ks_itemized_deductions: 0 + +- name: Kansas itemized deductions keep charity and drop wagering losses in 2026. + absolute_error_margin: 0.01 + period: 2026 + input: + age: 40 + employment_income: 40_000 + gambling_winnings: 10_000 + gambling_losses: 8_000 + charitable_deduction: 3_000 + state_code: KS + output: + wagering_losses_deduction: 7_200 + ks_itemized_deductions: 3_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.yaml index c43befc6998..3a938a4225f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.yaml @@ -17,3 +17,37 @@ medical_expense_deduction: 2 output: ky_itemized_deductions_unit: 8 + +- name: Kentucky allowed no wagering losses for 2018. + absolute_error_margin: 0.01 + period: 2018 + input: + gambling_winnings: 30_000 + gambling_losses: 30_000 + charitable_deduction: 4_000 + state_code: KY + output: + wagering_losses_deduction: 30_000 + ky_itemized_deductions_unit: 4_000 + +- name: Kentucky allows wagering losses from 2019. + absolute_error_margin: 0.01 + period: 2019 + input: + gambling_winnings: 30_000 + gambling_losses: 30_000 + charitable_deduction: 4_000 + state_code: KY + output: + ky_itemized_deductions_unit: 34_000 + +- name: Kentucky follows the 90% share from 2026. + absolute_error_margin: 0.01 + period: 2026 + input: + gambling_winnings: 30_000 + gambling_losses: 30_000 + charitable_deduction: 4_000 + state_code: KY + output: + ky_itemized_deductions_unit: 31_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_subtractions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_subtractions.yaml index 046978ff58e..0e29541db4d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_subtractions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_subtractions.yaml @@ -170,3 +170,47 @@ mi_subtractions: 49_422.75 # 110,000 - 49,422.75 - 11,600 mi_taxable_income: 48_977.25 + +- name: A Michigan filer who itemizes federally subtracts the wagering losses deducted. + absolute_error_margin: 0.01 + period: 2026 + input: + age: 40 + employment_income: 50_000 + gambling_winnings: 20_000 + gambling_losses: 20_000 + state_code: MI + output: + tax_unit_itemizes: true + # 90% of 20,000 from 2026 + mi_wagering_losses_deduction: 18_000 + mi_subtractions: 18_000 + +- name: A Michigan filer who takes the federal standard deduction subtracts no wagering losses. + absolute_error_margin: 0.01 + period: 2025 + input: + age: 40 + employment_income: 50_000 + gambling_winnings: 10_000 + gambling_losses: 8_000 + state_code: MI + output: + tax_unit_itemizes: false + wagering_losses_deduction: 8_000 + mi_wagering_losses_deduction: 0 + mi_subtractions: 0 + +- name: Michigan allowed no wagering losses before 2021. + absolute_error_margin: 0.01 + period: 2020 + input: + age: 40 + employment_income: 50_000 + gambling_winnings: 20_000 + gambling_losses: 20_000 + tax_unit_itemizes: true + state_code: MI + output: + wagering_losses_deduction: 20_000 + mi_subtractions: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml index 632dba542f1..d5fd12c21bd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml @@ -90,3 +90,19 @@ state_code: MN output: mn_itemized_deductions: 10_000 + +- name: Wagering losses are outside the Minnesota itemized deduction limitation. + absolute_error_margin: 0.01 + period: 2026 + input: + age: 40 + employment_income: 200_000 + gambling_winnings: 100_000 + gambling_losses: 50_000 + state_code: MN + output: + adjusted_gross_income: 300_000 + # 90% of 50,000 + wagering_losses_deduction: 45_000 + # Minn. Stat. 290.0122, subd. 2(d)(3): no reduction of the losses. + mn_itemized_deductions: 45_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml new file mode 100644 index 00000000000..c85fc97e272 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml @@ -0,0 +1,12 @@ +- name: Mississippi itemized deductions leave out the federal wagering losses deduction. + absolute_error_margin: 0.01 + period: 2025 + input: + gambling_winnings: 10_000 + gambling_losses: 8_000 + charitable_deduction: 3_000 + state_code: MS + output: + wagering_losses_deduction: 8_000 + # Mississippi income here has no gambling winnings to deduct them from. + ms_itemized_deductions_unit: 3_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_wagering_losses_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_wagering_losses_deduction.yaml new file mode 100644 index 00000000000..e466004d8e4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_wagering_losses_deduction.yaml @@ -0,0 +1,110 @@ +- name: Montana allows the federal wagering losses deduction. + absolute_error_margin: 0.01 + period: 2023 + input: + age: 40 + employment_income: 50_000 + gambling_winnings: 30_000 + gambling_losses: 20_000 + state_code: MT + output: + mt_wagering_losses_deduction: 20_000 + mt_misc_deductions: 20_000 + +- name: From 2026 Montana carries 90% of the losses. + absolute_error_margin: 0.01 + period: 2026 + input: + age: 40 + employment_income: 50_000 + gambling_winnings: 30_000 + gambling_losses: 20_000 + state_code: MT + output: + mt_wagering_losses_deduction: 18_000 + mt_misc_deductions: 18_000 + +- name: The Montana spouse who reports the winnings claims the losses. + absolute_error_margin: 0.01 + period: 2023 + input: + people: + head: + age: 40 + gambling_winnings: 10_000 + spouse: + age: 40 + gambling_losses: 8_000 + child: + age: 10 + is_tax_unit_dependent: true + gambling_winnings: 5_000 + tax_units: + tax_unit: + members: [head, spouse, child] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse, child] + state_code: MT + output: + wagering_losses_deduction: 8_000 + mt_wagering_losses_deduction: [8_000, 0, 0] + +- name: When both Montana spouses have winnings, each claims their own losses. + absolute_error_margin: 0.01 + period: 2023 + input: + people: + head: + age: 40 + gambling_winnings: 10_000 + gambling_losses: 8_000 + spouse: + age: 40 + gambling_winnings: 10_000 + tax_units: + tax_unit: + members: [head, spouse] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MT + output: + wagering_losses_deduction: 8_000 + mt_wagering_losses_deduction: [8_000, 0] + +- name: Montana losses beyond a spouse's own winnings go to the spouse with winnings left. + absolute_error_margin: 0.01 + period: 2023 + input: + people: + head: + age: 40 + gambling_winnings: 4_000 + gambling_losses: 9_000 + spouse: + age: 40 + gambling_winnings: 10_000 + gambling_losses: 1_000 + tax_units: + tax_unit: + members: [head, spouse] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MT + output: + # The return deducts min(10,000, 14,000) = 10,000. The head takes 4,000 + # against their own winnings and the spouse 1,000; the other 5,000 falls + # on the spouse's remaining 9,000 of winnings. + wagering_losses_deduction: 10_000 + mt_wagering_losses_deduction: [4_000, 6_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.yaml new file mode 100644 index 00000000000..b0eafe19c2f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.yaml @@ -0,0 +1,371 @@ +# N.C. Gen. Stat. 105-153.5(a)(2)d and the Repayment of Claim of Right +# Worksheet (2025 Form D-401 instructions, PDF p. 20; 2016 Form D-401 +# instructions, PDF p. 13). Worksheet for repayments of $3,000 or less, 2018 +# and later: line 3 = 2% of federal AGI (zero if negative), line 4 = line 1 - +# line 3. Repayments above $3,000 are deductible in full. From 2026 (S.L. +# 2026-31 section 1.8, PDF pp. 5-6) only repayments of amounts included in AGI +# as modified for North Carolina count. + +- name: Repayment of $3,000 or less is reduced by 2% of federal AGI. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Worksheet: line 1 2,500; line 3 = 0.02 * 50,000 = 1,000; line 4 = 1,500. + nc_claim_of_right_deduction: 1_500 + +- name: A repayment of exactly $3,000 is still reduced by the floor. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 3_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 100_000 + households: + household: + members: [head] + state_code: NC + output: + # "If the repayment is three thousand dollars ($3,000) or less": + # 3,000 - 0.02 * 100,000 = 1,000. + nc_claim_of_right_deduction: 1_000 + +- name: A repayment above $3,000 is deductible in full. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 3_001 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 100_000 + households: + household: + members: [head] + state_code: NC + output: + nc_claim_of_right_deduction: 3_001 + +- name: No deduction when 2% of AGI exceeds a small repayment. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 1_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 100_000 + households: + household: + members: [head] + state_code: NC + output: + # 1,000 - 2,000 is negative; the deduction is zero. + nc_claim_of_right_deduction: 0 + +- name: Negative AGI gives a zero floor. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: -5_000 + households: + household: + members: [head] + state_code: NC + output: + # Worksheet line 3: "If negative, enter the number zero". + nc_claim_of_right_deduction: 2_500 + +- name: Spouses' repayments are combined on a joint return; a dependent's is not. + period: 2023 + input: + people: + head: + age: 40 + claim_of_right_repayment: 1_500 + spouse: + age: 40 + claim_of_right_repayment: 1_000 + child: + age: 17 + claim_of_right_repayment: 4_000 + tax_units: + tax_unit: + members: [head, spouse, child] + adjusted_gross_income: 60_000 + marital_units: + marital_unit: + members: [head, spouse] + child_marital_unit: + members: [child] + households: + household: + members: [head, spouse, child] + state_code: NC + output: + # The child's repayment belongs on the child's own return. Line 1 is + # 1,500 + 1,000 = 2,500, not above 3,000; 2,500 - 0.02 * 60,000 = 1,300. + nc_claim_of_right_deduction: 1_300 + +- name: From 2018 other miscellaneous expenses do not absorb the floor. + period: 2022 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + unreimbursed_business_employee_expenses: 600 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Section 67(g) disallows other miscellaneous itemized deductions from + # 2018, so the worksheet subtracts the whole 2% floor: 2,500 - 1,000. + nc_claim_of_right_deduction: 1_500 + +- name: Before 2018 other miscellaneous deductions absorb the floor first. + period: 2016 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + unreimbursed_business_employee_expenses: 600 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # 2016 worksheet: line 1 2,500; line 2 = 0.02 * 50,000 = 1,000; line 3 + # (federal Schedule A line 24) 3,100; line 4 = 3,100 - 2,500 = 600; + # line 5 = 1,000 - 600 = 400; line 6 = 2,500 - 400 = 2,100. + nc_claim_of_right_deduction: 2_100 + +- name: Before 2018 other miscellaneous deductions above the floor leave the whole repayment. + period: 2016 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + unreimbursed_business_employee_expenses: 2_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Line 4 = 2,000; line 5 = 1,000 - 2,000, entered as zero; line 6 = 2,500. + nc_claim_of_right_deduction: 2_500 + +- name: Before 2018 a dependent's miscellaneous expenses do not absorb the floor. + period: 2016 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + child: + age: 10 + unreimbursed_business_employee_expenses: 600 + tax_units: + tax_unit: + members: [head, child] + adjusted_gross_income: 50_000 + households: + household: + members: [head, child] + state_code: NC + output: + # The child's expenses are not on the parent's federal Schedule A, so + # line 4 is zero: 2,500 - 0.02 * 50,000 = 1,500. + nc_claim_of_right_deduction: 1_500 + +- name: In 2025 a repayment of income North Carolina had excluded still counts. + period: 2025 + input: + people: + head: + age: 70 + claim_of_right_repayment: 5_000 + nc_claim_of_right_repayment_excluded_from_income: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Before 2026 the test is inclusion in federal AGI. + nc_claim_of_right_deduction: 5_000 + +- name: From 2026 a repayment of income North Carolina excluded is not deductible. + period: 2026 + input: + people: + head: + age: 70 + claim_of_right_repayment: 5_000 + nc_claim_of_right_repayment_excluded_from_income: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # S.L. 2026-31 section 1.8: "an amount included in adjusted gross income, + # as modified in G.S. 105-153.5 and G.S. 105-153.6". For example, repaid + # Social Security benefits that North Carolina deducted when received. + nc_claim_of_right_deduction: 0 + +- name: From 2026 only the part North Carolina taxed counts, and the $3,000 test applies to it. + period: 2026 + input: + people: + head: + age: 70 + claim_of_right_repayment: 5_000 + nc_claim_of_right_repayment_excluded_from_income: 3_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Eligible repayment 2,000, not above 3,000: 2,000 - 0.02 * 50,000 = 1,000. + nc_claim_of_right_deduction: 1_000 + +- name: From 2026 a repayment North Carolina taxed in full is deductible as before. + period: 2026 + input: + people: + head: + age: 40 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + nc_claim_of_right_deduction: 5_000 + +- name: No deduction outside North Carolina. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: SC + output: + nc_claim_of_right_deduction: 0 + +- name: The deduction is added to itemized deductions outside the $20,000 cap. + period: 2024 + input: + people: + head: + age: 40 + claim_of_right_repayment: 5_000 + mortgage_interest: 18_000 + real_estate_taxes: 6_000 + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + adjusted_gross_income: 90_000 + charitable_deduction: 2_000 + medical_expense_deduction: 1_000 + households: + household: + members: [head] + state_code: NC + output: + # Schedule A: line 5 = min(18,000 + 6,000, 20,000) = 20,000; line 6 + # 2,000; line 7d 1,000; line 8 5,000; line 10 = 28,000. + nc_claim_of_right_deduction: 5_000 + nc_itemized_deductions: 28_000 + +- name: A full repayment makes itemizing worthwhile and lowers North Carolina tax. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 80_000 + claim_of_right_repayment: 5_000 + real_estate_taxes: 4_000 + tax_units: + tax_unit: + members: [head] + charitable_deduction: 8_000 + households: + household: + members: [head] + state_code: NC + output: + adjusted_gross_income: 80_000 + # Schedule A: 4,000 + 8,000 + 5,000 = 17,000, above the 12,750 single + # standard deduction. Without the repayment, 12,000 would lose to it. + nc_itemized_deductions: 17_000 + nc_standard_or_itemized_deductions: 17_000 + nc_taxable_income: 63_000 + # 63,000 * 4.25% = 2,677.50 (2,858.13 without the repayment). + nc_income_tax: 2_677.50 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/itemized/nc_wagering_losses_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/itemized/nc_wagering_losses_deduction.yaml new file mode 100644 index 00000000000..155a383ab5e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/itemized/nc_wagering_losses_deduction.yaml @@ -0,0 +1,225 @@ +# N.C. Gen. Stat. 105-153.5(a)(2)e, added by S.L. 2026-41 section 44.2 for +# taxable years beginning on or after January 1, 2025: the wagering losses +# allowed under 26 U.S.C. 165(d), to the extent not deducted in arriving at +# AGI (Form D-400 Schedule A line 9 for 2025, per NCDOR's notice of July 23, +# 2026 and its FAQs of September 11, 2026). + +- name: No deduction before 2025. + period: 2024 + input: + people: + head: + age: 40 + gambling_winnings: 10_000 + gambling_losses: 12_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NC + output: + nc_wagering_losses_deduction: 0 + +- name: NCDOR example, losses above winnings are allowed up to the winnings. + period: 2025 + input: + people: + head: + age: 40 + gambling_winnings: 10_000 + gambling_losses: 12_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NC + output: + # NCDOR notice: Jane Smith had $10,000 of winnings and $12,000 of losses, + # was allowed $10,000 under section 165(d), and can include $10,000. + nc_wagering_losses_deduction: 10_000 + +- name: Losses below winnings are deductible in full. + period: 2025 + input: + people: + head: + age: 40 + gambling_winnings: 10_000 + gambling_losses: 4_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NC + output: + nc_wagering_losses_deduction: 4_000 + +- name: Spouses' winnings and losses are combined on a joint return. + period: 2025 + input: + people: + head: + age: 40 + gambling_winnings: 3_000 + gambling_losses: 5_000 + spouse: + age: 40 + gambling_winnings: 4_000 + gambling_losses: 1_000 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: NC + output: + # Losses 6,000, allowed to the extent of winnings of 7,000. + nc_wagering_losses_deduction: 6_000 + +- name: The deduction continues in 2026. + period: 2026 + input: + people: + head: + age: 40 + gambling_winnings: 10_000 + gambling_losses: 12_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NC + output: + # Section 165(d) as amended by P.L. 119-21 section 70114 allows 90% of + # losses, 10,800, to the extent of winnings: 10,000. + nc_wagering_losses_deduction: 10_000 + +- name: From 2026 only 90% of losses count. + period: 2026 + input: + people: + head: + age: 40 + gambling_winnings: 10_000 + gambling_losses: 8_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NC + output: + # P.L. 119-21 section 70114, in North Carolina's July 5, 2025 Code: + # 0.9 * 8,000 = 7,200, below the 10,000 of winnings. Depends on the + # federal deduction applying the 90% (PolicyEngine/policyengine-us#9637). + nc_wagering_losses_deduction: 7_200 + +- name: A dependent's gambling is not on the parent's return. + period: 2025 + input: + people: + head: + age: 40 + child: + age: 17 + gambling_winnings: 10_000 + gambling_losses: 10_000 + tax_units: + tax_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: NC + output: + # Federal gross income leaves out dependents' income, so their wagering + # losses cannot be deducted on the parent's return. Depends on the + # federal deduction counting only the head and spouse. + nc_wagering_losses_deduction: 0 + +- name: No deduction outside North Carolina. + period: 2025 + input: + people: + head: + age: 40 + gambling_winnings: 10_000 + gambling_losses: 12_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: SC + output: + nc_wagering_losses_deduction: 0 + +- name: NCDOR example as a 2025 return, with the winnings in AGI. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + gambling_winnings: 10_000 + gambling_losses: 12_000 + tax_units: + tax_unit: + members: [head] + # Wages plus the winnings, which section 61 and Schedule 1 line 8b + # put in federal AGI. + adjusted_gross_income: 70_000 + charitable_deduction: 5_000 + households: + household: + members: [head] + state_code: NC + output: + # Schedule A: line 6 5,000 + line 9 10,000 = 15,000, above the 12,750 + # single standard deduction. + nc_itemized_deductions: 15_000 + nc_taxable_income: 55_000 + # 55,000 * 4.25%. + nc_income_tax: 2_337.50 + +- name: The same return in 2024 gets no wagering deduction. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + gambling_winnings: 10_000 + gambling_losses: 12_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 70_000 + charitable_deduction: 5_000 + households: + household: + members: [head] + state_code: NC + output: + nc_itemized_deductions: 5_000 + # The 12,750 standard deduction exceeds itemized deductions of 5,000. + nc_taxable_income: 57_250 + # 57,250 * 4.5%. + nc_income_tax: 2_576.25 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_phase_out.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_phase_out.yaml index 07de472c2ad..78ea7537a7a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_phase_out.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_phase_out.yaml @@ -134,3 +134,20 @@ # L1 = 130,000 - 30,000 = 100,000; L4 = 80,000; L8 = 2,734.50. # Reduction = min(80,000, 2,734.50) = 2,734.50 (same as without tuition). ny_itemized_deductions_phase_out: 2_734.50 + +- name: Gambling losses are not subject to the New York overall limitation. + absolute_error_margin: 0.01 + period: 2025 + input: + filing_status: SINGLE + adjusted_gross_income: 1_000_000 + gambling_winnings: 50_000 + gambling_losses: 50_000 + ny_itemized_deductions_phase_out_applies: true + ny_salt_deduction: 0 + state_code: NY + output: + ny_wagering_losses_deduction: 50_000 + ny_itemized_deductions_max: 50_000 + # Worksheet line 1 equals line 2 (gambling losses), so nothing is limited. + ny_itemized_deductions_phase_out: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_wagering_losses_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_wagering_losses_deduction.yaml new file mode 100644 index 00000000000..127f2bf0a40 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_wagering_losses_deduction.yaml @@ -0,0 +1,51 @@ +- name: New York allows all gambling losses up to winnings in 2026. + absolute_error_margin: 0.01 + period: 2026 + input: + age: 40 + filing_status: SINGLE + employment_income: 50_000 + gambling_winnings: 20_000 + gambling_losses: 20_000 + ny_salt_deduction: 0 + state_code: NY + output: + # The federal deduction is 90% of the losses from 2026. + wagering_losses_deduction: 18_000 + ny_wagering_losses_deduction: 20_000 + ny_agi: 70_000 + ny_itemized_deductions: 20_000 + # 70,000 - 20,000 + ny_taxable_income: 50_000 + +- name: New York gambling losses are capped at winnings. + absolute_error_margin: 0.01 + period: 2025 + input: + gambling_winnings: 5_000 + gambling_losses: 8_000 + state_code: NY + output: + ny_wagering_losses_deduction: 5_000 + +- name: A dependent's gambling is not on the parent's New York return. + absolute_error_margin: 0.01 + period: 2025 + input: + people: + parent: + age: 40 + child: + age: 17 + is_tax_unit_dependent: true + gambling_winnings: 10_000 + gambling_losses: 10_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NY + output: + ny_wagering_losses_deduction: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/taxable_income/pa_total_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/taxable_income/pa_total_taxable_income.yaml index 9e21cddcfb6..587e961a7be 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/taxable_income/pa_total_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/taxable_income/pa_total_taxable_income.yaml @@ -73,3 +73,55 @@ pa_total_taxable_income: 30_000 # 30,000 x 3.07% pa_income_tax_before_forgiveness: 921 + +- name: Pennsylvania taxes gambling winnings net of each spouse's own wagering costs. + absolute_error_margin: 0.01 + period: 2026 + input: + people: + head: + age: 65 + employment_income: 30_000 + gambling_winnings: 10_000 + gambling_losses: 8_000 + ssi: 0 + spouse: + age: 65 + gambling_winnings: 2_000 + gambling_losses: 5_000 + ssi: 0 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: PA + output: + adjusted_gross_income: 42_000 + pa_gambling_loss_offset: [8_000, 2_000] + # 30,000 + (10,000 - 8,000) + max(2,000 - 5,000, 0); the spouse's extra + # 3,000 of costs cannot reduce the head's winnings, and the federal 90% + # limit does not apply. + pa_total_taxable_income: 32_000 + pa_eligibility_income: 32_000 + # PA-1000 line 11b counts the same net winnings, once. + pa_property_tax_or_rent_rebate_income: 32_000 + # 3.07% x 32,000 + pa_income_tax_before_forgiveness: 982.40 + +- name: Pennsylvania taxes gross winnings when there are no losses. + absolute_error_margin: 0.01 + period: 2025 + input: + age: 40 + employment_income: 30_000 + gambling_winnings: 10_000 + state_code: PA + output: + pa_gambling_loss_offset: 0 + pa_total_taxable_income: 40_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml index 5e60ce8d7b7..ddb6de2bf2b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml @@ -119,7 +119,6 @@ person1: age: 65 ssi: 0 - gambling_winnings: 1_000 alimony_income: 2_000 workers_compensation: 3_000 disability_benefits: 4_000 @@ -132,7 +131,31 @@ members: [person1] state_code: PA output: - pa_property_tax_or_rent_rebate_income: 20_000 + pa_property_tax_or_rent_rebate_income: 19_000 + +- name: Case 7b, gambling winnings count once, net of losses up to the winnings. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + ssi: 0 + employment_income: 10_000 + gambling_winnings: 1_000 + gambling_losses: 400 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: PA + output: + # Federal AGI already holds the 1,000 of winnings (line 11b is not added + # again), and the 400 of losses come off. + adjusted_gross_income: 11_000 + pa_property_tax_or_rent_rebate_income: 10_600 - name: Case 8, CSRS exclusion is capped by each recipient's CSRS income. period: 2025 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/va_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/va_itemized_deductions.yaml index 8928e81776f..541873951c3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/va_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/va_itemized_deductions.yaml @@ -66,3 +66,38 @@ state_code: VA output: va_itemized_deductions: 40_000 #20,000+20,000 + +- name: Gambling losses are Virginia itemized deductions below the limitation threshold. + absolute_error_margin: 0.01 + period: 2025 + input: + filing_status: SINGLE + adjusted_gross_income: 100_000 + gambling_winnings: 20_000 + gambling_losses: 30_000 + charitable_deduction: 10_000 + state_and_local_sales_or_income_tax: 0 + real_estate_taxes: 0 + state_code: VA + output: + wagering_losses_deduction: 20_000 + va_itemized_deductions: 30_000 + +- name: Above the threshold gambling losses stay in the total and are not reduced. + absolute_error_margin: 0.01 + period: 2025 + input: + filing_status: SINGLE + adjusted_gross_income: 400_000 + gambling_winnings: 20_000 + gambling_losses: 30_000 + charitable_deduction: 10_000 + state_and_local_sales_or_income_tax: 0 + real_estate_taxes: 0 + state_code: VA + output: + wagering_losses_deduction: 20_000 + # Worksheet line 1: 30,000. Line 2 (gambling losses): 20,000. Line 3: + # 10,000, 80% of which is 8,000. Line 8: 3% x (400,000 - 332,700) = + # 2,019. Deduction: 30,000 - 2,019. + va_itemized_deductions: 27_981 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_itemized_deduction_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_itemized_deduction_credit.yaml index 38850b7f5cd..ff8b7375da7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_itemized_deduction_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_itemized_deduction_credit.yaml @@ -25,3 +25,17 @@ state_code: WI output: wi_itemized_deduction_credit_potential: 0 + +- name: Gambling losses do not count for the Wisconsin itemized deduction credit. + absolute_error_margin: 0.01 + period: 2025 + input: + gambling_winnings: 20_000 + gambling_losses: 20_000 + charitable_deduction: 15_000 + wi_standard_deduction: 10_000 + state_code: WI + output: + wagering_losses_deduction: 20_000 + # 5% x (15,000 - 10,000); the 20,000 of gambling losses is left out. + wi_itemized_deduction_credit_potential: 250 diff --git a/policyengine_us/tests/unit/test_gambling_winnings_and_losses_properties.py b/policyengine_us/tests/unit/test_gambling_winnings_and_losses_properties.py new file mode 100644 index 00000000000..3707061a2fc --- /dev/null +++ b/policyengine_us/tests/unit/test_gambling_winnings_and_losses_properties.py @@ -0,0 +1,215 @@ +"""Properties of the federal treatment of gambling winnings and losses. + +26 U.S.C. 61(a) puts gambling winnings in gross income (Schedule 1 line 8b). +26 U.S.C. 165(d) lets an itemizer deduct wagering losses up to wagering gains +(Schedule A line 16), and P.L. 119-21 section 70114 allows 90% of the losses +for taxable years beginning after 2025. A dependent's gambling belongs on the +dependent's own return. + +YAML cases check specific households. These tests reuse a generated grid to +check relations between households at every point of winnings x losses x +other itemized deductions. Each year uses one vectorized simulation; the grid +point with no gambling is the baseline for the others. + +- The deduction equals min(share x losses, winnings), with the share restated + here from the statute (1 through 2025, 0.9 from 2026). +- It is non-negative, never exceeds the winnings or the share of losses, and + never falls as either rises. +- Adjusted gross income rises by exactly the winnings. +- The deduction is in itemized deductions once, and an itemizer's taxable + income is AGI less those deductions. +- Income tax never falls as winnings rise and never rises as losses rise. +- Gambling never lowers taxable income or income tax, including when winnings + equal losses. It raises taxable income by at most the winnings, and by + exactly the winnings less the deduction for a filer who itemizes either way. +- A dependent's winnings and losses change nothing on the filer's return. + +Before PolicyEngine/policyengine-us#9637 was fixed, gross income left the +winnings out while itemizers still deducted the losses, so gambling lowered +tax; the deduction had no 90% share; and it counted a dependent's gambling. +""" + +import itertools + +import numpy as np +import pytest + +from policyengine_us import Simulation + +AMOUNTS = [0, 1, 999, 5_000, 10_000, 25_000, 30_000, 250_000] +# A charitable deduction of 30,000 exceeds every standard deduction, so those +# filers itemize with or without gambling. +OTHER_ITEMIZED = [0, 30_000] +AXES = [AMOUNTS, AMOUNTS, OTHER_ITEMIZED] +GRID = list(itertools.product(*AXES)) +SHAPE = tuple(len(axis) for axis in AXES) +WINNINGS, LOSSES, OTHER = ( + np.array([point[i] for point in GRID], dtype=float) for i in range(len(AXES)) +) +# Winnings and losses of a dependent child; the first household has none. +DEPENDENT_GAMBLING = [ + (0, 0), + (10_000, 10_000), + (30_000, 25_000), + (250_000, 0), + (5_000, 250_000), +] +YEARS = [2024, 2025, 2026] +# Share of wagering losses section 165(d) allows (P.L. 119-21 section 70114). +LOSS_SHARE = {2024: 1.0, 2025: 1.0, 2026: 0.9} +WAGES = 60_000 +VARIABLES = [ + "adjusted_gross_income", + "wagering_losses_deduction", + "itemized_taxable_income_deductions", + "tax_unit_itemizes", + "taxable_income", + "income_tax", +] +# PolicyEngine computes in float32, whose spacing near 300,000 is 3 cents. +ATOL = 0.05 + + +def calculate(year): + """Grid filers, then parents whose dependent child has the gambling.""" + situation = {"people": {}, "tax_units": {}, "households": {}} + + def add_household(name, members, other_itemized): + situation["tax_units"][f"t_{name}"] = { + "members": members, + "charitable_deduction": {year: float(other_itemized)}, + # No estimated sales tax deduction, so deductions are exact. + "salt_deduction": {year: 0}, + } + situation["households"][f"h_{name}"] = { + "members": members, + "state_code": {year: "TX"}, + } + + for i, (winnings, losses, other) in enumerate(GRID): + situation["people"][f"filer_{i}"] = { + "age": {year: 40}, + "employment_income": {year: WAGES}, + "gambling_winnings": {year: winnings}, + "gambling_losses": {year: losses}, + } + add_household(f"grid_{i}", [f"filer_{i}"], other) + for j, (winnings, losses) in enumerate(DEPENDENT_GAMBLING): + situation["people"][f"parent_{j}"] = { + "age": {year: 40}, + "employment_income": {year: WAGES}, + } + situation["people"][f"child_{j}"] = { + "age": {year: 17}, + "gambling_winnings": {year: winnings}, + "gambling_losses": {year: losses}, + } + add_household(f"dependent_{j}", [f"parent_{j}", f"child_{j}"], 0) + simulation = Simulation(situation=situation) + values = { + variable: np.asarray(simulation.calculate(variable, year)) + for variable in VARIABLES + } + grid = {variable: value[: len(GRID)] for variable, value in values.items()} + dependents = {variable: value[len(GRID) :] for variable, value in values.items()} + return grid, dependents + + +@pytest.fixture(scope="module") +def results(): + return {year: calculate(year) for year in YEARS} + + +def baseline(values): + """Each grid point's value with no gambling and the same other deductions.""" + table = values.reshape(SHAPE) + return np.broadcast_to(table[:1, :1, :], SHAPE).reshape(-1) + + +@pytest.mark.parametrize("year", YEARS) +def test_deduction_is_the_section_165d_amount(results, year): + grid, _ = results[year] + np.testing.assert_allclose( + grid["wagering_losses_deduction"], + np.minimum(LOSS_SHARE[year] * LOSSES, WINNINGS), + atol=ATOL, + ) + + +@pytest.mark.parametrize("year", YEARS) +def test_deduction_is_bounded_and_monotone(results, year): + grid, _ = results[year] + deduction = grid["wagering_losses_deduction"] + assert np.all(deduction >= 0) + assert np.all(deduction <= WINNINGS + ATOL) + assert np.all(deduction <= LOSS_SHARE[year] * LOSSES + ATOL) + table = deduction.reshape(SHAPE) + assert np.all(np.diff(table, axis=0) >= -ATOL), "falls as winnings rise" + assert np.all(np.diff(table, axis=1) >= -ATOL), "falls as losses rise" + + +@pytest.mark.parametrize("year", YEARS) +def test_winnings_are_in_adjusted_gross_income(results, year): + grid, _ = results[year] + np.testing.assert_allclose( + grid["adjusted_gross_income"], WAGES + WINNINGS, atol=ATOL + ) + + +@pytest.mark.parametrize("year", YEARS) +def test_deduction_is_in_itemized_deductions_once(results, year): + grid, _ = results[year] + deduction = grid["wagering_losses_deduction"] + # No income here reaches the section 68 limitation. + np.testing.assert_allclose( + grid["itemized_taxable_income_deductions"], OTHER + deduction, atol=ATOL + ) + itemizes = grid["tax_unit_itemizes"] + assert itemizes.any() and not itemizes.all() + np.testing.assert_allclose( + grid["taxable_income"][itemizes], + (WAGES + WINNINGS - OTHER - deduction)[itemizes], + atol=ATOL, + ) + + +@pytest.mark.parametrize("year", YEARS) +def test_income_tax_is_monotone_in_winnings_and_losses(results, year): + grid, _ = results[year] + table = grid["income_tax"].reshape(SHAPE) + assert np.all(np.diff(table, axis=0) >= -ATOL), "falls as winnings rise" + assert np.all(np.diff(table, axis=1) <= ATOL), "rises as losses rise" + + +@pytest.mark.parametrize("year", YEARS) +def test_gambling_never_lowers_taxable_income_or_income_tax(results, year): + grid, _ = results[year] + change = grid["taxable_income"] - baseline(grid["taxable_income"]) + assert np.all(change >= -ATOL), "gambling lowered taxable income" + assert np.all(change <= WINNINGS + ATOL), "taxed more than the winnings" + tax_change = grid["income_tax"] - baseline(grid["income_tax"]) + assert np.all(tax_change >= -ATOL), "gambling lowered income tax" + # Equal winnings and losses are the case the issue reported. + equal = WINNINGS == LOSSES + assert np.all(tax_change[equal] >= -ATOL) + # A filer who itemizes either way pays tax on the winnings less the + # deduction. + itemizes_either_way = OTHER > 0 + assert np.all(grid["tax_unit_itemizes"][itemizes_either_way]) + net_gain = WINNINGS - grid["wagering_losses_deduction"] + np.testing.assert_allclose( + change[itemizes_either_way], net_gain[itemizes_either_way], atol=ATOL + ) + + +@pytest.mark.parametrize("year", YEARS) +def test_a_dependents_gambling_does_not_change_the_filers_return(results, year): + _, dependents = results[year] + for variable in VARIABLES: + values = dependents[variable] + # The first household's child has no gambling. + np.testing.assert_allclose( + values.astype(float), float(values[0]), atol=ATOL, err_msg=variable + ) + assert np.all(dependents["wagering_losses_deduction"] == 0) + assert np.all(dependents["adjusted_gross_income"] == WAGES) diff --git a/policyengine_us/tests/unit/test_nc_claim_of_right_properties.py b/policyengine_us/tests/unit/test_nc_claim_of_right_properties.py new file mode 100644 index 00000000000..7ff5a694e8d --- /dev/null +++ b/policyengine_us/tests/unit/test_nc_claim_of_right_properties.py @@ -0,0 +1,186 @@ +"""Properties of North Carolina's claim of right repayment deduction. + +N.C. Gen. Stat. 105-153.5(a)(2)d, added by S.L. 2016-5 for 2014 and later: a +repayment of $3,000 or less is deductible less (i) the section 67(a) floor, +two percent of federal AGI, minus (ii) the other miscellaneous itemized +deductions, not below zero; a larger repayment is deductible in full. From +2026 (S.L. 2026-31 section 1.8) only repayments of amounts included in AGI as +modified for North Carolina count. + +YAML cases check single households. These tests check properties at every +point of a grid, one vectorized simulation per year, and compare the model +with independent line-by-line implementations of NCDOR's worksheets. A YAML +case cannot state a property that holds for all inputs, such as monotonicity +across the $3,000 threshold: + +- the deduction matches the 2016 Repayment of Claim of Right Worksheet, + which subtracts other miscellaneous deductions from the floor, and the + 2025 worksheet, which subtracts the whole floor because section 67(g) + disallows those deductions from 2018; +- it is non-negative, never exceeds the repayment, rises with the repayment + (also across the $3,000 threshold) and falls as AGI rises; +- it adds to North Carolina itemized deductions one for one, including when + property taxes already exceed the $20,000 mortgage and property tax cap, + and never raises North Carolina income tax, including when the repayment + moves the filer from the standard deduction to itemizing; +- from 2026 it is the 2025 worksheet applied to the part of the repayment + North Carolina taxed when received; before 2026 that split has no effect. +""" + +import itertools + +import numpy as np +import pytest + +from policyengine_us import Simulation + +REPAYMENTS = [0, 1, 999, 1_000, 2_999, 3_000, 3_000.5, 3_001, 10_000, 250_000] +AGIS = [-5_000, 0, 50_000, 100_000, 150_000, 1_000_000] +OTHER_MISC = [0, 600, 5_000] +CHARITY = [0, 20_000] +PROPERTY_TAX = [0, 25_000] +AXES = [REPAYMENTS, AGIS, OTHER_MISC, CHARITY, PROPERTY_TAX] +GRID = list(itertools.product(*AXES)) +SHAPE = tuple(len(axis) for axis in AXES) +REPAYMENT, AGI, MISC, CHARITABLE, PROPERTY = ( + np.array([point[i] for point in GRID], dtype=float) for i in range(len(AXES)) +) +# 2026 grid: repayment x share of it North Carolina excluded when received x AGI. +SHARES = [0, 0.25, 0.75, 1] +GRID_2026 = list(itertools.product(REPAYMENTS, SHARES, AGIS)) +REPAYMENT_2026, SHARE_2026, AGI_2026 = ( + np.array([point[i] for point in GRID_2026], dtype=float) for i in range(3) +) + + +def grid_simulation(year, people, tax_units): + """One single-person North Carolina tax unit per grid point.""" + size = len(next(iter(people.values()))) + situation = {"people": {}, "tax_units": {}, "households": {}} + for i in range(size): + person = f"p{i}" + situation["people"][person] = { + "age": {year: 40}, + **{name: {year: float(values[i])} for name, values in people.items()}, + } + situation["tax_units"][f"t{i}"] = { + "members": [person], + **{name: {year: float(values[i])} for name, values in tax_units.items()}, + } + situation["households"][f"h{i}"] = { + "members": [person], + "state_code": {year: "NC"}, + } + return Simulation(situation=situation) + + +def worksheet_2016(repayment, agi, other_misc): + """2016 Form D-401 instructions, PDF p. 13, lines 1-6.""" + line1 = repayment + line2 = 0.02 * np.maximum(agi, 0) # federal Schedule A line 26 + line3 = other_misc + repayment # federal Schedule A line 24 + line4 = line3 - line1 + line5 = np.maximum(line2 - line4, 0) + line6 = np.maximum(line1 - line5, 0) + return np.where(repayment > 3_000, repayment, line6) + + +def worksheet_2025(repayment, agi): + """2025 Form D-401 instructions, PDF p. 20, lines 1-4.""" + line3 = np.maximum(0.02 * agi, 0) + line4 = np.maximum(repayment - line3, 0) + return np.where(repayment > 3_000, repayment, line4) + + +@pytest.fixture(scope="module") +def results(): + out = {} + for year in [2016, 2025]: + sim = grid_simulation( + year, + { + "claim_of_right_repayment": REPAYMENT, + "unreimbursed_business_employee_expenses": MISC, + "real_estate_taxes": PROPERTY, + }, + {"adjusted_gross_income": AGI, "charitable_deduction": CHARITABLE}, + ) + out[year] = { + variable: np.asarray(sim.calculate(variable, year)) + for variable in [ + "nc_claim_of_right_deduction", + "nc_itemized_deductions", + "nc_income_tax", + ] + } + return out + + +def test_deduction_matches_the_worksheets(results): + np.testing.assert_allclose( + results[2016]["nc_claim_of_right_deduction"], + worksheet_2016(REPAYMENT, AGI, MISC), + atol=1e-6, + ) + np.testing.assert_allclose( + results[2025]["nc_claim_of_right_deduction"], + worksheet_2025(REPAYMENT, AGI), + atol=1e-6, + ) + + +@pytest.mark.parametrize("year", [2016, 2025]) +def test_deduction_is_bounded_and_monotone(results, year): + deduction = results[year]["nc_claim_of_right_deduction"] + assert np.all(deduction >= 0) + assert np.all(deduction <= REPAYMENT + 1e-6) + above = REPAYMENT > 3_000 + np.testing.assert_allclose(deduction[above], REPAYMENT[above]) + table = deduction.reshape(SHAPE) + assert np.all(np.diff(table, axis=0) >= -1e-6), "falls as the repayment rises" + assert np.all(np.diff(table, axis=1) <= 1e-6), "rises as AGI rises" + assert np.all(np.diff(table, axis=2) >= -1e-6), "falls as other expenses rise" + + +def test_other_miscellaneous_expenses_matter_only_before_2018(results): + for year, matters in [(2016, True), (2025, False)]: + table = results[year]["nc_claim_of_right_deduction"].reshape(SHAPE) + spread = np.abs(table - table[:, :, :1]).max() + assert (spread > 0) == matters, year + + +@pytest.mark.parametrize("year", [2016, 2025]) +def test_deduction_adds_to_itemized_deductions_and_never_raises_tax(results, year): + deduction = results[year]["nc_claim_of_right_deduction"].reshape(SHAPE) + itemized = results[year]["nc_itemized_deductions"].reshape(SHAPE) + tax = results[year]["nc_income_tax"].reshape(SHAPE) + # Row 0 of the repayment axis is the same tax unit with no repayment. + np.testing.assert_allclose(itemized - itemized[:1], deduction, atol=1e-6) + assert np.all(tax <= tax[:1] + 1e-6), "a repayment raised North Carolina tax" + assert np.all(np.diff(tax, axis=0) <= 1e-6), "tax rises with the repayment" + + +def test_from_2026_only_the_part_north_carolina_taxed_counts(): + excluded = SHARE_2026 * REPAYMENT_2026 + deductions = {} + for year in [2025, 2026]: + sim = grid_simulation( + year, + { + "claim_of_right_repayment": REPAYMENT_2026, + "nc_claim_of_right_repayment_excluded_from_income": excluded, + }, + {"adjusted_gross_income": AGI_2026}, + ) + deductions[year] = np.asarray( + sim.calculate("nc_claim_of_right_deduction", year) + ) + np.testing.assert_allclose( + deductions[2025], worksheet_2025(REPAYMENT_2026, AGI_2026), atol=1e-6 + ) + np.testing.assert_allclose( + deductions[2026], + worksheet_2025(REPAYMENT_2026 - excluded, AGI_2026), + atol=1e-6, + ) + assert np.all(deductions[2026] <= deductions[2025] + 1e-6) diff --git a/policyengine_us/tests/unit/test_nc_wagering_losses_properties.py b/policyengine_us/tests/unit/test_nc_wagering_losses_properties.py new file mode 100644 index 00000000000..5d1eb2922ab --- /dev/null +++ b/policyengine_us/tests/unit/test_nc_wagering_losses_properties.py @@ -0,0 +1,166 @@ +"""Properties of North Carolina's wagering losses itemized deduction. + +N.C. Gen. Stat. 105-153.5(a)(2)e, added by S.L. 2026-41 section 44.2 for +taxable years beginning on or after January 1, 2025, allows the wagering +losses allowed under 26 U.S.C. 165(d), to the extent not deducted in arriving +at AGI. The winnings stay in AGI; NCDOR's FAQs (Q38) say they cannot be +reduced by the losses. North Carolina's Code is the Internal Revenue Code as +of July 5, 2025 (S.L. 2026-31 section 12.(a)), so P.L. 119-21 section 70114 +limits the deduction to 90% of losses from 2026. + +YAML cases check single households. These tests check properties at every +point of a winnings x losses x charitable deduction x property tax grid, +using three vectorized simulations per year (with the winnings in AGI, +without gambling, and with the AGI PolicyEngine computes): + +- the deduction is zero before 2025 and equals the section 165(d) amount, + min(share x losses, winnings), with share 1 in 2025 and 0.9 from 2026; +- it never exceeds the winnings or the losses and rises with each; +- it adds to North Carolina itemized deductions one for one, including when + property taxes already exceed the $20,000 mortgage and property tax cap; +- with the winnings in AGI, gambling never lowers North Carolina taxable + income and raises it by at most the winnings, by exactly the net gain for + a filer who itemizes either way; +- the same holds with the AGI PolicyEngine computes. + +The 2026 section 165(d) check and the modeled-AGI check depend on the federal +treatment fixed in PolicyEngine/policyengine-us#9637: gross income counts +gambling winnings, and the federal deduction has the 90% share. Both failed +before that fix. +""" + +import itertools + +import numpy as np +import pytest + +from policyengine_us import Simulation + +AMOUNTS = [0, 1, 999, 5_000, 10_000, 12_000, 250_000] +CHARITY = [0, 30_000] +PROPERTY_TAX = [0, 25_000] +AXES = [AMOUNTS, AMOUNTS, CHARITY, PROPERTY_TAX] +GRID = list(itertools.product(*AXES)) +SHAPE = tuple(len(axis) for axis in AXES) +WINNINGS, LOSSES, CHARITABLE, PROPERTY = ( + np.array([point[i] for point in GRID], dtype=float) for i in range(len(AXES)) +) +YEARS = [2024, 2025, 2026] +# Share of wagering losses section 165(d) allows (P.L. 119-21 section 70114). +LOSS_SHARE = {2025: 1.0, 2026: 0.9} +WAGES = 60_000 +# From 2026 the deduction is 90% of losses, so it is no longer a whole number, +# and float32 sums near 30,000 resolve to about 0.002. +FLOAT32_ATOL = 0.01 + + +def grid_simulation(year, gambling, agi=None): + """One single-person North Carolina 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}, + "employment_income": {year: WAGES}, + "real_estate_taxes": {year: PROPERTY[i]}, + "gambling_winnings": {year: WINNINGS[i] if gambling else 0}, + "gambling_losses": {year: LOSSES[i] if gambling else 0}, + } + tax_unit = { + "members": [person], + "charitable_deduction": {year: CHARITABLE[i]}, + } + if agi is not None: + tax_unit["adjusted_gross_income"] = {year: float(agi[i])} + situation["tax_units"][f"t{i}"] = tax_unit + situation["households"][f"h{i}"] = { + "members": [person], + "state_code": {year: "NC"}, + } + return Simulation(situation=situation) + + +def calculate(sim, year): + return { + variable: np.asarray(sim.calculate(variable, year)) + for variable in [ + "nc_wagering_losses_deduction", + "nc_itemized_deductions", + "nc_standard_deduction", + "nc_taxable_income", + ] + } + + +@pytest.fixture(scope="module") +def results(): + out = {} + for year in YEARS: + out[year] = { + "with": calculate(grid_simulation(year, True, agi=WAGES + WINNINGS), year), + "without": calculate( + grid_simulation(year, False, agi=np.full(len(GRID), WAGES)), + year, + ), + "modeled": calculate(grid_simulation(year, True), year), + } + return out + + +@pytest.mark.parametrize("year", YEARS) +def test_deduction_is_the_section_165d_amount_from_2025(results, year): + deduction = results[year]["with"]["nc_wagering_losses_deduction"] + if year < 2025: + assert np.all(deduction == 0) + return + np.testing.assert_allclose( + deduction, np.minimum(LOSS_SHARE[year] * LOSSES, WINNINGS), atol=1e-6 + ) + + +@pytest.mark.parametrize("year", [2025, 2026]) +def test_deduction_is_bounded_and_monotone(results, year): + deduction = results[year]["with"]["nc_wagering_losses_deduction"] + assert np.all(deduction >= 0) + assert np.all(deduction <= WINNINGS + 1e-6) + assert np.all(deduction <= LOSSES + 1e-6) + table = deduction.reshape(SHAPE) + assert np.all(np.diff(table, axis=0) >= -1e-6), "falls as winnings rise" + assert np.all(np.diff(table, axis=1) >= -1e-6), "falls as losses rise" + + +@pytest.mark.parametrize("year", YEARS) +def test_deduction_adds_to_itemized_deductions_outside_the_cap(results, year): + with_gambling = results[year]["with"] + change = ( + with_gambling["nc_itemized_deductions"] + - results[year]["without"]["nc_itemized_deductions"] + ) + np.testing.assert_allclose( + change, with_gambling["nc_wagering_losses_deduction"], atol=FLOAT32_ATOL + ) + + +@pytest.mark.parametrize("year", YEARS) +def test_gambling_never_lowers_taxable_income_when_winnings_are_in_agi(results, year): + with_gambling = results[year]["with"] + without = results[year]["without"] + change = with_gambling["nc_taxable_income"] - without["nc_taxable_income"] + assert np.all(change >= -1e-6), "gambling lowered NC taxable income" + assert np.all(change <= WINNINGS + 1e-6), "taxed more than the winnings" + itemizes = without["nc_itemized_deductions"] > without["nc_standard_deduction"] + assert itemizes.any() and not itemizes.all() + net_gain = WINNINGS - with_gambling["nc_wagering_losses_deduction"] + np.testing.assert_allclose(change[itemizes], net_gain[itemizes], atol=FLOAT32_ATOL) + + +@pytest.mark.parametrize("year", [2025, 2026]) +def test_gambling_never_lowers_taxable_income_with_modeled_agi(results, year): + # Depends on federal gross income counting gambling winnings + # (PolicyEngine/policyengine-us#9637): without them in AGI, this + # deduction would subtract losses from income that never held the gains. + change = ( + results[year]["modeled"]["nc_taxable_income"] + - results[year]["without"]["nc_taxable_income"] + ) + assert np.all(change >= -1e-6), "gambling lowered NC taxable income" diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/wagering_losses_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/wagering_losses_deduction.py index db1e3293763..d1e221ac6c8 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/wagering_losses_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/wagering_losses_deduction.py @@ -6,11 +6,23 @@ class wagering_losses_deduction(Variable): entity = TaxUnit label = "Wagering losses deduction" unit = USD - documentation = "Deduction from taxable income for gambling losses, capped at gambling winnings." + documentation = ( + "Itemized deduction for gambling losses (Schedule A line 16): the " + "allowed share of losses, up to gambling winnings." + ) definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/165#d" + reference = ( + "https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title26-section165&num=0&edition=prelim", + # P.L. 119-21 sec. 70114: 90% of losses from 2026 + "https://www.govinfo.gov/content/pkg/PLAW-119publ21/pdf/PLAW-119publ21.pdf#page=96", + "https://www.irs.gov/pub/irs-prior/i1040sca--2024.pdf#page=11", + ) def formula(tax_unit, period, parameters): - gambling_losses = add(tax_unit, period, ["gambling_losses"]) - gambling_winnings = add(tax_unit, period, ["gambling_winnings"]) - return min_(gambling_losses, gambling_winnings) + p = parameters(period).gov.irs.deductions.itemized.wagering_losses + # Losses are deductible only against gambling winnings reported on + # the return. A dependent's gambling is on the dependent's own + # return, as irs_gross_income leaves their winnings off this one. + losses = tax_unit_non_dep_add(tax_unit, period, ["gambling_losses"]) + winnings = tax_unit_non_dep_add(tax_unit, period, ["gambling_winnings"]) + return min_(p.rate * losses, winnings) diff --git a/policyengine_us/variables/gov/states/al/tax/income/al_gambling_winnings.py b/policyengine_us/variables/gov/states/al/tax/income/al_gambling_winnings.py new file mode 100644 index 00000000000..377b8043315 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/tax/income/al_gambling_winnings.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class al_gambling_winnings(Variable): + value_type = float + entity = Person + label = "Alabama gambling winnings" + unit = USD + documentation = ( + "Gambling winnings reported on the Alabama return of the tax unit's " + "head or spouse. A dependent who must file reports their own " + "winnings on their own return." + ) + definition_period = YEAR + reference = ( + # 2025 Form 40 booklet: who must file, and taxable income + # PDF pages 5, 7 + "https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=5", + ) + defined_for = StateCode.AL + + def formula(person, period, parameters): + is_dependent = person("is_tax_unit_dependent", period) + return ~is_dependent * person("gambling_winnings", period) diff --git a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_gambling_loss_deduction.py b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_gambling_loss_deduction.py new file mode 100644 index 00000000000..f13bd487aee --- /dev/null +++ b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_gambling_loss_deduction.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class al_gambling_loss_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Alabama gambling loss deduction" + unit = USD + documentation = ( + "Alabama itemized deduction for gambling losses, to the extent of " + "gambling winnings and not subject to the 2% limit on miscellaneous " + "deductions. Alabama does not follow the federal 90% limit that " + "starts in 2026." + ) + definition_period = YEAR + reference = ( + # 2025 Form 40 booklet, Schedule A line 25 + "https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=21", + # Ala. Admin. Code r. 810-3-17-.01 + "https://admincode.legislature.state.al.us/api/rule/810-3-17-.01", + # ALDOR summary of P.L. 119-21: section 165(d), "Tied to Federal: No" + # PDF pages 9-10 + "https://www.revenue.alabama.gov/wp-content/uploads/2025/11/OBBBA-Executive-Summary_FinalwAppendixA_10.31.25.pdf#page=9", + ) + defined_for = StateCode.AL + + def formula(tax_unit, period, parameters): + # Dependents report their gambling on their own returns. + losses = tax_unit_non_dep_add(tax_unit, period, ["gambling_losses"]) + winnings = tax_unit_non_dep_add(tax_unit, period, ["gambling_winnings"]) + return min_(losses, winnings) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py index 80c39a22960..7c8618fdac5 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py @@ -28,6 +28,7 @@ def formula(tax_unit, period, parameters): "charitable_deduction", "charitable_deduction_for_non_itemizers", "misc_deduction", + "wagering_losses_deduction", ) ] deductions += [ @@ -35,6 +36,7 @@ def formula(tax_unit, period, parameters): "real_estate_taxes", "ca_charitable_deduction", "ca_misc_deduction", + "ca_wagering_losses_deduction", ] # Federal investment interest is nested in interest_deduction. return add(tax_unit, period, deductions) - add( diff --git a/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_wagering_losses_deduction.py b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_wagering_losses_deduction.py new file mode 100644 index 00000000000..a2e8638a613 --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_wagering_losses_deduction.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * + + +class ca_wagering_losses_deduction(Variable): + value_type = float + entity = TaxUnit + label = "California wagering losses deduction" + unit = USD + documentation = ( + "California itemized deduction for gambling losses, up to gambling " + "winnings. California does not apply the federal 90% limit that " + "starts in 2026. California Lottery winnings are exempt and " + "California Lottery losses are not deductible; the gambling inputs " + "do not separate them, so they are treated as other gambling." + ) + definition_period = YEAR + reference = ( + # 2025 Form 540 booklet: Schedule CA (540) line 8b (California + # Lottery winnings), Part II line 16 (gambling losses) and the + # Itemized Deductions Worksheet + # PDF pages 62, 67-68 + "https://www.ftb.ca.gov/forms/2025/2025-540-booklet.pdf#page=62", + # California conforms to the Internal Revenue Code as of January 1, + # 2025, before P.L. 119-21. + "https://www.ftb.ca.gov/tax-pros/law/conformity.html", + ) + defined_for = StateCode.CA + + def formula(tax_unit, period, parameters): + # 26 U.S.C. 165(d) before P.L. 119-21: all losses, up to the gains. + # Dependents report their gambling on their own returns. + losses = tax_unit_non_dep_add(tax_unit, period, ["gambling_losses"]) + winnings = tax_unit_non_dep_add(tax_unit, period, ["gambling_winnings"]) + return min_(losses, winnings) diff --git a/policyengine_us/variables/gov/states/dc/tax/income/deductions/dc_itemized_deductions.py b/policyengine_us/variables/gov/states/dc/tax/income/deductions/dc_itemized_deductions.py index 93343decc14..e50ec510f36 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/deductions/dc_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/deductions/dc_itemized_deductions.py @@ -10,6 +10,8 @@ class dc_itemized_deductions(Variable): reference = ( "https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/52926_D-40_12.21.21_Final_Rev011122.pdf#page=18", "https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2022_D-40_Booklet_Final_blk_01_23_23_Ordc.pdf#page=17", + # D.C. Code 47-1803.03(b-4)(2)(B): deductions outside the phase-out + "https://code.dccouncil.gov/us/dc/council/code/sections/47-1803.03", ) defined_for = StateCode.DC @@ -24,6 +26,8 @@ def formula(tax_unit, period, parameters): EXEMPT_ITEMS = [ "medical_expense_deduction", "casualty_loss_deduction", + # Losses described in section 165(d) of the Internal Revenue Code. + "wagering_losses_deduction", ] exempt_deds = add(tax_unit, period, EXEMPT_ITEMS) nonexempt_deds = max_(0, dc_itm_deds - exempt_deds) diff --git a/policyengine_us/variables/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.py b/policyengine_us/variables/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.py index a7d2ed4f785..15096c6f5f3 100644 --- a/policyengine_us/variables/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.py +++ b/policyengine_us/variables/gov/states/de/tax/income/deductions/itemized/de_itemized_deductions_indv.py @@ -13,7 +13,11 @@ ], "interest_deduction": ["deductible_interest_expense"], "casualty_loss_deduction": ["casualty_loss"], + "wagering_losses_deduction": ["gambling_losses"], } +# Deductions that count only the head's and spouse's expenses, so a +# dependent's expense leaves nothing unallocated. +FILER_ONLY_DEDUCTIONS = ("wagering_losses_deduction",) def _share(numerator, denominator): @@ -64,6 +68,8 @@ def formula(person, period, parameters): continue amount = person.tax_unit(deduction, period) expense = add(person, period, expense_sources) + if deduction in FILER_ONLY_DEDUCTIONS: + expense = head_or_spouse * expense total_expense = person.tax_unit.sum(expense) allocated += amount * _share(head_or_spouse * expense, total_expense) # Prorate what could not be specifically allocated. diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py index b63c15e4543..fc8ddff3bcd 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_reduced_itemized_deductions.py @@ -9,6 +9,8 @@ class hi_reduced_itemized_deductions(Variable): reference = ( "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=15", "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=32", # total itemized deduction worksheet + # 2025 Total Itemized Deductions Worksheet, lines 2d and 4 + "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=34", ) definition_period = YEAR defined_for = StateCode.HI @@ -24,6 +26,8 @@ def formula(tax_unit, period, parameters): "hi_medical_expense_deduction", "investment_interest_expense", "hi_casualty_loss_deduction", + # Line 2d: gambling losses included in Worksheet A-6, line 30. + "wagering_losses_deduction", ], ) @@ -53,4 +57,7 @@ def formula(tax_unit, period, parameters): ) reduced_deductions = max_(0, total_deductions - smaller_reduced_ded) eligible = partial_deductions_less_than_total & agi_over_threshold - return eligible * reduced_deductions + # Worksheet lines 4 and 8: when the deductions outside the limit are + # not less than the total, or income is not over the threshold, the + # deduction is not limited and line 1 is deducted in full. + return where(eligible, reduced_deductions, total_deductions) diff --git a/policyengine_us/variables/gov/states/ia/tax/income/net_income/ia_gambling_winnings.py b/policyengine_us/variables/gov/states/ia/tax/income/net_income/ia_gambling_winnings.py new file mode 100644 index 00000000000..a9d014f7c58 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/tax/income/net_income/ia_gambling_winnings.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class ia_gambling_winnings(Variable): + value_type = float + entity = Person + label = "Iowa gambling winnings" + unit = USD + documentation = ( + "Gambling winnings reported on IA 1040 line 13 by the tax unit's " + "head or spouse, for the returns before 2023 that start from Iowa " + "gross income. A dependent reports their own winnings on their own " + "return." + ) + definition_period = YEAR + reference = ( + "https://revenue.iowa.gov/sites/default/files/2022-01/IA1040%2841-001%29.pdf#page=1", + "https://revenue.iowa.gov/sites/default/files/2023-01/2022IA1040%2841001%29.pdf#page=1", + ) + defined_for = StateCode.IA + + def formula(person, period, parameters): + is_dependent = person("is_tax_unit_dependent", period) + return ~is_dependent * person("gambling_winnings", period) diff --git a/policyengine_us/variables/gov/states/ks/tax/income/deductions/ks_itemized_deductions.py b/policyengine_us/variables/gov/states/ks/tax/income/deductions/ks_itemized_deductions.py index fd0b8dee13f..7d6e7331d6c 100644 --- a/policyengine_us/variables/gov/states/ks/tax/income/deductions/ks_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/ks/tax/income/deductions/ks_itemized_deductions.py @@ -10,6 +10,7 @@ class ks_itemized_deductions(Variable): reference = ( "https://www.ksrevenue.gov/pdf/ip21.pdf", "https://www.ksrevenue.gov/pdf/ip22.pdf", + "https://kslegislature.gov/b2025_26/laws/079_000_0000_chapter/079_032_0000_article/079_032_0120_section/079_032_0120_k/", ) defined_for = StateCode.KS @@ -25,4 +26,8 @@ def formula(tax_unit, period, parameters): # compute itemized deduction maximum itm_deds_less_salt = tax_unit("itemized_deductions_less_salt", period) uncapped_property_taxes = add(tax_unit, period, ["real_estate_taxes"]) - return itm_deds_less_salt + uncapped_property_taxes + # K.S.A. 79-32,120(a) limits Kansas itemized deductions to charitable + # contributions, medical care, qualified residence interest and + # property taxes, so federal wagering losses are not one of them. + wagering_losses = tax_unit("wagering_losses_deduction", period) + return itm_deds_less_salt - wagering_losses + uncapped_property_taxes diff --git a/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.py b/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.py index 7adcc1154a8..3ba86e2e5b5 100644 --- a/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.py +++ b/policyengine_us/variables/gov/states/ky/tax/income/deductions/ky_itemized_deductions_unit.py @@ -10,8 +10,18 @@ class ky_itemized_deductions_unit(Variable): reference = ( "https://revenue.ky.gov/Forms/Form%20740%20Schedule%20A%202022.pdf", "https://law.justia.com/codes/kentucky/2022/chapter-141/section-141-019/", # (2)(i) + # 2019 Ky. Acts ch. 151, sec. 37: wagering losses allowed from 2019 + # PDF pages 60, 112 + "https://apps.legislature.ky.gov/law/acts/19RS/documents/0151.pdf#page=60", ) defined_for = StateCode.KY - adds = ["itemized_deductions_less_salt"] - subtracts = ["medical_expense_deduction"] + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.ky.tax.income.deductions.itemized + deductions = tax_unit("itemized_deductions_less_salt", period) - tax_unit( + "medical_expense_deduction", period + ) + if p.wagering_losses_allowed: + return deductions + # KRS 141.019(2) excluded every section 165 loss for 2018. + return deductions - tax_unit("wagering_losses_deduction", period) diff --git a/policyengine_us/variables/gov/states/mi/tax/income/deductions/mi_wagering_losses_deduction.py b/policyengine_us/variables/gov/states/mi/tax/income/deductions/mi_wagering_losses_deduction.py new file mode 100644 index 00000000000..40c384104f1 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/tax/income/deductions/mi_wagering_losses_deduction.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * + + +class mi_wagering_losses_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Michigan wagering losses deduction" + unit = USD + documentation = ( + "Michigan subtraction, from 2021, for the wagering losses a casual " + "gambler deducts as an itemized deduction on the federal return." + ) + definition_period = YEAR + reference = ( + # 2021 PA 168, MCL 206.30 + "https://www.legislature.mi.gov/Laws/MCL?objectName=mcl-206-30", + "https://www.michigan.gov/taxes/rep-legal/notices/notice-new-tax-treatment-of-wagering-losses-for-casual-gamblers-under-the-michigan-income-tax-act", + # 2025 MI-1040 booklet, Schedule 1 line 23 + "https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Forms/IIT/TY2025/MI-1040-Book.pdf#page=16", + # 2025 Taxpayer Assistance Manual: the federal 90% limit carries over + "https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Tax-Professional/2025-Taxpayer-Assistance-Manual.pdf#page=99", + ) + defined_for = StateCode.MI + + def formula(tax_unit, period, parameters): + # Only the losses claimed as an itemized deduction on the federal + # return count, so a filer who takes the federal standard deduction + # has none. + itemizes = tax_unit("tax_unit_itemizes", period) + return itemizes * tax_unit("wagering_losses_deduction", period) diff --git a/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py index bbd8191185d..e6a4f08de02 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py @@ -12,6 +12,8 @@ class mn_itemized_deductions(Variable): "https://www.revenue.state.mn.us/sites/default/files/2023-12/m1_inst_21.pdf", "https://www.revenue.state.mn.us/sites/default/files/2023-12/m1_22.pdf", "https://www.revenue.state.mn.us/sites/default/files/2024-02/m1-inst-22.pdf", + # Minn. Stat. 290.0122, subd. 2(d) and subd. 8 + "https://www.revisor.mn.gov/statutes/cite/290.0122", ) defined_for = StateCode.MN @@ -27,10 +29,16 @@ def formula(tax_unit, period, parameters): mn_itm_deds = itm_deds_less_salt + capped_property_taxes # ... calculate itemized deductions offset p = parameters(period).gov.states.mn.tax.income.deductions.itemized + # Minn. Stat. 290.0122, subd. 2(d)(3) leaves the subd. 8 losses, + # which include section 165(d) wagering losses, out of the limitation. exempt_deds = add( tax_unit, period, - ["medical_expense_deduction", "casualty_loss_deduction"], + [ + "medical_expense_deduction", + "casualty_loss_deduction", + "wagering_losses_deduction", + ], ) net_deds = max_(0, mn_itm_deds - exempt_deds) filing_status = tax_unit("filing_status", period) diff --git a/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py b/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py index 0cbb0cdbd17..4137155500c 100644 --- a/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py +++ b/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py @@ -12,6 +12,10 @@ class ms_itemized_deductions_unit(Variable): "https://www.dor.ms.gov/sites/default/files/tax-forms/individual/80100221.pdf#page=15", "https://www.dor.ms.gov/sites/default/files/tax-forms/individual/80108228.pdf", # Line 7 "https://law.justia.com/codes/mississippi/title-27/chapter-7/article-1/section-27-7-17/", + # 2025 instructions: Schedule A line 7b (gaming losses), and the 3% + # tax on winnings reported by Mississippi casinos + # PDF pages 16, 25 + "https://www.dor.ms.gov/sites/default/files/tax-forms/individual/80100251%202.pdf#page=16", ) defined_for = StateCode.MS @@ -20,6 +24,12 @@ class ms_itemized_deductions_unit(Variable): "misc_deduction", "ms_real_estate_tax_deduction", ] + # Winnings reported by Mississippi casinos bear a separate 3% tax and are + # not Mississippi income, and Mississippi gaming losses come off the + # itemized deductions (Schedule A line 7b). Mississippi income here + # leaves out all gambling winnings, including those from other states, + # so the federal wagering losses deduction is left out as well. + subtracts = ["wagering_losses_deduction"] # Mississippi allows itemized deductions for gaming establishments and gender transition procedures # which are currently not modeled diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_wagering_losses_deduction.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_wagering_losses_deduction.py new file mode 100644 index 00000000000..fd79d28733d --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_wagering_losses_deduction.py @@ -0,0 +1,43 @@ +from policyengine_us.model_api import * + + +class mt_wagering_losses_deduction(Variable): + value_type = float + entity = Person + label = "Montana wagering losses deduction" + unit = USD + documentation = ( + "The person's part of the federal wagering losses deduction, which " + "Montana allows as an itemized deduction. Each spouse takes their " + "own losses against their own winnings; losses beyond that go to " + "the spouse with winnings left to absorb them, since the spouse who " + "reports the winnings claims the losses." + ) + definition_period = YEAR + reference = ( + # 2023 Form 2 instructions: allocating deductions between spouses, + # and gambling losses + # PDF pages 13, 39 + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2023_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=13", + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2025_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=13", + ) + defined_for = StateCode.MT + + def formula(person, period, parameters): + deduction = person.tax_unit("wagering_losses_deduction", period) + p = parameters(period).gov.irs.deductions.itemized.wagering_losses + # The federal deduction counts the head's and spouse's gambling. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + winnings = head_or_spouse * person("gambling_winnings", period) + losses = head_or_spouse * person("gambling_losses", period) + # Losses the person can deduct against their own winnings. + own = min_(p.rate * losses, winnings) + # What is left of the return's deduction falls on winnings that the + # winner's own losses did not cover. + remaining = max_(deduction - person.tax_unit.sum(own), 0) + unused_winnings = winnings - own + total_unused = person.tax_unit.sum(unused_winnings) + share = np.zeros_like(total_unused) + mask = total_unused > 0 + share[mask] = unused_winnings[mask] / total_unused[mask] + return own + remaining * share diff --git a/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.py b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.py new file mode 100644 index 00000000000..dbcbe3397ee --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.py @@ -0,0 +1,66 @@ +from policyengine_us.model_api import * + + +class nc_claim_of_right_deduction(Variable): + value_type = float + entity = TaxUnit + label = "North Carolina claim of right repayment itemized deduction" + unit = USD + documentation = ( + "North Carolina itemized deduction for repaying income included in " + "adjusted gross income in an earlier year under a claim of right; from " + "2026, only income North Carolina taxed that year. Repayments above " + "the threshold are deductible in full. Smaller repayments are reduced " + "by two percent of federal adjusted gross income less the filers' " + "other miscellaneous itemized deductions. Not modeled: the bar on the " + "deduction when federal tax for the year of repayment is computed " + "under 26 U.S.C. 1341(a)(5), and from 2026, repayments of amounts North " + "Carolina added to adjusted gross income that federal law excluded." + ) + definition_period = YEAR + reference = ( + # N.C. Gen. Stat. 105-153.5(a)(2)d + "https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html", + # S.L. 2026-31, section 1.8, PDF pages 5-6 + "https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-31.pdf#page=5", + # 2016 Form D-401 instructions, Repayment of Claim of Right Worksheet + "https://taxsim.nber.org/historical_state_tax_forms/NC/2016/D401.pdf#page=13", + # 2025 Form D-401 instructions, Form D-400 Schedule A line 8 and worksheet + "https://www.ncdor.gov/2025-d-401-individual-income-tax-instructions/open#page=20", + ) + defined_for = StateCode.NC + + def formula(tax_unit, period, parameters): + person = tax_unit.members + p = parameters(period).gov.states.nc.tax.income.deductions.itemized + repaid = person("claim_of_right_repayment", period) + if p.claim_of_right.nc_modified_income_basis: + # From 2026, only repayments of amounts included in AGI as + # modified for North Carolina in the earlier year. + excluded = person( + "nc_claim_of_right_repayment_excluded_from_income", period + ) + repaid = max_(repaid - excluded, 0) + # Dependents' repayments and expenses belong on their own returns. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + repayment = tax_unit.sum(repaid * head_or_spouse) + # Repayments of $3,000 or less are reduced by (i) the section 67(a) + # floor, two percent of federal AGI, minus (ii) the other + # miscellaneous itemized deductions, not below zero. Section 67(g) + # disallows those other deductions from 2018, so the 2018-and-later + # worksheet subtracts the whole floor. + p_misc = parameters(period).gov.irs.deductions.itemized.misc + floor = p_misc.floor * tax_unit("positive_agi", period) + if p_misc.applies: + other_misc_deductions = tax_unit.sum( + add(person, period, p_misc.sources) * head_or_spouse + ) + else: + other_misc_deductions = 0 + remaining_floor = max_(floor - other_misc_deductions, 0) + small_repayment_deduction = max_(repayment - remaining_floor, 0) + return where( + repayment > p.claim_of_right.threshold, + repayment, + small_repayment_deduction, + ) diff --git a/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_repayment_excluded_from_income.py b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_repayment_excluded_from_income.py new file mode 100644 index 00000000000..d849340a81e --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_repayment_excluded_from_income.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class nc_claim_of_right_repayment_excluded_from_income(Variable): + value_type = float + entity = Person + label = "Claim of right repayment excluded from North Carolina income when received" + unit = USD + documentation = ( + "Part of claim_of_right_repayment that North Carolina deducted from " + "adjusted gross income in the earlier year it was received, such as " + "repaid Social Security benefits or Bailey-exempt retirement benefits." + ) + definition_period = YEAR + reference = ( + # N.C. Gen. Stat. 105-153.5(a)(2)d as rewritten by S.L. 2026-31, + # section 1.8, PDF pages 5-6 + "https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-31.pdf#page=5", + ) + defined_for = StateCode.NC diff --git a/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_wagering_losses_deduction.py b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_wagering_losses_deduction.py new file mode 100644 index 00000000000..f16e3c8744d --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_wagering_losses_deduction.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class nc_wagering_losses_deduction(Variable): + value_type = float + entity = TaxUnit + label = "North Carolina wagering losses itemized deduction" + unit = USD + documentation = ( + "North Carolina itemized deduction, from 2025, for the wagering losses " + "allowed under 26 U.S.C. 165(d), to the extent they are not deducted " + "in arriving at adjusted gross income." + ) + definition_period = YEAR + reference = ( + # N.C. Gen. Stat. 105-153.5(a)(2)e + "https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html", + # S.L. 2026-41, Section 44.2, PDF pages 607-608 + "https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-41.pdf#page=607", + # NCDOR FAQs Regarding Recent Session Law Changes, Q36-Q40, PDF pages 11-12 + "https://www.ncdor.gov/faqs-recent-session-law-changes/open#page=11", + ) + defined_for = StateCode.NC + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.nc.tax.income.deductions.itemized + # The federal deduction is the amount allowed under section 165(d). + # PolicyEngine deducts no wagering losses in arriving at AGI. + return p.wagering_losses.in_effect * tax_unit( + "wagering_losses_deduction", period + ) diff --git a/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_itemized_deductions.py b/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_itemized_deductions.py index 994735af1a7..037c46fd128 100644 --- a/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_itemized_deductions.py @@ -27,10 +27,9 @@ def formula(tax_unit, period, parameters): # North Carolina specifies a state and local tax deduction cap which is currently not modeled in PolicyEngine - other_deductions = add( - tax_unit, - period, - ["charitable_deduction", "medical_expense_deduction"], - ) + # Charitable contributions, medical and dental expenses, and the + # other items in G.S. 105-153.5(a)(2), none subject to the cap. + p = parameters(period).gov.states.nc.tax.income.deductions.itemized + other_deductions = add(tax_unit, period, p.other_sources) return capped_mortage_and_property_taxes + other_deductions diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.py index 69366a1932e..3f4b1896f50 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_itemized_deductions_max.py @@ -20,6 +20,7 @@ class ny_itemized_deductions_max(Variable): - State/local income taxes are NOT deductible (only sales + property) - Miscellaneous deductions still allowed with 2% AGI floor - Casualty losses not limited to federally declared disasters + - Gambling losses up to winnings, without the 90% limit from 2026 - NY-specific college tuition deduction addition per § 615(d) """ adds = [ @@ -31,6 +32,7 @@ class ny_itemized_deductions_max(Variable): "ny_salt_deduction", "ny_misc_deduction", "ny_casualty_loss_deduction", + "ny_wagering_losses_deduction", # NY-specific addition per Tax Law § 615(d) "ny_college_tuition_deduction", ] diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out.py index 27565cc41af..d1efd56543c 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out.py @@ -28,19 +28,20 @@ class ny_itemized_deductions_phase_out(Variable): itemized deductions before the limitation. In PolicyEngine this is ny_itemized_deductions_max minus the NY-only college tuition addition (form line 48/43, which is added after line 40 and is not on this - worksheet). Form line 39 ("other itemized deductions": gambling losses, - estate tax on income in respect of a decedent, amortizable bond premium, - federal disaster loss, etc.) is not modeled in PolicyEngine and is therefore - treated as zero. + worksheet). Of form line 39 ("other itemized deductions"), gambling losses + (line 29) are ny_wagering_losses_deduction; the rest (estate tax on income + in respect of a decedent, amortizable bond premium, federal disaster loss, + etc.) is not modeled in PolicyEngine and is therefore treated as zero. Worksheet line 2 (L2) = form lines 4, 14, 16a, 20, 29, 30, 37, i.e. the deductions NOT subject to the limitation (26 U.S.C. 68(c)): - line 4 medical/dental -> medical_expense_deduction - line 14 investment interest -> investment_interest_expense - line 20 casualty/theft loss -> ny_casualty_loss_deduction - - lines 16a/29/30/37 (qualified contributions, gambling losses, - income-producing-property casualty, federal disaster loss) are not - separately modeled in PolicyEngine and are treated as zero. + - line 29 gambling losses -> ny_wagering_losses_deduction + - lines 16a/30/37 (qualified contributions, income-producing-property + casualty, federal disaster loss) are not separately modeled in + PolicyEngine and are treated as zero. L3 = L1 - L2 (worksheet line 3) is therefore the mortgage interest, real estate taxes, charitable gifts, and 2%-floor miscellaneous deductions - @@ -61,11 +62,13 @@ def formula(tax_unit, period, parameters): l1 = itemized_max - college_tuition # Worksheet line 2 (L2): deductions not subject to the limitation - # (26 U.S.C. 68(c)) - medical, investment interest, casualty/theft. + # (26 U.S.C. 68(c)) - medical, investment interest, casualty/theft, + # gambling losses. medical = tax_unit("medical_expense_deduction", period) investment_interest = add(tax_unit, period, ["investment_interest_expense"]) casualty = tax_unit("ny_casualty_loss_deduction", period) - l2 = medical + investment_interest + casualty + wagering = tax_unit("ny_wagering_losses_deduction", period) + l2 = medical + investment_interest + casualty + wagering # Worksheet line 3 (L3): deductions subject to the limitation. Floored at # zero to implement the worksheet's line-3 stop ("If line 2 is not less diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_wagering_losses_deduction.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_wagering_losses_deduction.py new file mode 100644 index 00000000000..706560dda75 --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_wagering_losses_deduction.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class ny_wagering_losses_deduction(Variable): + value_type = float + entity = TaxUnit + label = "NY wagering losses deduction" + unit = USD + definition_period = YEAR + reference = ( + "https://www.nysenate.gov/legislation/laws/TAX/615", + # Form IT-196 instructions, line 29: gambling losses + "https://www.tax.ny.gov/pdf/2025/inc/it196i_2025.pdf#page=16", + ) + defined_for = StateCode.NY + documentation = """ + NY Tax Law § 615 requires itemized deductions to be computed using + pre-TCJA federal rules. New York allows gambling losses up to gambling + winnings under the federal rules that applied to tax year 2017, so the + federal 90% limit that starts in 2026 does not apply. + """ + + def formula(tax_unit, period, parameters): + # Dependents report their gambling on their own returns. + losses = tax_unit_non_dep_add(tax_unit, period, ["gambling_losses"]) + winnings = tax_unit_non_dep_add(tax_unit, period, ["gambling_winnings"]) + return min_(losses, winnings) diff --git a/policyengine_us/variables/gov/states/pa/tax/income/taxable_income/pa_gambling_loss_offset.py b/policyengine_us/variables/gov/states/pa/tax/income/taxable_income/pa_gambling_loss_offset.py new file mode 100644 index 00000000000..cd19f2c68b8 --- /dev/null +++ b/policyengine_us/variables/gov/states/pa/tax/income/taxable_income/pa_gambling_loss_offset.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * + + +class pa_gambling_loss_offset(Variable): + value_type = float + entity = Person + label = "Pennsylvania gambling losses offset against winnings" + unit = USD + documentation = ( + "The cost of tickets, bets and other wagers that Pennsylvania " + "subtracts from the same person's gambling and lottery winnings " + "(PA-40 Schedule T). Spouses may not use each other's costs, and the " + "federal 90% limit does not apply. Noncash Pennsylvania Lottery " + "prizes, which Pennsylvania does not tax, are not separated in the " + "gambling inputs." + ) + definition_period = YEAR + reference = ( + # 72 P.S. 7303(a)(7) + "https://www.legis.state.pa.us/WU01/LI/LI/US/PDF/1971/0/0002..PDF#page=123", + # 2025 PA-40 Schedule T and its instructions + # PDF pages 1, 3 + "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2025/2025_pa-40t.pdf#page=1", + ) + defined_for = StateCode.PA + + def formula(person, period, parameters): + winnings = person("gambling_winnings", period) + losses = person("gambling_losses", period) + return min_(losses, winnings) diff --git a/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py b/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py index 1bd76724a54..58698b23e1e 100644 --- a/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py +++ b/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py @@ -17,6 +17,10 @@ class pa_property_tax_or_rent_rebate_income(Variable): "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/" "formsandpublications/formsforindividuals/ptrr/documents/" "2025_pa-1000_inst.pdf#page=9", + # PA-1000 booklet, line 11b: net gambling and lottery winnings + "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/" + "formsandpublications/formsforindividuals/ptrr/documents/" + "dfo-03.pdf#page=40", ) defined_for = StateCode.PA @@ -57,13 +61,18 @@ def formula(tax_unit, period, parameters): person, period, [ - "gambling_winnings", "alimony_income", "workers_compensation", "disability_benefits", ], ) ) + # Line 11b counts net gambling and lottery winnings: losses offset the + # winnings but may not exceed them. The winnings are already in + # federal adjusted gross income. + gambling_loss_offset = tax_unit.sum( + head_or_spouse * person("pa_gambling_loss_offset", period) + ) life_insurance_benefits = tax_unit.sum( head_or_spouse * person("life_insurance_benefits", period) ) @@ -84,6 +93,7 @@ def formula(tax_unit, period, parameters): + p.benefit_income_rate * half_counted_benefits + tax_exempt_income + pa_1000_line_11_income + - gambling_loss_offset + taxable_life_insurance_benefits + csrs_income - csrs_exclusion, diff --git a/policyengine_us/variables/gov/states/va/tax/income/deductions/va_reduced_itemized_deductions.py b/policyengine_us/variables/gov/states/va/tax/income/deductions/va_reduced_itemized_deductions.py index cd57fbba606..567c3261b23 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/deductions/va_reduced_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/va/tax/income/deductions/va_reduced_itemized_deductions.py @@ -16,7 +16,8 @@ def formula(tax_unit, period, parameters): # Part A: If AGI from federal return is over a certain amount, then # limitations to the itemized deduction are applied # Line 1 - sum of medical exepens ded., capped state and local tax, - # interest ded., charitable ded., and casualty loss ded. + # interest ded., charitable ded., casualty loss ded., and gambling + # losses (Schedule A line 16a) applicable_ded = add( tax_unit, period, diff --git a/policyengine_us/variables/gov/states/wi/tax/income/credits/itemized_deduction/wi_itemized_deduction_credit_potential.py b/policyengine_us/variables/gov/states/wi/tax/income/credits/itemized_deduction/wi_itemized_deduction_credit_potential.py index da76fe63638..8bda47e387d 100644 --- a/policyengine_us/variables/gov/states/wi/tax/income/credits/itemized_deduction/wi_itemized_deduction_credit_potential.py +++ b/policyengine_us/variables/gov/states/wi/tax/income/credits/itemized_deduction/wi_itemized_deduction_credit_potential.py @@ -11,11 +11,18 @@ class wi_itemized_deduction_credit_potential(Variable): "https://www.revenue.wi.gov/TaxForms2021/2021-Form1f.pdf#page=4", "https://www.revenue.wi.gov/TaxForms2022/2022-Form1f.pdf#page=4", "https://docs.legis.wisconsin.gov/misc/lfb/informational_papers/january_2023/0002_individual_income_tax_informational_paper_2.pdf#page=19", + # Publication 600: gambling losses are not deductible and may not + # be used in computing the itemized deduction credit. + # PDF pages 4, 6, 9-10 + "https://www.revenue.wi.gov/DOR%20Publications/pb600.pdf#page=4", ) defined_for = StateCode.WI def formula(tax_unit, period, parameters): - wi_itmded = tax_unit("itemized_deductions_less_salt", period) + # Wisconsin allows no deduction or credit for gambling losses. + wi_itmded = tax_unit("itemized_deductions_less_salt", period) - tax_unit( + "wagering_losses_deduction", period + ) wi_stdded = tax_unit("wi_standard_deduction", period) excess_itmded = max_(0, wi_itmded - wi_stdded) p = parameters(period).gov.states.wi.tax.income diff --git a/policyengine_us/variables/household/expense/person/claim_of_right_repayment.py b/policyengine_us/variables/household/expense/person/claim_of_right_repayment.py new file mode 100644 index 00000000000..ed205b53da5 --- /dev/null +++ b/policyengine_us/variables/household/expense/person/claim_of_right_repayment.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class claim_of_right_repayment(Variable): + value_type = float + entity = Person + label = "Repayment of income received under a claim of right" + unit = USD + documentation = ( + "Amount repaid this year of income that was included in adjusted gross " + "income in an earlier year because it appeared the person had an " + "unrestricted right to it (26 U.S.C. 1341). Excludes repayments deducted " + "in arriving at adjusted gross income this year." + ) + definition_period = YEAR + reference = "https://www.law.cornell.edu/uscode/text/26/1341" diff --git a/policyengine_us/variables/household/income/person/gambling_losses.py b/policyengine_us/variables/household/income/person/gambling_losses.py index b93b6bb0f64..6d40dbbfc19 100644 --- a/policyengine_us/variables/household/income/person/gambling_losses.py +++ b/policyengine_us/variables/household/income/person/gambling_losses.py @@ -6,4 +6,13 @@ class gambling_losses(Variable): entity = Person label = "Gambling losses" unit = USD + documentation = ( + "Losses from wagering transactions of a person who does not gamble " + "as a trade or business, such as the cost of losing bets and of " + "non-winning lottery and raffle tickets (Schedule A (Form 1040) " + "line 16), before any limit. This input does not separate losses " + "by where the wager was placed, so state rules that depend on it " + "(a state lottery, in-state licensed gaming) are not modeled." + ) definition_period = YEAR + reference = "https://www.irs.gov/pub/irs-prior/i1040sca--2024.pdf#page=11" diff --git a/policyengine_us/variables/household/income/person/gambling_winnings.py b/policyengine_us/variables/household/income/person/gambling_winnings.py index 9b3c9b21f1e..c946902f1ac 100644 --- a/policyengine_us/variables/household/income/person/gambling_winnings.py +++ b/policyengine_us/variables/household/income/person/gambling_winnings.py @@ -6,4 +6,16 @@ class gambling_winnings(Variable): entity = Person label = "Gambling winnings" unit = USD + documentation = ( + "Gambling winnings of a person who does not gamble as a trade or " + "business, as reported on Schedule 1 (Form 1040) line 8b: the full " + "winnings, including lottery and raffle prizes, not reduced by " + "losses. This input does not separate winnings by source or by " + "prize, so state rules that depend on them (a state's own lottery, " + "prizes under a threshold, noncash prizes) are not modeled." + ) definition_period = YEAR + reference = ( + "https://www.irs.gov/pub/irs-prior/f1040s1--2024.pdf#page=1", + "https://www.irs.gov/pub/irs-prior/i1040gi--2025.pdf#page=91", + )