Skip to content

Fix North Dakota 2026 income tax thresholds - #9165

Merged
PavelMakarchuk merged 3 commits into
mainfrom
fix/nd-2026-income-tax-thresholds
Aug 26, 2026
Merged

Fix North Dakota 2026 income tax thresholds#9165
PavelMakarchuk merged 3 commits into
mainfrom
fix/nd-2026-income-tax-thresholds

Conversation

@PavelMakarchuk

Copy link
Copy Markdown
Collaborator

Summary

  • add official TY2026 North Dakota individual income-tax thresholds for every filing status
  • pin the complete official schedule in raw parameter assertions
  • test the marginal schedule at and immediately above both boundaries for all five statuses

Source

North Dakota Office of State Tax Commissioner, 2026 Form ND-1ES, page 2.

Checks

  • make format (includes Ruff formatting and lint)
  • uv run pytest -c pyproject.toml policyengine_us/tests/core/test_state_indexed_frozen_parameters.py -k nd -q (32 passed)
  • focused ND boundary YAML (37 passed)
  • full ND income-tax YAML suite (73 passed)
  • uv run pytest -c pyproject.toml policyengine_us/tests/test_parameter_files.py -q (6 passed)
  • independent pre-PR review: CLEAN; no actionable findings

@PavelMakarchuk
PavelMakarchuk requested a review from MaxGhenis July 28, 2026 04:30
@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

@MaxGhenis This is ready for maintainer review: exact head ec61a0344d is now 31/31 green. The sole first-attempt failure was an unrelated Hugging Face CAS download error after five retries in Baseline (rest-b); rerunning that failed job passed. Independent review and all focused ND checks are clean.

@DTrim99

DTrim99 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Program Review — PR #9165 (Fix North Dakota 2026 income tax thresholds)

Author: PavelMakarchuk

Source Documents

  • ND Office of State Tax Commissioner, 2026 Form ND-1ES (SFN 28709, 12-2025), page 2 — "2026 Forms ND-1 and ND-EZ Tax Rate Schedules" · PDF #page=2

Branch Status

⚠ 6 commits behind main (1 ahead). Minor; review scoped to the merge-base diff.

Summary

Clean, correct TY2026 value update across all five filing statuses. 0 critical. Every 2026 bracket threshold matches ND-1ES 2026 p.2 exactly, correctly routed to the right status, correctly dated at 2026-01-01 with prior years untouched, the 3-tier scale structure (0% / 1.95% / 2.50%) preserved, and the frozen-parameter test + per-status boundary tests are aligned. CI green (31/31).

PDF / value audit — all 10 thresholds match ND-1ES 2026 p.2

Status 1.95% start 2.50% start
Single 49,575 250,400
MFJ + Qualifying Surviving Spouse 82,800 304,850
Married filing separately 41,400 152,425
Head of household 66,400 277,600

Base-amount cross-checks reproduce the form's printed "your tax is" column exactly ((250,400−49,575)×0.0195 = $3,916.09; MFJ $4,329.98; separate $2,164.99; HoH $4,118.40). MFJ and QSS correctly share one schedule per the form; no status file received another's thresholds; nd_income_tax_before_credits.py routes each status to its own scale. No mismatches.

Critical

None.

Should Address (minor / cosmetic)

  1. Reference title wording. The 2026 reference title on each of the five files reads "2026 North Dakota estimated income tax worksheet," but the schedule lives on the Form ND-1ES tax rate schedules page (the frozen-param test docstring correctly calls it that). The #page=2 href is correct and file-page-accurate — only the title wording is slightly off. Consider "2026 North Dakota Form ND-1ES tax rate schedules" for consistency.
  2. Pre-existing (not from this PR): joint.yaml carries 0.0250 trailing-zero rate values on the 2023-01-01 top bracket (should be 0.025; the other four files are correct). Since joint.yaml is a touched file, worth a cleanup pass — but not introduced here, and the new 2026 threshold values are all correctly formatted.

