Skip to content

Fix duplicate 2024 Arkansas low-income table threshold and guard scale thresholds - #9599

Open
MaxGhenis wants to merge 1 commit into
mainfrom
fix-ar-low-income-duplicate-threshold
Open

MaxGhenis wants to merge 1 commit into
mainfrom
fix-ar-low-income-duplicate-threshold

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Fixes the 2024 Arkansas low-income tax table for head of household and surviving spouse filers with two or more dependents. The $92 row was keyed at $24,200, the same threshold as the $104 row, and policyengine-core summed the two into a $196 row.
  • Removes a duplicated top bracket from calibration/gov/irs/soi/agi/total_agi.yaml.
  • Adds a repo-level test that fails when any parameter scale has two brackets with the same finite threshold at any instant, plus YAML tests covering AGI from $24,100 to $24,401 for both filing statuses.

The bug

ParameterScale._get_at_instant passes each bracket to add_bracket, and AmountTaxScaleLike.add_bracket adds the amount to an existing row when the threshold is already present (read in policyengine-core 3.30.2, the version in uv.lock). Before this PR, the 2024 scale was:

[(0, 0), (24200, 196), (24300, 116), (24400, 129), ...]

ar_low_income_tax_joint evaluates it with right=True, so:

AGI Official 2024 table Before After
$24,177–$24,200 $92 $0 $92
$24,201–$24,300 $104 $196 $104
$24,301–$24,400 $116 $116 $116

The official 2024 AR1000F/AR1000NR instructions (PDF page 28, Rev 09/23/2024) list $0–$24,176 → $0 and $24,177–$24,200 → $92. With right=True, each threshold is the inclusive upper bound of the preceding published row, so the first bracket's 2024 threshold is 24_176. This matches the 2024 single (14_265) and HoH/surviving spouse 0–1 dependent (20_282) tables. The duplicate predates 875b73a (#9068) and d8a4317, which both edited this line without catching it. This PR also moves the 2024 reference anchor from #page=24 (now the self-employed health insurance worksheet in DFA's current PDF) to #page=28.

Verification against the official tables

I parsed every Arkansas low-income table from the DFA PDFs for 2021 (PDF pp. 26–27), 2022 (pp. 29–30), 2023 (pp. 29–30), 2024 (pp. 28–29), and 2025 (pp. 28–29), plus the standalone TaxTables_FI_2024.pdf and TaxTables_FI_2025.pdf. I parsed deterministically with pdfplumber and checked that each column is contiguous from $0 to its "Above $X" cutoff. Then I compared scale.calc(agi, right=True) from the loaded model against the published tax for every integer AGI from $0 to cutoff + $200:

  • This branch: 0 mismatches across all 7 PE tables × 5 years × both source documents.
  • Base (12345e9): mismatches only in 2024 HoH/surviving spouse 2+ dependents at AGI $24,177–$24,300. No other table or year differs.

soi/agi/total_agi triage

  • The last two brackets were identical (threshold 10_000_000, amount 824_093_126_000), so the loaded scale's top bucket was 1_648_186_252_000.
  • All 18 bucket values match IRS SOI Table 1.1 for tax year 2020 (20in11si.xls, column 3, "Adjusted gross income less deficit"). The "$10,000,000 or more" row appears once, so the second bracket is a copy.
  • Nothing reads this parameter. I searched policyengine-us, policyengine-us-data (main 42ed5d45c5; its SOI calibration targets use only soi.long_term_capital_gains), policyengine.py, and policyengine-api, and ran an org-wide GitHub code search. The only reference is an inventory CSV in ai-inequality. The duplicate predates 5dd64eb, the root of the current history.

The new test

policyengine_us/tests/test_parameter_scale_thresholds.py walks every ParameterScale in the loaded system (867 scales). For every instant at which any bracket's threshold changes, including uprated values, it flags two brackets with the same finite threshold. It reads each threshold's step function from values_list directly: _get_at_instant would already have merged the duplicates, and some parameters carry padded 0000-01-01 entries that core cannot turn into a date. On base it finds exactly these three scales. The scan takes under a second on top of the system load.

Two self-tests pin the guard: it flags a collision that starts at a later instant (and confirms core's silent sum), and it ignores duplicate .inf thresholds, which are common and harmless because tables that change length park unused brackets at infinity. It deliberately does not check ordering: add_bracket bisect-inserts brackets, so out-of-order YAML (for example gov.states.la.tax.income.credits.school_readiness.rate, listed 5→1) evaluates correctly.

Tests

  • pytest policyengine_us/tests/test_parameter_scale_thresholds.py policyengine_us/tests/test_parameter_files.py policyengine_us/tests/test_system_import.py: 15 passed.
  • policyengine-core test policyengine_us/tests/policy/baseline/gov/states/ar -c policyengine_us: 498 passed.
  • On the base tree, the two new YAML cases fail, and the new scan test fails listing the three scales.

Out of scope

  • total_agi.yaml keys the tax year 2020 values at 2015-01-01, and its reference reads "IRS SOI PUF 2015 (20in11.xls)" with no href. Rekeying changes the uprated values, so it belongs in its own change.
  • A companion policyengine-core issue proposes rejecting duplicate thresholds from a single YAML scale. That guard belongs in ParameterScale, not in add_bracket, because MarginalRateTaxScale.combine_bracket relies on the summing: Reject duplicate finite thresholds within a single parameter scale policyengine-core#550.

🤖 Generated with Claude Code

…esholds

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>

This branch has not been deployed

No deployments
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