Skip to content

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

Merged
MaxGhenis merged 6 commits into
mainfrom
fix-ar-low-income-duplicate-threshold
Oct 3, 2026
Merged

MaxGhenis merged 6 commits 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 threshold (other than +inf) 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, and the 2023 anchor in both files from #page=25 (the Inflationary Relief worksheet) to #page=29.

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.

Microsimulation impact

Default dataset (populace_us_2024.h5@populace-us-2024-spm-20260909), tax year 2024, main (615a0f2) against this branch with main merged in. One real run of each, on policyengine-core 3.32.12, compared record by record.

  • Arkansas income tax (ar_income_tax): $2.694B on both. No tax unit changes (0 of 79,729 records).
  • ar_low_income_tax_joint: identical for all 166,321 person records.
  • Why nothing moves: the fix changes only the 2024 threshold of one bracket. It reaches only head of household and surviving spouse filers with two or more dependents and AGI of $24,177 to $24,300, and the sample has no affected record. The 2025 threshold has its own value (24,818), so later years don't change.

This is a household-accuracy fix. It does not move the aggregate in this dataset. The total_agi calibration parameter has no reader, so removing its duplicate bracket changes no output.

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 has been in the file since ca1bbd0 (2023-09-19).

The new tests

Threshold guard. policyengine_us/tests/test_parameter_scale_thresholds.py walks every ParameterScale in the loaded system (869 scales). At every date on which any bracket child changes (including uprated values), it lists the brackets that ParameterScale._get_at_instant would pass to add_bracket and flags two with the same threshold. A bracket counts only when its threshold and its value child are both non-None, with the value child chosen by scale type exactly as core chooses it. Values come from core's own Parameter._get_at_instant, so entries sharing a date resolve first-match as in core. Duplicate +inf thresholds are exempt, because tables that change length park unused brackets there; -inf is a reachable bottom row, so ties there are flagged. On main it fails with exactly three lines, one per affected scale.

  • Self-tests: a collision that starts later; +inf exempt, -inf flagged; a bracket core skips because its amount has not started yet; and an uprating start_instant tie reproduced through uprate_parameters, in both directions.
  • Differential test: at every calendar-valid change date of every scale, the thresholds the guard reconstructs equal, repeats included, the ones core passes to add_bracket.
  • Ordering is not checked: 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.

