Skip to content

Add the IRS 2015-2021 local sales tax tables, selector and heading rates - #9911

Open
MaxGhenis wants to merge 9 commits into
mainfrom
local-sales-tax-ty2015-2021
Open

MaxGhenis wants to merge 9 commits into
mainfrom
local-sales-tax-ty2015-2021

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

#9879 models the IRS local general sales tax worksheet with parameters that start in 2022. PolicyEngine backdates parameters to 2015, so tax years 2015-2021 use the 2022 local tables, selector and heading rates. This PR adds each year's own values from that year's Instructions for Schedule A (Form 1040):

  • the Optional Local Sales Tax Tables A-D: 3,192 cells (19 income rows × 6 columns × 4 tables × 7 years);
  • worksheet line 2's state list;
  • the table selector's state defaults, counties and cities, by 2020 Census FIPS code with each name in a comment;
  • the state table heading rates (worksheet line 4) for every state.

Updated after #9879 merged as d08051cdd440174d287d0601c9bfe1ee10b7ed78. This PR merged main (0e7ced1747bae8f802a627c6f90d4d0ee4ba9c31) at 836562da1a9b55890b0ce58ecc409eb23f5c4ef7 by replaying only its own three commits (ac7d08d4c4, c75adad230, 92830f26d1), without rewriting the published branch. Main’s final formulas, NONE semantics, geography fallbacks, documentation and 2022+ values are preserved.

Sources

https://www.irs.gov/pub/irs-prior/i1040sca--YYYY.pdf, retrieved 2026-10-06:

TY sha256 Worksheet Line 2 instructions State tables Local tables and selector
2015 d9cc7dc4531d45f7caa2e8d09825f28227c9ad368badddceee084742dcecbb18 p. 5 p. 4 pp. 14-17 p. 18
2016 a4729a800558051d10a477b3937f97d20ac9600f6738271afc40c5d79dfede69 p. 5 p. 4 pp. 14-17 p. 18
2017 0721ad42ce2903543e9519394a4f28633e83de3f029c5c4304233338279c1991 p. 5 p. 4 pp. 15-18 p. 19
2018 dda49498ced026d1e815a697b76517693210de38cf12af5258b7f90929a58e13 p. 5 p. 6 pp. 14-17 p. 18
2019 ac7dec07f0f7aead76b8078a707bc905b0ec9ce6a68ce9122ff52e970dbaa940 p. 5 p. 6 pp. 15-18 p. 19
2020 91768eb9011042963ab33acad31ecf73ca243e9154f7a1f20e5142b6843ef2f9 p. 5 p. 6 pp. 14-17 p. 18
2021 dabdcc727779b89caa2969cc5274b716ce7588855e1d8605bd81e195a6ef333d p. 5 p. 6 pp. 14-17 p. 18

These are the same files #9631 and #9651 used. The 2017, 2018 and 2019 files are revised printings. TheAxiomFoundation/rulespec-us#1402 records that the earliest archived edition of each prints the same local tables; the revisions concern other deductions. For 2017 every extraction here also ran on the original January 2018 printing (sha256 e8e3f45e4199afc611205184c364ac87b727fb972b0fef9aea147d172e147e24, as #9651 found). It prints the same local tables, selector, headings and worksheet.

The worksheet, 2015-2021

The 2015-2021 worksheets have the same lines 1-8 as 2022:

  • Line 2. The same 15 states every year: "Did you live in Alaska, Arizona, Arkansas, Colorado, Georgia, Illinois, Louisiana, Mississippi, Missouri, New York, North Carolina, South Carolina, Tennessee, Utah, or Virginia in 2015?"
  • Columns. Through 2017 the tables' columns are exemptions, not family size: "The exemptions column refers to the number of exemptions claimed on Form 1040, line 6d." PolicyEngine counts both as tax_unit_size, as it already does for the state tables (Add the IRS Optional State Sales Tax Tables for 2015-2017 #9651).
  • Line 3. In 2015 and 2016 California's threshold is 7.5%: "check the 'No' box if your combined state and local general sales tax rate is 7.5000%." The heading rate is 7.5% too, so the formula needs no change.
  • Captions. Only the 2019 tables carry one: "Note. The numbers provided in this table are the base local general sales taxes, figured using a local sales tax rate of 1 percent."

What changes

Parameters (gov/irs/deductions/itemized/salt_and_real_estate/):

  • local_sales_tax_table/tax.yaml: 2015-2021 values for every cell; 2015-2021 references; notes on the exemptions columns, the 2019 caption, and Table D.
  • local_sales_tax_table/states.yaml: the list starts in 2015. The 2015-2022 worksheets list the same states, so the separate 2022 entry is dropped; values in force are unchanged.
  • local_sales_tax_table/default_table/, county/, place/ (13 files): 2015-2021 entries with references and notes on the readings below. none.yaml starts Mississippi at 2015, retaining its explicit 2022 value; A/B no longer default Mississippi to a named city's table. New York City's five counties are Table A counties in every year, since the city is Table A in every year; Model the IRS local sales tax worksheet with official locality rates #9879 does the same from 2022.
  • state_sales_tax_table/rate.yaml: 2015-2021 heading rates for all 46 states with a table, plus Alaska at 0. Notes quote the California footnote (7.50% in 2015 and 2016) and the averaged-rate footnotes.

Variables: No formula changes.

  • 2015-2021 references in local_sales_tax, local_sales_tax_rate and local_sales_tax_table, and a comment on the exemptions columns.
  • local_sales_tax's documentation now says that only the locality and official state rates still extend back from 2022.
  • local_sales_tax_rate's documentation now gives California's 7.5% threshold for 2015 and 2016, and says that the official state rates' 2022 values apply before 2022.

Verification

Two independent parsers on every cell. Each was written blind to the other:

  • P1 uses poppler only: pdftotext -layout, cross-checked within the script by pdftotext -raw.
  • P2 uses MuPDF word boxes only. It assigns each number to a row by y-coordinate and to a column by x-coordinate against the printed headers.

Results:

  • Local tables: P1 and P2 agree on all 4,104 cells (7 years plus the 2017 original printing and the 2022 control, 456 each).

  • Earlier parsers: they also agree with the three parsers the state-table PRs ran, which had emitted local tables too: 0 differences over 2015-2022.

  • Headings: they agree on every heading rate and footnote marker, 46 states × 9 editions. P2's word boxes also reproduce main's state tables (5,244 of 5,244 cells per year, 2015-2022).

  • Controls: both reproduce the 2022 local tables and heading rates in the repo exactly. 2017 equals the 2017 original printing.

  • Visual check: a reviewer, blind to both parsers' output, transcribed all 3,192 cells and the 322 heading rates (46 states × 7 years) from 200-dpi page images. Every value equals the YAML.

  • Round trip: the YAML, read back through the parameter system, equals P2 and S2 on every 2015-2021 value:

    • 3,192 cells;
    • 322 heading rates;
    • 35 zero rates (AK, DE, MT, NH and OR);
    • 91 lists (7 line-2 lists plus 84 table lists).

    After the merge and Mississippi correction, a fresh comparison against main (0e7ced1747bae8f802a627c6f90d4d0ee4ba9c31) confirms zero 2022+ differences across 16 parameter files: 517 dated leaves and 2051 effective-value comparisons at all timeline boundaries from 2022 onward. The historical parser counts above describe the original extraction; the Mississippi default interpretation is corrected below.

Two independent selector mappers. S1 worked from poppler text and S2 from MuPDF word boxes. Each confirmed row-to-table alignment on rendered page images and coded names against the 2020 Census national county and place files. At the original PR head they agreed on every county list, place list and state default in every year. The Mississippi state-default interpretation is corrected here using the worksheet’s absent-table instruction; county/place lists and all printed table cells are unchanged. Every code exists in the Census files and lies in its row's state. Both reproduce #9879's 2022 lists as of 082ee8c9c1 exactly. #9879 has since listed New York City's five counties under Table A, and this PR applies that rule to 2015-2021.

Independent review. Two further Opus reviewers, blind to the extractions, also checked the data:

  • The selector reviewer re-derived all 160 selector rows (827 counties and 398 cities) from page images. All 84 year-by-table lists matched at the original head; the Mississippi default correction below supersedes that default reading.
  • The tests-and-code reviewer re-parsed every cell, heading rate, footnote marker and quotation from the PDFs and found 0 differences. Its findings, all addressed here, concerned how line 3 interacts with Model the IRS local sales tax worksheet with official locality rates #9879 and some documentation.

Readings

Each is documented in the YAML:

  • 2018, South Carolina, a bare "Allendale" in the Table A row ("Aiken County, Allendale, Andersonn County, Bamberg County, ..."). Read as Allendale County: 28 of the row's 29 other entries are counties, and 2017 and 2019-2022 print "Allendale County". The other reading, Allendale town, would leave the rest of the county on the state's Table B.
  • 2018, South Carolina, a bare "Charleston" in the Table B row. Read as Charleston County. Both readings give Table B, since B is also the state's other-locality table that year.
  • 2015 and 2016, Colorado, "Denver City/Denver County." Both codes are listed in Table A. The two cover the same area, and A is also the state default.
  • 2015, New York, "New York City or Norwich City" follows the "Counties:" label. Both are coded as cities, and New York City's five counties take its table as in every other year.
  • Bare city names that are also county names in the same state (Yuma, Boulder, Champaign, Peoria) are the cities, as in 2022-2025: their rows list cities, and the selector names counties as "... County". Reading one as the county would change results; for example, Yuma is Table A in 2018 while Arizona's default is B.
  • Mississippi has no table (NONE) outside Jackson and Tupelo in 2015-2021, as Model the IRS local sales tax worksheet with official locality rates #9879 does from 2022: "If there is no table for your locality, the table amount is considered to be zero" (line 2 instructions: 2015-2017 PDF p. 4; 2018-2021 PDF p. 6). Named-place lists are unchanged: Jackson B in 2015 and A in 2016-2021; Tupelo B in 2015-2018 and C in 2019-2021. The seven PDFs were fetched again on 2026-10-26; every sha256 matches the Sources table, and each contains that line 2 sentence.
  • 2021, Georgia, "Dekalb County (excluding Atlanta)." DeKalb County is listed in Table B and Atlanta in the default Table C, as Model the IRS local sales tax worksheet with official locality rates #9879 does for 2022.
  • Misprints, read as the evident county: "Andersonn County" (SC 2016-2018), "Yatess" (NY 2018), "Yate" (NY 2021), and the repeated "Columbia, Columbia" (NY 2016).

Heading rates

Most are unchanged from 2022, with these exceptions:

  • California: 7.5% in 2015 and 2016 (footnote 3: "6.25% state sales tax rate for a total of 7.50%"), then 7.25%.
  • Footnoted annual averages: DC 5.8130% in 2018 (footnote 7), Louisiana 4.7227% in 2018 (footnote 8), Utah 4.8130% in 2019 (footnote 7).
  • Rates printed without such a footnote: Kansas 6.3264% (2015), Louisiana 4.7514% (2016), South Dakota 4.2923% (2016) and Utah 4.8130% (2020).
  • Other changes: New Jersey 7% to 6.875% (2017) to 6.625%; Louisiana 4% (2015) and 5% (2017); South Dakota 4% (2015); Utah 4.7% (2015-2018) and 4.85% from 2021; DC 5.75% to 6% from 2019; New Mexico 5.125% through 2021.

Line 3 before 2022

#9879 takes line 3 as the combined rate less the state's general rate: the official rate from state_rates.csv in the official-rate states other than Nevada, else the heading. Its first value, dated 2022-01-01, also covers 2015-2021.

  • Default path. With no rate entered, the 2022 combined rate less the 2022 state rate gives the 2022 local rate in every year. That is coherent.
  • Entered rates in three states. Where the state rate differed before 2022, a combined rate entered for that year has the 2022 state rate subtracted, not that year's heading. This affects Kansas 2015 (6.5%, not 6.3264%), South Dakota 2015 and 2016 (4.5%, not 4% or 4.2923%) and Utah 2015-2020 (4.85%, not 4.7% or 4.813%).
  • Everywhere else the 2015-2021 official state rates equal the headings.

This PR keeps #9879's rule:

  • Its entered-rate cases for these states (Kansas 2015, Utah 2019) enter line 3 directly, so they test the tables and line 4, not the backdated state rate.
  • Two cases pin the default path at 0: South Dakota and Kansas 2015 county areas that have no local tax in the 2022 files. Subtracting the 2015 heading instead would invent a local rate there, for example 0.5 points in South Dakota.

#9914 tracks a follow-up: entered rates before the state files begin would use the year's state rate (the heading), while the default path keeps its 2022 local rate.

Invariants

Each is tested; (H) marks Hypothesis.

  • I1 method partition, 2015-2025: each year, the 50 states and DC split into the no-local, local-table, ratio and no-table states, and the A-D defaults partition the local-table states excluding MS, while the no-local-table default set is exactly {MS} in every year. The 2015-2021 footnote 1 and 2 sets equal 2022's.
  • I2-I5, I11 (H): non-negativity, the no-local zero, the state-rate threshold, linearity, and line 3 entered directly versus combined. Hypothesis draws from 2015-2026 and 2030, with about four examples per year (52), as before (25 over six years).
  • I4: California and Nevada at their heading rates enter no local rate; 0.01 point above, they do. The test asserts California's heading is 7.5% through 2016.
  • I6 differential: local_sales_tax equals a pure-Python worksheet built from the raw YAML for every state, family size 1-8, income row, table and five rates, in each year 2015-2026 and 2030.
  • I7: never falls as the income row or family size rises, in every year.
  • I8: Table D equals the New York state table / 4, rounded half up, in every cell of 2015-2024. In 2025 it is within 1, as printed.
  • I9: with no rate input, every year 2015-2026 and 2030.
  • L7: each year's selector county and place lists, 2015-2025, are disjoint across tables and name only counties and places in local-table states.

Tests

Verification for 73b2ad4355bd22caa33ef501f5cdd2fc4ccaad25, against main 0e7ced1747bae8f802a627c6f90d4d0ee4ba9c31. Full-file runs used the identical model at 9c47c15a1468d7c00ed62e0c640a17e449a04b5e; the final test-only change declares the same one-cent tolerance as main for the two rounded Mississippi ratio expectations, and the bounded local-amount cases were reverified with that tolerance. Every test process runs in the foreground with the six requested thread caps and run_capped.py --rss-gb 4 --timeout 1800. No full microsimulation or impact run was performed.

  • New YAML coverage (68 cases): 42 selector cases (the original 35 plus one vectorized geography-fallback case for each year 2015-2021), 21 local-amount cases (the original 19 plus Gulfport in 2015 and 2018), and five rate cases. The fallback cases cover principal-county lookup from an unnamed place, explicit county enums, the NYC flag, and the state default. Existing named-place precedence cases are retained.
  • Mississippi arithmetic: at $85,000 and one exemption/family member, Gulfport enters zero on line 2 and a 1% local rate: 2015 is $1,139 × 1/7 = $162.71; 2018 is $1,232 × 1/7 = $176. The state-table cells and 7% headings are on PDF p. 15 in each year.
  • Fail before, pass after: unchanged copies of the new cases run against main’s parameter files and the identical formulas, one YAML file per capped process. Local-amount cases are split into at most four tax years per process to stay within the memory cap. Current-head selector/rate passes come from the requested full-file runs; current-head local-amount passes use the same bounded copies after restoring this PR’s parameters, because the full amount file exceeded the memory cap.
New-case file Cases Fail on main Pass on main Pass after
local_sales_tax_rate.yaml 5 5 0 5
local_state_tax.yaml 21 14 7 21
local_sales_tax_table.yaml 42 20 22 42

Requested full-file runs:

File Result Capped process seconds
local_state_tax.yaml 4 GB cap reached; full-file run deferred to CI 309.7
combined_sales_tax_rate.yaml 26 passed, 1 warning in 42.01s 404.5
local_sales_tax_rate.yaml 13 passed, 1 warning in 30.22s 373.2
local_sales_tax_table.yaml 73 passed, 1 warning in 30.22s 318.3
salt_deduction.yaml 8 passed, 1 warning in 28.05s 290.3
state_sales_tax.yaml 155 passed, 1 warning in 43.02s 577.1
state_sales_tax_income_bracket.yaml 12 passed, 1 warning in 266.31s (0:04:26) 1161.4
test_local_sales_tax_worksheet.py 81 passed in 121.84s (0:02:01) 298.0
test_locality_sales_tax_rates.py 40 passed in 170.66s (0:02:50) 420.2
test_state_sales_tax_table.py 4 GB cap reached; full-file run deferred to CI 157.2
test_local_sales_tax_county_carryover.py 4 passed in 11.81s 210.7
test_local_sales_tax_rate_build.py 10 passed in 16.92s 252.7
test_source_conflicts.py 103 passed in 499.05s (0:08:19) 841.3
test_no_group_variables_in_person_adds.py 10 passed in 67.41s (0:01:07) 486.1

Six complete YAML files pass (287 cases), as do six complete Python files (248 tests). The full local_state_tax.yaml and test_state_sales_tax_table.py runs reached the 4 GB RSS cap and are left to CI; the 21 new local-amount cases pass in bounded runs. The carryover test uses its existing three-household synthetic fixture; no population microsimulation was run. The source-conflict guard passes all 103 tests with zero flags; no KNOWN_UNMARKED allowance was added. Previous authorship runs of other YAML files are historical evidence, not current-head pass claims.

Impact

Only tax years 2015-2021 change. The round trip above shows every parameter value in force from 2022 on is unchanged, so 2022+ household and microsimulation results are unchanged. For 2015-2021, local_sales_tax now uses that year's table instead of the 2022 one. It moves only where a local amount is computed: in the local-table states, and in ratio states with an entered or official combined rate.

The hub reruns the impact assessment at the head that lands. Mississippi outside Jackson and Tupelo now uses the ratio method in 2015-2021 instead of a named city’s local table.

axiom: TheAxiomFoundation/rulespec-us#1402 queued

#1402 is the pe-parity item for 26 USC 164(b)(5)(H), TY2015-2025. It covers the Optional Local Sales Tax Tables and the selector. A comment on #1402 adds this PR's 2015-2021 companion cases. TY2015-2020 still need a corpus ingest, as #1402 records, before they can be encoded.

🤖 Generated with Claude Code

