Skip to content

Add North Carolina's claim of right repayment itemized deduction (G.S. 105-153.5(a)(2)d) - #10028

Merged
MaxGhenis merged 3 commits into
mainfrom
nc-claim-of-right-deduction
Oct 12, 2026
Merged

MaxGhenis merged 3 commits into
mainfrom
nc-claim-of-right-deduction

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

North Carolina itemizers may deduct repayments of income that they included in AGI in an earlier year under a claim of right (Form D-400 Schedule A line 8). PolicyEngine had no input for these repayments and left the deduction out. This PR adds:

  • claim_of_right_repayment, a person-level input: the amount repaid this year of income included in AGI in an earlier year because it appeared the person had an unrestricted right to it (26 U.S.C. 1341), excluding repayments deducted in arriving at AGI;
  • nc_claim_of_right_repayment_excluded_from_income, a person-level input used from 2026: the part of that repayment North Carolina deducted from AGI when it was received, such as repaid Social Security benefits;
  • nc_claim_of_right_deduction, North Carolina's deduction under G.S. 105-153.5(a)(2)d;
  • gov.states.nc.tax.income.deductions.itemized.other_sources, a parameter list of the itemized items outside the $20,000 mortgage and property tax cap (charitable, medical, and now claim of right). It replaces a hard-coded list in nc_itemized_deductions, preserving the existing charitable and medical sources and adding the default-zero repayment deduction. Historical microdata comparisons showed no change; impact at the final merged head remains pending for the hub.

The law

N.C. Gen. Stat. § 105-153.5(a)(2)d, added by S.L. 2016-5 § 2.1.(a) for taxable years beginning on or after January 1, 2014 (§ 2.1.(d)):

d. Repayment in the current taxable year of an amount included in adjusted gross income in an earlier taxable year because it appeared that the taxpayer had an unrestricted right to such item, to the extent the repayment is not deducted in arriving at adjusted gross income in the current taxable year. If the repayment is three thousand dollars ($3,000) or less, the deduction is the amount of repayment less (i) the limitation provided under section 67(a) of the Code minus (ii) all other items deductible under section 67(b) of the Code, not to exceed the limitation provided under section 67(a) of the Code. If the repayment is more than three thousand dollars ($3,000), the deduction is the amount of repayment. No deduction is allowed if the taxpayer calculates the federal income tax for the year of repayment under section 1341(a)(5) of the Code.

S.L. 2026-31 § 1.8 (PDF pp. 5–6, approved July 2, 2026) rewrites the first sentence for taxable years beginning on or after January 1, 2026: "an amount included in adjusted gross income income, as modified in G.S. 105-153.5 and G.S. 105-153.6, in an earlier taxable year". The statute page on ncleg.gov did not yet show this on October 9, 2026.

NCDOR's Repayment of Claim of Right Worksheet, 2025 Form D-401 instructions, PDF p. 20 (the same on PDF p. 20 for 2021 through 2024), for repayments of $3,000 or less: line 3 is 2% of federal AGI ("If negative, enter the number zero"), and line 4, the deduction, is line 1 minus line 3.

Term (ii) is read from the 2016 worksheet (PDF p. 13), the first year NCDOR printed one. Other miscellaneous itemized deductions absorb the 2% floor first: line 5 = 2% of AGI − the other federal miscellaneous deductions (not below zero), and line 6 = repayment − line 5 (not below zero). Section 67(g) disallows those other deductions from 2018. That is why the 2018-and-later worksheet subtracts the whole floor. The formula encodes the statute, so it reproduces both worksheets: it subtracts the head's and spouse's miscellaneous expenses only while gov.irs.deductions.itemized.misc.applies is true.