Arkansas table invariants. .../states/ar/tax/income/low_income/test_ar_low_income_tax_tables.py checks every integer AGI for all 7 tables and every year from 2015 (the first year main now has tables for, via #9615) to 2030.

Invariants

These hold for every input and are tested exhaustively rather than by sampling:

  1. No scale repeats a threshold other than +inf at any instant. Core would silently sum the two rows. Enforced over all 869 scales at every change date.
  2. The guard agrees with core. At every calendar-valid change date of every scale, the thresholds the guard reconstructs equal, repeats included, the thresholds core actually passes to add_bracket. The differential test records those calls rather than comparing the built scale, whose de-duplicated thresholds would hide a disagreement over a bracket at a threshold that is already taken.
  3. Each Arkansas low-income table tax is non-decreasing in AGI up to its cutoff, and infinite above it (the filer uses the regular table and never re-enters). On main this fails for exactly the 2024 HoH and SS 2+ tables, where $196 was followed by $116.
  4. The head of household and surviving spouse tables are identical for each dependent tier, because the booklet publishes one "Filing Status 3 or 6" column.
  5. The model matches the published tables at every integer AGI, verified offline against all DFA PDFs for 2021–2025 (not a CI test, since CI does not download the PDFs). The YAML cases pin the edges.

Tests

  • Guard tests: 6/6 pass, and on main fail with exactly the three scales.
  • Arkansas invariant tests: 145/145 pass (2015–2030); on main, 2 fail.
  • policyengine-core test .../states/ar/tax/income/low_income (35 YAML cases, including Give Arkansas 2015-2020 income tax parameters their own values #9615's 2015–2020 cases, plus the 145 invariant cases it also collects): 180/180 pass after merging main, on policyengine-core 3.32.7, the version CI upgrades to.
  • Before the review round: policyengine-core test policyengine_us/tests/policy/baseline/gov/states/ar -c policyengine_us passed 498/498, and test_parameter_files.py plus test_system_import.py passed.
  • Base tree: on main, Cases 19, 20 and 23 fail; Cases 21 and 22 (the last 2024 rows and the cutoff) pass there too.

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 is in its own PR, Key IRS SOI AGI-by-size calibration amounts to tax year 2020 and fix SOI citations #9604.
  • The instructions say to round all amounts to the nearest dollar (2024 instructions, PDF page 12), but ar_low_income_tax_joint looks up unrounded AGI. So a fractional AGI just past any row edge gets the next row: on main, head of household with 0–1 dependents in 2024, $20,282.25 gets $65 where the rounded $20,282 gets $0. That affects every table and year, so it is a separate fix.
  • gov.states.ny.nyserda.drive_clean.amount has two entries dated 2021-06-31, which is not a real date; the differential test skips it. That is also a separate fix.
  • 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.

axiom: TheAxiomFoundation/rulespec-us#1425 queued

🤖 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>
@DTrim99

DTrim99 commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Program Review — PR #9599

"Fix duplicate 2024 Arkansas low-income table threshold and guard scale thresholds" (author MaxGhenis). PR head a8b188437e. This was a read-only review; no repo files were edited.

Path shorthand: LIT/ = policyengine_us/parameters/gov/states/ar/tax/income/rates/low_income_tax_tables/.

Source Documents

Document Pages used (file page = URL anchor)
2024 AR1000F/AR1000NR Instructions (combined resident/nonresident booklet, so one set of tables covers both forms) Single + HoH/SS tables: #page=28 (printed p.24, Rev 09/23/2024). MFJ tables: #page=29 (printed p.25, Rev 09/19/2024). Eligibility (Line 26/27): #page=14. QUALIFICATIONS box: top of #page=28
TaxTables_FI_2024.pdf (standalone DFA table extract, same Rev 09/23/2024) #page=2: all 116 rows are identical to booklet p.28
2023 AR1000F/AR1000NR Instructions (prior-year context) #page=29 (Single + HoH/SS) and #page=30 (MFJ)
2021 booklet and TaxTables_FI_2025.pdf (used only to check out-of-diff flags) 2021 #page=26, 2025 #page=2
Ark. Code Ann. § 26-51-301 (Justia) (b)(2)(B), (c)(2)(B), (e) indexing (CPI-U, 3% cap, round to the nearest dollar), (f)(5)(B), (g), (h)
IRS SOI Table 1.1, TY2020 (20in11si.xls) Used to check the total_agi.yaml deletion

Scope: the PR's changes, plus all five 2024 Arkansas low-income tables (7 parameter files, since HoH and SS are kept as separate files).

Branch Status

The branch is 1 commit ahead of and 10 behind main (merge-base 12345e9565, main 4da6f566bc). git merge-tree reports a clean merge, and the guard passes on a simulated merged tree. Main's #9494 moved the 2021–2023 hrefs to wp-content/uploads/ (lines 13, 15 and 17 of the AR files). The PR edits line 19, so the two do not conflict. This is informational only, not a finding.

Summary

The PR fixes a 2024 data error in the Arkansas low-income tax tables for head of household and surviving spouse filers with two or more dependents. The $92 row was keyed at $24,200, the threshold of the $104 row, and policyengine-core sums brackets that share a threshold. So AGI of $24,177–$24,200 got $0 and AGI of $24,201–$24,300 got $196.

The PR also:

  • deletes a duplicated top bracket from the IRS SOI total_agi calibration scale;
  • adds a repo-wide pytest guard against duplicate finite scale thresholds.

The value fix, the tests and the calibration deletion are all correct and match the official sources and the statute. There are no critical issues. Two should-address items remain: the guard exempts -inf duplicates, and one adjacent 2023 page anchor was left stale. Verdict: COMMENT.

Arkansas table fix — verification

  • Old vs new. Bracket 1 (the $92 row), line 31 in both LIT/head_of_household/two_or_more_dependents.yaml and LIT/surviving_spouse/two_or_more_dependents.yaml: 2024-01-01: 24_200 becomes 24_176.
    • The 2024 href on line 19 changes from #page=24 (a worksheet page, printed p.20) to #page=28.
    • Bracket 2 (24,200 → $104) and every later row were already correct and are untouched. The $92 amount was already correct. The PR changed the right row and shifted nothing.
  • Matches the booklet. 2024 booklet #page=28, "Head of Household/Surviving Spouse with 2 or More Dependents (FILING STATUS 3 or 6)", reads: 0–24,176 → $0; 24,177–24,200 → $92; 24,201–24,300 → $104; 24,301–24,400 → $116; 24,401–24,500 → $129.
    • This was confirmed on a 600-DPI render and on TaxTables_FI_2024.pdf#page=2.
    • With calc(..., right=True), each threshold is the prior row's TO value, so 24,176 is the correct encoding. It is consistent with the other bracket-1 thresholds (Single 14,265, HoH 0-1 20,282, HoH 2+ 2023 23,472, HoH 2+ 2025 24,818).
  • Statutory 3% indexing check (§ 26-51-301(e)).
    • 23,472 × 1.03 = 24,176.16, which rounds to 24,176.
    • The old 24,200 implied +3.10%, above the statutory cap. The other four 2024 tables all grew 3.00% (Single 3.004%, HoH/SS 0-1 3.001%, MFJ 0-1 3.001%, MFJ 2+ 2.999%).
    • The $92 row is also statute-derived. PE's regular tax on 24,176 − 2,410 = 21,766 is $458.67, and 20% of that is $91.7, which rounds to $92. Later rows follow the $9-per-$100 phase-down to within $1.
  • Pre-fix impact (main still has the bug; 2024 only; HoH and SS with 2+ dependents).
    • The core summing turned the scale into [(0,0), (24200,196), (24300,116), ...].
    • Line-29 table tax was $92 too low at AGI $24,177–$24,200 ($0 instead of $92) and $92 too high at $24,201–$24,300 ($196 instead of $104). That is 124 integer AGIs wrong per file.
    • Bottom line for a typical HoH or SS with two children: AR income tax was overstated by $20 at AGI $24,201–$24,300. On main, $196 − $116 personal credits − $60 additional credit = $20. On head, the $104 is fully absorbed by the credits.
    • There was no net effect at $24,177–$24,200 for that household. ar_uses_low_income_tax_tables did not change anywhere in the range.
  • Every other table, every year.
    • All 2024 tables in all 7 files match #page=28/#page=29: 317/317 bracket comparisons, plus integer-AGI sweeps with 0 mismatches. This covers Single ($17,000 cap), HoH/SS 0-1 ($24,600), HoH/SS 2+ ($28,200), MFJ 0-1 ($28,300) and MFJ 2+ ($35,100).
    • The 2023 first-row thresholds and caps match the 2023 booklet.
    • Full sweeps of 2021 and 2025 against the official PDFs found 0 mismatches.
    • A structural scan of all 7 files over 2021–2025 (duplicates, non-increasing thresholds, falling amounts, .inf placement, $100 row width) found that the 2024 HoH/SS 2+ collision was the only defect in any year. It is clean on head. After the fix, the HoH and SS scales evaluate identically in every year, as the single "FILING STATUS 3 or 6" column requires.
  • Tests. policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/low_income/ar_low_income_tax_joint.yaml is an existing file (Cases 1–18), not a new one as the PR context stated. The PR appends Case 19 (HoH, :345) and Case 20 (SS, :417).
    • Each case uses AGIs 24,100 / 24,176 / 24,177 / 24,200 / 24,201 / 24,250 / 24,300 / 24,301 / 24,400 / 24,401 and expects [0, 0, 92, 92, 104, 104, 104, 116, 116, 129] (:415, :487). All 20 values match #page=28, and they cover both sides of both affected edges.
    • On main, each case fails on 5 of 10 entries (0, 0, 196, 196, 196). On head, both pass.
    • The cases fail independently, because each status reads its own file. The test comment (:337-344) cites "PDF page 28" correctly.
  • Changelog. changelog.d/fix-ar-low-income-duplicate-threshold.fixed.md is top-level, uses a valid type and describes the change accurately.

Scale-threshold guard — verification

File: policyengine_us/tests/test_parameter_scale_thresholds.py (new, 117 lines).

  • Runs in CI? Yes, in the Full Suite - Rest (Python + variables) job.
    • pr.yaml:461-495 runs make test-other-python-rest, which is pytest policyengine_us/tests/ minus contrib, microsimulation and 3 SPM files (Makefile:186-187).
    • In this run all 3 tests passed (job 107767298087; scan 0.45s; core 3.32.6). Quick Feedback also ran it, because the file changed (3 passed).
    • It will run on every future PR.
  • What it scans.
    • All 867 ParameterScale nodes from system.parameters.get_descendants(): gov 865 (states 682, contrib 74, local 38, irs 23, hhs 16, …) and calibration 2.
    • All scale types: single_amount 652, marginal_rate 213, untyped 2. Being type-agnostic is correct, because both AmountTaxScaleLike.add_bracket and RateTaxScaleLike.add_bracket sum on an exact threshold tie.
    • All dates: the union of every bracket's threshold values_list instants, including uprated values through 2100 and padded 0000-01-01 entries. None values (not yet started or ended) are skipped.
  • Infinite and equal thresholds.
    • Equal finite thresholds at any instant are flagged, with an exact float key. Because core bisect-inserts brackets, this is the same as "strictly increasing after sorting", which is the right invariant for the silent-sum defect. It does not check declared YAML order (see Suggestion 4).
    • Both +inf and -inf are skipped (math.isinf, :55). Skipping +inf is justified: 62 scales park unused brackets there, and those rows are unreachable. Skipping -inf is not justified (Should 1).
  • Result on PR head: 3 passed. On the simulated merge with current main: 3 passed.
  • Result on main: 25 violation lines across exactly 3 scales. All are fixed by this PR:
    • calibration.gov.irs.soi.agi.total_agi at 2015-01-01, brackets [17, 18] sharing 10,000,000. Fixed by deleting the 7-line duplicate bracket.
    • gov.states.ar.tax.income.rates.low_income_tax_tables.head_of_household.two_or_more_dependents, brackets [1, 2] sharing 24,200 at 12 monthly instants (2024-01-01 to 2024-12-01; the monthly instants come from CPI-U uprating of bracket 0). Fixed by 24,176.
    • ...low_income_tax_tables.surviving_spouse.two_or_more_dependents: same as the HoH scale. Fixed by 24,176.
  • Allowlist: there is none. The only coded exemptions are None values and infinite values (:55). There are no path-based exceptions.
  • Cross-check against core: the set of scales the guard flags equals the set of finite merges core actually performs, on both main and head. _value_at agrees with core's Parameter(instant) at every threshold instant (0 mismatches).
  • False-positive risk: low. The only theoretical triggers are a threshold-only bracket (no amount or rate child) or a tie before a bracket's amount starts (Suggestion 3). No current cases: the IA and SC threshold-only brackets sit at inf. No scale uses equal thresholds on purpose.
  • total_agi.yaml deletion: correct data cleanup.
    • The removed 19th bracket was a verbatim copy of the 18th (threshold 10,000,000, amount 824,093,126,000). Core summed the two, so the top bucket loaded as 1,648,186,252,000.
    • IRS SOI TY2020 Table 1.1 (20in11si.xls, column 3) has one "$10,000,000 or more" row, and the 18 remaining buckets match it exactly. The sibling number_of_returns.yaml also has 18 brackets.
    • Nothing reads the parameter: no hits in policyengine-us, the local populace, policyengine-us-data or policyengine-api clones.

Critical (Must Fix)

None.

Should Address

  1. The guard exempts -inf duplicates, but core silently sums them. policyengine_us/tests/test_parameter_scale_thresholds.py:55: if value is None or math.isinf(value): continue.

    • The docstring (:13-14) only justifies exempting +inf parking. -inf is never a parking value. It is the first-bracket sentinel, used in 124 parameter files, and a row at -inf is reachable (for example, negative AGI).
    • I verified this with core: a single_amount scale with two -inf rows (amounts 10 and 5) loads as [-inf, 100] / [15, 1], and calc(50) returns 15. A marginal-rate scale doubles its bottom rate. That is exactly the silent sum this guard exists to catch.
    • Current exposure is zero on main, head and the merged tree, so tightening the check cannot break CI.
    • Fix:
      • change the condition to value == math.inf;
      • add a -inf duplicate self-test next to test_duplicate_threshold_guard_ignores_infinite_thresholds (:96);
      • update the docstring to say only +inf is exempt.
    • Agents split between SUGGESTION and SHOULD. I resolved it as SHOULD: the gap is in new code and was confirmed with core, and it is an edge case in the guard's own contract.
  2. The 2023 page anchor in the two edited files still opens the wrong page. LIT/head_of_household/two_or_more_dependents.yaml:17 and LIT/surviving_spouse/two_or_more_dependents.yaml:17 use 2023_AR1000F_and_AR1000NR_Instructions.pdf#page=25.

    • File p.25 is the Inflationary Relief Credit worksheet (printed p.21). The HoH/SS tables are on 2023 #page=29.
    • The 2023 values themselves match that page. Only the anchor is wrong.
    • Agents rated this SHOULD, SUGGESTION or PRE-EXISTING. I resolved it as SHOULD because:
      • it is the same printed-vs-file page error the PR fixes two lines below, in the same reference block, and the fix is one character per file;
      • I verified that current main still has #page=25 on this line, so merging main will not fix it.
    • Rebase onto main before editing, because main changed this line's domain to wp-content/uploads/.

Suggestions

  1. A self-test pins core's silent-sum behavior (test_parameter_scale_thresholds.py:90-93: at_2024.thresholds == [0, 200], at_2024.amounts == [0, 3]).
    • policyengine-core#550 (open, same author) would raise ValueError on duplicate finite thresholds.
    • Every CI job runs uv lock --upgrade-package policyengine-core, so a core release would turn every open PR's Rest and Quick Feedback jobs red.
    • Fix: drop these asserts, accept either outcome, or ship the change together with core#550.
  2. Failure message (:117, assert errors == []). Locally, pytest truncated the 25 main-tree errors to one line (1 of the 3 scales was visible). The 12 identical monthly lines per AR scale add noise.
    • Use assert not errors, "\n".join(errors), as code_health/parameters.py does.
    • Collapse consecutive identical instants, and optionally append scale.file_path.
  3. Possible false-positive edge cases (:51-57). The guard counts brackets that have a threshold but no amount or rate child, and ties before a bracket's amount starts. Core ignores both. There are no cases today. Optionally skip brackets whose value child is missing or None at the instant.
  4. Optional declared-order check. A mistyped threshold that falls between its neighbors (for example, 24,250 instead of 24,176) would be silently re-sorted by core's bisect, and the guard would not fire.
    • An "increasing in declared order" check would catch such typos. It would need to handle gov.states.la.tax.income.credits.school_readiness.rate, which is legitimately declared out of order.
    • Resolution between agents: uniqueness is the correct invariant for this PR's defect class, so this is hardening only.
  5. Placement nit. Repo-wide static parameter guards also live in policyengine_us/tests/code_health/. The top-level location is acceptable (next to test_parameter_files.py).
  6. Make the 2024 reference titles more specific (LIT/head_of_household/two_or_more_dependents.yaml:18, LIT/surviving_spouse/two_or_more_dependents.yaml:18). Name the specific table and printed page, e.g. "2024 AR1000F/AR1000NR Instructions, Low Income Tax Tables, Head of Household/Surviving Spouse with 2 or More Dependents (Filing Status 3 or 6), page 24". The href is already correct.
  7. Optional extra tests for the corrected tables.
    • Upper edge of 2024 HoH/SS 2+: AGI 28,100 → 575, 28,101 / 28,200 → 588, and 28,201 → ar_uses_low_income_tax_tables: false (#page=28).
    • End-to-end check of ar_income_tax_before_non_refundable_credits_joint for a HoH with two children in 2024: AGI 24,190 → 92 and 24,250 → 104 (main gives 0 and 196).

Pre-existing issues (outside this PR — candidates for follow-up issues)

  1. Stale booklet anchors in the five sibling AR low-income files (unchanged files; same printed-vs-file page error). The values are corroborated.

    File:line Current Should be
    LIT/single.yaml:17 2023 #page=25 2023 #page=29
    LIT/single.yaml:19 2024 #page=24 2024 #page=28
    LIT/head_of_household/no_or_one_dependent.yaml:17 2023 #page=25 2023 #page=29
    LIT/surviving_spouse/no_or_one_dependent.yaml:17 2023 #page=25 2023 #page=29
    LIT/surviving_spouse/no_or_one_dependent.yaml:19 2024 #page=24 2024 #page=28
    LIT/joint/no_or_one_dependent.yaml:17 2023 #page=26 2023 #page=30
    LIT/joint/no_or_one_dependent.yaml:19 2024 #page=25 2024 #page=29
    LIT/joint/two_or_more_dependents.yaml:17 2023 #page=26 2023 #page=30
    LIT/joint/two_or_more_dependents.yaml:19 2024 #page=25 2024 #page=29

    Related notes:

    • LIT/head_of_household/no_or_one_dependent.yaml:19 cites TaxTables_FI_2024.pdf#page=2. That link was verified correct; it is only inconsistent with its SS twin.
    • All 7 files list 2022 before 2021 (lines 12-15), a cosmetic ordering issue.
    • The PR-head 2021 href (images/uploads/incomeTaxOffice/...) returns 404, but main has already moved it to wp-content/uploads/, and #page=26 is correct there.
    • These were kept out of Should because they are in files the PR does not touch.
  2. The ar_low_income_tax_joint label is wrong. policyengine_us/variables/gov/states/ar/tax/income/low_income/ar_low_income_tax_joint.py:7 reads "Arkansas low income tax when married couples are filing separately". The variable actually computes the combined-return table tax for FS 1/2/3/6; separate filers get np.inf (:42).

    • The local name filing_status_separate (:18) is also misleading.
    • reference (:10) cites only the 2022 booklet.
    • Suggested label: "Arkansas low income table tax (combined return)".
    • Agents rated this SUGGESTION or PRE-EXISTING. It is placed here because the variable file is outside the diff. The test file name is correct and needs no rename.
  3. QUALIFICATION 1 is not modeled: the all-source income test in § 26-51-301(g) (#page=28, item 1). ar_low_income_tax_joint.py:14 tests eligibility on AR AGI after exemptions. It leaves out AR-exempt income (US government interest, excluded retirement income, Social Security), so some over-limit filers get table treatment.

  4. QUALIFICATION 3 is not modeled: the military/retirement exclusion bar in § 26-51-301(h) (#page=14; #page=28 item 3). The table is looked up on AGI net of ar_capped_retirement_or_disability_benefits_exemption_person, via ar_agi_joint.py:15-16. Filers therefore get both the exclusion and the table.

    • Example: MFJ with 2+ dependents, $30,000 wages and a $6,000 pension gets a $212 table tax, but under AR rules must use the regular table.
    • Correct approach: min(regular tax with the exclusion, table tax on AGI without it).
  5. Line 24 AR1000ADJ adjustments are not subtracted from the lookup AGI (#page=14). ar_agi_joint omits IRA/HSA payments, student-loan interest and similar adjustments. This overstates the lookup AGI and can move a filer to a higher row or above the cap.

  6. Out-year uprating only approximates the statute. All 7 files use uprating: gov.bls.cpi.cpi_u with propagate_metadata_to_children: true. That scales thresholds and amounts by uncapped, unrounded calendar CPI-U.

    • § 26-51-301(e)-(f) instead caps the COLA at 3%, uses the 12-month average ending Aug 31, rounds to whole dollars, and recomputes the amounts from the regular tax.
    • Example: PE's 2026 HoH/SS 2+ first threshold is 25,401.49, with fractional amounts.
    • Add the 2026 DFA tables when they are published.
  7. Calibration SOI vintage and labeling.

    • policyengine_us/parameters/calibration/gov/irs/soi/agi/total_agi.yaml:6-7: the reference has no href, and its title "IRS SOI PUF 2015 (20in11.xls)" does not match the data, which is TY2020 keyed at 2015-01-01.
    • The sibling number_of_returns.yaml holds TY2021 counts; its title says 20in11si.xls but its href is 21in11si.xls.
    • The two scales therefore describe different tax years. There are no consumers, but the vintages and references should be aligned. The PR body acknowledges this.
  8. Implicit carry-forward keys and cosmetic divergence between the twin files (no numeric impact).

    • LIT/joint/two_or_more_dependents.yaml: the 30,800-threshold bracket has no 2024-01-01 amount key. It falls back to 2023's 307, which happens to equal the published 2024 amount (#page=29). The 31,400 and 31,500 brackets similarly lack 2023 keys.
    • LIT/head_of_household/two_or_more_dependents.yaml:127 lacks the 2022-01-01: 185 entry that LIT/surviving_spouse/two_or_more_dependents.yaml:128 has.
    • SS 1-or-no has an explicit 2022-01-01: .inf entry that HoH lacks.
    • 2021 HoH/SS 1-or-no has duplicate .inf thresholds (harmless).
    • Longer term, HoH and SS could share one parameter per dependent tier, since the booklet publishes one column for FS 3 and 6. The duplication is why this fix had to be made twice.
  9. Test gaps that predate the PR (ar_low_income_tax_joint.yaml).

    • No case has exactly 1 dependent, so the <= 1 vs > 1 selector boundary is untested.
    • There are no surviving spouse 0-1 cases, no 2024 MFJ 1-or-none case, and no 2024 MFJ 2+ first-edge case (28,953 → 0, 28,954 → 108).
    • Cases 1 and 5 are named "1 dependent" but set tax_unit_dependents: 2. This has no effect because the Single table ignores dependents.
  10. Concatenated reference strings. ar_income_tax_before_non_refundable_credits_indiv.py:11-12, _joint.py:13-14 and _unit.py:11-12 are missing commas, so two URLs merge into one invalid URL. Cosmetic.

  11. policyengine_us/tests/code_health/parameters.py is never collected. Its name does not match test_*.py, so its checks do not run.

PDF Audit Summary

Metric Count Notes
Confirmed correct 317 All 2024 brackets in all 7 files: p.28 = 208 (Single 30, HoH 0-1 46, SS 0-1 46, HoH 2+ 43, SS 2+ 43); p.29 = 109 (MFJ 0-1 45, MFJ 2+ 64). Integer-AGI sweeps: 0 mismatches. Also verified: 20/20 new test expectations, 3 existing 2024 cases, 4 existing 2023 cases, and full 2021/2025 sweeps (0 mismatches).
Mismatches (at PR head) 0 Before the fix: 2 rows in each of the 2 HoH/SS 2+ files, both fixed by this PR.
Mismatches rejected 0 No PDF audit reported a mismatch at PR head, so no code-path or 600-DPI mismatch verification was needed (none exists).
Unmodeled 3 QUALIFICATION 1 all-source income; QUALIFICATION 3 exclusion bar; Line 24 adjustments (Pre-existing 3-5).
Pre-existing 2 Non-value issues: stale sibling anchors, implicit carry-forward keys. Separately, 3 out-of-diff value flags (TaxTables_FI_2024 p.2, the 2021 $732/$746 cliff, the 2025 Single $29→$33 step) were checked against the official PDFs: all REPO CORRECT.

Validation Summary

Check Result
Regulatory Accuracy PASS. The new value matches booklet #page=28, TaxTables_FI_2024 #page=2 and § 26-51-301(e) 3% indexing. No other defect in any AR table, 2021–2025.
Reference Quality PASS with 1 should. The changed 2024 anchors are correct; the adjacent 2023 anchor (:17) is stale (Should 2).
Code Patterns PASS. No hard-coded values, correct periods and entities, underscore numerals.
Formatting PASS. ruff format --check and ruff check are clean. The changelog fragment is top-level with a valid type.
Test Coverage PASS. Cases 19-20 pin every row edge from $24,100 to $24,401 for both statuses; they fail on main and pass on head.
Infrastructure guard PASS with 1 should. It runs in CI (Rest + Quick Feedback) over all 867 scales at all instants, matches core's merges exactly, passes on head and on the merge, and has no allowlist. The -inf exemption is unjustified (Should 1).
PDF Value Audit 317/317 match. 0 mismatches.
CI Status 35/35 passing.
Partner contract tests Not touched.

Review Severity: COMMENT

No critical issues. The two should-address items are non-blocking: tighten the guard's -inf exemption, and fix the adjacent 2023 anchor.

Next Steps

  • To auto-fix issues: /fix-pr 9599 (Should 1-2; Suggestions 1-2 are cheap to include).
  • Rebase onto main before touching line 17 of the two HoH/SS 2+ files.
  • File follow-up issues for the pre-existing items. Suggested grouping:
    • AR anchor/label cleanup (Pre-existing 1, 2, 10);
    • AR table eligibility gaps (3, 4, 5);
    • AR out-year uprating (6);
    • SOI calibration vintage (7);
    • test-gap backfill (9).

@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.

Requesting two small changes per the program review above (#9599 (comment)).

The Arkansas fix is correct: the 2024 head-of-household / surviving-spouse 2+ dependents threshold of 24,176 matches the booklet (#page=28) and the statute's 3% cap on 23,472. The PDF audit confirmed 317 values with 0 mismatches across 2021–2025. The new guard runs in CI over all 867 scales with no allowlist, and on main it flags exactly the three scales this PR fixes.

Two should-address items:

  1. test_parameter_scale_thresholds.py:55 skips duplicate -inf thresholds as well as +inf. Core silently adds duplicate -inf rows together, so the guard should exempt only +inf, with a small self-test.
  2. Line 17 of both edited Arkansas files still links the 2023 booklet at #page=25, which opens a worksheet; it should be #page=29. This PR already fixes the same mistake for the 2024 link two lines below.

MaxGhenis and others added 2 commits September 26, 2026 09:03
…dd 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>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Thanks @DTrim99, that's a thorough review. 24de218 addresses both requested changes, plus several suggestions and two findings from a separate Opus review. The branch now also merges current main, which moved the 2021–2023 hrefs to wp-content/uploads/.

Requested changes

  1. -inf duplicates: fixed. Only +inf is exempt now, the docstring explains why, and test_duplicate_threshold_guard_ignores_only_positive_infinity pins both directions.
  2. 2023 anchor: line 17 of both files now points at #page=29. I checked the wp-content copy (its hash differs from the old images/uploads file): page 25 is the Inflationary Relief worksheet, page 29 is the 2023 Low Income Tax Tables, and its parsed tables are identical to the ones I audited.

Suggestions taken

  • S1: dropped the self-test assertions that pinned core's summing, so policyengine-core#550 can land without breaking this test.
  • S2: failures now use assert not errors, "\n".join(errors), one line per collision with its instant range. On main it prints exactly three lines, for example ...head_of_household.two_or_more_dependents: brackets [1, 2] share threshold 24,200 at 12 instants from 2024-01-01 to 2024-12-01.
  • S3: the guard now counts only brackets that ParameterScale._get_at_instant actually passes to add_bracket: both the threshold and the value child must be non-None, with the value child chosen by scale type as core chooses it.
  • S7: added Case 21 (the last 2024 rows: $28,100 → $575, $28,101/$28,200 → $588, both statuses), Case 22 ($28,201 → ar_uses_low_income_tax_tables: false), and Case 23 (HoH with 2 dependents, tax before credits: $24,190 → $92, $24,250 → $104).

From the independent review (Opus 5.5)

  • Tied dates: the old _value_at took the last values_list entry on a date, but core takes the first. An uprating start_instant can write a second entry for a date that already has an explicit value, and the guard then missed a real collision. It now calls core's own Parameter._get_at_instant, and test_duplicate_threshold_guard_reads_tied_dates_like_core reproduces the case through uprate_parameters in both directions. No current scale has such a tie.
  • Amount-start collisions: the guard also scans every date any bracket child changes, not only threshold dates. A collision can begin when an amount starts, which test_duplicate_threshold_guard_skips_brackets_core_skips covers.
  • Differential test: test_add_bracket_thresholds_match_core_for_every_scale_and_instant checks, for all 867 scales at every calendar-valid change date, that the thresholds the guard sees equal the ones core builds. The only instants it skips are padded 0000-01-01 entries and 2021-06-31 in gov.states.ny.nyserda.drive_clean.amount, which is not a real date. I'm filing a follow-up for that.

New invariant tests

low_income/test_ar_low_income_tax_tables.py checks every integer AGI for 7 tables × 2021–2030 that:

  • tax never falls as AGI rises, up to the cutoff;
  • tax is infinite above the cutoff and never re-enters the table;
  • the head of household and surviving spouse tables are identical.

On main, exactly two cases fail: the 2024 HoH and SS 2+ monotonicity checks.

Not in this PR: fractional AGI at row edges. The independent review found that $24,176.25 now reaches the $92 row, while the instructions say to round every amount to the nearest dollar (2024 instructions, PDF page 12). That affects every row edge of every table on main, for example $24,200.25 gets $104 where the rounded $24,200 gets $92, so it gets its own PR along with your pre-existing items. SOI vintage is in #9604.

Locally: the guard tests pass 6/6, the invariant tests 91/91, and ar_low_income_tax_joint.yaml 23/23 on policyengine-core 3.32.7, the version CI upgrades to.

@MaxGhenis
MaxGhenis requested a review from DTrim99 September 26, 2026 13:36
MaxGhenis and others added 2 commits September 27, 2026 04:30
- 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

Copy link
Copy Markdown
Contributor Author

Update at e237a74: merged current main (including #9615's 2015–2020 tables and #9611), plus the second independent review's findings.

  • Correction to my earlier reply: on main, $24,200.25 does not get $104. It gets $196, from the duplicate this PR fixes. A correct main example of the rounding gap: head of household with 0–1 dependents in 2024, $20,282.25 gets $65 where the rounded $20,282 gets $0. The PR description is fixed.
  • 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 over a bracket at a threshold that is already taken.
  • The tied-date self-test skips rather than fails if a future core stops writing the tie it reproduces.
  • The Arkansas invariants now cover 2015–2030 and hold for Give Arkansas 2015-2020 income tax parameters their own values #9615's new tables: 145/145. The low_income/ YAML passes 180/180 on the merged tree.
  • Axiom parity: Encoding debt: Arkansas low income tax tables, TY2021-2025 (PE #9599 parity) TheAxiomFoundation/rulespec-us#1425 (pe-parity) queues the TY2021–2025 encoding. The pinned corpus release has no Arkansas income-tax forms, and TY2024 is not ingested yet.

@DTrim99, both of your requested changes (the -inf exemption and the 2023 #page=29 anchor) have been in since 24de218. Could you re-review?

@DTrim99

DTrim99 commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Program Review — Round 2

This re-review covers the author's changes since round 1: commits 24de218 and e237a74. Merges 6cb224c and c25a41a are clean. They brought in main's #9494 and #9615, which are not the author's work.

Path shorthand:

  • LIT/ = policyengine_us/parameters/gov/states/ar/tax/income/rates/low_income_tax_tables/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/low_income/
  • GUARD = policyengine_us/tests/test_parameter_scale_thresholds.py

Round-1 items

Item Status Evidence
SHOULD #1: the guard exempted -inf duplicates RESOLVED GUARD:85 is now if threshold != math.inf, and the docstring (:13-15) says only +inf is exempt. The self-test at :135-147 checks both directions, and an injected duplicate -inf row is flagged (mutation b).
SHOULD #2: the 2023 anchor opened #page=25, a worksheet RESOLVED LIT/head_of_household/two_or_more_dependents.yaml:29 and LIT/surviving_spouse/two_or_more_dependents.yaml:29 now point to main's wp-content/uploads/2023_...Instructions.pdf#page=29; the line moved from 17 because #9615 added references above it. A 300-DPI render of that page shows the 2023 HoH/SS 2+ table (printed p.25: 0–23,472 → $0, 23,473–23,500 → $89), which matches the repo.
S1: a self-test pinned core's silent summing (core#550 compatibility) RESOLVED The summing asserts are gone, and no self-test calls scale(...). The only such call is the differential test on real scales (:246). The tied-date test skips rather than fails if core writes no tie (:207-208).
S2: the failure message was truncated and noisy RESOLVED GUARD:262 is assert not errors, "\n".join(errors), and instants are collapsed to one line per collision (:91-98). The optional scale.file_path suffix was not added.
S3: false positives from threshold-only or not-yet-started brackets RESOLVED _add_bracket_thresholds (:33-64) reproduces core's inclusion rule, including choosing the value child by scale type (:52-59); the self-test is at :150-162. An injected threshold-only bracket is not flagged (mutation d), which is correct because core ignores it.
S4: optional declared-order check UNRESOLVED (optional) Not added, and the author did not reply. Round 1 rated this hardening only.
S5: placement (tests/ vs tests/code_health/) UNRESOLVED (optional) The file is still at policyengine_us/tests/, and the author did not reply. Round 1 rated the location acceptable.
S6: make the 2024 reference titles more specific UNRESOLVED (optional) Line 30 of both HoH/SS 2+ files still reads "Arkansas Income Tax Instructions, 2024, Low Income Tax Tables"; the author neither addressed nor disputed it. The href is correct and the title matches the other titles in the file; the same applies to the 2023 title at :28.
S7: extra tests (2024 upper edge; end-to-end tax before credits) RESOLVED T/ar_low_income_tax_joint.yaml adds Case 21 at :489-536 (28,100 → 575; 28,101 and 28,200 → 588; both statuses) and Case 22 at :538-560 (28,201 → ar_uses_low_income_tax_tables: false). It also adds Case 23 at :562-585 (HoH: 24,190 → 92 and 24,250 → 104, for both table tax and tax before credits).

Round 1's 11 pre-existing items were not re-audited: they are outside the diff and unchanged. The author deferred them to a separate PR, together with the fractional-AGI rounding gap at row edges that the author disclosed. The SOI vintage item moved to #9604.

New findings

Critical

None.

Should address

None.

Suggestions

  1. The differential test keeps about 61 MB of parameter cache alive (GUARD:241-246).
    • Its 11,556 scale(instant_str) builds leave 121,295 bracket _at_instant_cache entries on the module-level system, and they stay for the rest of the Rest job's pytest process. RSS grew by 60.9 MB locally.
    • This does not affect correctness, and CI passes.
    • Fix: build from scale.clone(), which starts with an empty cache, or clear each bracket's cache after its scale is checked.
  2. The collapsed failure message implies an end date (GUARD:92-94).
    • "N instants from X to Y" names the first and last change dates at which the collision was seen. Y is not when the collision ends: mutation (b) reports "to 2021-01-01", but the collision persists to the present.
    • Fix: reword to "starting X (seen at N change dates through Y)".
  3. Changelog wording (changelog.d/fix-ar-low-income-duplicate-threshold.fixed.md).
    • The fragment says the AR test fails when a table "stops rising with AGI". In fact the test allows flat stretches and fails only when tax falls.
    • Fix: change the wording to "falls as AGI rises".

Guard verification

This section uses policyengine-core 3.32.7, the version CI upgrades to. Its scale-building code is byte-identical to the local 3.26.0.

  • How core builds brackets. ParameterScale._get_at_instant (parameter_scale.py:129-176) passes a bracket to add_bracket only when the bracket's threshold and its value child (chosen by scale type) are both non-None at the instant.
    • On a tied date, a parameter's value is the first values_list entry (parameter.py:223-227).
    • Both add_bracket definitions (amount_tax_scale_like.py:37-49, rate_tax_scale_like.py:45-57) add the value to the existing row when the threshold is already present. Otherwise they insert a new row with bisect_left.
    • No threshold gets special treatment: finite, -inf and +inf are all handled the same way.
  • The guard's model matches core. It reuses core's Parameter._get_at_instant, applies the same inclusion rule, and flags every repeated threshold except +inf.
    • The +inf exemption is still justified: no finite base reaches a summed +inf row. 66 real scales pass several +inf rows, and 0 have a duplicate -inf.
    • Eight probes all agree with core: finite, -inf and +inf ties, a threshold-only bracket, a late-starting amount, a nulled amount, an untyped amount+rate bracket, and average_rate.
  • Head results. 6/6 tests pass in 2.60s. The differential test matches core at 11,556 of 11,585 scale-instants. It skips 29: 28 padded 0000-01-01 entries, plus 2021-06-31 in gov.states.ny.nyserda.drive_clean.amount, which is not a real date.
  • Mutation results. On a mutated tree, the main guard fails as intended.
    • Flagged: a reintroduced 24,200 threshold (a) and a duplicate -inf row (b).
    • Not flagged: a duplicate +inf row (c) and a threshold-only bracket (d), as designed.
    • The differential test also passes on the mutated tree. This is expected: it checks that the guard's multiset of thresholds equals what core passes to add_bracket (including the three repeats), not that duplicates are absent.
  • Cost. 0.73s + 0.38s in CI.

Value audit

  • Round-1 values are unchanged.
    • The author's only value change is still 24_200 → 24_176, at line 61 of both HoH/SS 2+ files.
    • I evaluated all 7 files at both a8b188437e and e237a7491a, at every integer AGI from 0 to 40,000 for 2021–2025. 0 AGIs differ, so round 1's 317 confirmed 2024 values stand.
    • The only row-level differences are unreachable +inf parking rows that main's Give Arkansas 2015-2020 income tax parameters their own values #9615 added.
  • New test expectations: 12/12 match the 2024 booklet, file p.28 (printed p.24), HoH/SS 2+ column:
    • Case 21, 6/6: 28,001–28,100 → 575; 28,101–28,200 → 588.
    • Case 22, 2/2: "Above $28,200, use Standard or Itemized".
    • Case 23, 4/4: 24,177–24,200 → 92; 24,201–24,300 → 104.
    • Cases 19–20 are byte-identical to round 1.
  • Newly covered 2015–2020 tables: 0 mismatches. e237a74 widens the invariant test's years to range(2015, 2031).
    • The source for 2015 is the DFA 2015 tax tables (TaxTables_2015.pdf), the document the YAML cites; the full 2015 booklet URL returns 404. Single, HoH and SS are on p.2; MFJ is on p.3.
    • 2015: 347/347 rows and cutoffs match, across all 7 files. 2016–2020: 1,644/1,644 match.
    • All 2015–2020 anchors are correct.
    • These values came from main's Give Arkansas 2015-2020 income tax parameters their own values #9615, not from the author.
    • One 2017 booklet typo is a source error, not a repo error: the last row of the HoH/QW 0–1 table is printed "21,900 - 22,000". The repo follows the table's intent.
  • Invariant test (T/test_ar_low_income_tax_tables.py): 145 tests (112 monotonicity, 32 HoH/SS agreement, 1 coverage). All pass at head. On main, exactly two fail: the 2024 HoH 2+ and SS 2+ monotonicity checks.
  • total_agi.yaml is unchanged since round 1 (git diff a8b188437e e237a7491a is empty).

CI

All 35 checks pass on e237a7491a (run 36306514925). The new tests ran in CI:

  • In the Rest job (108584171295; Python 3.14.7, core 3.32.7), all 6 guard tests passed, and the tied-date test ran rather than being skipped. All 145 invariant tests passed.
  • Quick Feedback ran ar_low_income_tax_joint.yaml: 23/23 passed.

Partner tests (policyengine_us/tests/policy/baseline/partners/**) are not touched.

Branch status

PR branch is 4 commit(s) behind main. Consider rebasing before merging. Review was scoped to the PR's actual changes — staleness did not affect findings.

Review Severity: APPROVE

Both round-1 should-address items are resolved, no new critical or should-address issues were found, and the three open round-1 suggestions and three new suggestions are optional.

@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.

Approving per the round-2 review (#9599 (comment)). Both round-1 should-address items are resolved:

  • The guard now mirrors core: it exempts only duplicate +inf thresholds, and its self-test covers both cases. Checked against policyengine-core 3.32.7's bracket construction. With a bug planted, it flags a duplicate at 24,200 and a duplicate -inf, and lets a duplicate +inf through as intended.
  • The 2023 booklet anchor now points to #page=29.

All values hold: the 317 from round 1 are unchanged, the newly covered 2015 tables match the booklet (347/347, plus 1,644/1,644 across 2016–2020), and all 35 checks pass.

Optional suggestions: the differential test keeps about 61 MB of cache alive; the shortened failure message implies an end date; and the changelog says the test fails when a table "stops rising", but it only fails when tax falls.

…uplicate-threshold

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit 38d6775 into main Oct 3, 2026
101 of 105 checks passed
@MaxGhenis
MaxGhenis deleted the fix-ar-low-income-duplicate-threshold branch October 3, 2026 11:55
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged on gates under Max's 2026-10-02 rule that obvious bug fixes merge without his approval, whatever they do to aggregates (cos card closed with "merge on gates").

  • Head merged: 2b37057812d7a020fab607b549c0c89b7ef69e24 (squash, --match-head-commit).
  • CI: gh pr checks exited 0 on this head: 35 passed, 0 skipped (Pull request run 37062698750 (attempt 3)). The earlier failures were runner out-of-memory deaths in states-shard-2/3 and ssa-usda on policyengine-core 3.32.12, re-run after 3.32.13 (policyengine-core#578) was released.
  • Mergeable: MERGEABLE, not a draft, no changes requested.
  • Independent review: an Opus 5.5 Subfleet review approved this exact head: ~/reviews/pe-us-merge-backlog-2026-10-02/reviews/9599-r1.md (local review folder). It also had a maintainer approval on GitHub.
  • Impact: stated in the PR body from real microsimulation runs (~/reviews/pe-us-merge-backlog-2026-10-02/impact/).

MaxGhenis added a commit that referenced this pull request Oct 3, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 6, 2026
The fused-URL half of this branch (0ccab71) landed on main as #9786,
with its own test_no_concatenated_reference_urls.py. Every file that
commit touched now carries main's version: 189 were already identical,
46 differ and take main's. The only file kept from it is this branch's
runtime test, tests/code_health/test_reference_urls.py, which also covers
reform modules and parameter references.

The Arkansas DFA anchor commit (e11a9d2) and the parameter-metadata
commit (6a7b430) are re-applied on top of main:
- tuition weighted-average files: main's new 2015-2020 values and
  references, plus this branch's "Tuition"/"education" title fixes.
- low-income tables (two or more dependents): main (#9599) already has
  the 2023 p29 and 2024 p28 anchors; re-applied the 2025 TaxTables_FI
  title.
- rates/main/{rate,reduction}.yaml: main already points the 2025 booklet
  at p30; main's version.
- ar_misc_deduction_{indiv,joint}.py: main (#9625) already replaced the
  AR1075 citation with the statute and the 2025 AR3 Lines 20-25
  instructions; main's version.
- MA HECS prior-year cost threshold: moved main's reference block, with
  main's archived FY2023/FY2025 URLs (#9765), under metadata.

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.

2 participants