diff --git a/changelog.d/nc-claim-of-right-deduction.added.md b/changelog.d/nc-claim-of-right-deduction.added.md new file mode 100644 index 00000000000..d9a675b5026 --- /dev/null +++ b/changelog.d/nc-claim-of-right-deduction.added.md @@ -0,0 +1 @@ +Add North Carolina's itemized deduction for repaying income received under a claim of right (G.S. 105-153.5(a)(2)d), with a new claim_of_right_repayment input. diff --git a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/nc_modified_income_basis.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/nc_modified_income_basis.yaml new file mode 100644 index 00000000000..ce8a8a199ec --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/nc_modified_income_basis.yaml @@ -0,0 +1,13 @@ +description: North Carolina limits the claim of right repayment deduction to repayments of amounts included in adjusted gross income, as modified for North Carolina, in the earlier year, if this is true. +values: + 2014-01-01: false + 2026-01-01: true +metadata: + unit: bool + period: year + label: North Carolina claim of right repayment deduction uses North Carolina modified income + reference: + - title: N.C. Gen. Stat. § 105-153.5(a)(2)d + href: https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html + - title: "S.L. 2026-31, Section 1.8.(a) and (b) (effective for taxable years beginning on or after January 1, 2026) #page 5-6" + href: https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-31.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/threshold.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/threshold.yaml new file mode 100644 index 00000000000..941fc92e523 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/claim_of_right/threshold.yaml @@ -0,0 +1,16 @@ +description: North Carolina allows the whole repayment of claim of right income as an itemized deduction when the repayment exceeds this amount; smaller repayments are reduced by the federal two percent floor on miscellaneous itemized deductions. +values: + 2014-01-01: 3_000 +metadata: + unit: currency-USD + period: year + label: North Carolina claim of right repayment threshold + reference: + - title: N.C. Gen. Stat. § 105-153.5(a)(2)d + href: https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html + - title: S.L. 2016-5, Section 2.1.(a) and (d) (effective for taxable years beginning on or after January 1, 2014) + href: https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2015-2016/SL2016-5.pdf#page=4 + - title: 2021 Form D-401 instructions, Form D-400 Schedule A line 8, Repayment of Claim of Right Worksheet + href: https://www.ncdor.gov/2021-d-401-individual-income-tax-instructions/open#page=20 + - title: 2025 Form D-401 instructions, Form D-400 Schedule A line 8, Repayment of Claim of Right Worksheet + href: https://www.ncdor.gov/2025-d-401-individual-income-tax-instructions/open#page=20 diff --git a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/other_sources.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/other_sources.yaml new file mode 100644 index 00000000000..822f8c60db7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/itemized/other_sources.yaml @@ -0,0 +1,17 @@ +description: North Carolina adds these deductions to the capped mortgage interest and property tax deduction to compute itemized deductions. +values: + 2021-01-01: + - charitable_deduction + - medical_expense_deduction + - nc_claim_of_right_deduction +metadata: + unit: list + period: year + label: North Carolina itemized deductions other than mortgage interest and property taxes + reference: + - title: N.C. Gen. Stat. § 105-153.5(a)(2) + href: https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html + - title: 2021 Form D-401 instructions, Form D-400 Schedule A lines 6 through 10 + href: https://www.ncdor.gov/2021-d-401-individual-income-tax-instructions/open#page=20 + - title: "2025 Form D-401 instructions, Form D-400 Schedule A lines 6 through 10 #page 20-21" + href: https://www.ncdor.gov/2025-d-401-individual-income-tax-instructions/open#page=20 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.yaml new file mode 100644 index 00000000000..b0eafe19c2f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.yaml @@ -0,0 +1,371 @@ +# N.C. Gen. Stat. 105-153.5(a)(2)d and the Repayment of Claim of Right +# Worksheet (2025 Form D-401 instructions, PDF p. 20; 2016 Form D-401 +# instructions, PDF p. 13). Worksheet for repayments of $3,000 or less, 2018 +# and later: line 3 = 2% of federal AGI (zero if negative), line 4 = line 1 - +# line 3. Repayments above $3,000 are deductible in full. From 2026 (S.L. +# 2026-31 section 1.8, PDF pp. 5-6) only repayments of amounts included in AGI +# as modified for North Carolina count. + +- name: Repayment of $3,000 or less is reduced by 2% of federal AGI. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Worksheet: line 1 2,500; line 3 = 0.02 * 50,000 = 1,000; line 4 = 1,500. + nc_claim_of_right_deduction: 1_500 + +- name: A repayment of exactly $3,000 is still reduced by the floor. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 3_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 100_000 + households: + household: + members: [head] + state_code: NC + output: + # "If the repayment is three thousand dollars ($3,000) or less": + # 3,000 - 0.02 * 100,000 = 1,000. + nc_claim_of_right_deduction: 1_000 + +- name: A repayment above $3,000 is deductible in full. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 3_001 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 100_000 + households: + household: + members: [head] + state_code: NC + output: + nc_claim_of_right_deduction: 3_001 + +- name: No deduction when 2% of AGI exceeds a small repayment. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 1_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 100_000 + households: + household: + members: [head] + state_code: NC + output: + # 1,000 - 2,000 is negative; the deduction is zero. + nc_claim_of_right_deduction: 0 + +- name: Negative AGI gives a zero floor. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: -5_000 + households: + household: + members: [head] + state_code: NC + output: + # Worksheet line 3: "If negative, enter the number zero". + nc_claim_of_right_deduction: 2_500 + +- name: Spouses' repayments are combined on a joint return; a dependent's is not. + period: 2023 + input: + people: + head: + age: 40 + claim_of_right_repayment: 1_500 + spouse: + age: 40 + claim_of_right_repayment: 1_000 + child: + age: 17 + claim_of_right_repayment: 4_000 + tax_units: + tax_unit: + members: [head, spouse, child] + adjusted_gross_income: 60_000 + marital_units: + marital_unit: + members: [head, spouse] + child_marital_unit: + members: [child] + households: + household: + members: [head, spouse, child] + state_code: NC + output: + # The child's repayment belongs on the child's own return. Line 1 is + # 1,500 + 1,000 = 2,500, not above 3,000; 2,500 - 0.02 * 60,000 = 1,300. + nc_claim_of_right_deduction: 1_300 + +- name: From 2018 other miscellaneous expenses do not absorb the floor. + period: 2022 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + unreimbursed_business_employee_expenses: 600 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Section 67(g) disallows other miscellaneous itemized deductions from + # 2018, so the worksheet subtracts the whole 2% floor: 2,500 - 1,000. + nc_claim_of_right_deduction: 1_500 + +- name: Before 2018 other miscellaneous deductions absorb the floor first. + period: 2016 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + unreimbursed_business_employee_expenses: 600 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # 2016 worksheet: line 1 2,500; line 2 = 0.02 * 50,000 = 1,000; line 3 + # (federal Schedule A line 24) 3,100; line 4 = 3,100 - 2,500 = 600; + # line 5 = 1,000 - 600 = 400; line 6 = 2,500 - 400 = 2,100. + nc_claim_of_right_deduction: 2_100 + +- name: Before 2018 other miscellaneous deductions above the floor leave the whole repayment. + period: 2016 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + unreimbursed_business_employee_expenses: 2_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Line 4 = 2,000; line 5 = 1,000 - 2,000, entered as zero; line 6 = 2,500. + nc_claim_of_right_deduction: 2_500 + +- name: Before 2018 a dependent's miscellaneous expenses do not absorb the floor. + period: 2016 + input: + people: + head: + age: 40 + claim_of_right_repayment: 2_500 + child: + age: 10 + unreimbursed_business_employee_expenses: 600 + tax_units: + tax_unit: + members: [head, child] + adjusted_gross_income: 50_000 + households: + household: + members: [head, child] + state_code: NC + output: + # The child's expenses are not on the parent's federal Schedule A, so + # line 4 is zero: 2,500 - 0.02 * 50,000 = 1,500. + nc_claim_of_right_deduction: 1_500 + +- name: In 2025 a repayment of income North Carolina had excluded still counts. + period: 2025 + input: + people: + head: + age: 70 + claim_of_right_repayment: 5_000 + nc_claim_of_right_repayment_excluded_from_income: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Before 2026 the test is inclusion in federal AGI. + nc_claim_of_right_deduction: 5_000 + +- name: From 2026 a repayment of income North Carolina excluded is not deductible. + period: 2026 + input: + people: + head: + age: 70 + claim_of_right_repayment: 5_000 + nc_claim_of_right_repayment_excluded_from_income: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # S.L. 2026-31 section 1.8: "an amount included in adjusted gross income, + # as modified in G.S. 105-153.5 and G.S. 105-153.6". For example, repaid + # Social Security benefits that North Carolina deducted when received. + nc_claim_of_right_deduction: 0 + +- name: From 2026 only the part North Carolina taxed counts, and the $3,000 test applies to it. + period: 2026 + input: + people: + head: + age: 70 + claim_of_right_repayment: 5_000 + nc_claim_of_right_repayment_excluded_from_income: 3_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + # Eligible repayment 2,000, not above 3,000: 2,000 - 0.02 * 50,000 = 1,000. + nc_claim_of_right_deduction: 1_000 + +- name: From 2026 a repayment North Carolina taxed in full is deductible as before. + period: 2026 + input: + people: + head: + age: 40 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: NC + output: + nc_claim_of_right_deduction: 5_000 + +- name: No deduction outside North Carolina. + period: 2025 + input: + people: + head: + age: 40 + claim_of_right_repayment: 5_000 + tax_units: + tax_unit: + members: [head] + adjusted_gross_income: 50_000 + households: + household: + members: [head] + state_code: SC + output: + nc_claim_of_right_deduction: 0 + +- name: The deduction is added to itemized deductions outside the $20,000 cap. + period: 2024 + input: + people: + head: + age: 40 + claim_of_right_repayment: 5_000 + mortgage_interest: 18_000 + real_estate_taxes: 6_000 + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + adjusted_gross_income: 90_000 + charitable_deduction: 2_000 + medical_expense_deduction: 1_000 + households: + household: + members: [head] + state_code: NC + output: + # Schedule A: line 5 = min(18,000 + 6,000, 20,000) = 20,000; line 6 + # 2,000; line 7d 1,000; line 8 5,000; line 10 = 28,000. + nc_claim_of_right_deduction: 5_000 + nc_itemized_deductions: 28_000 + +- name: A full repayment makes itemizing worthwhile and lowers North Carolina tax. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 80_000 + claim_of_right_repayment: 5_000 + real_estate_taxes: 4_000 + tax_units: + tax_unit: + members: [head] + charitable_deduction: 8_000 + households: + household: + members: [head] + state_code: NC + output: + adjusted_gross_income: 80_000 + # Schedule A: 4,000 + 8,000 + 5,000 = 17,000, above the 12,750 single + # standard deduction. Without the repayment, 12,000 would lose to it. + nc_itemized_deductions: 17_000 + nc_standard_or_itemized_deductions: 17_000 + nc_taxable_income: 63_000 + # 63,000 * 4.25% = 2,677.50 (2,858.13 without the repayment). + nc_income_tax: 2_677.50 diff --git a/policyengine_us/tests/unit/test_nc_claim_of_right_properties.py b/policyengine_us/tests/unit/test_nc_claim_of_right_properties.py new file mode 100644 index 00000000000..7ff5a694e8d --- /dev/null +++ b/policyengine_us/tests/unit/test_nc_claim_of_right_properties.py @@ -0,0 +1,186 @@ +"""Properties of North Carolina's claim of right repayment deduction. + +N.C. Gen. Stat. 105-153.5(a)(2)d, added by S.L. 2016-5 for 2014 and later: a +repayment of $3,000 or less is deductible less (i) the section 67(a) floor, +two percent of federal AGI, minus (ii) the other miscellaneous itemized +deductions, not below zero; a larger repayment is deductible in full. From +2026 (S.L. 2026-31 section 1.8) only repayments of amounts included in AGI as +modified for North Carolina count. + +YAML cases check single households. These tests check properties at every +point of a grid, one vectorized simulation per year, and compare the model +with independent line-by-line implementations of NCDOR's worksheets. A YAML +case cannot state a property that holds for all inputs, such as monotonicity +across the $3,000 threshold: + +- the deduction matches the 2016 Repayment of Claim of Right Worksheet, + which subtracts other miscellaneous deductions from the floor, and the + 2025 worksheet, which subtracts the whole floor because section 67(g) + disallows those deductions from 2018; +- it is non-negative, never exceeds the repayment, rises with the repayment + (also across the $3,000 threshold) and falls as AGI rises; +- it adds to North Carolina itemized deductions one for one, including when + property taxes already exceed the $20,000 mortgage and property tax cap, + and never raises North Carolina income tax, including when the repayment + moves the filer from the standard deduction to itemizing; +- from 2026 it is the 2025 worksheet applied to the part of the repayment + North Carolina taxed when received; before 2026 that split has no effect. +""" + +import itertools + +import numpy as np +import pytest + +from policyengine_us import Simulation + +REPAYMENTS = [0, 1, 999, 1_000, 2_999, 3_000, 3_000.5, 3_001, 10_000, 250_000] +AGIS = [-5_000, 0, 50_000, 100_000, 150_000, 1_000_000] +OTHER_MISC = [0, 600, 5_000] +CHARITY = [0, 20_000] +PROPERTY_TAX = [0, 25_000] +AXES = [REPAYMENTS, AGIS, OTHER_MISC, CHARITY, PROPERTY_TAX] +GRID = list(itertools.product(*AXES)) +SHAPE = tuple(len(axis) for axis in AXES) +REPAYMENT, AGI, MISC, CHARITABLE, PROPERTY = ( + np.array([point[i] for point in GRID], dtype=float) for i in range(len(AXES)) +) +# 2026 grid: repayment x share of it North Carolina excluded when received x AGI. +SHARES = [0, 0.25, 0.75, 1] +GRID_2026 = list(itertools.product(REPAYMENTS, SHARES, AGIS)) +REPAYMENT_2026, SHARE_2026, AGI_2026 = ( + np.array([point[i] for point in GRID_2026], dtype=float) for i in range(3) +) + + +def grid_simulation(year, people, tax_units): + """One single-person North Carolina tax unit per grid point.""" + size = len(next(iter(people.values()))) + situation = {"people": {}, "tax_units": {}, "households": {}} + for i in range(size): + person = f"p{i}" + situation["people"][person] = { + "age": {year: 40}, + **{name: {year: float(values[i])} for name, values in people.items()}, + } + situation["tax_units"][f"t{i}"] = { + "members": [person], + **{name: {year: float(values[i])} for name, values in tax_units.items()}, + } + situation["households"][f"h{i}"] = { + "members": [person], + "state_code": {year: "NC"}, + } + return Simulation(situation=situation) + + +def worksheet_2016(repayment, agi, other_misc): + """2016 Form D-401 instructions, PDF p. 13, lines 1-6.""" + line1 = repayment + line2 = 0.02 * np.maximum(agi, 0) # federal Schedule A line 26 + line3 = other_misc + repayment # federal Schedule A line 24 + line4 = line3 - line1 + line5 = np.maximum(line2 - line4, 0) + line6 = np.maximum(line1 - line5, 0) + return np.where(repayment > 3_000, repayment, line6) + + +def worksheet_2025(repayment, agi): + """2025 Form D-401 instructions, PDF p. 20, lines 1-4.""" + line3 = np.maximum(0.02 * agi, 0) + line4 = np.maximum(repayment - line3, 0) + return np.where(repayment > 3_000, repayment, line4) + + +@pytest.fixture(scope="module") +def results(): + out = {} + for year in [2016, 2025]: + sim = grid_simulation( + year, + { + "claim_of_right_repayment": REPAYMENT, + "unreimbursed_business_employee_expenses": MISC, + "real_estate_taxes": PROPERTY, + }, + {"adjusted_gross_income": AGI, "charitable_deduction": CHARITABLE}, + ) + out[year] = { + variable: np.asarray(sim.calculate(variable, year)) + for variable in [ + "nc_claim_of_right_deduction", + "nc_itemized_deductions", + "nc_income_tax", + ] + } + return out + + +def test_deduction_matches_the_worksheets(results): + np.testing.assert_allclose( + results[2016]["nc_claim_of_right_deduction"], + worksheet_2016(REPAYMENT, AGI, MISC), + atol=1e-6, + ) + np.testing.assert_allclose( + results[2025]["nc_claim_of_right_deduction"], + worksheet_2025(REPAYMENT, AGI), + atol=1e-6, + ) + + +@pytest.mark.parametrize("year", [2016, 2025]) +def test_deduction_is_bounded_and_monotone(results, year): + deduction = results[year]["nc_claim_of_right_deduction"] + assert np.all(deduction >= 0) + assert np.all(deduction <= REPAYMENT + 1e-6) + above = REPAYMENT > 3_000 + np.testing.assert_allclose(deduction[above], REPAYMENT[above]) + table = deduction.reshape(SHAPE) + assert np.all(np.diff(table, axis=0) >= -1e-6), "falls as the repayment rises" + assert np.all(np.diff(table, axis=1) <= 1e-6), "rises as AGI rises" + assert np.all(np.diff(table, axis=2) >= -1e-6), "falls as other expenses rise" + + +def test_other_miscellaneous_expenses_matter_only_before_2018(results): + for year, matters in [(2016, True), (2025, False)]: + table = results[year]["nc_claim_of_right_deduction"].reshape(SHAPE) + spread = np.abs(table - table[:, :, :1]).max() + assert (spread > 0) == matters, year + + +@pytest.mark.parametrize("year", [2016, 2025]) +def test_deduction_adds_to_itemized_deductions_and_never_raises_tax(results, year): + deduction = results[year]["nc_claim_of_right_deduction"].reshape(SHAPE) + itemized = results[year]["nc_itemized_deductions"].reshape(SHAPE) + tax = results[year]["nc_income_tax"].reshape(SHAPE) + # Row 0 of the repayment axis is the same tax unit with no repayment. + np.testing.assert_allclose(itemized - itemized[:1], deduction, atol=1e-6) + assert np.all(tax <= tax[:1] + 1e-6), "a repayment raised North Carolina tax" + assert np.all(np.diff(tax, axis=0) <= 1e-6), "tax rises with the repayment" + + +def test_from_2026_only_the_part_north_carolina_taxed_counts(): + excluded = SHARE_2026 * REPAYMENT_2026 + deductions = {} + for year in [2025, 2026]: + sim = grid_simulation( + year, + { + "claim_of_right_repayment": REPAYMENT_2026, + "nc_claim_of_right_repayment_excluded_from_income": excluded, + }, + {"adjusted_gross_income": AGI_2026}, + ) + deductions[year] = np.asarray( + sim.calculate("nc_claim_of_right_deduction", year) + ) + np.testing.assert_allclose( + deductions[2025], worksheet_2025(REPAYMENT_2026, AGI_2026), atol=1e-6 + ) + np.testing.assert_allclose( + deductions[2026], + worksheet_2025(REPAYMENT_2026 - excluded, AGI_2026), + atol=1e-6, + ) + assert np.all(deductions[2026] <= deductions[2025] + 1e-6) diff --git a/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.py b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.py new file mode 100644 index 00000000000..dbcbe3397ee --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_deduction.py @@ -0,0 +1,66 @@ +from policyengine_us.model_api import * + + +class nc_claim_of_right_deduction(Variable): + value_type = float + entity = TaxUnit + label = "North Carolina claim of right repayment itemized deduction" + unit = USD + documentation = ( + "North Carolina itemized deduction for repaying income included in " + "adjusted gross income in an earlier year under a claim of right; from " + "2026, only income North Carolina taxed that year. Repayments above " + "the threshold are deductible in full. Smaller repayments are reduced " + "by two percent of federal adjusted gross income less the filers' " + "other miscellaneous itemized deductions. Not modeled: the bar on the " + "deduction when federal tax for the year of repayment is computed " + "under 26 U.S.C. 1341(a)(5), and from 2026, repayments of amounts North " + "Carolina added to adjusted gross income that federal law excluded." + ) + definition_period = YEAR + reference = ( + # N.C. Gen. Stat. 105-153.5(a)(2)d + "https://www.ncleg.gov/EnactedLegislation/Statutes/HTML/BySection/Chapter_105/GS_105-153.5.html", + # S.L. 2026-31, section 1.8, PDF pages 5-6 + "https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-31.pdf#page=5", + # 2016 Form D-401 instructions, Repayment of Claim of Right Worksheet + "https://taxsim.nber.org/historical_state_tax_forms/NC/2016/D401.pdf#page=13", + # 2025 Form D-401 instructions, Form D-400 Schedule A line 8 and worksheet + "https://www.ncdor.gov/2025-d-401-individual-income-tax-instructions/open#page=20", + ) + defined_for = StateCode.NC + + def formula(tax_unit, period, parameters): + person = tax_unit.members + p = parameters(period).gov.states.nc.tax.income.deductions.itemized + repaid = person("claim_of_right_repayment", period) + if p.claim_of_right.nc_modified_income_basis: + # From 2026, only repayments of amounts included in AGI as + # modified for North Carolina in the earlier year. + excluded = person( + "nc_claim_of_right_repayment_excluded_from_income", period + ) + repaid = max_(repaid - excluded, 0) + # Dependents' repayments and expenses belong on their own returns. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + repayment = tax_unit.sum(repaid * head_or_spouse) + # Repayments of $3,000 or less are reduced by (i) the section 67(a) + # floor, two percent of federal AGI, minus (ii) the other + # miscellaneous itemized deductions, not below zero. Section 67(g) + # disallows those other deductions from 2018, so the 2018-and-later + # worksheet subtracts the whole floor. + p_misc = parameters(period).gov.irs.deductions.itemized.misc + floor = p_misc.floor * tax_unit("positive_agi", period) + if p_misc.applies: + other_misc_deductions = tax_unit.sum( + add(person, period, p_misc.sources) * head_or_spouse + ) + else: + other_misc_deductions = 0 + remaining_floor = max_(floor - other_misc_deductions, 0) + small_repayment_deduction = max_(repayment - remaining_floor, 0) + return where( + repayment > p.claim_of_right.threshold, + repayment, + small_repayment_deduction, + ) diff --git a/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_repayment_excluded_from_income.py b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_repayment_excluded_from_income.py new file mode 100644 index 00000000000..d849340a81e --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/tax/income/deductions/itemized/nc_claim_of_right_repayment_excluded_from_income.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class nc_claim_of_right_repayment_excluded_from_income(Variable): + value_type = float + entity = Person + label = "Claim of right repayment excluded from North Carolina income when received" + unit = USD + documentation = ( + "Part of claim_of_right_repayment that North Carolina deducted from " + "adjusted gross income in the earlier year it was received, such as " + "repaid Social Security benefits or Bailey-exempt retirement benefits." + ) + definition_period = YEAR + reference = ( + # N.C. Gen. Stat. 105-153.5(a)(2)d as rewritten by S.L. 2026-31, + # section 1.8, PDF pages 5-6 + "https://www.ncleg.gov/EnactedLegislation/SessionLaws/PDF/2025-2026/SL2026-31.pdf#page=5", + ) + defined_for = StateCode.NC diff --git a/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_itemized_deductions.py b/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_itemized_deductions.py index 994735af1a7..037c46fd128 100644 --- a/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/nc/tax/income/deductions/nc_itemized_deductions.py @@ -27,10 +27,9 @@ def formula(tax_unit, period, parameters): # North Carolina specifies a state and local tax deduction cap which is currently not modeled in PolicyEngine - other_deductions = add( - tax_unit, - period, - ["charitable_deduction", "medical_expense_deduction"], - ) + # Charitable contributions, medical and dental expenses, and the + # other items in G.S. 105-153.5(a)(2), none subject to the cap. + p = parameters(period).gov.states.nc.tax.income.deductions.itemized + other_deductions = add(tax_unit, period, p.other_sources) return capped_mortage_and_property_taxes + other_deductions diff --git a/policyengine_us/variables/household/expense/person/claim_of_right_repayment.py b/policyengine_us/variables/household/expense/person/claim_of_right_repayment.py new file mode 100644 index 00000000000..ed205b53da5 --- /dev/null +++ b/policyengine_us/variables/household/expense/person/claim_of_right_repayment.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class claim_of_right_repayment(Variable): + value_type = float + entity = Person + label = "Repayment of income received under a claim of right" + unit = USD + documentation = ( + "Amount repaid this year of income that was included in adjusted gross " + "income in an earlier year because it appeared the person had an " + "unrestricted right to it (26 U.S.C. 1341). Excludes repayments deducted " + "in arriving at adjusted gross income this year." + ) + definition_period = YEAR + reference = "https://www.law.cornell.edu/uscode/text/26/1341"