Design notes

  • Leaf inputs. Before this PR, PE had no input for claim-of-right repayments. The repayment and, from 2026, the part North Carolina excluded when received are facts about the taxpayer's returns. The model derives the deduction from them.
  • Head and spouse only. A dependent's repayments and miscellaneous expenses belong on the dependent's own return. YAML cases cover both.
  • The $3,000 test applies to the return's total. Worksheet line 1 is a single amount for the return. From 2026 the test applies to the amount the first sentence allows.
  • Not modeled: the section 1341(a)(5) bar.
    • Under 1341(a), federal tax for the repayment year is the lesser of tax with the deduction (1341(a)(4)) and tax without it minus the prior-year tax decrease (1341(a)(5)). So the method is computed, not elected.
    • PE models no part of section 1341 federally at this head. The stacked follow-up #10038 adds it, with the prior-year tax decrease as a leaf input, and then wires this bar; the hub re-stacks and reviews that PR after this build.
    • Until then this PR applies the deduction method. Section 1341 applies only when the federal repayment exceeds $3,000. The omitted section 1341(a)(5) bar therefore does not affect federal repayments of $3,000 or less, or (a)(4) filers.
    • For (a)(5) filers it may overstate the North Carolina deduction. From 2026, that includes a filer whose federal repayment exceeds $3,000 while the NC-eligible part is smaller.
    • North Carolina separately treats (a)(5) filers as having paid tax equal to the prior-year NC tax on the item, less the current-year NC saving (G.S. 105-266.2, refundable). PE does not model that either, so the net error for an (a)(5) filer can go either way.
    • Before this PR, no repayer could get the deduction.
  • Not modeled: from 2026, repayments of amounts that North Carolina added to AGI but federal AGI excluded. This remains a coverage limitation.

Historical household comparisons (2025, North Carolina, single)

The earlier main and claim-branch runs are saved in ~/reviews/us-hub/nc-itemized-other/evidence/households_{main,claim}.json (script households.py there). These are prior-run examples, rather than measurements repeated at the final merged head.

Household main NC income tax Branch NC income tax
Wages $80,000; real estate taxes $4,000; charity $8,000; repays $5,000 of 2024 wages $2,858.12 (no way to enter the repayment; the $12,750 standard deduction beats $12,000 itemized) $2,677.50: itemized $17,000; taxable income $63,000 × 4.25%
Same, charity $12,000, repays $2,500 $2,720.00 $2,681.75: deduction $2,500 − 2% × $80,000 = $900

Tests

Current tested head: 81528e0207591db0a453552cfba4d8c1c12a43c6. The first three YAML files ran individually, and the remaining six ran in one permitted baseline batch; the repeated standalone standard-deduction run is counted once. Python test files ran individually, all in the foreground. The lightweight untracked wrapper completes each test module, session and fixture teardown, then skips interpreter finalization. The six-file YAML batch reported 23 passed; its outer /usr/bin/time -l exited 1 afterward because the sandbox denied sysctl kern.clockrate during timing-resource reporting.

The incomplete test_parameter_yaml_keys.py attempts are preserved and excluded from pass counts: the initial pytest 9 attempt stopped with a Python segmentation fault (exit 139); a shared-startup retry was interrupted (exit 130); an isolated importlib retry was interrupted before assertions (exit 2); and a default-import-mode retry with a conftest cutoff stopped after partial progress (exit 2). None produced a complete test result. The fatal traceback references pytest capture and logging; the crash cause remains unconfirmed. The completed code-health runs used workspace-local pytest 8.4.2, matching the lock, with the existing shared policyengine-core 3.32.15 dependencies, default prepend import mode, --confcutdir=policyengine_us/tests/code_health, unbuffered and uncaptured output (-u -s), and no custom periodic traceback thread. These environment and invocation changes do not establish the crash cause. Version evidence is saved in hub-evidence/pytest-env-versions.log; the completed results are reported below.

The test_rest_python_groups.py invocation additionally set PYTEST_DISABLE_PLUGIN_AUTOLOAD=1, inherited by its child collectors; pytest built-in and module-local fixtures remain available. Its children only collect placeholders with --collect-only --noconftest: they neither import the collected test modules nor execute their cases. The guard checks make -n output and uses a stub pytest command with synthetic success, failure and kill outcomes to test group orchestration. Its workflow checks read YAML; it runs no Rest groups, SPM tests or microsimulations.

Test file Result Execution
nc_claim_of_right_deduction.yaml 17 passed Individual YAML file
nc_itemized_deductions.yaml 4 passed Individual YAML file
nc_standard_or_itemized_deductions.yaml 1 passed Individual YAML file
nc_standard_deduction.yaml 3 passed Six-file baseline YAML batch
nc_income_tax.yaml 3 passed Six-file baseline YAML batch
nc_income_tax_before_refundable_credits.yaml 3 passed Six-file baseline YAML batch
wagering_losses_deduction.yaml 3 passed Six-file baseline YAML batch
taxable_income_deductions_if_itemizing.yaml 2 passed Six-file baseline YAML batch
ar_gambling_loss_deduction.yaml 9 passed Six-file baseline YAML batch
test_nc_claim_of_right_properties.py 7 passed Individual Python file
test_parameter_yaml_keys.py 34 passed Individual Python file
test_reference_urls.py 32 passed Individual Python file
test_no_concatenated_reference_urls.py 48 passed Individual Python file
test_rest_python_groups.py 12 passed Individual Python file