Suggestions (optional)

  • The 20 new test cases use free-form names rather than the file's existing Test N, … numbered convention (arguably more readable; correctly appended at the bottom).
  • The "at/just-above threshold" currency cases assert fractional-cent outputs (e.g. 3_916.0875) with no absolute_error_margin; adding 0.01 would match house style (they pass exactly as-is, so this makes them stricter, not vacuous). A one-line #($1 over × 1.95%) comment would clarify the 0.0195 outputs.

Validation Summary

Check Result
Regulatory / Value Audit All 10 thresholds exact-match ND-1ES 2026 p.2; base-amount cross-checks reproduce; MFJ+QSS shared correctly; 0 mismatches
Reference Quality ND-1ES cited on each of 5 files with correct #page=2; statute subsection preserved; minor title-wording nit
Code Patterns 0 critical; clean 2026-01-01 keys across all 5 statuses, scale structure intact, frozen-param test + changelog aligned; 1 pre-existing trailing-zero
Test Coverage Tight — all 5 statuses tested at AND just-above both bracket boundaries (20 cases); would catch a transposed threshold or wrong-schedule routing
CI Status Passing (31/31)

Review Severity: APPROVE

An exemplary, fully-verified 2026 multi-status threshold update: every value matched against ND-1ES 2026 p.2 with base-amount cross-checks, correct MFJ/QSS sharing, and comprehensive per-status boundary tests. No functional issues — only a cosmetic reference-title wording fix and a pre-existing trailing-zero cleanup. Rebase off the 6-commit lag before merge.

Review generated with Claude Code via /review-program

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small Claude nits

PavelMakarchuk and others added 2 commits August 25, 2026 16:08
…ing zeros

- Retitle the 2026 reference on all five filing-status files from
  "estimated income tax worksheet" to "Form ND-1ES tax rate schedules";
  the href and #page=2 anchor were already correct.
- Normalize joint.yaml's three 2023-01-01 top-bracket rates 0.0250 -> 0.025
  to match the other four files, and drop a whitespace-only line.
- Document the TY2026 boundary-case arithmetic so the fractional expected
  values are self-explaining.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings the branch up to date with main (was 507 commits behind) as requested
in review. The only conflict was additive in
policyengine_us/tests/core/test_state_indexed_frozen_parameters.py, where main
added a Rhode Island 2026 block and this branch adds a North Dakota one; both
are kept.

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

# Conflicts:
#	policyengine_us/tests/core/test_state_indexed_frozen_parameters.py
@PavelMakarchuk
PavelMakarchuk requested a review from DTrim99 August 25, 2026 14:12
@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

@DTrim99 review items addressed in f454be4 — re-requesting review.

Applied

  1. Reference title (your Should Address Basic prototype #1). All five filing-status files now read 2026 North Dakota Form ND-1ES tax rate schedules instead of "estimated income tax worksheet". The href and #page=2 anchor were already correct and are unchanged.
  2. joint.yaml trailing zeros (your Should Address Run black formatting on docs #2). The three 2023-01-01 top-bracket rates go 0.02500.025, matching the other four files. Also dropped a whitespace-only line in the same file.
  3. Branch freshness. The branch was 507 commits behind main (not 6 — it had drifted a lot further since your review). Now merged up to date. The only conflict was additive, in policyengine_us/tests/core/test_state_indexed_frozen_parameters.py: main added a Rhode Island 2026 block while this branch adds a North Dakota one. Both are kept, RI first to match main's ordering.

Not applied, with reasoning — happy to change if you disagree

  1. absolute_error_margin: 0.01 on the fractional-cent cases (your Suggestion Run black formatting on docs #2). I added the clarifying comment you asked for but skipped the margin. Two reasons: this file has no absolute_error_margin anywhere — all 22 pre-existing cases assert exact hand-calculated values under a # hand calculated to avoid whole-dollar rounding problems convention — and a margin widens an otherwise-exact comparison rather than tightening it, so it would make these cases slightly weaker, not stronger. Instead of a per-case margin I documented the derivation in a header comment above the TY2026 block (zero-rate boundary → 0; $1 above → $1 × 1.95%; top-rate boundary → (top − zero-rate) × 1.95%; $1 above → base + $1 × 2.50%), plus an inline # $1 over the threshold x 1.95% on the five 0.0195 outputs. Say the word and I'll add the margins.

  2. Test naming (your Suggestion Basic prototype #1). Left as-is — the new names are descriptive and appended at the bottom, and renumbering into the Test N, … scheme would churn the whole file.

Verification on the merged head: test_state_indexed_frozen_parameters.py 33 passed (both the RI and ND blocks), full ND YAML suite 322 passed, test_parameter_files.py 7 passed, ruff format --check clean. No parameter values changed in this update — only a reference title, a rate literal's formatting, and comments.

@DTrim99

DTrim99 commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Program Review

Source Documents

Critical (Must Fix)

None.

Should Address

None.

Suggestions

  1. Add an absolute_error_margin to the household boundary suitepolicyengine_us/tests/policy/baseline/gov/states/nd/tax/income/nd_income_tax_before_credits.yaml. The 20 new cases carry 4-decimal expected outputs (e.g. 3_916.0875, 2_164.9875) with no margin, relying on the file default. Float64 evaluation of count * 0.0195 can carry sub-cent representation error. The pre-existing Tests 1–17 use the same no-margin convention and pass in CI, so this is consistent and almost certainly fine; consider absolute_error_margin: 0.01 only if a future rounding change makes exact-float matches brittle. Not blocking.
  2. Add a mid-bracket / deep-in-top-bracket 2026 case — every 2026 household case sits within $1 of a threshold. A single deep-in-the-top-bracket point (e.g. single at $500,000 → 3_916.0875 + (500_000 − 250_400) × 0.025 = 10_156.0875) would confirm the 2.5% rate keeps applying far above brackets[2] (with brackets[3]/[4] = inf). The pre-existing TY2023 tests already exercise this shape for prior years, so this is optional depth, not a gap.
  3. Optionally harden the frozen-parameter pintest_state_indexed_frozen_parameters.py::test_nd_2026_income_tax_brackets pins the two indexed breakpoints (brackets[1], brackets[2]) for all five statuses but not the 2026 rate values (0% / 1.95% / 2.5%) or the inf top thresholds (brackets[3]/[4]). This matches the existing NE/ME convention, and the household suite exercises the rates end-to-end, so they are not untested — just not pinned. Asserting them would guard against a future stray rate/uprating edit silently un-freezing the 2026 schedule. Not required.

PDF Audit Summary

Category Count
Thresholds confirmed correct 10
Mismatches 0
Unmodeled / dropped items 0
Filing statuses covered 5 of 5

All 10 added TY2026 thresholds (2 breakpoints × 5 filing statuses) match the official 2026 Form ND-1ES page 2 exactly. Independent regulatory recomputation confirms both the thresholds and the printed base-tax amounts:

  • Single: 49,575 / 250,400 (base 3,916.09)
  • MFJ + QSS: 82,800 / 304,850 (base 4,329.98)
  • MFS: 41,400 / 152,425 (base 2,164.99)
  • HoH: 66,400 / 277,600 (base 4,118.40)

The 0% / 1.95% / 2.50% rate structure is unchanged; the only rate-line edit is a harmless 0.02500.025 literal normalization in joint.yaml (same value).

Validation Summary

Check Result
Regulatory Accuracy PASS — all 5 statuses match Form ND-1ES 2026 / NDCC 57-38-30.3; base-tax hand-recomputed
Reference Quality PASS — 2026 Form ND-1ES reference added (title + #page=2 href), NDCC sub-citations retained
Code Patterns PASS — 0 CRITICAL / 0 SHOULD / 3 SUGGESTION; correct period keying, .fixed changelog, value formatting
Test Coverage PASS — 20 household boundary cases + 5 frozen-parameter assertions; all expected values re-derived
PDF Value Audit PASS — 10 confirmed / 0 mismatches
CI Status 7 pass / 24 pending / 0 fail (at review time)

Review Severity: APPROVE

Next Steps

To auto-fix issues: /fix-pr 9165

🤖 Generated with Claude Code /review-program

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude nits are optional, lgtm

@PavelMakarchuk
PavelMakarchuk merged commit c9b3d81 into main Aug 26, 2026
31 checks passed
@PavelMakarchuk
PavelMakarchuk deleted the fix/nd-2026-income-tax-thresholds branch August 26, 2026 13:29
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.

2 participants