diff --git a/changelog.d/federal-claim-of-right-1341.added.md b/changelog.d/federal-claim-of-right-1341.added.md new file mode 100644 index 00000000000..1ece6e26c6d --- /dev/null +++ b/changelog.d/federal-claim-of-right-1341.added.md @@ -0,0 +1 @@ +Add the federal claim of right repayment deduction and section 1341 credit (26 U.S.C. 1341), North Carolina's bar on its claim of right deduction when federal tax is computed under section 1341(a)(5), and North Carolina's tax payment for restored income (G.S. 105-266.2). diff --git a/changelog.d/in-ma-wv-claim-of-right.added.md b/changelog.d/in-ma-wv-claim-of-right.added.md new file mode 100644 index 00000000000..1cba64cc296 --- /dev/null +++ b/changelog.d/in-ma-wv-claim-of-right.added.md @@ -0,0 +1 @@ +Add Indiana's repayment of previously taxed income deduction (IC 6-3-1-3.5(a)(22)), Massachusetts's claim of right deduction (M.G.L. c. 62, s. 3(B)(a)(14)) and West Virginia's IRC 1341 repayments modification, which apply whichever federal method is used. Indiana users who entered a repayment in in_other_deductions should move it to claim_of_right_repayment, or it counts twice. diff --git a/policyengine_us/parameters/gov/household/household_refundable_credits.yaml b/policyengine_us/parameters/gov/household/household_refundable_credits.yaml index 5938f3e3b76..1cf574d021c 100644 --- a/policyengine_us/parameters/gov/household/household_refundable_credits.yaml +++ b/policyengine_us/parameters/gov/household/household_refundable_credits.yaml @@ -27,7 +27,7 @@ values: - mo_refundable_credits # Missouri. - ms_refundable_credits # Mississippi. - mt_refundable_credits # Montana. - # Skip NC which has no refundable credits. + - nc_refundable_credits # North Carolina. - nd_refundable_credits # North Dakota. - ne_refundable_credits # Nebraska. - nh_refundable_credits # New Hampshire. diff --git a/policyengine_us/parameters/gov/household/household_refundable_state_credits.yaml b/policyengine_us/parameters/gov/household/household_refundable_state_credits.yaml index 2fe90dbccf3..4bdde27f54f 100644 --- a/policyengine_us/parameters/gov/household/household_refundable_state_credits.yaml +++ b/policyengine_us/parameters/gov/household/household_refundable_state_credits.yaml @@ -26,7 +26,7 @@ values: - mo_refundable_credits # Missouri. - ms_refundable_credits # Mississippi. - mt_refundable_credits # Montana. - # Skip NC which has no refundable credits. + - nc_refundable_credits # North Carolina. - nd_refundable_credits # North Dakota. - ne_refundable_credits # Nebraska. - nh_refundable_credits # New Hampshire. diff --git a/policyengine_us/parameters/gov/household/household_tax_before_refundable_credits.yaml b/policyengine_us/parameters/gov/household/household_tax_before_refundable_credits.yaml index 656d4b40bf3..eb236bac70b 100644 --- a/policyengine_us/parameters/gov/household/household_tax_before_refundable_credits.yaml +++ b/policyengine_us/parameters/gov/household/household_tax_before_refundable_credits.yaml @@ -29,7 +29,7 @@ values: - mo_income_tax_before_refundable_credits - ms_income_tax_before_credits_unit - mt_income_tax_before_refundable_credits_unit - - nc_income_tax # NC has no refundable credits. + - nc_income_tax_before_refundable_credits - nd_income_tax_before_refundable_credits - ne_income_tax_before_refundable_credits - nh_income_tax_before_refundable_credits diff --git a/policyengine_us/parameters/gov/irs/credits/refundable.yaml b/policyengine_us/parameters/gov/irs/credits/refundable.yaml index 7676cc12363..1f4a2415d29 100644 --- a/policyengine_us/parameters/gov/irs/credits/refundable.yaml +++ b/policyengine_us/parameters/gov/irs/credits/refundable.yaml @@ -6,6 +6,7 @@ values: - refundable_ctc - recovery_rebate_credit - refundable_payroll_tax_credit + - claim_of_right_credit # Currently, the premium tax credit is not included in the refundable tax credits. # Add CDCC in 2021. 2021-01-01: @@ -14,6 +15,7 @@ values: - refundable_ctc - recovery_rebate_credit - refundable_payroll_tax_credit + - claim_of_right_credit # Currently, the premium tax credit is not included in the refundable tax credits. - cdcc # Remove CDCC in 2022. @@ -23,6 +25,7 @@ values: - refundable_ctc - recovery_rebate_credit - refundable_payroll_tax_credit + - claim_of_right_credit # Currently, the premium tax credit is not included in the refundable tax credits. metadata: unit: list diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/claim_of_right/threshold.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/claim_of_right/threshold.yaml new file mode 100644 index 00000000000..ec07a3987f1 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/claim_of_right/threshold.yaml @@ -0,0 +1,16 @@ +description: The IRS computes tax for the year of repayment as the lesser of the tax with a deduction for the repayment and the tax without it less the resulting decrease in prior-year tax, when the repayment of income held under a claim of right exceeds this amount. +values: + 1954-01-01: 3_000 +metadata: + unit: currency-USD + period: year + label: Claim of right repayment threshold + reference: + - title: 26 U.S. Code § 1341(a)(3) + href: https://www.govinfo.gov/content/pkg/USCODE-2024-title26/html/USCODE-2024-title26-subtitleA-chap1-subchapQ-partV-sec1341.htm + - title: 26 U.S. Code § 67(b)(9) + href: https://www.law.cornell.edu/uscode/text/26/67#b_9 + - title: 26 CFR 1.1341-1(a)(3) + href: https://www.ecfr.gov/current/title-26/chapter-I/subchapter-A/part-1/subject-group-ECFR4c74bc8cf7c11fb/section-1.1341-1 + - title: "Publication 525 (2025), Repayments #page 36-37" + href: https://www.irs.gov/pub/irs-prior/p525--2025.pdf#page=36 diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml index 14d369f6999..d0dc9c77b9c 100644 --- a/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml @@ -7,6 +7,7 @@ values: - medical_expense_deduction - casualty_loss_deduction - misc_deduction + - claim_of_right_deduction 2020-01-01: - charitable_deduction - charitable_deduction_for_non_itemizers @@ -15,6 +16,7 @@ values: - medical_expense_deduction - casualty_loss_deduction - misc_deduction + - claim_of_right_deduction 2022-01-01: - charitable_deduction - interest_deduction @@ -22,6 +24,7 @@ values: - medical_expense_deduction - casualty_loss_deduction - misc_deduction + - claim_of_right_deduction metadata: unit: list label: itemized deductions diff --git a/policyengine_us/parameters/gov/states/household/state_income_tax_before_refundable_credits.yaml b/policyengine_us/parameters/gov/states/household/state_income_tax_before_refundable_credits.yaml index c3877dbb5d1..612fc062e93 100644 --- a/policyengine_us/parameters/gov/states/household/state_income_tax_before_refundable_credits.yaml +++ b/policyengine_us/parameters/gov/states/household/state_income_tax_before_refundable_credits.yaml @@ -27,7 +27,7 @@ values: - mo_income_tax_before_refundable_credits - ms_income_tax_before_credits_unit - mt_income_tax_before_refundable_credits_unit - - nc_income_tax + - nc_income_tax_before_refundable_credits - nd_income_tax_before_refundable_credits - ne_income_tax_before_refundable_credits - nh_income_tax_before_refundable_credits diff --git a/policyengine_us/parameters/gov/states/household/state_refundable_credits.yaml b/policyengine_us/parameters/gov/states/household/state_refundable_credits.yaml index 75c3f95eee0..4dc2984441f 100644 --- a/policyengine_us/parameters/gov/states/household/state_refundable_credits.yaml +++ b/policyengine_us/parameters/gov/states/household/state_refundable_credits.yaml @@ -26,7 +26,7 @@ values: - mo_refundable_credits # Missouri. - ms_refundable_credits # Mississippi. - mt_refundable_credits # Montana. - # Skip NC which has no refundable credits. + - nc_refundable_credits # North Carolina. - nd_refundable_credits # North Dakota. - ne_refundable_credits # Nebraska. - nh_refundable_credits # New Hampshire. diff --git a/policyengine_us/parameters/gov/states/in/tax/income/deductions/deductions.yaml b/policyengine_us/parameters/gov/states/in/tax/income/deductions/deductions.yaml index 8debf2504a8..0951763fd25 100644 --- a/policyengine_us/parameters/gov/states/in/tax/income/deductions/deductions.yaml +++ b/policyengine_us/parameters/gov/states/in/tax/income/deductions/deductions.yaml @@ -11,6 +11,8 @@ values: - in_nol - in_unemployment_compensation_deduction - in_other_deductions + # IC 6-3-1-3.5(a)(22) / IT-40 Schedule 2 (code 630) + - in_claim_of_right_deduction - in_civil_service_annuity_deduction 2023-01-01: @@ -34,6 +36,8 @@ values: - in_unemployment_compensation_deduction # IC 6-3-2 - in_other_deductions + # IC 6-3-1-3.5(a)(22) / IT-40 Schedule 2 (code 630) + - in_claim_of_right_deduction # IC 6-3-2-4 (b) - military_basic_pay # IC 6-3-2-3.7 @@ -60,6 +64,8 @@ values: - in_unemployment_compensation_deduction # IC 6-3-2 - in_other_deductions + # IC 6-3-1-3.5(a)(22) / IT-40 Schedule 2 (code 630) + - in_claim_of_right_deduction # IC 6-3-2-4 (b) - military_basic_pay # IC 6-3-2-28 @@ -88,6 +94,8 @@ values: - in_unemployment_compensation_deduction # IC 6-3-2 - in_other_deductions + # IC 6-3-1-3.5(a)(22) / IT-40 Schedule 2 (code 630) + - in_claim_of_right_deduction # IC 6-3-2-4 (b) - military_basic_pay # IC 6-3-2-28 @@ -122,6 +130,8 @@ values: - in_unemployment_compensation_deduction # IC 6-3-2 - in_other_deductions + # IC 6-3-1-3.5(a)(22) / IT-40 Schedule 2 (code 630) + - in_claim_of_right_deduction # IC 6-3-2-4 (b) - military_basic_pay # IC 6-3-2-28 @@ -140,5 +150,5 @@ metadata: href: http://iga.in.gov/legislative/laws/2021/ic/titles/006#6-3 - title: U.S. Code href: https://www.law.cornell.edu/uscode/text - - title: 2025 IT-40 Income Tax Form Instruction Booklet, pg. 19 + - title: "2025 IT-40 Income Tax Form Instruction Booklet #page 19,23" href: https://forms.in.gov/Download.aspx?id=16915#page=19 diff --git a/policyengine_us/parameters/gov/states/mo/tax/income/deductions/federal_income_tax/ignored_credits.yaml b/policyengine_us/parameters/gov/states/mo/tax/income/deductions/federal_income_tax/ignored_credits.yaml index f82c34178f1..e32c3b86053 100644 --- a/policyengine_us/parameters/gov/states/mo/tax/income/deductions/federal_income_tax/ignored_credits.yaml +++ b/policyengine_us/parameters/gov/states/mo/tax/income/deductions/federal_income_tax/ignored_credits.yaml @@ -4,23 +4,27 @@ description: Missouri ignores the following credits in federal income tax deduct # Part I Line 3, and the net premium tax credit (Schedule 3 Line 9). Credits # claimed on Line 28 (refundable CTC) and Line 30 (recovery rebate), and the # 2021 refundable CDCC (Schedule 3 Line 13g), never reduce Line 22, so they -# are added back here. The EIC is not added back: the Line 9 instructions say +# are added back here. So is the section 1341 credit for repaid claim of right +# income (Schedule 3 Line 13b). The EIC is not added back: the Line 9 instructions say # "If you have an earned income credit, you must subtract the credit from the # tax on your federal return." values: 2018-01-01: - recovery_rebate_credit - refundable_ctc + - claim_of_right_credit # The CDCC is a refundable credit only in 2021 (ARPA); in other years it # already reduces income tax before refundable credits, so adding it back # would overstate the base. 2021-01-01: - recovery_rebate_credit - refundable_ctc + - claim_of_right_credit - cdcc 2022-01-01: - recovery_rebate_credit - refundable_ctc + - claim_of_right_credit metadata: unit: list label: Missouri ignored credits in federal income tax deduction 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..012bffd43b0 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: [] - + # Form 2 itemized deductions line 18 follows federal Schedule A line 16. + - mt_claim_of_right_deduction + # From 2024 Form 2 line 2 takes the federal itemized deductions (Worksheet A + # line 1: Form 1040 line 12), which include Schedule A line 16. + 2024-01-01: + - mt_claim_of_right_deduction + metadata: unit: list - label: Montana miscellaneous deductions + label: Montana miscellaneous deductions period: year reference: + - title: 2023 Montana Form 2 instructions, itemized deductions line 18 + 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: 2024 Montana Form 2 instructions, line 2 and Worksheet A + href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2024_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=15 - 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/credits/refundable.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/credits/refundable.yaml new file mode 100644 index 00000000000..8d654cd21be --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/tax/income/credits/refundable.yaml @@ -0,0 +1,13 @@ +description: North Carolina provides these refundable income tax credits and payments. +values: + 1995-01-01: + - nc_claim_of_right_payment +metadata: + unit: list + period: year + label: North Carolina refundable income tax credits + reference: + - title: N.C. Gen. Stat. § 105-266.2 + href: https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-266.2.html + - title: S.L. 1997-213, Section 2 (effective for taxable years beginning on or after January 1, 1995) + href: https://www.ncleg.gov/EnactedLegislation/SessionLaws/HTML/1997-1998/SL1997-213.html 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..27407e3bc2e 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 16b, other itemized deductions. + - claim_of_right_deduction metadata: unit: list period: year label: Virginia itemized deductions reduction applicable deduction sources reference: + - title: "2025 Virginia Schedule A Itemized Deductions, line 16b 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/wv/tax/income/subtractions/claim_of_right/threshold.yaml b/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/claim_of_right/threshold.yaml new file mode 100644 index 00000000000..d184a19dbee --- /dev/null +++ b/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/claim_of_right/threshold.yaml @@ -0,0 +1,16 @@ +description: West Virginia allows the IRC 1341 repayments decreasing modification when the income repaid on the return is more than this amount. +values: + 2017-01-01: 3_000 +metadata: + unit: currency-USD + period: year + label: West Virginia IRC 1341 repayments modification threshold + reference: + - title: "West Virginia 2025 Personal Income Tax Forms and Instructions, Schedule M line 42 #page 5,29" + href: https://tax.wv.gov/Documents/PIT/2025/it140.PersonalIncomeTaxFormsAndInstructions.2025.pdf#page=5 + - title: West Virginia 2020 Personal Income Tax Forms and Instructions, Schedule M line 40 + href: https://tax.wv.gov/Documents/TaxForms/2020/it140.booklet.pdf#page=24 + - title: West Virginia 2017 Personal Income Tax Forms and Instructions, Schedule M line 50 (money paid back under IRC 1341) + href: https://tax.wv.gov/Documents/TaxForms/2017/it140.booklet.pdf#page=23 + - title: "IRS Publication 525 (2025), Repayments: consider the total amount being repaid on the return #page 36-37" + href: https://www.irs.gov/pub/irs-prior/p525--2025.pdf#page=36 diff --git a/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/subtractions.yaml index a3fe563b8fb..c92f34dfc9a 100644 --- a/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/subtractions.yaml @@ -8,6 +8,7 @@ values: - military_service_income # Line 34 - salt_refund_income # Line 36 - wv_529_plan_deduction + - wv_claim_of_right_subtraction # Line 42 - wv_senior_citizen_disability_deduction # Line 47 metadata: diff --git a/policyengine_us/reforms/states/nc/cdcc/nc_cdcc_reform.py b/policyengine_us/reforms/states/nc/cdcc/nc_cdcc_reform.py index 94e4efaf931..5ef1728228b 100644 --- a/policyengine_us/reforms/states/nc/cdcc/nc_cdcc_reform.py +++ b/policyengine_us/reforms/states/nc/cdcc/nc_cdcc_reform.py @@ -23,41 +23,35 @@ class nc_refundable_credits(Variable): unit = USD definition_period = YEAR defined_for = StateCode.NC + # Baseline computes this via `adds`; replacing it with a + # formula requires clearing the inherited computation mode + # (the core engine rejects `formula` + `adds`/`subtracts`). + adds = None + subtracts = None def formula(tax_unit, period, parameters): + # Preserve the baseline refundable credits and ADD the new + # CDCC (see #8775). Baseline nc_income_tax subtracts + # nc_refundable_credits and excludes nc_use_tax, which + # state_use_tax already counts, so no override of it is needed. + baseline_credits = parameters( + period + ).gov.states.nc.tax.income.credits.refundable + total = add(tax_unit, period, list(baseline_credits)) + tax_unit( + "nc_cdcc", period + ) # Stack with nc_eitc when the NC EITC contrib reform is also # active, so enabling both NC contrib credits sums rather than # overwrites. - total = tax_unit("nc_cdcc", period) variables = tax_unit.simulation.tax_benefit_system.variables if "nc_eitc" in variables: total = total + tax_unit("nc_eitc", period) return total - class nc_income_tax(Variable): - value_type = float - entity = TaxUnit - label = "North Carolina income tax" - unit = USD - definition_period = YEAR - defined_for = StateCode.NC - - def formula(tax_unit, period, parameters): - tax_before_credits = add( - tax_unit, period, ["nc_income_tax_before_credits", "nc_use_tax"] - ) - non_refundable_credits = tax_unit("nc_non_refundable_credits", period) - tax_before_refundable = max_(0, tax_before_credits - non_refundable_credits) - - refundable_credits = tax_unit("nc_refundable_credits", period) - - return tax_before_refundable - refundable_credits - class reform(Reform): def apply(self): self.update_variable(nc_cdcc) self.update_variable(nc_refundable_credits) - self.update_variable(nc_income_tax) return reform diff --git a/policyengine_us/reforms/states/nc/eitc/nc_eitc_reform.py b/policyengine_us/reforms/states/nc/eitc/nc_eitc_reform.py index c483b1dea26..31eff7aba40 100644 --- a/policyengine_us/reforms/states/nc/eitc/nc_eitc_reform.py +++ b/policyengine_us/reforms/states/nc/eitc/nc_eitc_reform.py @@ -23,35 +23,28 @@ class nc_refundable_credits(Variable): unit = USD definition_period = YEAR defined_for = StateCode.NC + # Baseline computes this via `adds`; replacing it with a + # formula requires clearing the inherited computation mode + # (the core engine rejects `formula` + `adds`/`subtracts`). + adds = None + subtracts = None def formula(tax_unit, period, parameters): - return tax_unit("nc_eitc", period) - - class nc_income_tax(Variable): - value_type = float - entity = TaxUnit - label = "North Carolina income tax" - unit = USD - definition_period = YEAR - defined_for = StateCode.NC - - def formula(tax_unit, period, parameters): - # Mirror baseline nc_income_tax, which uses - # nc_income_tax_before_credits and does NOT add nc_use_tax. This - # reform's only change is subtracting the new refundable EITC; - # adding use tax here would raise tax for every filer (even those - # with no EITC). See #8775. - tax_before_credits = tax_unit("nc_income_tax_before_credits", period) - non_refundable_credits = tax_unit("nc_non_refundable_credits", period) - tax_before_refundable = max_(0, tax_before_credits - non_refundable_credits) - refundable_credits = tax_unit("nc_refundable_credits", period) - return tax_before_refundable - refundable_credits + # Preserve the baseline refundable credits and ADD the new + # EITC. Baseline nc_income_tax subtracts nc_refundable_credits + # and excludes nc_use_tax, so no override of it is needed. + # See #8775. + baseline_credits = parameters( + period + ).gov.states.nc.tax.income.credits.refundable + return add(tax_unit, period, list(baseline_credits)) + tax_unit( + "nc_eitc", period + ) class reform(Reform): def apply(self): self.update_variable(nc_eitc) self.update_variable(nc_refundable_credits) - self.update_variable(nc_income_tax) return reform diff --git a/policyengine_us/tests/core/test_override_branches.py b/policyengine_us/tests/core/test_override_branches.py index 9e46a63644e..7abea0e424a 100644 --- a/policyengine_us/tests/core/test_override_branches.py +++ b/policyengine_us/tests/core/test_override_branches.py @@ -29,7 +29,10 @@ on the seeded sample and on random households (Hypothesis). 3. Each comparison branch equals a fresh simulation that sets the overridden input before anything is calculated (a differential test of the branch - against the reference path). + against the reference path). These households repay no claim of right + income; with a qualifying repayment the itemizing branches also fix the + claim of right method, so a reference must fix it too + (``test_claim_of_right_section_1341_properties.py`` covers that case). 4. The same holds when the parent has already calculated the overridden input, and when an earlier year was calculated first. 5. When the branch drops what it copied, it keeps exactly the values set as diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/claim_of_right_credit.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/claim_of_right_credit.yaml new file mode 100644 index 00000000000..511377a29dc --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/claim_of_right_credit.yaml @@ -0,0 +1,26 @@ +- name: Case 1, the credit is the prior-year decrease when tax is computed under section 1341(a)(5). + period: 2025 + input: + claim_of_right_section_1341_eligible: true + claim_of_right_credit_applies: true + claim_of_right_prior_year_tax_decrease: 1_234 + output: + claim_of_right_credit: 1_234 + +- name: Case 2, no credit under section 1341(a)(4). + period: 2025 + input: + claim_of_right_section_1341_eligible: true + claim_of_right_credit_applies: false + claim_of_right_prior_year_tax_decrease: 1_234 + output: + claim_of_right_credit: 0 + +- name: Case 3, no credit without a qualifying repayment. + period: 2025 + input: + claim_of_right_section_1341_eligible: false + claim_of_right_credit_applies: true + claim_of_right_prior_year_tax_decrease: 1_234 + output: + claim_of_right_credit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/claim_of_right_credit_applies.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/claim_of_right_credit_applies.yaml new file mode 100644 index 00000000000..d380d895243 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/claim_of_right_credit_applies.yaml @@ -0,0 +1,128 @@ +# 26 CFR 1.1341-1(b)(3): when the two computations give the same tax, tax is +# computed under 1341(a)(4), with the deduction. The household is +# Publication 525 (2025) Example 40: $5,155.50 with the deduction and $5,903 +# without it. +- name: Case 1, equal tax under both methods keeps the deduction. + period: 2025 + input: + age: 40 + employment_income: 65_700 + charitable_cash_donations: 15_750 + claim_of_right_repayment: 5_000 + # $5,903 - $747.50 = $5,155.50, the tax with the deduction. + claim_of_right_prior_year_tax_decrease: 747.5 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + income_tax_if_claiming_claim_of_right_credit: 5_155.5 + income_tax_if_claiming_claim_of_right_deduction: 5_155.5 + claim_of_right_credit_applies: false + income_tax: 5_155.5 + +- name: Case 2, a prior-year decrease 50 cents larger makes the credit apply. + period: 2025 + input: + age: 40 + employment_income: 65_700 + charitable_cash_donations: 15_750 + claim_of_right_repayment: 5_000 + claim_of_right_prior_year_tax_decrease: 748 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + claim_of_right_credit_applies: true + claim_of_right_deduction: 0 + claim_of_right_credit: 748 + income_tax: 5_155 + +- name: Case 3, a repayment of $3,000 cannot use section 1341. + period: 2025 + input: + age: 40 + employment_income: 65_700 + charitable_cash_donations: 15_750 + claim_of_right_repayment: 3_000 + claim_of_right_prior_year_tax_decrease: 2_000 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + claim_of_right_credit_applies: false + claim_of_right_deduction: 0 + claim_of_right_credit: 0 + # Section 67(g) (now 67(h)) disallows the miscellaneous deduction, so tax is + # unchanged at the tax on $49,950. + income_tax: 5_903 + +- name: Case 4, with no prior-year decrease the deduction applies. + period: 2025 + input: + age: 40 + employment_income: 65_700 + charitable_cash_donations: 15_750 + # $3,001 is the smallest whole-dollar repayment that qualifies. + claim_of_right_repayment: 3_001 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + claim_of_right_credit_applies: false + claim_of_right_deduction: 3_001 + # $65,700 - $15,750 - $3,001 = $46,949; $1,192.50 + 12% x $35,024. + taxable_income: 46_949 + income_tax: 5_395.38 + +- name: Case 5, the credit applies when it gives one cent less tax. + period: 2025 + input: + age: 40 + employment_income: 65_700 + charitable_cash_donations: 15_750 + claim_of_right_repayment: 5_000 + # $5,903 - $747.51 = $5,155.49, one cent below the tax with the deduction. + claim_of_right_prior_year_tax_decrease: 747.51 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + claim_of_right_credit_applies: true + claim_of_right_credit: 747.51 + income_tax: 5_155.49 + +- name: Case 6, the method is the same when itemizing is decided by comparing deductions. + # Publication 525 (2025) Example 40 with + # gov.simulation.branch_to_determine_itemization off. + period: 2025 + input: + gov.simulation.branch_to_determine_itemization: false + age: 40 + employment_income: 65_700 + charitable_cash_donations: 15_750 + claim_of_right_repayment: 5_000 + claim_of_right_prior_year_tax_decrease: 568 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + income_tax_if_claiming_claim_of_right_deduction: 5_155.5 + income_tax_if_claiming_claim_of_right_credit: 5_335 + claim_of_right_credit_applies: false + income_tax: 5_155.5 + +- name: Case 7, the credit applies for a standard deduction filer when itemizing is decided by comparing deductions. + period: 2025 + input: + gov.simulation.branch_to_determine_itemization: false + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + claim_of_right_prior_year_tax_decrease: 2_200 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + claim_of_right_credit_applies: true + claim_of_right_credit: 2_200 + income_tax: 471.5 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/claim_of_right_section_1341_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/claim_of_right_section_1341_eligible.yaml new file mode 100644 index 00000000000..701c7120800 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/claim_of_right_section_1341_eligible.yaml @@ -0,0 +1,51 @@ +# 26 U.S.C. 1341(a)(3): the deduction must exceed $3,000. Publication 525 +# (2025), PDF page 36: "consider the total amount being repaid on the +# return. Each instance of repayment isn't considered separately." +- name: Case 1, a repayment of exactly $3,000 does not qualify. + period: 2025 + input: + tax_unit_claim_of_right_repayment: 3_000 + output: + claim_of_right_section_1341_eligible: false + +- name: Case 2, a repayment of $3,000.01 qualifies. + period: 2025 + input: + tax_unit_claim_of_right_repayment: 3_000.01 + output: + claim_of_right_section_1341_eligible: true + +- name: Case 3, no repayment. + period: 2025 + input: + tax_unit_claim_of_right_repayment: 0 + output: + claim_of_right_section_1341_eligible: false + +- name: Case 4, two repayments of $2,000 on a joint return qualify together. + period: 2025 + input: + people: + person1: + age: 45 + claim_of_right_repayment: 2_000 + person2: + age: 44 + claim_of_right_repayment: 2_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + tax_unit_claim_of_right_repayment: 4_000 + claim_of_right_section_1341_eligible: true + +- name: Case 5, the $3,000 threshold applied before 2018 too. + period: 2017 + input: + tax_unit_claim_of_right_repayment: 3_001 + output: + claim_of_right_section_1341_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/integration.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/integration.yaml new file mode 100644 index 00000000000..4d457ec6996 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/integration.yaml @@ -0,0 +1,161 @@ +# Publication 525, Repayments, Example 40, in three editions. A single filer +# repaid $5,000 of income included in the prior year's income under a claim +# of right. Taxable income is $49,950 without the deduction and $44,950 with +# it; excluding the $5,000 cut the prior year's tax by $568 (2025, 2024) or +# $750 (2018). In each edition method 1, the deduction, gives less tax. +# Wages and charitable gifts are chosen to reproduce those taxable incomes: +# without the repayment, itemized deductions equal the standard deduction. +# Publication 525 figures tax with the Tax Table in 2024 and 2018 (the +# midpoint of each $50 row); PolicyEngine applies the rate schedule. + +- name: Case 1, Publication 525 (2025) Example 40, the deduction gives less tax. + # https://www.irs.gov/pub/irs-prior/p525--2025.pdf#page=36 + period: 2025 + input: + age: 40 + employment_income: 65_700 + charitable_cash_donations: 15_750 + claim_of_right_repayment: 5_000 + claim_of_right_prior_year_tax_decrease: 568 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + claim_of_right_section_1341_eligible: true + # Method 1: tax on $44,950 = $1,192.50 + 12% x ($44,950 - $11,925). + # Publication 525 rounds $5,155.50 to $5,156. + income_tax_if_claiming_claim_of_right_deduction: 5_155.5 + # Method 2: tax on $49,950, $5,903, less the $568 prior-year decrease. + income_tax_if_claiming_claim_of_right_credit: 5_335 + claim_of_right_credit_applies: false + claim_of_right_deduction: 5_000 + claim_of_right_credit: 0 + taxable_income: 44_950 + income_tax: 5_155.5 + +- name: Case 2, Publication 525 (2024) Example 40, the deduction gives less tax. + # https://www.irs.gov/pub/irs-prior/p525--2024.pdf#page=37 + period: 2024 + input: + age: 40 + employment_income: 64_550 + charitable_cash_donations: 14_600 + claim_of_right_repayment: 5_000 + claim_of_right_prior_year_tax_decrease: 568 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + # $1,160 + 12% x ($44,950 - $11,600) = $5,162; the Tax Table gives + # $5,165. + income_tax_if_claiming_claim_of_right_deduction: 5_162 + # $1,160 + $4,266 + 22% x ($49,950 - $47,150) = $6,042 (Tax Table + # $6,048), less $568 = $5,474 (Publication 525: $5,480). + income_tax_if_claiming_claim_of_right_credit: 5_474 + claim_of_right_credit_applies: false + claim_of_right_deduction: 5_000 + taxable_income: 44_950 + income_tax: 5_162 + +- name: Case 3, Publication 525 (2018) Example 40, the deduction gives less tax. + # https://www.irs.gov/pub/irs-prior/p525--2018.pdf#page=34 + period: 2018 + input: + age: 40 + employment_income: 61_950 + charitable_cash_donations: 12_000 + claim_of_right_repayment: 5_000 + claim_of_right_prior_year_tax_decrease: 750 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + # $952.50 + $3,501 + 22% x ($44,950 - $38,700) = $5,828.50 (Tax Table + # $5,834). + income_tax_if_claiming_claim_of_right_deduction: 5_828.5 + # $4,453.50 + 22% x ($49,950 - $38,700) = $6,928.50 (Tax Table $6,934), + # less $750 = $6,178.50 (Publication 525: $6,184). + income_tax_if_claiming_claim_of_right_credit: 6_178.5 + claim_of_right_credit_applies: false + claim_of_right_deduction: 5_000 + taxable_income: 44_950 + income_tax: 5_828.5 + +- name: Case 4, a filer who takes the standard deduction gains only from the credit. + # Publication 525 (2025) method 2 steps: tax without the deduction, less + # the decrease in the earlier year's tax. + period: 2025 + input: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + claim_of_right_prior_year_tax_decrease: 2_200 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + # Itemizing $10,000 loses to the $15,750 standard deduction, so the + # deduction leaves tax on $24,250 at $1,192.50 + 12% x $12,325. + income_tax_if_claiming_claim_of_right_deduction: 2_671.5 + income_tax_if_claiming_claim_of_right_credit: 471.5 + claim_of_right_credit_applies: true + claim_of_right_deduction: 0 + claim_of_right_credit: 2_200 + taxable_income: 24_250 + income_tax: 471.5 + +- name: Case 5, a prior-year decrease above the year's tax is refunded. + # 26 U.S.C. 1341(b)(1): the excess is considered a payment of tax. + period: 2025 + input: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + claim_of_right_prior_year_tax_decrease: 3_000 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + claim_of_right_credit_applies: true + claim_of_right_credit: 3_000 + income_tax_before_refundable_credits: 2_671.5 + income_tax: -328.5 + +- name: Case 6, a joint return counts the spouses' repayments, not a dependent's. + period: 2025 + input: + people: + person1: + age: 45 + employment_income: 90_000 + claim_of_right_repayment: 2_000 + person2: + age: 44 + claim_of_right_repayment: 2_000 + person3: + age: 30 + is_tax_unit_dependent: true + claim_of_right_repayment: 9_000 + tax_units: + tax_unit: + members: [person1, person2, person3] + # The decrease in the couple's earlier joint tax from excluding the + # $4,000 they repaid. + claim_of_right_prior_year_tax_decrease: 500 + state_sales_tax: 0 + local_sales_tax: 0 + households: + household: + members: [person1, person2, person3] + state_code: TX + output: + tax_unit_claim_of_right_repayment: 4_000 + claim_of_right_section_1341_eligible: true + # Taxable income $90,000 - $31,500 = $58,500; tax $2,385 + 12% x + # $34,650 = $6,543, less the $500 credit for other dependents. The + # $4,000 deduction loses to the standard deduction. + income_tax_if_claiming_claim_of_right_deduction: 6_043 + income_tax_if_claiming_claim_of_right_credit: 5_543 + claim_of_right_credit_applies: true + claim_of_right_credit: 500 + income_tax: 5_543 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/tax_unit_claim_of_right_repayment.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/tax_unit_claim_of_right_repayment.yaml new file mode 100644 index 00000000000..20e1e01985c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/claim_of_right/tax_unit_claim_of_right_repayment.yaml @@ -0,0 +1,40 @@ +- name: Case 1, the head's and spouse's repayments add up. + period: 2025 + input: + people: + person1: + age: 45 + claim_of_right_repayment: 2_000 + person2: + age: 44 + claim_of_right_repayment: 1_500 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + tax_unit_claim_of_right_repayment: 3_500 + +- name: Case 2, a dependent's repayment belongs on the dependent's own return. + period: 2025 + input: + people: + person1: + age: 45 + claim_of_right_repayment: 1_000 + person2: + age: 20 + is_tax_unit_dependent: true + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + tax_unit_claim_of_right_repayment: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/claim_of_right_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/claim_of_right_deduction.yaml new file mode 100644 index 00000000000..8afcaf3dcbd --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/claim_of_right_deduction.yaml @@ -0,0 +1,69 @@ +- name: Case 1, the whole repayment is deductible under section 1341(a)(4). + period: 2025 + input: + tax_unit_claim_of_right_repayment: 8_000 + claim_of_right_section_1341_eligible: true + claim_of_right_credit_applies: false + output: + claim_of_right_deduction: 8_000 + +- name: Case 2, no deduction under section 1341(a)(5). + # 26 U.S.C. 1341(b)(3). + period: 2025 + input: + tax_unit_claim_of_right_repayment: 8_000 + claim_of_right_section_1341_eligible: true + claim_of_right_credit_applies: true + output: + claim_of_right_deduction: 0 + +- name: Case 3, a repayment of $3,000 or less is not a section 1341 deduction. + period: 2025 + input: + tax_unit_claim_of_right_repayment: 3_000 + claim_of_right_section_1341_eligible: false + claim_of_right_credit_applies: false + output: + claim_of_right_deduction: 0 + +- name: Case 4, the deduction is subject to the overall limitation on itemized deductions before 2018. + # 2017 Schedule A instructions, Itemized Deductions Worksheet, PDF page 14: + # line 2 removes only gambling and casualty or theft losses on line 28, so + # the line 28 claim of right deduction stays in the limited base. + period: 2017 + input: + age: 40 + employment_income: 400_000 + claim_of_right_repayment: 50_000 + # Federal income tax is modeled from 2018, so fix the method here + # instead of deriving it from 2017 tax. + claim_of_right_credit_applies: false + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + claim_of_right_deduction: 50_000 + total_itemized_taxable_income_deductions: 50_000 + # min(3% x ($400,000 - $261,500), 80% x $50,000) = $4,155. + itemized_taxable_income_deductions_reduction: 4_155 + itemized_taxable_income_deductions: 45_845 + +- name: Case 5, the deduction is subject to the limitation on itemized deductions from 2026. + # 26 U.S.C. 68 as amended by P.L. 119-21, sec. 70111: 2/37 of the lesser + # of itemized deductions and taxable income plus itemized deductions above + # the $640,600 start of the 37% bracket for single filers in 2026. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 40 + employment_income: 1_000_000 + claim_of_right_repayment: 100_000 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: TX + output: + claim_of_right_deduction: 100_000 + total_itemized_taxable_income_deductions: 100_000 + # 2/37 x min($100,000, $1,000,000 - $640,600) = $5,405.41. + itemized_taxable_income_deductions_reduction: 5_405.41 + itemized_taxable_income_deductions: 94_594.59 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.yaml index 3cfeaae9e08..5f1570845b9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.yaml @@ -127,6 +127,22 @@ # min($8,000, $10,000) = $8,000 az_itemized_deductions: 8_000 +- name: The federal claim of right repayment deduction gives no Arizona deduction + # A.R.S. 43-1021(9) adds the section 1341(a)(4) deduction back to Arizona + # gross income; A.R.S. 43-1029 computes Arizona tax without it. + absolute_error_margin: 0.01 + period: 2025 + input: + state_code: AZ + interest_deduction: 6_000 + claim_of_right_deduction: 5_000 + salt_deduction: 0 + charitable_deduction: 0 + az_charitable_contributions_credit_potential: 0 + itemized_medical_expenses: 0 + output: + az_itemized_deductions: 6_000 + - name: Arizona excludes self-employed premiums from its medical deduction without an AGI floor period: 2025 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml index 01b7b1b685f..d490ca69c44 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml @@ -413,3 +413,15 @@ # The federal deduction, now positive, is replaced by California's 4,900, # not added to it. ca_itemized_deductions_pre_limitation: 4_900 + +- name: The federal claim of right repayment deduction does not carry over. + # 2025 Schedule CA (540) instructions, Part II line 16: California allows + # its own deduction or credit, and neither for income it did not tax. + period: 2025 + input: + state_code: CA + interest_deduction: 6_000 + claim_of_right_deduction: 5_000 + output: + itemized_deductions_less_salt: 11_000 + ca_itemized_deductions_pre_limitation: 6_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/deductions/in_claim_of_right_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/deductions/in_claim_of_right_deduction.yaml new file mode 100644 index 00000000000..237ed6e4a4c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/deductions/in_claim_of_right_deduction.yaml @@ -0,0 +1,227 @@ +# IC 6-3-1-3.5(a)(22) and the 2025 IT-40 instructions, deduction 630 (PDF +# page 23): a repayment of income included in Indiana AGI in an earlier year, +# of any amount, whichever federal method applies. + +- name: Case 1, 2025 IT-40 Example 1, $345 of repaid unemployment less the $15 smaller earlier deduction. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 345 + in_claim_of_right_repayment_excluded_from_income: 15 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: IN + output: + claim_of_right_section_1341_eligible: false + in_claim_of_right_deduction: 330 + +- name: Case 2, 2025 IT-40 Example 2, $3,400 repaid with the federal credit. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 3_400 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: true + households: + household: + members: [head] + state_code: IN + output: + in_claim_of_right_deduction: 3_400 + +- name: Case 3, repaid Social Security that Indiana never taxed gives no deduction. + period: 2025 + input: + people: + head: + age: 70 + claim_of_right_repayment: 4_000 + in_claim_of_right_repayment_excluded_from_income: 4_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: IN + output: + in_claim_of_right_deduction: 0 + +- name: Case 4, the federal deduction method with the standard deduction still gets the Indiana deduction. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: false + tax_unit_itemizes: false + households: + household: + members: [head] + state_code: IN + output: + in_claim_of_right_deduction: 10_000 + +- name: Case 5, a joint return deducts both spouses' repayments but not a dependent's. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 300 + spouse: + age: 40 + claim_of_right_repayment: 3_000 + in_claim_of_right_repayment_excluded_from_income: 500 + child: + age: 17 + claim_of_right_repayment: 1_000 + tax_units: + tax_unit: + members: [head, spouse, child] + households: + household: + members: [head, spouse, child] + state_code: IN + output: + in_claim_of_right_deduction: 2_800 + +- name: Case 6, outside Indiana. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: OH + output: + in_claim_of_right_deduction: 0 + +# Wages $40,000, repays $345 (Example 1 without an unemployment deduction). +# IT-40: Indiana AGI $40,000 - $345 deduction - $1,000 exemption = $38,655; +# state tax at 3% = $1,159.65. +- name: Case 7, the deduction lowers Indiana AGI and state tax. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 345 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: IN + output: + in_claim_of_right_deduction: 345 + in_deductions: 345 + in_agi: 38_655 + in_agi_tax: 1_159.65 + +# Wages $40,000, repays $10,000: Indiana AGI $40,000 - $10,000 - $1,000 = +# $29,000; tax at 3% = $870, whichever federal method applies. + +- name: Case 8, Indiana tax is $870 under the federal credit method. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: true + households: + household: + members: [head] + state_code: IN + output: + in_agi_tax: 870 + +- name: Case 9, Indiana tax is $870 under the federal deduction method on a return that itemizes. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: false + tax_unit_itemizes: true + households: + household: + members: [head] + state_code: IN + output: + in_agi_tax: 870 + +- name: Case 10, Indiana tax is $870 under the federal deduction method on a return taking the standard deduction. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: false + tax_unit_itemizes: false + households: + household: + members: [head] + state_code: IN + output: + in_agi_tax: 870 + +- name: Case 11, the same amount entered as an existing other deduction gives the same Indiana tax. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 40_000 + tax_units: + tax_unit: + members: [head] + in_other_deductions: 10_000 + households: + household: + members: [head] + state_code: IN + output: + in_agi_tax: 870 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..bdcf4857084 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,16 @@ state_code: KS output: ks_itemized_deductions: 12 + 9 + 2 + +- name: KS itemized deductions leave out the federal claim of right repayment deduction + # K.S.A. 79-32,120(a) lists charitable contributions, medical care, + # qualified residence interest and property taxes only. + period: 2025 + input: + real_estate_taxes: 3_000 + interest_deduction: 9_000 + claim_of_right_deduction: 5_000 + state_code: KS + output: + itemized_deductions_less_salt: 14_000 + ks_itemized_deductions: 12_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/deductions/ma_claim_of_right_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/deductions/ma_claim_of_right_deduction.yaml new file mode 100644 index 00000000000..b0ffeab101c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/deductions/ma_claim_of_right_deduction.yaml @@ -0,0 +1,209 @@ +# M.G.L. c. 62, s. 3(B)(a)(14) and the 2025 Form 1 instructions, Schedule Y +# line 14 (PDF page 21): a repayment of income previously included in +# Massachusetts taxable income, of any amount, whichever federal method +# applies. + +- name: Case 1, a repayment of $3,000 or less is deductible. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MA + output: + claim_of_right_section_1341_eligible: false + ma_claim_of_right_deduction: 2_000 + +- name: Case 2, the federal credit method still gets the Massachusetts deduction. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: true + households: + household: + members: [head] + state_code: MA + output: + ma_claim_of_right_deduction: 10_000 + +- name: Case 3, income Massachusetts did not tax when received is not deducted. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 10_000 + ma_claim_of_right_repayment_excluded_from_income: 2_500 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MA + output: + ma_claim_of_right_deduction: 7_500 + +- name: Case 4, a joint return deducts both spouses' repayments but not a dependent's. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 1_000 + spouse: + age: 40 + claim_of_right_repayment: 1_500 + child: + age: 17 + claim_of_right_repayment: 700 + tax_units: + tax_unit: + members: [head, spouse, child] + households: + household: + members: [head, spouse, child] + state_code: MA + output: + ma_claim_of_right_deduction: 2_500 + +- name: Case 5, outside Massachusetts. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NH + output: + ma_claim_of_right_deduction: 0 + +# Wages $60,000, repays $5,000. Deductions: Social Security and Medicare (FICA) +# paid, capped at $2,000 (Form 1 line 11a), plus the $5,000 claim of right +# deduction (Schedule Y line 14) = $7,000. Form 1: Part B income $60,000 - +# $7,000 = $53,000, less the $4,400 personal exemption = $48,600 Part B taxable +# income. +- name: Case 6, the deduction lowers Part B taxable income. + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MA + output: + ma_claim_of_right_deduction: 5_000 + ma_part_b_taxable_income_deductions: 7_000 + ma_part_b_taxable_income_before_exemption: 53_000 + ma_part_b_taxable_income: 48_600 + +# Wages $60,000, repays $10,000: Part B income $60,000 - ($2,000 FICA on Form 1 +# line 11a + $10,000 on Schedule Y line 14) - $4,400 = $43,600, whichever +# federal method applies. + +- name: Case 7, Part B taxable income is $43,600 under the federal credit method. + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: true + households: + household: + members: [head] + state_code: MA + output: + ma_part_b_taxable_income: 43_600 + +- name: Case 8, Part B taxable income is $43,600 under the federal deduction method on a return that itemizes. + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: false + tax_unit_itemizes: true + households: + household: + members: [head] + state_code: MA + output: + ma_part_b_taxable_income: 43_600 + +- name: Case 9, Part B taxable income is $43,600 under the federal deduction method on a return taking the standard deduction. + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: false + tax_unit_itemizes: false + households: + household: + members: [head] + state_code: MA + output: + ma_part_b_taxable_income: 43_600 + +# Wages $60,000, repays $60,000: Part B income $60,000 - ($2,000 FICA on Form 1 +# line 11a + $60,000 on Schedule Y line 14) is negative, so Part B income before +# the exemption is zero. +- name: Case 10, the deduction lowers Part B income only to zero. + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + claim_of_right_repayment: 60_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MA + output: + ma_claim_of_right_deduction: 60_000 + ma_part_b_taxable_income_before_exemption: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_federal_income_tax_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_federal_income_tax_deduction.yaml index b1a7ebea29c..e1e12da80db 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_federal_income_tax_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_federal_income_tax_deduction.yaml @@ -166,3 +166,20 @@ state_code: MO output: mo_federal_income_tax_deduction: 0 + +- name: The section 1341 credit does not reduce the tax on the federal return + # The MO-1040 Line 9 chart starts from Form 1040 Line 22 and does not + # subtract Schedule 3 Line 13b. Income tax of 340 is 1,840 on Line 22 less + # the 1,500 credit; 25% of 1,840 is 460. + period: 2025 + absolute_error_margin: 0.01 + input: + mo_adjusted_gross_income: 30_000 + income_tax: 340 + claim_of_right_credit: 1_500 + recovery_rebate_credit: 0 + refundable_ctc: 0 + filing_status: SINGLE + state_code: MO + output: + mo_federal_income_tax_deduction: 460 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_claim_of_right_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_claim_of_right_deduction.yaml new file mode 100644 index 00000000000..cd13b6b920b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_claim_of_right_deduction.yaml @@ -0,0 +1,112 @@ +# 2023 Form 2 instructions, itemized deductions line 18: "These deductions +# are the same deductions that are allowed on your federal Form 1040, +# Schedule A, line 16." +- name: Case 1, Montana follows the federal deduction through 2023. + period: 2023 + input: + people: + person1: + age: 40 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [person1] + claim_of_right_deduction: 5_000 + households: + household: + members: [person1] + state_code: MT + output: + mt_claim_of_right_deduction: 5_000 + mt_misc_deductions: 5_000 + +- name: Case 2, no Montana deduction when the federal deduction is zero. + period: 2023 + input: + people: + person1: + age: 40 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [person1] + claim_of_right_deduction: 0 + households: + household: + members: [person1] + state_code: MT + output: + mt_claim_of_right_deduction: 0 + +- name: Case 3, each spouse deducts their own repayment and a dependent's is left out. + period: 2023 + input: + people: + person1: + age: 45 + claim_of_right_repayment: 3_000 + person2: + age: 44 + claim_of_right_repayment: 1_500 + person3: + age: 20 + is_tax_unit_dependent: true + claim_of_right_repayment: 9_000 + tax_units: + tax_unit: + members: [person1, person2, person3] + claim_of_right_deduction: 4_500 + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + mt_claim_of_right_deduction: [3_000, 1_500, 0] + +# 2024 Form 2 instructions, line 2, Worksheet A line 1: "Enter your total +# federal itemized deductions from Form 1040, line 12", which includes +# Schedule A line 16. +- name: Case 4, from 2024 Montana keeps the deduction within federal itemized deductions. + period: 2024 + input: + people: + person1: + age: 40 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [person1] + claim_of_right_deduction: 5_000 + households: + household: + members: [person1] + state_code: MT + output: + mt_claim_of_right_deduction: 5_000 + mt_misc_deductions: 5_000 + +- name: Case 5, the 2025 deduction reaches Montana itemized deductions. + period: 2025 + input: + people: + person1: + age: 40 + claim_of_right_repayment: 5_000 + investment_interest_expense: 0 + deductible_mortgage_interest: 0 + mt_medical_expense_deduction_joint: 0 + mt_salt_deduction: 0 + tax_units: + tax_unit: + members: [person1] + claim_of_right_deduction: 5_000 + charitable_deduction: 0 + mt_child_dependent_care_expense_deduction_joint: 0 + mt_federal_income_tax_deduction_unit: 0 + households: + household: + members: [person1] + state_code: MT + output: + # Interest 0 + charity 0 + miscellaneous 5,000 + medical 0 + SALT 0. + mt_itemized_deductions_joint: 5_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/credits/claim_of_right/nc_claim_of_right_payment.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/credits/claim_of_right/nc_claim_of_right_payment.yaml new file mode 100644 index 00000000000..a2181be2bc1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/credits/claim_of_right/nc_claim_of_right_payment.yaml @@ -0,0 +1,32 @@ +# N.C. Gen. Stat. 105-266.2: a filer subject to section 1341(a)(5) is +# considered to have paid North Carolina tax equal to the increase in the +# earlier year's North Carolina tax from including the restored item. +- name: Case 1, the payment is the prior-year North Carolina tax increase under section 1341(a)(5). + period: 2025 + input: + claim_of_right_credit_applies: true + nc_claim_of_right_prior_year_tax_increase: 300 + state_code: NC + output: + nc_claim_of_right_payment: 300 + +- name: Case 2, no payment under section 1341(a)(4). + period: 2025 + input: + claim_of_right_credit_applies: false + nc_claim_of_right_prior_year_tax_increase: 300 + state_code: NC + output: + nc_claim_of_right_payment: 0 + +- name: Case 3, the payment is modeled for current North Carolina residents only. + # A coverage limit of the model, not a rule of G.S. 105-266.2: a filer who + # paid the earlier North Carolina tax and has since moved away is not + # covered. + period: 2025 + input: + claim_of_right_credit_applies: true + nc_claim_of_right_prior_year_tax_increase: 300 + state_code: SC + output: + nc_claim_of_right_payment: 0 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 index b0eafe19c2f..3341d040242 100644 --- 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 @@ -369,3 +369,122 @@ nc_taxable_income: 63_000 # 63,000 * 4.25% = 2,677.50 (2,858.13 without the repayment). nc_income_tax: 2_677.50 + +- name: No deduction when federal tax is computed under section 1341(a)(5). + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 80_000 + claim_of_right_credit_applies: true + households: + household: + members: [head] + state_code: NC + output: + nc_claim_of_right_deduction: 0 + +- name: The deduction is allowed when federal tax is computed under section 1341(a)(4). + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 80_000 + claim_of_right_credit_applies: false + households: + household: + members: [head] + state_code: NC + output: + nc_claim_of_right_deduction: 5_000 + +- name: A filer the federal credit method suits loses the North Carolina deduction and is treated as paying the prior-year North Carolina tax on the item. + # The household above, now with a $1,100 decrease in 2024 federal tax and + # a $212.50 increase in 2024 North Carolina tax from the repaid $5,000. + 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 + claim_of_right_prior_year_tax_decrease: 1_100 + nc_claim_of_right_prior_year_tax_increase: 212.5 + households: + household: + members: [head] + state_code: NC + output: + claim_of_right_credit_applies: true + claim_of_right_credit: 1_100 + nc_claim_of_right_deduction: 0 + # 4,000 + 8,000 = 12,000 loses to the 12,750 standard deduction. + nc_standard_or_itemized_deductions: 12_750 + nc_taxable_income: 67_250 + # 67,250 * 4.25% = 2,858.13, less the 212.50 payment. + nc_income_tax_before_refundable_credits: 2_858.13 + nc_claim_of_right_payment: 212.5 + nc_income_tax: 2_645.63 + +- name: A filer the federal deduction method suits keeps the North Carolina deduction and gets no payment. + 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 + claim_of_right_prior_year_tax_decrease: 568 + nc_claim_of_right_prior_year_tax_increase: 212.5 + households: + household: + members: [head] + state_code: NC + output: + claim_of_right_credit_applies: false + nc_claim_of_right_deduction: 5_000 + nc_claim_of_right_payment: 0 + nc_income_tax: 2_677.50 + +- name: Before 2018 a repayment above $3,000 with no prior-year decrease keeps the deduction. + # No method is supplied: with no prior-year tax decrease the credit method + # cannot give less tax, so tax is computed with the deduction. + period: 2016 + 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: + claim_of_right_credit_applies: false + nc_claim_of_right_deduction: 5_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/nc_income_tax.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/nc_income_tax.yaml index 6957ae44fac..02a5fb29344 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/nc_income_tax.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/nc_income_tax.yaml @@ -27,3 +27,15 @@ state_code: NC output: nc_income_tax: 0 + +- name: The refundable claim of right payment can make North Carolina income tax negative + period: 2025 + input: + nc_income_tax_before_credits: 100 + nc_non_refundable_credits: 30 + nc_claim_of_right_payment: 500 + state_code: NC + output: + nc_income_tax_before_refundable_credits: 70 + nc_refundable_credits: 500 + nc_income_tax: -430 diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.yaml index 4e8190d3f01..380a592755c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.yaml @@ -112,3 +112,18 @@ output: # 50% x 8,750 or_federal_tax_liability_subtraction: 4_375 + +- name: The section 1341 credit does not reduce the federal tax liability subtraction + # 2025 Publication OR-17, federal tax worksheet: Form 1040 line 22 less the + # American Opportunity, refundable adoption and premium tax credits only. + period: 2025 + input: + state_code: OR + filing_status: SINGLE + adjusted_gross_income: 60_000 + # 4,000 on Form 1040 line 22 less a 1,500 section 1341 credit. + income_tax: 2_500 + eitc: 0 + claim_of_right_credit: 1_500 + output: + or_federal_tax_liability_subtraction: 4_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/va_reduced_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/va_reduced_itemized_deductions.yaml index 80257f26b5c..2e42bfbd9c8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/va_reduced_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/deductions/va_reduced_itemized_deductions.yaml @@ -40,3 +40,20 @@ state_code: VA output: va_reduced_itemized_deductions: 35_282 + +- name: The federal claim of right repayment deduction stays in the limited itemized deduction total + # 2025 Schedule A line 16b is in line 1 of the Limited Itemized Deduction + # Worksheet and not in line 2. + period: 2025 + absolute_error_margin: 0.01 + input: + filing_status: SINGLE + adjusted_gross_income: 500_000 # > $332,700 + charitable_deduction: 20_000 + claim_of_right_deduction: 10_000 + state_and_local_sales_or_income_tax: 0 + state_code: VA + output: + # Line 1: 30,000. Line 9: min(80% x 30,000, 3% x (500,000 - 332,700)) + # = 5,019. Line 12a: 30,000 - 5,019. + va_reduced_itemized_deductions: 24_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..72cc1f65438 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: WI itemized deduction credit leaves out the federal claim of right repayment deduction + # Wis. Stat. 71.07(5)(a)7; Schedule 1 has lines for medical expenses, + # interest, gifts to charity and casualty losses only. + absolute_error_margin: 0.01 + period: 2025 + input: + wi_standard_deduction: 10_000 + charitable_deduction: 3_000 + interest_deduction: 12_000 + claim_of_right_deduction: 20_000 + state_code: WI + output: + wi_itemized_deduction_credit_potential: 250 # = (3K + 12K - 10K) * 5% diff --git a/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/wv_claim_of_right_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/wv_claim_of_right_subtraction.yaml new file mode 100644 index 00000000000..59bcd3239f8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/wv_claim_of_right_subtraction.yaml @@ -0,0 +1,204 @@ +# 2025 IT-140 instructions, Schedule M line 42 (PDF page 29): "The amount of +# income repaid MUST be more than $3,000.00 to qualify." + +- name: Case 1, a repayment over $3,000 is a decreasing modification. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: WV + output: + wv_claim_of_right_subtraction: 10_000 + wv_subtractions: 10_000 + +- name: Case 2, a repayment of $3,000 does not qualify. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 3_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: WV + output: + wv_claim_of_right_subtraction: 0 + +- name: Case 3, the federal credit method does not change the modification. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: true + households: + household: + members: [head] + state_code: WV + output: + wv_claim_of_right_subtraction: 10_000 + +- name: Case 4, spouses' repayments count together toward $3,000; a dependent's does not. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_000 + spouse: + age: 40 + claim_of_right_repayment: 2_000 + child: + age: 17 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [head, spouse, child] + households: + household: + members: [head, spouse, child] + state_code: WV + output: + wv_claim_of_right_subtraction: [2_000, 2_000, 0] + wv_subtractions: 4_000 + +- name: Case 5, outside West Virginia. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: VA + output: + wv_claim_of_right_subtraction: 0 + +# Wages $40,000, repays $10,000. IT-140: West Virginia AGI $40,000 - $10,000 = +# $30,000; less the $2,000 exemption = $28,000. +- name: Case 6, the modification lowers West Virginia AGI and taxable income. + period: 2025 + input: + people: + head: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: WV + output: + wv_agi: 30_000 + wv_taxable_income: 28_000 + +# Age 70, wages $9,000, repays $4,000. Schedule M line 47, box (c), is "all +# income ... not reported on lines 35 through 48": the $9,000 of wages, capped +# at $8,000; W. Va. Code 11-21-12(c)(9) counts income included in federal AGI. +# The repayment is not income, so it does not reduce box (c). Total decreasing +# modifications $4,000 + $8,000 = $12,000. +- name: Case 7, the repayment does not reduce the senior citizen modification's base. + period: 2025 + input: + people: + head: + age: 70 + employment_income: 9_000 + claim_of_right_repayment: 4_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: WV + output: + wv_claim_of_right_subtraction: 4_000 + wv_senior_citizen_disability_deduction_person: 8_000 + wv_subtractions: 12_000 + # $9,000 - $12,000; taxable income does not go below zero. + wv_agi: -3_000 + wv_taxable_income: 0 + +- name: Case 8, West Virginia taxable income is $28,000 under the federal credit method. + period: 2025 + input: + people: + head: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: true + households: + household: + members: [head] + state_code: WV + output: + wv_taxable_income: 28_000 + +- name: Case 9, West Virginia taxable income is $28,000 under the federal deduction method on a return that itemizes. + period: 2025 + input: + people: + head: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: false + tax_unit_itemizes: true + households: + household: + members: [head] + state_code: WV + output: + wv_taxable_income: 28_000 + +- name: Case 10, West Virginia taxable income is $28,000 under the federal deduction method on a return taking the standard deduction. + period: 2025 + input: + people: + head: + age: 40 + employment_income: 40_000 + claim_of_right_repayment: 10_000 + tax_units: + tax_unit: + members: [head] + claim_of_right_credit_applies: false + tax_unit_itemizes: false + households: + household: + members: [head] + state_code: WV + output: + wv_taxable_income: 28_000 diff --git a/policyengine_us/tests/policy/contrib/states/nc/cdcc/nc_cdcc.yaml b/policyengine_us/tests/policy/contrib/states/nc/cdcc/nc_cdcc.yaml index 9dab37908b0..8a0ab67fdcb 100644 --- a/policyengine_us/tests/policy/contrib/states/nc/cdcc/nc_cdcc.yaml +++ b/policyengine_us/tests/policy/contrib/states/nc/cdcc/nc_cdcc.yaml @@ -81,3 +81,63 @@ cdcc_potential: 1_000 output: nc_cdcc: 0 + +- name: NC CDCC reform keeps the baseline claim of right payment + # Regression test for #8775: the reform adds its CDCC to North Carolina's + # baseline refundable credits instead of replacing them. + period: 2026 + reforms: policyengine_us.reforms.states.nc.cdcc.nc_cdcc + input: + gov.contrib.states.nc.cdcc.in_effect: true + state_code: NC + cdcc_potential: 1_000 + nc_claim_of_right_payment: 250 + nc_income_tax_before_credits: 600 + nc_use_tax: 0 + nc_non_refundable_credits: 0 + output: + nc_cdcc: 300 + nc_refundable_credits: 550 + nc_income_tax: 50 + +- name: NC CDCC reform does not add use tax to income tax + # Use tax is counted at the household level through state_use_tax (#8775). + period: 2026 + reforms: policyengine_us.reforms.states.nc.cdcc.nc_cdcc + input: + gov.contrib.states.nc.cdcc.in_effect: true + state_code: NC + cdcc_potential: 1_000 + nc_income_tax_before_credits: 600 + nc_use_tax: 100 + nc_non_refundable_credits: 0 + output: + nc_cdcc: 300 + # 600 - 300 = 300; it would be 400 if use tax were added. + nc_income_tax: 300 + +- name: Both NC contrib credits stack on the claim of right payment, and use tax stays out of income tax + # Review r2 of PolicyEngine/policyengine-us#10038. Refundable credits: + # 250 payment + 100 EITC (10% of 1,000) + 300 CDCC (30% of 1,000) = 650. + period: 2026 + reforms: + - policyengine_us.reforms.states.nc.eitc.nc_eitc + - policyengine_us.reforms.states.nc.cdcc.nc_cdcc + input: + gov.contrib.states.nc.eitc.match: 0.1 + gov.contrib.states.nc.cdcc.in_effect: true + state_code: NC + eitc: 1_000 + cdcc_potential: 1_000 + nc_claim_of_right_payment: 250 + nc_income_tax_before_credits: 600 + nc_use_tax: 100 + nc_non_refundable_credits: 0 + output: + nc_refundable_credits: 650 + # 600 - 650; the 100 of use tax is counted once, through state_use_tax. + nc_income_tax: -50 + state_income_tax: -50 + state_use_tax: 100 + household_state_tax_before_refundable_credits: 700 + household_refundable_state_tax_credits: 650 diff --git a/policyengine_us/tests/policy/contrib/states/nc/eitc/nc_eitc.yaml b/policyengine_us/tests/policy/contrib/states/nc/eitc/nc_eitc.yaml index ed383cb202b..09145e7375a 100644 --- a/policyengine_us/tests/policy/contrib/states/nc/eitc/nc_eitc.yaml +++ b/policyengine_us/tests/policy/contrib/states/nc/eitc/nc_eitc.yaml @@ -49,3 +49,21 @@ nc_eitc: 3_000 nc_refundable_credits: 3_000 nc_income_tax: -2_500 + +- name: NC EITC reform keeps the baseline claim of right payment + # Regression test for #8775: the reform adds its EITC to North Carolina's + # baseline refundable credits instead of replacing them. + period: 2026 + reforms: policyengine_us.reforms.states.nc.eitc.nc_eitc + input: + gov.contrib.states.nc.eitc.match: 0.1 + state_code: NC + eitc: 1_000 + nc_claim_of_right_payment: 250 + nc_income_tax_before_credits: 600 + nc_use_tax: 0 + nc_non_refundable_credits: 0 + output: + nc_eitc: 100 + nc_refundable_credits: 350 + nc_income_tax: 250 diff --git a/policyengine_us/tests/policy/contrib/states/nc/eitc/nc_eitc_excludes_use_tax.yaml b/policyengine_us/tests/policy/contrib/states/nc/eitc/nc_eitc_excludes_use_tax.yaml index bf877dda502..4abc4d48e43 100644 --- a/policyengine_us/tests/policy/contrib/states/nc/eitc/nc_eitc_excludes_use_tax.yaml +++ b/policyengine_us/tests/policy/contrib/states/nc/eitc/nc_eitc_excludes_use_tax.yaml @@ -1,5 +1,5 @@ -# Regression test for #8775: the NC EITC reform's nc_income_tax override must -# mirror baseline (nc_income_tax_before_credits minus credits) and must NOT add +# Regression test for #8775: under the NC EITC reform, nc_income_tax must +# match baseline (nc_income_tax_before_credits minus credits) and must NOT add # nc_use_tax. Use tax is already counted at the household level via # state_use_tax; adding it here would raise tax for every NC filer (even with # no EITC) and double-count it. diff --git a/policyengine_us/tests/unit/test_claim_of_right_section_1341_properties.py b/policyengine_us/tests/unit/test_claim_of_right_section_1341_properties.py new file mode 100644 index 00000000000..2846b3a7504 --- /dev/null +++ b/policyengine_us/tests/unit/test_claim_of_right_section_1341_properties.py @@ -0,0 +1,547 @@ +"""Branch isolation and cross-case properties of the section 1341 computation. + +26 U.S.C. 1341(a): when a filer repays more than $3,000 of income included in +an earlier year under a claim of right, federal tax is the lesser of (4) the +tax with a deduction for the repayment and (5) the tax without it minus the +decrease in the earlier year's tax from excluding the income. Ties use (4) +(26 CFR 1.1341-1(b)(3)); any excess of the decrease over the tax is refunded +(1341(b)(1)). North Carolina allows no claim of right deduction under +1341(a)(5) (G.S. 105-153.5(a)(2)d) and instead treats the increase in the +earlier year's North Carolina tax as a payment (G.S. 105-266.2). + +The policy cases are in YAML (``gov/irs/credits/claim_of_right``). These tests +cover what a YAML case cannot. The model picks the method by calculating +federal tax in two override branches, so the tests build separate simulations +and compare them: + +- each branch equals a simulation that fixes the method as an input before + anything is calculated, and income tax equals the smaller of the two + (ties to the deduction), where the credit side comes from a simulation with + no repayment at all less the prior-year decrease; +- results do not depend on which variable is calculated first, including + when a tax liability under one itemizing choice is calculated before the + method is known, under both settings of + ``gov.simulation.branch_to_determine_itemization``; the itemizing branches + keep the simulation's method, including the 2026 branch that + ``tax_unit_itemizes`` opens for the section 68 limitation; +- a later year calculated on the same simulation equals a simulation of that + year alone, and an itemizing branch of a simulation whose itemizing input + changed after its tax was calculated does not reuse that tax; +- relations between cases over a grid: tax never rises with the repayment + (also across the $3,000 threshold) or with the prior-year decrease, and a + repayment of $3,000 or less changes nothing (section 67(h), formerly 67(g)). + +They also check, at every grid point, that the deduction is the whole +repayment or nothing, the credit is the whole decrease or nothing, and never +both; that North Carolina's deduction is zero whenever the credit method +applies and otherwise is what it is with the deduction method fixed; and that +its payment is the prior-year increase when the credit method applies and zero +otherwise. + +Last, one itemizing household per state: with the credit method fixed, the +repayment leaves every state's income tax unchanged (section 1341(b)(3)), +except that it lowers the tax of each state listed in +STATES_WITH_RELIEF_UNDER_THE_CREDIT whose rule is in the model; with the +deduction method fixed it leaves Arizona's, California's, Kansas's and +Wisconsin's unchanged. +""" + +import gc +import itertools + +import numpy as np +import pytest +from policyengine_core.periods import instant +from policyengine_core.reforms import Reform + +from policyengine_us import CountryTaxBenefitSystem, Simulation +from policyengine_us.variables.household.demographic.geographic.state_code import ( + StateCode, +) + +REPAYMENTS = [0, 2_999, 3_000, 3_001, 5_000, 20_000, 150_000] +DECREASES = [0, 400, 2_500, 12_000, 60_000] +WAGES = [0, 30_000, 80_000, 400_000, 1_500_000] +CHARITY = [0, 20_000] +AXES = [REPAYMENTS, DECREASES, WAGES, CHARITY] +GRID = list(itertools.product(*AXES)) +SHAPE = tuple(len(axis) for axis in AXES) +REPAYMENT, DECREASE, WAGE, CHARITABLE = ( + np.array([point[i] for point in GRID], dtype=float) for i in range(len(AXES)) +) +# An illustrative North Carolina prior-year tax increase on the repaid income. +NC_INCREASE = 0.05 * REPAYMENT +ZEROS = np.zeros_like(REPAYMENT) +THRESHOLD = 3_000 +YEARS = [2025, 2026] +TOLERANCE = 0.01 +# The 50 states and the District of Columbia. +STATES = [ + state.name + for state in StateCode + if state.name not in {"AA", "AE", "AP", "AS", "GU", "MP", "PR", "PW", "VI"} +] +# States whose own rules leave out the federal claim of right repayment +# deduction: A.R.S. 43-1021(9), the California Schedule CA (540) instructions +# for Part II line 16, K.S.A. 79-32,120(a) and Wisconsin Form 1 Schedule 1. +STATES_WITHOUT_THE_DEDUCTION = ["AZ", "CA", "KS", "WI"] +# States whose own claim of right relief applies when federal tax is computed +# with the section 1341 credit, and the variable that carries it. Under the +# credit method these lower the household's state tax; every other state's +# tax must not change. A listed state whose variable is not in the model yet +# must not change either. +STATES_WITH_RELIEF_UNDER_THE_CREDIT = { + # Ark. Code 26-51-457: a deduction whichever federal method applies. + "AR": "ar_claim_of_right_deduction", + # Act 56, SLH 2023: section 1341 computed on Hawaii tax, independently. + "HI": "hi_claim_of_right_credit", + # 35 ILCS 5/203(a)(2)(P): the deduction used to compute the federal credit. + "IL": "il_claim_of_right_subtraction", + # IC 6-3-1-3.5(a)(22). + "IN": "in_claim_of_right_deduction", + # M.G.L. c. 62, s. 3(B)(a)(14). + "MA": "ma_claim_of_right_deduction", + # N.J.S.A. 54A:5-2 and TAM 2011-7: an offset within the income category. + "NJ": "nj_claim_of_right_offset", + # R.C. 5747.01(A)(12). + "OH": "oh_claim_of_right_deduction", + # IT-140 Schedule M, IRC 1341 Repayments. + "WV": "wv_claim_of_right_subtraction", +} +BRANCHES = [ + "income_tax_if_claiming_claim_of_right_deduction", + "income_tax_if_claiming_claim_of_right_credit", +] +VARIABLES = [ + "income_tax", + "tax_unit_itemizes", + "tax_liability_if_itemizing", + "tax_liability_if_not_itemizing", + "claim_of_right_section_1341_eligible", + "claim_of_right_credit_applies", + "claim_of_right_deduction", + "claim_of_right_credit", + "nc_claim_of_right_deduction", + "nc_claim_of_right_payment", + "nc_income_tax_before_refundable_credits", + "nc_income_tax", +] + BRANCHES + + +def grid_simulation( + years, repayment=REPAYMENT, decrease=DECREASE, nc_increase=NC_INCREASE, method=None +): + """One single-person North Carolina tax unit per grid point.""" + + def yearly(value): + return {year: value for year in years} + + situation = {"people": {}, "tax_units": {}, "households": {}} + for i in range(len(GRID)): + person = f"p{i}" + situation["people"][person] = { + "age": yearly(40), + "employment_income": yearly(WAGE[i]), + "charitable_cash_donations": yearly(CHARITABLE[i]), + "claim_of_right_repayment": yearly(float(repayment[i])), + } + tax_unit = { + "members": [person], + "claim_of_right_prior_year_tax_decrease": yearly(float(decrease[i])), + "nc_claim_of_right_prior_year_tax_increase": yearly(float(nc_increase[i])), + } + if method is not None: + tax_unit["claim_of_right_credit_applies"] = yearly(method) + situation["tax_units"][f"t{i}"] = tax_unit + situation["households"][f"h{i}"] = { + "members": [person], + "state_code": yearly("NC"), + } + return Simulation(situation=situation) + + +def calculate(simulation, year, variables): + return {v: np.asarray(simulation.calculate(v, year)) for v in variables} + + +@pytest.fixture(scope="module") +def results(): + out = {} + for year in YEARS: + out[year] = calculate(grid_simulation([year]), year, VARIABLES) + # Independent references: no repayment at all, and each method fixed + # as an input rather than chosen in a branch. + none = grid_simulation([year], ZEROS, ZEROS, ZEROS) + out[year]["tax_without_repayment"] = np.asarray( + none.calculate("income_tax", year) + ) + deduction = grid_simulation([year], method=False) + out[year]["tax_with_deduction"] = np.asarray( + deduction.calculate("income_tax", year) + ) + out[year]["nc_deduction_if_deduction"] = np.asarray( + deduction.calculate("nc_claim_of_right_deduction", year) + ) + credit = grid_simulation([year], method=True) + out[year]["nc_deduction_if_credit"] = np.asarray( + credit.calculate("nc_claim_of_right_deduction", year) + ) + return out + + +def eligible(): + return REPAYMENT > THRESHOLD + + +@pytest.mark.parametrize("year", YEARS) +def test_tax_is_the_lesser_of_the_two_methods(results, year): + r = results[year] + tax_with_deduction = r["tax_with_deduction"] + tax_with_credit = r["tax_without_repayment"] - DECREASE + # Compared to the cent; equal amounts keep the deduction. + credit = eligible() & ( + np.round(tax_with_credit, 2) < np.round(tax_with_deduction, 2) + ) + assert credit.any() and (eligible() & ~credit).any(), "both methods occur" + np.testing.assert_array_equal(r["claim_of_right_credit_applies"], credit) + expected = np.where( + eligible(), + np.where(credit, tax_with_credit, tax_with_deduction), + r["tax_without_repayment"], + ) + np.testing.assert_allclose(r["income_tax"], expected, atol=TOLERANCE) + + +@pytest.mark.parametrize("year", YEARS) +def test_branches_match_independent_simulations(results, year): + r = results[year] + np.testing.assert_allclose( + r["income_tax_if_claiming_claim_of_right_deduction"], + r["tax_with_deduction"], + atol=TOLERANCE, + ) + tax_with_credit = r["tax_without_repayment"] - DECREASE + np.testing.assert_allclose( + r["income_tax_if_claiming_claim_of_right_credit"][eligible()], + tax_with_credit[eligible()], + atol=TOLERANCE, + ) + + +@pytest.mark.parametrize( + "first", ["tax_liability_if_itemizing", "tax_liability_if_not_itemizing"] + BRANCHES +) +def test_results_do_not_depend_on_calculation_order(results, first): + # A tax liability under one itemizing choice is calculated in a branch. + # Asked for before the method is known, it must not leave a value behind + # that a later method branch reuses. + year = YEARS[0] + simulation = grid_simulation([year]) + simulation.calculate(first, year) + for variable in VARIABLES: + np.testing.assert_allclose( + np.asarray(simulation.calculate(variable, year)), + results[year][variable], + atol=TOLERANCE, + err_msg=f"{variable} changes when {first} is calculated first", + ) + + +def test_a_later_year_matches_a_simulation_of_that_year_alone(results): + simulation = grid_simulation(YEARS) + simulation.calculate("income_tax", YEARS[0]) + for variable in VARIABLES: + np.testing.assert_allclose( + np.asarray(simulation.calculate(variable, YEARS[1])), + results[YEARS[1]][variable], + atol=TOLERANCE, + err_msg=f"{variable} differs after an earlier year was calculated", + ) + + +@pytest.mark.parametrize("year", YEARS) +def test_deduction_and_credit_are_all_or_nothing_and_exclusive(results, year): + r = results[year] + credit_applies = r["claim_of_right_credit_applies"] + deduction = r["claim_of_right_deduction"] + credit = r["claim_of_right_credit"] + np.testing.assert_array_equal(r["claim_of_right_section_1341_eligible"], eligible()) + np.testing.assert_allclose( + deduction, np.where(eligible() & ~credit_applies, REPAYMENT, 0) + ) + np.testing.assert_allclose(credit, np.where(credit_applies, DECREASE, 0)) + assert not np.any((deduction > 0) & (credit > 0)) + + +@pytest.mark.parametrize("year", YEARS) +def test_a_repayment_of_3000_or_less_changes_nothing(results, year): + r = results[year] + small = ~eligible() + assert not r["claim_of_right_credit_applies"][small].any() + np.testing.assert_allclose( + r["income_tax"][small], r["tax_without_repayment"][small], atol=TOLERANCE + ) + + +@pytest.mark.parametrize("year", YEARS) +def test_tax_never_rises(results, year): + r = results[year] + assert np.all(r["income_tax"] <= r["tax_without_repayment"] + TOLERANCE) + table = r["income_tax"].reshape(SHAPE) + assert np.all(np.diff(table, axis=0) <= TOLERANCE), "rises with the repayment" + assert np.all(np.diff(table, axis=1) <= TOLERANCE), "rises with the decrease" + + +@pytest.mark.parametrize("year", YEARS) +def test_a_decrease_above_the_tax_is_refunded(results, year): + r = results[year] + credit_applies = r["claim_of_right_credit_applies"] + refunded = credit_applies & (DECREASE > r["tax_without_repayment"]) + assert refunded.any(), "the grid should include refunds" + assert np.all(r["income_tax"][refunded] < 0) + + +@pytest.mark.parametrize("year", YEARS) +def test_north_carolina_follows_the_federal_method(results, year): + r = results[year] + credit_applies = r["claim_of_right_credit_applies"] + np.testing.assert_allclose( + r["nc_claim_of_right_deduction"], + np.where(credit_applies, 0, r["nc_deduction_if_deduction"]), + ) + assert np.all(r["nc_deduction_if_credit"] == 0) + np.testing.assert_allclose( + r["nc_claim_of_right_payment"], np.where(credit_applies, NC_INCREASE, 0) + ) + np.testing.assert_allclose( + r["nc_income_tax"], + r["nc_income_tax_before_refundable_credits"] - r["nc_claim_of_right_payment"], + atol=TOLERANCE, + ) + + +def state_simulation(year, repayment, method=None): + """An itemizing single filer in each state.""" + situation = {"people": {}, "tax_units": {}, "households": {}} + for i, state in enumerate(STATES): + situation["people"][f"p{i}"] = { + "age": {year: 40}, + "employment_income": {year: 60_000}, + "charitable_cash_donations": {year: 25_000}, + "claim_of_right_repayment": {year: repayment}, + } + situation["tax_units"][f"t{i}"] = { + "members": [f"p{i}"], + "claim_of_right_prior_year_tax_decrease": {year: 2_500}, + } + if method is not None: + situation["tax_units"][f"t{i}"]["claim_of_right_credit_applies"] = { + year: method + } + situation["households"][f"h{i}"] = { + "members": [f"p{i}"], + "state_code": {year: state}, + } + return Simulation(situation=situation) + + +def state_income_tax(simulation, year): + return np.asarray(simulation.calculate("state_income_tax", year)) + + +def test_states_under_each_federal_method(): + # One year: each of these three simulations computes every state's tax. + year = YEARS[0] + without_repayment = state_income_tax(state_simulation(year, 0), year) + credit = state_simulation(year, 10_000, method=True) + with_credit = state_income_tax(credit, year) + variables = credit.tax_benefit_system.variables + relief = np.isin( + STATES, + [ + state + for state, variable in STATES_WITH_RELIEF_UNDER_THE_CREDIT.items() + if variable in variables + ], + ) + np.testing.assert_allclose( + with_credit[~relief], without_repayment[~relief], atol=TOLERANCE + ) + assert np.all(with_credit[relief] < without_repayment[relief] - 1), ( + "each listed state's own rule should lower its tax under the credit method" + ) + with_deduction = state_income_tax( + state_simulation(year, 10_000, method=False), year + ) + assert np.any(np.abs(with_deduction - without_repayment) > 1), ( + "the deduction should reach the states that follow federal itemized deductions" + ) + excluded = np.isin(STATES, STATES_WITHOUT_THE_DEDUCTION) + np.testing.assert_allclose( + with_deduction[excluded], without_repayment[excluded], atol=TOLERANCE + ) + + +# The household from reviews r1 and r2 of PolicyEngine/policyengine-us#10038. +# With the deduction, $20,000 of itemized deductions leave $40,000 of taxable +# income: $1,192.50 + 12% x $28,075 = $4,561.50. Without it, the $15,750 +# standard deduction leaves $44,250: $5,071.50, less the $100 decrease is +# $4,971.50. So the deduction applies, and tax without itemizing under that +# method is $5,071.50. +TEXAS_YEAR = 2025 +# From 2026 tax_unit_itemizes takes the itemized deductions from the branch +# that itemizes (the P.L. 119-21 section 68 limitation). A household for which +# that branch, choosing a method for itself, would pick the deduction while +# the simulation picks the credit. Standard deduction $16,100 plus the $1,000 +# section 170(p) charitable deduction: taxable income $42,900, tax $1,240 + +# 12% x $30,500 = $4,900, less the $800 decrease is $4,100. With the deduction: +# gifts of $10,000 less the 0.5% floor ($300) plus the $10,000 repayment are +# $19,700 of itemized deductions, taxable income $40,300, tax $4,588. Itemizing +# under the credit method: $9,700 of deductions, taxable income $50,300, tax +# $5,788 less $800 is $4,988. +ITEMIZING_BRANCH_YEAR = 2026 +ITEMIZING_BRANCH_EXPECTED = { + "income_tax": 4_100, + "claim_of_right_credit_applies": True, + "tax_unit_itemizes": False, + "tax_liability_if_itemizing": 4_988, + "tax_liability_if_not_itemizing": 4_100, + "income_tax_if_claiming_claim_of_right_deduction": 4_588, + "income_tax_if_claiming_claim_of_right_credit": 4_100, +} +TEXAS_EXPECTED = { + "income_tax": 4_561.5, + "claim_of_right_credit_applies": False, + "tax_liability_if_itemizing": 4_561.5, + "tax_liability_if_not_itemizing": 5_071.5, + "income_tax_if_claiming_claim_of_right_deduction": 4_561.5, + "income_tax_if_claiming_claim_of_right_credit": 4_971.5, +} + + +class no_itemization_branching(Reform): + def apply(self): + def modify(parameters): + parameters.gov.simulation.branch_to_determine_itemization.update( + start=instant(f"{TEXAS_YEAR}-01-01"), + stop=instant(f"{ITEMIZING_BRANCH_YEAR}-12-31"), + value=False, + ) + return parameters + + self.modify_parameters(modify) + + +@pytest.fixture(scope="module") +def systems(): + systems = { + True: None, + False: CountryTaxBenefitSystem(reform=(no_itemization_branching,)), + } + yield systems + # Release the reformed system before the rest of the test process runs. + systems.clear() + gc.collect() + + +def texas_simulation(system, year=TEXAS_YEAR, decrease=100, **tax_unit_inputs): + situation = { + "people": { + "p": { + "age": {year: 40}, + "employment_income": {year: 60_000}, + "charitable_cash_donations": {year: 10_000}, + "claim_of_right_repayment": {year: 10_000}, + } + }, + "tax_units": { + "t": { + "members": ["p"], + "claim_of_right_prior_year_tax_decrease": {year: decrease}, + "state_sales_tax": {year: 0}, + "local_sales_tax": {year: 0}, + **{name: {year: value} for name, value in tax_unit_inputs.items()}, + } + }, + "households": {"h": {"members": ["p"], "state_code": {year: "TX"}}}, + } + if system is None: + return Simulation(situation=situation) + return Simulation(tax_benefit_system=system, situation=situation) + + +@pytest.mark.parametrize("branching", [True, False]) +@pytest.mark.parametrize("first", list(TEXAS_EXPECTED)) +def test_texas_household_does_not_depend_on_calculation_order( + systems, branching, first +): + simulation = texas_simulation(systems[branching]) + simulation.calculate(first, TEXAS_YEAR) + for variable, expected in TEXAS_EXPECTED.items(): + np.testing.assert_allclose( + np.asarray(simulation.calculate(variable, TEXAS_YEAR)), + [expected], + atol=TOLERANCE, + err_msg=f"{variable} when {first} is calculated first", + ) + + +@pytest.mark.parametrize("branching", [True, False]) +@pytest.mark.parametrize("first", list(ITEMIZING_BRANCH_EXPECTED)) +def test_the_itemizing_branch_keeps_the_simulation_method(systems, branching, first): + # From policyengine-core 3.33, set_input on a branch drops the values + # stored after the overridden variable was first stored anywhere in the + # simulation. A branch fixing only itemization then lost the inherited + # method and chose its own, and with itemization branching off its + # itemized deductions alone decided whether to itemize: $4,988 here. + year = ITEMIZING_BRANCH_YEAR + forced = texas_simulation(systems[branching], year, 800, tax_unit_itemizes=True) + assert not forced.calculate("claim_of_right_credit_applies", year)[0], ( + "a filer who must itemize would choose the deduction" + ) + simulation = texas_simulation(systems[branching], year, 800) + simulation.calculate(first, year) + for variable, expected in ITEMIZING_BRANCH_EXPECTED.items(): + np.testing.assert_allclose( + np.asarray(simulation.calculate(variable, year)), + [expected], + atol=TOLERANCE, + err_msg=f"{variable} when {first} is calculated first", + ) + + +def test_a_branch_does_not_reuse_tax_calculated_before_an_input_changed(): + # Review r3 of PolicyEngine/policyengine-us#10038. No repayment, $20,000 of + # charitable gifts: $5,071.50 with the $15,750 standard deduction (taxable + # income $44,250), $4,561.50 itemizing (taxable income $40,000). + year = TEXAS_YEAR + simulation = Simulation( + situation={ + "people": { + "p": { + "age": {year: 40}, + "employment_income": {year: 60_000}, + "charitable_cash_donations": {year: 20_000}, + } + }, + "tax_units": { + "t": { + "members": ["p"], + "tax_unit_itemizes": {year: False}, + "state_sales_tax": {year: 0}, + "local_sales_tax": {year: 0}, + } + }, + "households": {"h": {"members": ["p"], "state_code": {year: "TX"}}}, + } + ) + np.testing.assert_allclose( + simulation.calculate("income_tax", year), [5_071.5], atol=TOLERANCE + ) + # A plain branch whose itemizing input now equals the itemizing branch's + # override still holds the tax calculated without itemizing. + branch = simulation.get_branch("itemizes_after_the_fact") + branch.set_input("tax_unit_itemizes", year, np.array([True])) + np.testing.assert_allclose( + branch.calculate("tax_liability_if_itemizing", year), [4_561.5], atol=TOLERANCE + ) 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 index 7ff5a694e8d..d86fc79c5d5 100644 --- a/policyengine_us/tests/unit/test_nc_claim_of_right_properties.py +++ b/policyengine_us/tests/unit/test_nc_claim_of_right_properties.py @@ -65,6 +65,12 @@ def grid_simulation(year, people, tax_units): } situation["tax_units"][f"t{i}"] = { "members": [person], + # These tests check the deduction's arithmetic, which applies + # when federal tax is computed with the deduction (section + # 1341(a)(4)). Fixing that method here skips the two extra + # federal tax computations that choose it; + # test_claim_of_right_section_1341_properties.py covers the choice. + "claim_of_right_credit_applies": {year: False}, **{name: {year: float(values[i])} for name, values in tax_units.items()}, } situation["households"][f"h{i}"] = { diff --git a/policyengine_us/tools/general.py b/policyengine_us/tools/general.py index d2bd4f5fd5f..bc8446b01b2 100644 --- a/policyengine_us/tools/general.py +++ b/policyengine_us/tools/general.py @@ -185,6 +185,32 @@ def filer_share(person, period, amounts): return where(total > 0, proportional, equal) +def get_itemization_branch(tax_unit, period, itemizes: bool): + """ + The branch of ``tax_unit``'s simulation that fixes ``tax_unit_itemizes``. + + It is named ``itemizing`` or ``not_itemizing``. When some tax unit repays + more than $3,000 of claim of right income (26 U.S.C. 1341), the branch + also takes this simulation's method, ``claim_of_right_credit_applies``, as + an input, so it never chooses one for itself: with itemizing fixed, a + branch would choose the method best for that choice, not the method this + simulation uses. Every caller asks for the same inputs, so they share one + branch. With no qualifying repayment the method is false everywhere and is + left out, which keeps the branch sharing this simulation's cache. + """ + overrides = {"tax_unit_itemizes": np.full(tax_unit.count, itemizes, dtype=bool)} + if tax_unit("claim_of_right_section_1341_eligible", period).any(): + overrides["claim_of_right_credit_applies"] = tax_unit( + "claim_of_right_credit_applies", period + ) + return get_override_branch( + tax_unit.simulation, + "itemizing" if itemizes else "not_itemizing", + period, + overrides, + ) + + def sum_contained_tax_units(var, population, period): tax_unit = population.members.tax_unit.reference_entity values = tax_unit(var, period) diff --git a/policyengine_us/tools/period_branch.py b/policyengine_us/tools/period_branch.py index 1270a9490ce..469b239ec1a 100644 --- a/policyengine_us/tools/period_branch.py +++ b/policyengine_us/tools/period_branch.py @@ -1,11 +1,15 @@ """Formula branches that calculate one period, under overridden inputs. A policyengine-core branch starts from every array its parent has cached when -the branch is created and never sees the parent's later calculations, and -``set_input`` on the branch stores the new value without clearing anything -that was calculated from the old one. A branch therefore answers any variable -its parent had already calculated with the parent's value, whatever the -branch's inputs say. A branch kept from another period also answers this +the branch is created and never sees the parent's later calculations. Before +policyengine-core 3.33, ``set_input`` on the branch stored the new value +without clearing anything that was calculated from the old one, so a branch +answered any variable its parent had already calculated with the parent's +value, whatever the branch's inputs said. From 3.33 it drops the values stored +after the overridden variable was first stored anywhere in the simulation, +which can include a choice the caller wants the branch to keep; pass such a +choice as an input (as ``get_itemization_branch`` does for the claim of right +method). A branch kept from another period also answers this period from a copy taken before any of this period was calculated, unlike the branch a simulation calculating only this period would create, so a later year came out differently when an earlier year had been calculated first. @@ -23,8 +27,15 @@ formula branches while its parent is still calculating the variable the branch overrides. Otherwise the branch drops every array it copied except inputs, each for the periods it was set for, and calculates the rest - itself. + itself. A parent value equal to the override is not enough: the parent's + cache can predate a later change to another input. - A branch reused within its period with different inputs is created again. +- A caller can ask the branch never to keep the parent's calculated values + (``inherit_calculated=False``). The parent can hold a value that depends on + the overridden variable although it has no value for that variable yet: + one it calculated in another branch, which chose the overridden variable + for itself. The branches that fix the claim of right method (26 U.S.C. + 1341) start from inputs for that reason. ``get_branch_for_period`` is the same with no inputs, for branches that change the tax-benefit system rather than inputs: the caller swaps the system @@ -139,11 +150,14 @@ def get_override_branch( name: str, period: Period, inputs: Dict[str, Override], + inherit_calculated: bool = True, ) -> Simulation: """Return ``simulation``'s branch ``name`` for ``period``, with ``inputs`` set. ``inputs`` maps each overridden variable to its value for ``period``, or - to a ``(period, value)`` pair for another period (e.g. a month). + to a ``(period, value)`` pair for another period (e.g. a month). With + ``inherit_calculated=False`` a new branch keeps only inputs, whether or + not the parent has calculated an overridden variable. """ period = to_period(period) overrides = list(_overrides(period, inputs)) @@ -167,7 +181,7 @@ def get_override_branch( ) branch = simulation.get_branch(name) branch.branch_period = period - if parent_knows_override: + if parent_knows_override or not inherit_calculated: drop_inherited_values(branch) for variable, input_period, value in overrides: branch.set_input(variable, input_period, value) diff --git a/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_credit.py b/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_credit.py new file mode 100644 index 00000000000..494a6cb24e7 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_credit.py @@ -0,0 +1,29 @@ +from policyengine_us.model_api import * + + +class claim_of_right_credit(Variable): + value_type = float + entity = TaxUnit + label = "Section 1341 credit for repayment of amounts included in income from earlier years" + unit = USD + documentation = ( + "When tax is computed under section 1341(a)(5), the decrease in " + "prior-year tax from excluding the repaid income. It is reported as " + "a payment (Schedule 3, line 13b), so any excess over the year's tax " + "is refunded (section 1341(b)(1))." + ) + definition_period = YEAR + reference = ( + "https://www.govinfo.gov/content/pkg/USCODE-2024-title26/html/USCODE-2024-title26-subtitleA-chap1-subchapQ-partV-sec1341.htm", + "https://www.irs.gov/pub/irs-prior/f1040s3--2025.pdf", + # 2025 Form 1040 instructions, Schedule 3 line 13b + "https://www.irs.gov/pub/irs-prior/i1040gi--2025.pdf#page=117", + # Publication 525 (2025), Repayments, method 2 + "https://www.irs.gov/pub/irs-prior/p525--2025.pdf#page=36", + ) + + def formula(tax_unit, period, parameters): + eligible = tax_unit("claim_of_right_section_1341_eligible", period) + credit_applies = tax_unit("claim_of_right_credit_applies", period) + decrease = tax_unit("claim_of_right_prior_year_tax_decrease", period) + return where(eligible & credit_applies, decrease, 0) diff --git a/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_credit_applies.py b/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_credit_applies.py new file mode 100644 index 00000000000..7c46719ad72 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_credit_applies.py @@ -0,0 +1,40 @@ +from policyengine_us.model_api import * + + +class claim_of_right_credit_applies(Variable): + value_type = bool + entity = TaxUnit + label = "Tax is computed under section 1341(a)(5)" + documentation = ( + "Whether federal income tax for the year of repayment is computed " + "under 26 U.S.C. 1341(a)(5): the tax without a deduction for the " + "repayment, minus the decrease in prior-year tax from excluding the " + "repaid income (the credit method). Section 1341 makes the tax the " + "lesser of this and the tax computed with the deduction under " + "1341(a)(4), so the method is computed, not elected. When the two " + "are equal, the tax is computed under 1341(a)(4). The comparison is " + "of federal income tax after credits; the parts of it outside " + "chapter 1 do not depend on the repayment." + ) + definition_period = YEAR + reference = ( + "https://www.govinfo.gov/content/pkg/USCODE-2024-title26/html/USCODE-2024-title26-subtitleA-chap1-subchapQ-partV-sec1341.htm", + # 26 CFR 1.1341-1(b)(1) and (b)(3) + "https://www.ecfr.gov/current/title-26/chapter-I/subchapter-A/part-1/subject-group-ECFR4c74bc8cf7c11fb/section-1.1341-1", + # Publication 525 (2025), Repayments, methods 1 and 2 + "https://www.irs.gov/pub/irs-prior/p525--2025.pdf#page=36", + ) + + def formula(tax_unit, period, parameters): + eligible = tax_unit("claim_of_right_section_1341_eligible", period) + if not eligible.any(): + # Comparing the two methods takes two more tax computations; + # skip them when no tax unit repays more than the threshold. + return np.zeros(tax_unit.count, dtype=bool) + tax_if_credit = tax_unit("income_tax_if_claiming_claim_of_right_credit", period) + tax_if_deduction = tax_unit( + "income_tax_if_claiming_claim_of_right_deduction", period + ) + # Ties go to the deduction (26 CFR 1.1341-1(b)(3)). Comparing to the + # cent keeps floating-point noise from deciding a tie. + return eligible & (np.round(tax_if_credit, 2) < np.round(tax_if_deduction, 2)) diff --git a/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_prior_year_tax_decrease.py b/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_prior_year_tax_decrease.py new file mode 100644 index 00000000000..6ed12f6a3d1 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_prior_year_tax_decrease.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class claim_of_right_prior_year_tax_decrease(Variable): + value_type = float + entity = TaxUnit + label = "Prior-year tax decrease from excluding repaid claim of right income" + unit = USD + documentation = ( + "Decrease in the filers' federal income tax under chapter 1 of the " + "Internal Revenue Code for the earlier year or years that would " + "result solely from excluding from gross income all the income they " + "repaid this year (claim_of_right_repayment): the chapter 1 tax " + "previously determined, after credits and including the alternative " + "minimum tax, less that tax refigured without the repaid income. It " + "leaves out the net investment income tax, self-employment tax and " + "payroll taxes." + ) + definition_period = YEAR + reference = ( + "https://www.govinfo.gov/content/pkg/USCODE-2024-title26/html/USCODE-2024-title26-subtitleA-chap1-subchapQ-partV-sec1341.htm", + # 26 CFR 1.1341-1(b)(1)(ii) and (d)(4) + "https://www.ecfr.gov/current/title-26/chapter-I/subchapter-A/part-1/subject-group-ECFR4c74bc8cf7c11fb/section-1.1341-1", + # Publication 525 (2025), Repayments, method 2, steps 2 and 3 + "https://www.irs.gov/pub/irs-prior/p525--2025.pdf#page=36", + ) diff --git a/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_section_1341_eligible.py b/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_section_1341_eligible.py new file mode 100644 index 00000000000..14de55d9256 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/claim_of_right/claim_of_right_section_1341_eligible.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class claim_of_right_section_1341_eligible(Variable): + value_type = bool + entity = TaxUnit + label = "Claim of right repayment qualifies for section 1341" + documentation = ( + "Whether the repayment of claim of right income on the return " + "exceeds the section 1341(a)(3) threshold, so that tax is the " + "lesser of the tax with a deduction for the repayment and the tax " + "without it less the prior-year tax decrease. The threshold applies " + "to the total repaid on the return, not to each repayment." + ) + definition_period = YEAR + reference = ( + "https://www.govinfo.gov/content/pkg/USCODE-2024-title26/html/USCODE-2024-title26-subtitleA-chap1-subchapQ-partV-sec1341.htm", + "https://www.ecfr.gov/current/title-26/chapter-I/subchapter-A/part-1/subject-group-ECFR4c74bc8cf7c11fb/section-1.1341-1", + "https://www.irs.gov/pub/irs-prior/p525--2025.pdf#page=36", + ) + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.irs.deductions.itemized.claim_of_right + repayment = tax_unit("tax_unit_claim_of_right_repayment", period) + return repayment > p.threshold diff --git a/policyengine_us/variables/gov/irs/credits/claim_of_right/income_tax_if_claiming_claim_of_right_credit.py b/policyengine_us/variables/gov/irs/credits/claim_of_right/income_tax_if_claiming_claim_of_right_credit.py new file mode 100644 index 00000000000..91095a583d8 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/claim_of_right/income_tax_if_claiming_claim_of_right_credit.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class income_tax_if_claiming_claim_of_right_credit(Variable): + value_type = float + entity = TaxUnit + label = "Federal income tax under section 1341(a)(5)" + unit = USD + documentation = ( + "Federal income tax computed without a deduction for the repayment of " + "claim of right income, less the decrease in prior-year tax from " + "excluding the repaid income (section 1341(a)(5))." + ) + definition_period = YEAR + reference = "https://www.govinfo.gov/content/pkg/USCODE-2024-title26/html/USCODE-2024-title26-subtitleA-chap1-subchapQ-partV-sec1341.htm" + + def formula(tax_unit, period, parameters): + branch = get_override_branch( + tax_unit.simulation, + "claim_of_right_credit", + period, + {"claim_of_right_credit_applies": np.ones(tax_unit.count, dtype=bool)}, + # A value the parent calculated in another branch reflects the + # method that branch chose, so start from inputs only. + inherit_calculated=False, + ) + return branch.calculate("income_tax", period) diff --git a/policyengine_us/variables/gov/irs/credits/claim_of_right/income_tax_if_claiming_claim_of_right_deduction.py b/policyengine_us/variables/gov/irs/credits/claim_of_right/income_tax_if_claiming_claim_of_right_deduction.py new file mode 100644 index 00000000000..1e5372ca046 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/claim_of_right/income_tax_if_claiming_claim_of_right_deduction.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class income_tax_if_claiming_claim_of_right_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Federal income tax under section 1341(a)(4)" + unit = USD + documentation = ( + "Federal income tax computed with a deduction for the repayment of " + "claim of right income (section 1341(a)(4))." + ) + definition_period = YEAR + reference = "https://www.govinfo.gov/content/pkg/USCODE-2024-title26/html/USCODE-2024-title26-subtitleA-chap1-subchapQ-partV-sec1341.htm" + + def formula(tax_unit, period, parameters): + branch = get_override_branch( + tax_unit.simulation, + "claim_of_right_deduction", + period, + {"claim_of_right_credit_applies": np.zeros(tax_unit.count, dtype=bool)}, + # A value the parent calculated in another branch reflects the + # method that branch chose, so start from inputs only. + inherit_calculated=False, + ) + return branch.calculate("income_tax", period) diff --git a/policyengine_us/variables/gov/irs/credits/claim_of_right/tax_unit_claim_of_right_repayment.py b/policyengine_us/variables/gov/irs/credits/claim_of_right/tax_unit_claim_of_right_repayment.py new file mode 100644 index 00000000000..be284128fc5 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/claim_of_right/tax_unit_claim_of_right_repayment.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class tax_unit_claim_of_right_repayment(Variable): + value_type = float + entity = TaxUnit + label = "Repayment of claim of right income on the return" + unit = USD + documentation = ( + "Total repaid this year by the head and spouse of income included in " + "an earlier year under a claim of right, excluding repayments " + "deducted in arriving at adjusted gross income. Dependents' " + "repayments belong on their own returns." + ) + definition_period = YEAR + reference = ( + "https://www.govinfo.gov/content/pkg/USCODE-2024-title26/html/USCODE-2024-title26-subtitleA-chap1-subchapQ-partV-sec1341.htm", + # Publication 525 (2025), Repayments: consider the total amount + # repaid on the return + "https://www.irs.gov/pub/irs-prior/p525--2025.pdf#page=36", + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + repayment = person("claim_of_right_repayment", period) + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + return tax_unit.sum(repayment * head_or_spouse) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/claim_of_right_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/claim_of_right_deduction.py new file mode 100644 index 00000000000..bb8b52793c1 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/claim_of_right_deduction.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class claim_of_right_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Claim of right repayment deduction" + unit = USD + documentation = ( + "Itemized deduction for repaying more than the section 1341 threshold " + "of income included in an earlier year under a claim of right " + "(Schedule A, line 16), when tax is computed with the deduction " + "under section 1341(a)(4). It is not a miscellaneous itemized " + "deduction (section 67(b)(9)), so neither the two percent floor nor " + "the suspension of those deductions from 2018 applies. Under " + "section 1341(a)(5) the repayment is not deducted (section " + "1341(b)(3)). Not modeled: before 2018 a repayment of the threshold " + "amount or less was a miscellaneous itemized deduction subject to " + "the two percent floor; from 2018 it is disallowed (section 67(g), " + "redesignated 67(h) by P.L. 119-21)." + ) + definition_period = YEAR + reference = ( + "https://www.govinfo.gov/content/pkg/USCODE-2024-title26/html/USCODE-2024-title26-subtitleA-chap1-subchapQ-partV-sec1341.htm", + "https://www.law.cornell.edu/uscode/text/26/67#b_9", + # 2025 Schedule A instructions, line 16, PDF pages 11-12 + "https://www.irs.gov/pub/irs-prior/i1040sca--2025.pdf#page=11", + # 2017 Schedule A instructions, line 28 + "https://www.irs.gov/pub/irs-prior/i1040sca--2017.pdf#page=13", + ) + + def formula(tax_unit, period, parameters): + eligible = tax_unit("claim_of_right_section_1341_eligible", period) + credit_applies = tax_unit("claim_of_right_credit_applies", period) + repayment = tax_unit("tax_unit_claim_of_right_repayment", period) + return where(eligible & ~credit_applies, repayment, 0) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_itemizing.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_itemizing.py index bbfb5c097ba..c12237b7abc 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_itemizing.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_itemizing.py @@ -9,11 +9,5 @@ class tax_liability_if_itemizing(Variable): definition_period = YEAR def formula(tax_unit, period, parameters): - simulation = tax_unit.simulation - itemized_branch = get_override_branch( - simulation, - "itemizing", - period, - {"tax_unit_itemizes": np.ones((tax_unit.count,), dtype=bool)}, - ) + itemized_branch = get_itemization_branch(tax_unit, period, itemizes=True) return itemized_branch.calculate("income_tax", period) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_not_itemizing.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_not_itemizing.py index 07279c5639b..5b85efef0c3 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_not_itemizing.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_not_itemizing.py @@ -10,11 +10,5 @@ class tax_liability_if_not_itemizing(Variable): definition_period = YEAR def formula(tax_unit, period, parameters): - simulation = tax_unit.simulation - non_itemized_branch = get_override_branch( - simulation, - "not_itemizing", - period, - {"tax_unit_itemizes": np.zeros((tax_unit.count,), dtype=bool)}, - ) + non_itemized_branch = get_itemization_branch(tax_unit, period, itemizes=False) return non_itemized_branch.calculate("income_tax", period) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.py index ab7736c1835..1625937db1e 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.py @@ -18,14 +18,10 @@ def formula(tax_unit, period, parameters): # qualified business income deduction, which depends on the # itemization choice, so they come from the branch that itemizes # (the one that also gives tax_liability_if_itemizing). - # Calculated before branching, so the branch inherits it. + # Calculated before branching, so the branch inherits it when no + # one repays claim of right income above $3,000. tax_unit("total_itemized_taxable_income_deductions", period) - itemizing_branch = get_override_branch( - tax_unit.simulation, - "itemizing", - period, - {"tax_unit_itemizes": np.ones((tax_unit.count,), dtype=bool)}, - ) + itemizing_branch = get_itemization_branch(tax_unit, period, itemizes=True) itemized_deductions = itemizing_branch.calculate( "itemized_taxable_income_deductions", period ) diff --git a/policyengine_us/variables/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.py b/policyengine_us/variables/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.py index 30916228f82..f5d3a3e073f 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.py @@ -12,6 +12,8 @@ class az_itemized_deductions(Variable): reference = ( "https://law.justia.com/codes/arizona/2022/title-43/section-43-1042/", "https://www.azleg.gov/ars/43/01042.htm", + "https://www.azleg.gov/ars/43/01021.htm", + "https://www.azleg.gov/ars/43/01029.htm", "https://azdor.gov/forms/individual/itemized-deduction-adjustments-form", "https://azdor.gov/forms/individual/form-140-resident-personal-income-tax-form-calculating", ) @@ -23,6 +25,12 @@ def formula(tax_unit, period, parameters): az_itemized = parameters(period).gov.states.az.tax.income.deductions.itemized # Medical and charitable are handled separately below; the state and # local tax deduction is also handled separately so Arizona can cap it. + # Arizona adds the federal claim of right repayment deduction back to + # Arizona gross income (A.R.S. 43-1021(9)) and computes its tax + # without it (A.R.S. 43-1029), so it gives no net Arizona deduction. + # Leaving it out here gives the same taxable income for an Arizona + # itemizer, but Arizona AGI is not raised by the addition, and the + # Arizona claim of right credit is not modeled. deductions = [ deduction for deduction in p.itemized_deductions @@ -31,6 +39,7 @@ def formula(tax_unit, period, parameters): "medical_expense_deduction", "charitable_deduction", "salt_deduction", + "claim_of_right_deduction", ] ] federal_deductions = add(tax_unit, period, deductions) 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 091b5150824..2b9edc6ef0e 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 @@ -10,6 +10,7 @@ class ca_itemized_deductions_pre_limitation(Variable): reference = ( "https://www.ftb.ca.gov/forms/2021/2021-540-ca-instructions.html", "https://www.ftb.ca.gov/forms/2022/2022-540-ca-instructions.html", + # Schedule CA (540) Part II line 16, claim of right "https://www.ftb.ca.gov/forms/2025/2025-540-ca-instructions.html", "https://www.ftb.ca.gov/about-ftb/data-reports-plans/summary-of-federal-income-tax-changes/index.html", # 2024 Schedule CA (540) instructions, line 15 @@ -34,6 +35,11 @@ def formula(tax_unit, period, parameters): "charitable_deduction_for_non_itemizers", "misc_deduction", "casualty_loss_deduction", + # California chooses between its own claim of right deduction + # and credit whichever method federal tax used, and allows + # neither for income it did not tax, so the federal deduction + # does not carry over. California's own rule is not modeled. + "claim_of_right_deduction", ) ] deductions += [ diff --git a/policyengine_us/variables/gov/states/in/tax/income/deductions/claim_of_right/in_claim_of_right_deduction.py b/policyengine_us/variables/gov/states/in/tax/income/deductions/claim_of_right/in_claim_of_right_deduction.py new file mode 100644 index 00000000000..17d0004a857 --- /dev/null +++ b/policyengine_us/variables/gov/states/in/tax/income/deductions/claim_of_right/in_claim_of_right_deduction.py @@ -0,0 +1,33 @@ +from policyengine_us.model_api import * + + +class in_claim_of_right_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Indiana repayment of previously taxed income deduction" + unit = USD + documentation = ( + "Indiana deduction for repaying income included in Indiana adjusted " + "gross income in an earlier year under a claim of right (IT-40 " + "Schedule 2, code 630). It applies to repayments of any amount, " + "whether the federal return deducts the repayment, claims the section " + "1341 credit or takes the standard deduction." + ) + definition_period = YEAR + reference = ( + # IC 6-3-1-3.5(a)(22), as amended by HEA 1001-2021, section 71 + "https://www.in.gov/sba/files/HB1001-Final.pdf#page=117", + # 2018 IT-40 instructions, deduction 630 + "https://forms.in.gov/Download.aspx?id=13952#page=23", + # 2025 IT-40 instructions, deduction 630 + "https://forms.in.gov/Download.aspx?id=16915#page=23", + ) + defined_for = StateCode.IN + + def formula(tax_unit, period, parameters): + person = tax_unit.members + repayment = person("claim_of_right_repayment", period) + excluded = person("in_claim_of_right_repayment_excluded_from_income", period) + # Dependents' repayments belong on their own returns. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + return tax_unit.sum(max_(repayment - excluded, 0) * head_or_spouse) diff --git a/policyengine_us/variables/gov/states/in/tax/income/deductions/claim_of_right/in_claim_of_right_repayment_excluded_from_income.py b/policyengine_us/variables/gov/states/in/tax/income/deductions/claim_of_right/in_claim_of_right_repayment_excluded_from_income.py new file mode 100644 index 00000000000..8059a047ea8 --- /dev/null +++ b/policyengine_us/variables/gov/states/in/tax/income/deductions/claim_of_right/in_claim_of_right_repayment_excluded_from_income.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class in_claim_of_right_repayment_excluded_from_income(Variable): + value_type = float + entity = Person + label = "Claim of right repayment not included in Indiana income when received" + unit = USD + documentation = ( + "Part of claim_of_right_repayment that was not included in Indiana " + "adjusted gross income in the earlier year it was received, such as " + "repaid Social Security benefits, or the extra Indiana unemployment " + "compensation deduction the earlier return would have had without the " + "repaid amount. Indiana's repayment deduction excludes it." + ) + definition_period = YEAR + reference = ( + # IC 6-3-1-3.5(a)(22) + "https://www.in.gov/sba/files/HB1001-Final.pdf#page=117", + # 2025 IT-40 instructions, deduction 630, unemployment compensation note + "https://forms.in.gov/Download.aspx?id=16915#page=23", + ) + defined_for = StateCode.IN diff --git a/policyengine_us/variables/gov/states/in/tax/income/deductions/in_other_deductions.py b/policyengine_us/variables/gov/states/in/tax/income/deductions/in_other_deductions.py index e3650257bea..b34bc9a6e1b 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/deductions/in_other_deductions.py +++ b/policyengine_us/variables/gov/states/in/tax/income/deductions/in_other_deductions.py @@ -7,6 +7,6 @@ class in_other_deductions(Variable): label = "Indiana other deductions" unit = USD definition_period = YEAR - documentation = "Other deductions available in Indiana including those for civil service annuities, disability retirement, government or civic group capital contributions, human services for Medicaid recipients, infrastructure fund gifts, indiana lottery winings annuity, Indiana partnership long-term care policy premiums, military retirement income or survivor's benefits, National Guard and reserve component members, Olympic/Paralymic medal winners, qualified patents income eemption, railroad unemployment and sickness benefits, repayment of previously taxed income deductions, COVID-related employee retention credit dissalowed expenses, and Indiana-only tax-exempt bonds." + documentation = "Other deductions available in Indiana including those for civil service annuities, disability retirement, government or civic group capital contributions, human services for Medicaid recipients, infrastructure fund gifts, indiana lottery winings annuity, Indiana partnership long-term care policy premiums, military retirement income or survivor's benefits, National Guard and reserve component members, Olympic/Paralymic medal winners, qualified patents income eemption, railroad unemployment and sickness benefits, COVID-related employee retention credit dissalowed expenses, and Indiana-only tax-exempt bonds. Excludes the repayment of previously taxed income deduction (code 630), which in_claim_of_right_deduction computes." reference = "http://iga.in.gov/legislative/laws/2021/ic/titles/006#6-3-2" defined_for = StateCode.IN 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..fa987b8ec7c 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://www.ksrevisor.gov/statutes/chapters/ch79/079_032_0120.html", ) defined_for = StateCode.KS @@ -25,4 +26,9 @@ 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 the federal claim of right repayment deduction + # is not one of them. + claim_of_right_deduction = tax_unit("claim_of_right_deduction", period) + return itm_deds_less_salt - claim_of_right_deduction + uncapped_property_taxes diff --git a/policyengine_us/variables/gov/states/ma/tax/income/deductions/claim_of_right/ma_claim_of_right_deduction.py b/policyengine_us/variables/gov/states/ma/tax/income/deductions/claim_of_right/ma_claim_of_right_deduction.py new file mode 100644 index 00000000000..65c6456aa53 --- /dev/null +++ b/policyengine_us/variables/gov/states/ma/tax/income/deductions/claim_of_right/ma_claim_of_right_deduction.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class ma_claim_of_right_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Massachusetts claim of right deduction" + unit = USD + documentation = ( + "Massachusetts Part B deduction for repaying income included in " + "Massachusetts taxable income in an earlier year under a claim of " + "right (Schedule Y, line 14). It applies to repayments of any amount, " + "whichever federal method applies." + ) + definition_period = YEAR + reference = ( + # M.G.L. c. 62, s. 3(B)(a)(14) + "https://malegislature.gov/Laws/GeneralLaws/PartI/TitleIX/Chapter62/Section3", + # 2025 Form 1 instructions, Schedule Y line 14 + "https://taxsim.nber.org/historical_state_tax_forms/MA/2025/ma-dor-2025-inc-form-1-inst.pdf#page=21", + "https://taxsim.nber.org/historical_state_tax_forms/MA/2025/ma-dor-2025-inc-sch-y.pdf", + ) + defined_for = StateCode.MA + + def formula(tax_unit, period, parameters): + person = tax_unit.members + repayment = person("claim_of_right_repayment", period) + excluded = person("ma_claim_of_right_repayment_excluded_from_income", period) + # Dependents' repayments belong on their own returns. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + return tax_unit.sum(max_(repayment - excluded, 0) * head_or_spouse) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/deductions/claim_of_right/ma_claim_of_right_repayment_excluded_from_income.py b/policyengine_us/variables/gov/states/ma/tax/income/deductions/claim_of_right/ma_claim_of_right_repayment_excluded_from_income.py new file mode 100644 index 00000000000..d36617954e2 --- /dev/null +++ b/policyengine_us/variables/gov/states/ma/tax/income/deductions/claim_of_right/ma_claim_of_right_repayment_excluded_from_income.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class ma_claim_of_right_repayment_excluded_from_income(Variable): + value_type = float + entity = Person + label = ( + "Claim of right repayment not included in Massachusetts income when received" + ) + unit = USD + documentation = ( + "Part of claim_of_right_repayment that was not included in " + "Massachusetts taxable income in the earlier year it was received. " + "Massachusetts's claim of right deduction excludes it." + ) + definition_period = YEAR + reference = ( + # M.G.L. c. 62, s. 3(B)(a)(14)(i) + "https://malegislature.gov/Laws/GeneralLaws/PartI/TitleIX/Chapter62/Section3", + ) + defined_for = StateCode.MA diff --git a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_b_taxable_income_deductions.py b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_b_taxable_income_deductions.py index 2a338d9cfcc..8a060af1b78 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_b_taxable_income_deductions.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_b_taxable_income_deductions.py @@ -51,12 +51,15 @@ def formula(tax_unit, period, parameters): ) # (B)(a)(13): Charitable contributions deduction. charitable_deduction = tax_unit("charitable_deduction", period) - # (B)(a)(14): 529 plan contribution deduction. + # (B)(a)(14): Claim of right deduction. + claim_of_right_deduction = tax_unit("ma_claim_of_right_deduction", period) + # (B)(a)(19): 529 plan contribution deduction. plan_529_deduction = tax_unit("ma_529_deduction", period) return ( fica + bank_interest_deduction + rent_deduction + charitable_deduction + + claim_of_right_deduction + plan_529_deduction ) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_claim_of_right_deduction.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_claim_of_right_deduction.py new file mode 100644 index 00000000000..e4a00c0745e --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_claim_of_right_deduction.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * + + +class mt_claim_of_right_deduction(Variable): + value_type = float + entity = Person + label = "Montana claim of right repayment deduction" + unit = USD + documentation = ( + "The person's part of the federal claim of right repayment deduction " + "(Schedule A, line 16), which Montana allows as an itemized " + "deduction: through 2023 on Form 2 itemized deductions line 18, and " + "from 2024 within the federal itemized deductions on Form 2 line 2." + ) + definition_period = YEAR + reference = ( + "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", + # 2024 Form 2 instructions, line 2, Worksheet A line 1: "Enter your + # total federal itemized deductions from Form 1040, line 12". + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2024_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=15", + ) + defined_for = StateCode.MT + + def formula(person, period, parameters): + # The federal deduction is the head's and spouse's repayments when + # federal tax is computed with the deduction, and zero otherwise. + deducted = person.tax_unit("claim_of_right_deduction", period) > 0 + repayment = person("claim_of_right_repayment", period) + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + return deducted * head_or_spouse * repayment diff --git a/policyengine_us/variables/gov/states/nc/tax/income/credits/claim_of_right/nc_claim_of_right_payment.py b/policyengine_us/variables/gov/states/nc/tax/income/credits/claim_of_right/nc_claim_of_right_payment.py new file mode 100644 index 00000000000..abcfca2ca20 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/tax/income/credits/claim_of_right/nc_claim_of_right_payment.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class nc_claim_of_right_payment(Variable): + value_type = float + entity = TaxUnit + label = "North Carolina tax payment for restored claim of right income" + unit = USD + documentation = ( + "A filer whose federal tax is computed under 26 U.S.C. 1341(a)(5) is " + "considered to have paid North Carolina tax equal to the increase in " + "the earlier year's North Carolina tax from including the restored " + "income, minus the decrease in this year's North Carolina tax because " + "the item was deductible. An overpayment it creates is refunded. " + "Modeled for current North Carolina residents only: a filer who " + "paid the earlier North Carolina tax and has since moved away is " + "not covered, because the model computes a state's tax for its " + "residents." + ) + definition_period = YEAR + reference = ( + "https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-266.2.html", + "https://www.ncleg.gov/EnactedLegislation/SessionLaws/HTML/1997-1998/SL1997-213.html", + ) + defined_for = StateCode.NC + + def formula(tax_unit, period, parameters): + credit_applies = tax_unit("claim_of_right_credit_applies", period) + prior_year_tax_increase = tax_unit( + "nc_claim_of_right_prior_year_tax_increase", period + ) + # Term (ii), the decrease in this year's North Carolina tax because + # the item was deductible, is zero: under section 1341(a)(5) the + # repayment is not deducted federally (section 1341(b)(3)), and + # G.S. 105-153.5(a)(2)d allows no North Carolina deduction for it. + return where(credit_applies, prior_year_tax_increase, 0) diff --git a/policyengine_us/variables/gov/states/nc/tax/income/credits/claim_of_right/nc_claim_of_right_prior_year_tax_increase.py b/policyengine_us/variables/gov/states/nc/tax/income/credits/claim_of_right/nc_claim_of_right_prior_year_tax_increase.py new file mode 100644 index 00000000000..814b4df7615 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/tax/income/credits/claim_of_right/nc_claim_of_right_prior_year_tax_increase.py @@ -0,0 +1,18 @@ +from policyengine_us.model_api import * + + +class nc_claim_of_right_prior_year_tax_increase(Variable): + value_type = float + entity = TaxUnit + label = "North Carolina prior-year tax increase from restored claim of right income" + unit = USD + documentation = ( + "Amount by which the filers' North Carolina individual income tax " + "(Article 4 of Chapter 105) for the earlier year or years was " + "increased because the income they repaid this year " + "(claim_of_right_repayment) was included in gross income for that " + "year." + ) + definition_period = YEAR + reference = "https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-266.2.html" + defined_for = StateCode.NC 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 index dbcbe3397ee..c978d27021d 100644 --- 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 @@ -12,10 +12,11 @@ class nc_claim_of_right_deduction(Variable): "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." + "other miscellaneous itemized deductions. No deduction is allowed " + "when federal tax for the year of repayment is computed under 26 " + "U.S.C. 1341(a)(5). Not modeled: from 2026, repayments of amounts " + "North Carolina added to adjusted gross income that federal law " + "excluded." ) definition_period = YEAR reference = ( @@ -59,8 +60,13 @@ def formula(tax_unit, period, parameters): other_misc_deductions = 0 remaining_floor = max_(floor - other_misc_deductions, 0) small_repayment_deduction = max_(repayment - remaining_floor, 0) - return where( + deduction = where( repayment > p.claim_of_right.threshold, repayment, small_repayment_deduction, ) + # No deduction when federal tax for the year of repayment is + # computed under section 1341(a)(5); G.S. 105-266.2 instead treats + # the prior-year North Carolina tax on the item as a payment. + credit_applies = tax_unit("claim_of_right_credit_applies", period) + return where(credit_applies, 0, deduction) diff --git a/policyengine_us/variables/gov/states/nc/tax/income/nc_income_tax.py b/policyengine_us/variables/gov/states/nc/tax/income/nc_income_tax.py index 83a4c333d99..f42b325f956 100644 --- a/policyengine_us/variables/gov/states/nc/tax/income/nc_income_tax.py +++ b/policyengine_us/variables/gov/states/nc/tax/income/nc_income_tax.py @@ -8,9 +8,5 @@ class nc_income_tax(Variable): unit = USD definition_period = YEAR defined_for = StateCode.NC - - def formula(tax_unit, period, parameters): - tax_before_credits = tax_unit("nc_income_tax_before_credits", period) - # North Carolina provides no refundable income tax credits - credits = tax_unit("nc_non_refundable_credits", period) - return max_(0, tax_before_credits - credits) + adds = ["nc_income_tax_before_refundable_credits"] + subtracts = ["nc_refundable_credits"] diff --git a/policyengine_us/variables/gov/states/nc/tax/income/nc_income_tax_before_refundable_credits.py b/policyengine_us/variables/gov/states/nc/tax/income/nc_income_tax_before_refundable_credits.py new file mode 100644 index 00000000000..92cabeac3b5 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/tax/income/nc_income_tax_before_refundable_credits.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class nc_income_tax_before_refundable_credits(Variable): + value_type = float + entity = TaxUnit + label = "North Carolina income tax before refundable credits" + unit = USD + definition_period = YEAR + defined_for = StateCode.NC + + def formula(tax_unit, period, parameters): + tax_before_credits = tax_unit("nc_income_tax_before_credits", period) + credits = tax_unit("nc_non_refundable_credits", period) + return max_(0, tax_before_credits - credits) diff --git a/policyengine_us/variables/gov/states/nc/tax/income/nc_refundable_credits.py b/policyengine_us/variables/gov/states/nc/tax/income/nc_refundable_credits.py new file mode 100644 index 00000000000..8f6815920db --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/tax/income/nc_refundable_credits.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class nc_refundable_credits(Variable): + value_type = float + entity = TaxUnit + label = "North Carolina refundable credits" + unit = USD + definition_period = YEAR + defined_for = StateCode.NC + adds = "gov.states.nc.tax.income.credits.refundable" diff --git a/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py b/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py index 43168c0596f..5f16cde2e48 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py +++ b/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py @@ -10,6 +10,8 @@ class or_federal_tax_liability_subtraction(Variable): reference = ( "https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2021.pdf#page=71", "https://www.oregonlegislature.gov/bills_laws/ors/ors316.html", # Subsection 316.800 + # 2025 Publication OR-17, federal tax worksheet, PDF pages 71-72 + "https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2025.pdf#page=71", ) defined_for = StateCode.OR @@ -17,7 +19,12 @@ def formula(tax_unit, period, parameters): # calculate Oregon concept of federal income tax federal_itax = tax_unit("income_tax", period) federal_eitc = tax_unit("eitc", period) - or_federal_income_tax = max_(0, federal_itax + federal_eitc) + # The federal tax worksheet starts from Form 1040 line 22 and does + # not subtract the section 1341 credit (Schedule 3, line 13b). + claim_of_right_credit = tax_unit("claim_of_right_credit", period) + or_federal_income_tax = max_( + 0, federal_itax + federal_eitc + claim_of_right_credit + ) # limit subtraction based on caps scaled to federal AGI filing_status = tax_unit("filing_status", period) status = filing_status.possible_values 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..5228fd84ff3 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,20 @@ 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", + # Wis. Stat. 71.07(5)(a) + "https://docs.legis.wisconsin.gov/statutes/statutes/71.pdf#page=39", + "https://www.revenue.wi.gov/TaxForms2025/2025-Form1.pdf#page=4", ) defined_for = StateCode.WI def formula(tax_unit, period, parameters): - wi_itmded = tax_unit("itemized_deductions_less_salt", period) + # Wis. Stat. 71.07(5)(a)7 leaves miscellaneous itemized deductions + # out of the credit whether or not the two percent floor applies to + # them, and Schedule 1 has no line for other itemized deductions, so + # the federal claim of right repayment deduction does not count. + wi_itmded = tax_unit("itemized_deductions_less_salt", period) - tax_unit( + "claim_of_right_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/gov/states/wv/tax/income/subtractions/claim_of_right/wv_claim_of_right_subtraction.py b/policyengine_us/variables/gov/states/wv/tax/income/subtractions/claim_of_right/wv_claim_of_right_subtraction.py new file mode 100644 index 00000000000..09a0ac3f996 --- /dev/null +++ b/policyengine_us/variables/gov/states/wv/tax/income/subtractions/claim_of_right/wv_claim_of_right_subtraction.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class wv_claim_of_right_subtraction(Variable): + value_type = float + entity = Person + label = "West Virginia IRC 1341 repayments modification" + unit = USD + documentation = ( + "West Virginia decreasing modification for repaying income received " + "in an earlier year under a claim of right (Schedule M, IRC 1341 " + "Repayments). The amount repaid must be more than $3,000; the " + "instructions point to IRS Publication 525, which counts the total " + "repaid on the return, so the model tests the head's and spouse's " + "total. Schedule M has a column for each spouse; the model puts each " + "spouse's own repayment in their column. The instructions do not " + "depend on the federal method." + ) + definition_period = YEAR + reference = ( + # 2025 IT-140 instructions, Schedule M line 42 + "https://tax.wv.gov/Documents/PIT/2025/it140.PersonalIncomeTaxFormsAndInstructions.2025.pdf#page=29", + # 2021 IT-140 instructions, Schedule M line 40 + "https://tax.wv.gov/Documents/TaxForms/2021/it140.booklet.pdf#page=24", + # 2017 IT-140 instructions, Schedule M line 50 + "https://tax.wv.gov/Documents/TaxForms/2017/it140.booklet.pdf#page=23", + ) + defined_for = StateCode.WV + + def formula(person, period, parameters): + p = parameters(period).gov.states.wv.tax.income.subtractions.claim_of_right + total = person.tax_unit("tax_unit_claim_of_right_repayment", period) + repayment = person("claim_of_right_repayment", period) + # Dependents' repayments belong on their own returns. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + return where((total > p.threshold) & head_or_spouse, repayment, 0)