YAML total: 45 passed across nine unique files. The six-file batch accounts for 23 passing cases in 62.04 s of pytest time; the repeated standalone standard-deduction run adds no cases to this total.

Python total: 133 passed across five files (property 7, parameter keys 34, reference URLs 32, no-concatenated URLs 48, Rest groups 12). Together with 45 YAML cases across nine files, 178 current-head cases passed across all fourteen targeted files.

Partner contract tests were neither run nor edited. Static inspection of the 144 partner YAML files found neither new repayment input, and no partner movement attributable to this PR was identified.

Standalone test_parameter_yaml_keys.py: 34 passed in 3,169.53 s of pytest time; 3,267.67 s from wrapper start through module completion; 285,949,952 bytes maximum RSS; module exit status 0. This is a local sandbox measurement. Evidence: hub-evidence/pytest_parameter_yaml_keys.pytest8.{log,metrics.json}.

Standalone test_reference_urls.py: 32 passed in 2,491.45 s of pytest time; 2,594.51 s from wrapper start through module completion; 753,172,480 bytes maximum RSS; module exit status 0. Evidence: hub-evidence/pytest_reference_urls.{log,metrics.json}.

Standalone test_no_concatenated_reference_urls.py: 48 passed in 1,149.98 s of pytest time; 1,181.61 s from wrapper start through module completion; 60,555,264 bytes maximum RSS; module exit status 0. Evidence: hub-evidence/pytest_no_concatenated_reference_urls.{log,metrics.json}.