MaxGhenis and others added 4 commits October 6, 2026 08:30
Replace the 20%-of-state-amount approximation in local_sales_tax with the
State and Local General Sales Tax Deduction Worksheet: the Optional Local
Sales Tax Tables (with the selector's named counties and cities) in the
line-2 states and the ratio method elsewhere, with line 3 the combined rate
above the state table heading rate.

Combined rates come only from official state files: the Streamlined Sales
Tax rate and boundary files, New York Publication 718 and 718-A, and
Virginia Tax's rates by locality, weighted to Census 2020 places. Where no
official file exists the combined rate defaults to the heading rate, so the
local amount is 0 unless the user enters a combined or local rate.

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

Address the independent review of #9879:
- Recode SST place codes that match no Census place or county subdivision of
  their county by the records' postal city, or by the code the same ZIP
  codes carry after the records end. This fixes the DeKalb County part of
  Atlanta (Georgia code 05000; 8.0% before, 8.9% now) and Vermont's 2022
  town taxes (older town codes, and town-wide taxes under CDP codes). In
  Vermont codes resolve to towns. The build logs every recoding and every
  code left unmatched.
- Write each covered state's general sales tax rate (state_rates.csv) and
  take worksheet line 3 as the combined rate less it, so rounded or averaged
  headings (Minnesota 6.88%, South Dakota 2023 4.35%) no longer shift
  line 3. California and Nevada keep the IRS rule: the combined rate above
  the heading.
- List New York City's five counties as Table A counties.
- Accept 7-digit place GEOIDs; document the county_fips/place_fips keys,
  the pre-2022 backdating and the remaining limits (Iowa, Vermont towns
  outside places, a few Utah codes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tax years 2015-2021 used the 2022 Optional Local Sales Tax Tables, table
selector and state table heading rates through parameter backdating. Add
each year's own values from that year's Instructions for Schedule A (Form
1040): Local Tables A-D, worksheet line 2's states, the selector's state
defaults, counties and cities (2020 Census FIPS codes), and every state's
heading rate. Every cell was extracted by two independent parsers (poppler
-layout and MuPDF word boxes) and the selector by two independent mappers;
they agree everywhere. New York City's five counties take the city's table
(Table A in every year), as from 2022. 2022 and later values are unchanged.

Extend the worksheet invariants (I1, I4, I6-I9) and the selector check
(L7) to 2015-2025, add 2015-2021 YAML cases, and say in local_sales_tax's
documentation that only the locality and official state rates still
extend back from 2022.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis force-pushed the local-sales-tax-ty2015-2021 branch from aa9604b to ac7d08d Compare October 6, 2026 16:26
- Cover the default path in official-rate states whose state rate changed
  before 2022: South Dakota and Kansas 2015 county areas with no local tax
  in the 2022 files keep line 3 at 0.
- Replace the California 2015 no-input case, which could not fail before,
  with California at its 7.5% heading.
- Say in local_sales_tax_rate's documentation that California's line 3
  threshold is 7.5% in 2015 and 2016.
- Cite the 2015-2019 line 6 instruction pages in local_sales_tax.
- Keep about four Hypothesis examples per checked year.
- Quote Georgia's 2021 and 2022 other-locality rows exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Say in local_sales_tax_rate's documentation that the official state
  rates' 2022 values also apply before 2022.
- Cite p5 of the 2016 instructions, where the Nevada and in-year rate
  change parts of the line 3 instructions are.
- Describe the 2015-2021 local_state_tax cases' inputs accurately and
  reflow a long test comment.

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

Copy link
Copy Markdown
Contributor Author

Hand-off to the US + core hub

Head: 92830f26d1dceb20fc37d7770b9e2ebde13d746c

Base: stacked on #9879 (local-sales-tax-official-rates at 944686e0d4). Mergeable and clean against it.

Merge order:

  1. Don't merge this into Model the IRS local sales tax worksheet with official locality rates #9879's branch.
  2. After Model the IRS local sales tax worksheet with official locality rates #9879 merges to main, rebase this branch onto upstream/main and retarget the PR to main. If Model the IRS local sales tax worksheet with official locality rates #9879 is squash-merged, drop its commits from the branch.
  3. Let the full CI suite run; the PR workflow only runs on PRs into main, so no CI has run here yet.

Review: independent Opus reviews, all approving at the current head. Verdicts and findings are in ~/TheAxiomFoundation/_axiom-runs/164b5h-sales-tax-tables-20260924/local-ty2015-2021/notes/reviews.md.

  • Round 1. The selector reviewer approved. The tests-and-code and claims reviewers requested changes, which c75adad230 and 92830f26d1 address.
  • Round 2. The re-review approved c75adad230 and confirmed 92830f26d1. Its remaining gate: the full suite has to pass after retargeting.

Passed locally on this head (host rule ~/reviews/us-hub/test-run-rule.md, heavy runs under the lock):

  • the salt_and_real_estate YAML folder (307 cases);
  • the 130 other YAML files with 2015-2021 sales-tax or itemizing cases (700 cases, 0 failures);
  • test_local_sales_tax_worksheet.py (81) and test_locality_sales_tax_rates.py (30).

Pending: CI after the retarget; #9879's own review and merge.

Follow-ups:

Axiom: axiom: TheAxiomFoundation/rulespec-us#1402 queued. 2015-2021 companion cases are posted at TheAxiomFoundation/rulespec-us#1402 (comment).

Work files: extractions, parsers, comparison and build scripts, and run logs are in ~/TheAxiomFoundation/_axiom-runs/164b5h-sales-tax-tables-20260924/local-ty2015-2021/. tools/build_yaml.py and tools/make_yaml_cases.py regenerate the YAML.

Base automatically changed from local-sales-tax-official-rates to main October 26, 2026 06:58

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