Skip to content

Give Arkansas 2015-2020 income tax parameters their own values - #9615

Merged
MaxGhenis merged 4 commits into
mainfrom
fix-ar-2015-2020-parameter-history
Sep 26, 2026
Merged

MaxGhenis merged 4 commits into
mainfrom
fix-ar-2015-2020-parameter-history

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

Summary

policyengine_us/system.py runs backdate_parameters(first_instant="2015-01-01"), which copies each parameter's earliest value back to 2015. Most Arkansas income-tax parameters start in 2021 or 2022, so 2015-2020 Arkansas returns silently used 2021-2022 law.

This PR gives those parameters their own 2015-2020 values from DFA's forms and instructions, and fixes the one formula that depended on them. It follows #9611, which fixes the 2014-2020 rate schedules. The two PRs are independent and touch different files.

What Before (backdated) After (DFA)
Low income tax tables, all 7 files 2021 tables: higher zero-tax ceilings and tops Every row of each year's tables, 2015-2020
Personal tax credit $29 $26 for 2014-2019, $29 from 2020
Tuition deduction caps (AR1075) 2021 caps 2015-2020 caps
Unemployment compensation Taxed in 2015-2017 Exempt through 2017; taxable from 2018 (Act 141 of 2017)
Military retirement Fully exempt in every year Before 2018, shares the $6,000 Line 17 exemption with pensions; fully exempt from 2018 (Act 141)
65 Special credit Ignores military retirement Before 2018, not allowed to a filer claiming the Line 17 exemption for military retirement
Additional tax credit for qualified individuals, inflationary relief credit 2022 amounts backdated to 2015. ar_files_separately reads the first credit's maximum in every year when it compares the joint and separate paths 0 before 2022, when they were created

Also fixes credits/personal/age_threshold.yaml's unit (currency-USD → year).

Examples

ar_income_tax, with only the main rate schedules. The #9611 fixes are not included, so these isolate this PR.

Household Before After
2015 single, wages $13,050 (low income table) $0.00 $112.00
2018 joint, 2 children, wages $26,000 (low income table) $0.00 $144.00
2016 single, wages $25,000 + unemployment compensation $8,000 $1,379.04 $1,093.83
2017 single, age 55, military retirement $30,000 $0.00 $1,033.83

How the low income tables were built

  • Each year's DFA table was parsed twice, with two independent parsers: one from text layout and one from word bounding boxes. Every table-year agrees with a third parse by an independent reviewer: 30 of 30.
  • With calc(agi, right=True), the model now equals DFA's table at every whole-dollar AGI from $0 to $45,000, for all 7 files and 2015-2020. That is 1.89 million comparisons with 0 mismatches, and inf above each table's top row.
    • The one DFA quirk is the 2017 head-of-household/qualifying-widow(er) table with 0-1 dependents. Its last row is printed "21,900-22,000", overlapping the previous row's 21,900, and the model keeps the earlier row's $574 at exactly $21,900.
  • 2021+ is unchanged.
    • Brackets whose first value was dated after 2021 get an explicit 2021-01-01 value equal to their old earliest value, which is what backdating produced before.
    • New trailing brackets end in 2021-01-01: .inf.
    • The tables evaluated at every AGI from $0 to $60,000 for 2021-2026 are identical to main.

Tests

  • low_income/ar_low_income_tax_joint_2015_2020.yaml: for each year, all 7 tables at the zero-tax ceiling, the first taxed row and the top row. Also a check that the table applies at the ceiling and not $1 above the top. The eligibility cases use a $30,000 taxable income so they fail on main.
  • Cases added to the existing files for the personal credit (2015, 2019, 2020), the 65 Special credit (2017, 2018), tuition caps (2015, 2016, 2020), unemployment compensation (2016, 2017, 2018), military retirement (2016, 2017, 2018), and both 2022 credits before 2022.
  • All but the boundary cases fail on main. The boundary cases are 2020's $29 credit, 2018's taxable unemployment compensation, and 2018's full military exemption.
  • policyengine-core test on gov/states/ar plus contrib/states/ar: 541 passed. ruff passes.
  • Three independent reviewers covered DFA data, 2021+ invariance and CI, and legal semantics with vectorized simulations. Their only finding against this branch, the 65 Special credit, is fixed here.

Not changed here

Already tracked in #9614, or pre-existing:

Separately, 2015-2016 households with a member 65+ cannot compute federal tax at all (#9613). The tests here supply inputs directly.

axiom: TheAxiomFoundation/rulespec-us#1405 debt

🤖 Generated with Claude Code

MaxGhenis and others added 4 commits September 25, 2026 04:22
backdate_parameters copies each parameter's first value back to 2015, so
2015-2020 Arkansas returns used 2021-2022 values. Add the DFA values for:
the low income tax tables (every row, all filing statuses), the personal
tax credit ($26 through 2019), and the tuition deduction caps; exempt
unemployment compensation through 2017; make military retirement share the
$6,000 retirement exemption before 2018 (Act 141 of 2017); and zero the
additional tax credit for qualified individuals and the inflationary relief
credit before 2022, when they were created. 2021+ values are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e Line 17 exemption

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

codecov Bot commented Sep 25, 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 (c152c04).
⚠️ Report is 49 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #9615   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         2    -2     
  Lines           76        46   -30     
  Branches         2         1    -1     
=========================================
- Hits            76        46   -30     
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

Copy link
Copy Markdown
Contributor Author

Heads-up from #9636 (Arkansas gambling winnings and losses): #9636 adds gambling_winnings to every period of gov.states.ar.tax.income.gross_income.sources.{joint,individual}. This PR adds a new 2015-01-01 block to those lists. Whichever PR merges second needs gambling_winnings in the 2015 block too; otherwise 2015-2017 silently drop gambling winnings from Arkansas gross income. The 2015 cases in ar_gambling_winnings_and_losses.yaml (from #9636) fail if that happens, so CI on the later PR will catch it.

@MaxGhenis
MaxGhenis merged commit 9b2ea64 into main Sep 26, 2026
37 checks passed
@MaxGhenis
MaxGhenis deleted the fix-ar-2015-2020-parameter-history branch September 26, 2026 14:59
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Admin-merged (squash, --match-head-commit c152c04) under Max's 2026-09-26 ruling (d399: obvious improvements need no permission; admin-merge when the only block is a required review only Max could give). Gates at the reviewed head: gh pr checks exit 0, MERGEABLE, no CHANGES_REQUESTED. Approvals: an independent adversarial Opus 5.5 review of the final head (workflow wf_bfc37ebf, no blocker or major findings) plus the session-management main session's own diff review. Closes decision d287.

MaxGhenis added a commit that referenced this pull request Sep 27, 2026
- The differential test now records every threshold core passes to
  add_bracket (repeats included) and compares that multiset with the
  guard's list. Comparing the built scale's de-duplicated thresholds could
  not see a disagreement about a bracket at an already-taken threshold,
  which is the case the guard decides.
- The tied-date self-test skips instead of failing if core's uprating ever
  stops writing the tied entry it reproduces, since CI upgrades core.
- The Arkansas table invariants now start in 2015, now that main has the
  2015-2020 tables (#9615); they hold for every year 2015-2030.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 3, 2026
…e thresholds (#9599)

* Fix duplicate Arkansas low-income table threshold and guard scale thresholds

The 2024 Arkansas low-income tax table for head of household and surviving
spouse filers with two or more dependents keyed its $92 row at $24,200, the
threshold of the $104 row. ParameterScale._get_at_instant passes each bracket
to add_bracket, which sums the amounts of equal thresholds, so the 2024 scale
became [(0, 0), (24200, 196), (24300, 116), ...]: AGI of $24,177-$24,200 got
$0 instead of $92 and $24,201-$24,300 got $196 instead of $104.

The official table (2024 AR1000F/AR1000NR instructions, PDF page 28) reads
$0-$24,176 -> $0 and $24,177-$24,200 -> $92. The tables are evaluated with
right=True, so the threshold is the upper bound of the $0 row: 24_176. The
2024 reference anchor now points at PDF page 28.

calibration/gov/irs/soi/agi/total_agi repeated its $10,000,000-or-more
bracket, which doubled the top bucket when loaded. Its 18 values match IRS
SOI Table 1.1 for tax year 2020 (20in11si.xls, column 3), where the top
bucket appears once. No code reads this parameter.

Add YAML tests for AGI of $24,100-$24,401 for both filing statuses and a
repo-level test that fails when any parameter scale has two brackets with
the same finite threshold at any instant.

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

* Address review: mirror core in the threshold guard, flag -inf ties, add invariants

Review fixes for #9599:
- The guard now reads values with core's own Parameter._get_at_instant, so
  entries sharing a date resolve first-match as core does (an uprating
  start_instant can write a second entry for a date), and it counts only the
  brackets ParameterScale._get_at_instant passes to add_bracket (threshold
  and value child both non-None, value child chosen by scale type).
- It checks every date any bracket child changes, not only threshold dates,
  since a collision can start when an amount starts.
- Only +inf duplicates are exempt; -inf is a reachable bottom row and core
  sums duplicates there too.
- Failures print one line per collision with its instant range.
- Drop the self-test assertions that pinned core's summing, which
  policyengine-core#550 would change.
- New differential test: for all 867 scales at every calendar-valid change
  date, the thresholds the guard sees match the scale core builds.
- New exhaustive invariant tests for the Arkansas low-income tables
  (2021-2030): tax is non-decreasing in integer AGI up to the cutoff and
  infinite above it, and the head of household and surviving spouse tables
  agree. On main the monotonicity test fails for exactly the two 2024
  tables fixed here.
- Point the 2023 booklet anchor in both edited Arkansas files at PDF page
  29 (the low-income tables) instead of page 25 (a worksheet).
- Add YAML cases for the last 2024 rows, the $28,200 cutoff, and tax
  before credits.

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

* Compare add_bracket calls in the differential test; cover 2015 tables

- The differential test now records every threshold core passes to
  add_bracket (repeats included) and compares that multiset with the
  guard's list. Comparing the built scale's de-duplicated thresholds could
  not see a disagreement about a bracket at an already-taken threshold,
  which is the case the guard decides.
- The tied-date self-test skips instead of failing if core's uprating ever
  stops writing the tied entry it reproduces, since CI upgrades core.
- The Arkansas table invariants now start in 2015, now that main has the
  2015-2020 tables (#9615); they hold for every year 2015-2030.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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