Standalone test_rest_python_groups.py: 12 passed in 1,872.34 s of pytest time; 1,909.60 s from wrapper start through module completion; 40,943,616 bytes maximum RSS; module exit status 0. Evidence: hub-evidence/pytest_rest_python_groups.{log,metrics.json}.

  • nc_claim_of_right_deduction.yaml, 17 cases from the worksheets and the statute:
    • the floor; exactly $3,000 (still reduced); $3,001 (deducted in full); a floor above the repayment; negative AGI;
    • a joint return with a dependent's repayment excluded;
    • 2022 under section 67(g);
    • three 2016-worksheet cases with other miscellaneous expenses, including a dependent's;
    • 2025 and 2026 cases for the S.L. 2026-31 rewrite;
    • a non-NC household; the itemized total outside the $20,000 cap; and an nc_income_tax integration case.
  • test_nc_claim_of_right_properties.py, in tests/unit (the Rest job's remaining Python group). It runs a 720-point grid (repayment × AGI × other miscellaneous expenses × charity × property tax), one vectorized simulation each for 2016 and 2025, and a 240-point 2026 grid. The Python file compares the model with independent line-by-line implementations of the 2016 and 2025 worksheets over these finite grids. It also checks bounds, monotonicity, additivity outside the cap, and tax direction across grid points, including the standard-deduction crossover. The worksheet comparisons follow the worksheets' line structure.

Invariants checked over the finite grids

  1. Worksheet agreement: the deduction equals the 2016 worksheet in 2016 and the 2025 worksheet in 2025 at every grid point.
  2. Bounds in the 2016/2025 grids: 0 ≤ deduction ≤ repayment, and deduction = repayment whenever the repayment exceeds $3,000.
  3. Monotonicity: non-decreasing in the repayment, including across the $3,000 threshold; non-increasing in AGI; non-decreasing in other miscellaneous expenses.
  4. Other miscellaneous expenses change the deduction in 2016 and never in 2025 (section 67(g)).
  5. Additivity outside the cap: North Carolina itemized deductions rise by exactly the deduction, also when property taxes already exceed the $20,000 cap.
  6. Tax direction: North Carolina income tax never rises with the repayment, also across the standard-deduction crossover.
  7. From 2026 the deduction is the 2025 worksheet applied to the part North Carolina taxed. In 2025 that split has no effect, and the 2026 deduction never exceeds the 2025 one.

Current property-test cost

Standalone test_nc_claim_of_right_properties.py: 7 passed in 9.14 s of pytest time; 298.33 s from wrapper start through module completion, including startup; 975,110,144 bytes maximum RSS, measured with Darwin resource.getrusage; module exit status 0. The long startup reflected slow sandbox filesystem access. This local run is not a CI cost estimate. Metrics are saved in hub-evidence/pytest_property.metrics.json, with the pytest summary in hub-evidence/pytest_property.log.

Historical test and cost evidence

The following measurements predate this merge and are retained as context:

  • Prior CI group. The Rest job's remaining group on main (run 37926760594) collected 2,151 tests, including 11 skipped: 1,315.161 s of test time, 24:05.67 wall clock, 12,816,372 kB maximum RSS on the 16 GB runner.
  • Prior isolated local measurement. The earlier body and r2 review reported this file alone at 7 tests in 1.08 s (four simulations), with 1,107 MB peak RSS against 1,037 MB for importing policyengine_us and computing one household. No standalone log for that measurement was found in the saved evidence directory; the current isolated measurement above supersedes it for this head.
  • Prior comparison. The merged precedent test_ar_gambling_properties.py accounted for 32 cases and 4.636 s in that CI group.

Prior locked runs (~/reviews/us-hub/nc-itemized-other/evidence/):

  • main ff479aa74a: the NC and gambling YAML files, 28 passed.
  • This branch 3b57231636, YAML: nc_claim_of_right_deduction.yaml plus those files, 45 passed in 9.24 s.
  • This branch, Python: test_nc_claim_of_right_properties.py plus four parameter, reference and Rest-group code-health files, 133 passed in 29.84 s; 49.11 s wall clock and 1,244,741,632-byte maximum RSS for the combined run. These are combined-run measurements.

Hub microsimulation impact (current head)

A real microsimulation on the default dataset, main at the merge base 35f3c1f92a against this PR's head 81528e0207, for 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.

Output 2026 change 2026 records changed (weighted)
nc_itemized_deductions +0 0 (0)
nc_income_tax +0 0 (0)
state_income_tax +0 0 (0)
income_tax +0 0 (0)
household_net_income +0 0 (0)
household_benefits +0 0 (0)

Variables this PR adds (nc_claim_of_right_deduction) have no value on the base side, so the table leaves them out.

Comparison files are in fixes/10028-impact/ on the hub host.

Historical microsimulation evidence

The prior body and evidence logs attribute four real microsimulation runs on the default dataset (hf://datasets/policyengine/populace-us/populace_us_2024.h5@populace-us-2024-spm-20260909, 79,729 tax units): main ff479aa74a and this branch 3b57231636, for 2025 and 2026. They ran one at a time under the shared lock (~/reviews/pe-us-merge-backlog-2026-10-02/impact/lockrun.sh). Record-by-record comparisons are in ~/reviews/us-hub/nc-itemized-other/evidence/impact-ncit-c-{2025,2026}.json. The saved run JSONs and run logs do not record commit SHAs; attribution to ff479aa74a and 3b57231636 comes from the prior PR body, evidence summary, and r3 review. These results are historical evidence and do not replace the hub's impact run at the current head.

Year NC income tax, main Change Records changed (state_income_tax, nc_income_tax, nc_taxable_income, nc_itemized_deductions, nc_standard_or_itemized_deductions, household_net_income)
2025 $16.606bn $0 0 for every variable
2026 $16.560bn $0 0 for every variable

Those historical comparisons show no change because the dataset has no claim-of-right repayments. claim_of_right_repayment and nc_claim_of_right_deduction are zero on every record. The change matters for households that report a repayment, as in the table above. The parameter-list refactor changed no record in those historical runs.

Axiom

axiom: TheAxiomFoundation/rulespec-us#1656 queued

RuleSpec parity issue #1656 was filed by MaxGhenis on October 11, 2026 at 14:05:53 UTC with the pe-parity label. It covers the claim-of-right repayment deduction in G.S. 105-153.5(a)(2)d and the wagering-loss deduction in (a)(2)e, including their dated amendments, prerequisites and companion cases. It was filed from the existing draft at ~/reviews/us-hub/nc-itemized-other/axiom/nc-105-153.5-a2-d-e-issue.md. Encoding remains pending. The hub corrected the issue's status line to "queued" on 2026-10-11.

The draft records the prior RuleSpec/corpus checks:

  • In rulespec-us main 207486d8b5, us-nc/statutes/105/105-153.5/a.yaml is status: deferred: it encodes the $3,000 threshold and leaves #deduction_amount deferred.
  • Its prerequisites 26 U.S.C. 67(a)/(b)/(g) and 165(d) were not encoded at that checked head.
  • The axiom-corpus TY2026 builder applies S.L. 2026-31 § 1.8 and S.L. 2026-41 § 44.2 to this section; the prior checks found that scope unpublished. The hub rechecks the draft's corpus conditions before dispatch.

Follow-ups (not in this PR)

The PR remains a draft. The hub owns the confirmation review at the pushed head, marking ready, CI, landing, and re-stacking #10038 afterward.

  • North Carolina's wagering losses deduction, G.S. 105-153.5(a)(2)e (S.L. 2026-41 § 44.2, from 2025), is developed on branch nc-wagering-losses-deduction, stacked on this one. It awaits its federal prerequisites and hub review. It cannot land before Federal gross income omits gambling winnings but still deducts wagering losses; OBBBA 90% wagering loss limit (2026+) not modeled #9637 is fully fixed:
    • federal gross income must count gambling winnings;
    • the deduction must apply the 90% share of losses from 2026;
    • only the head's and spouse's gambling may count.
      Guards on that branch fail until all three are fixed.
  • #10038, federal section 1341: the claim-of-right deduction above $3,000 (Schedule A line 16), the credit computation under 1341(a)(5), and then the 1341(a)(5) bar above. The hub owns re-stacking and reviewing that follow-up.

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 9, 2026 08:50
G.S. 105-153.5(a)(2)d, added by S.L. 2016-5 for 2014 and later, lets North
Carolina itemizers deduct repayments of income included in AGI in an earlier
year under a claim of right (Form D-400 Schedule A line 8). Repayments above
$3,000 are deductible in full; smaller ones are reduced by 2% of federal AGI
less other miscellaneous itemized deductions, which the 2016 and 2025 NCDOR
worksheets both reproduce. PE had no input for these repayments, so it adds
claim_of_right_repayment (person level; only the head's and spouse's count)
and moves North Carolina's uncapped itemized items into a parameter list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…C taxed

Review r1 (subfleet 20261009-090558-nc-itemized-review):
- S.L. 2026-31 section 1.8 rewrites G.S. 105-153.5(a)(2)d for taxable years
  beginning on or after January 1, 2026: the repaid amount must have been
  included in AGI "as modified in G.S. 105-153.5 and G.S. 105-153.6" in the
  earlier year. New person input
  nc_claim_of_right_repayment_excluded_from_income (the part NC deducted when
  received, such as repaid Social Security) and a dated parameter apply it.
- Count only the head's and spouse's other miscellaneous expenses against
  the pre-2018 floor; a dependent's are on the dependent's return.
- The property grid now varies property taxes (so the $20,000 cap binds) and
  adds a 2026 grid for the excluded part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Context from the session that opened this PR, for the US + core hub, which now owns it. I will not push to this branch.

Reviews (GPT-6.1 Sol via Subfleet). Files are in ~/reviews/us-hub/nc-itemized-other/:

  • r1 review-out.md: REQUEST CHANGES. It found the S.L. 2026-31 § 1.8 rewrite and dependents' miscellaneous expenses; both are fixed in 3b57231636. The author's response is review-response-r1.md.
  • r2 review-r2-out.md: the code is sound; REQUEST CHANGES on documentation only. It withdrew the request that the section 1341(a)(5) bar needs Max.
  • r3 review-r3-out.md: one wording fix, in the held Axiom draft's status line.
  • r4 review-r4-out.md: it confirmed that fix. Its one FAIL was that its sandbox could not reach GitHub to verify the upstream head.
  • None of these reviewed 81528e0207, the hub's merge of main. My rebase onto main fe0c3a6, saved as wip/rescue-10028-rebase (6f37b3baa2), had a range-diff identical to 3b57231636.

Evidence is in ~/reviews/us-hub/nc-itemized-other/evidence/:

  • households_*.json;
  • the locked test logs, at 3b57231636;
  • impact-ncit-c-{2025,2026}.json: real microsims against main ff479aa74a, zero change on every record, because the dataset has no repayments;
  • ci-rest-timings/: the remaining-group baseline used for the test-cost note.

Known gaps, documented in the variable and the body:

Follow-ups:

@MaxGhenis MaxGhenis closed this Oct 11, 2026
@MaxGhenis MaxGhenis reopened this Oct 11, 2026
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 11, 2026 23:20
@codecov

codecov Bot commented Oct 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (909176a) to head (81528e0).
⚠️ Report is 414 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10028   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         3    -1     
  Lines           76        56   -20     
  Branches         2         2           
=========================================
- Hits            76        56   -20     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MaxGhenis
MaxGhenis merged commit 557b539 into main Oct 12, 2026
45 of 49 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10028 at 81528e0207591db0a453552cfba4d8c1c12a43c6 (squash): Add North Carolina's claim of right repayment itemized deduction (G.S. 105-153.5(a)(2)d).

@MaxGhenis
MaxGhenis deleted the nc-claim-of-right-deduction branch October 12, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant