Repository navigation
Add the 2015-2017 federal income tax brackets - #10637
Conversation
gov.irs.income.bracket started in 2018, so backdating to the first modeled year (2015) gave 2015-2017 the TCJA schedule: 12/22/24/32/37% rates and 2018 thresholds. Add the pre-TCJA rates (10/15/25/28/33/35/ 39.6%) from 2015 and each year's thresholds from Rev. Procs. 2014-61, 2015-53 and 2016-55, with hand-computed YAML cases for each filing status and a grid property that the main-rates tax never falls as taxable income rises. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review Head: Item and resolution
Sources and judgment calls
Tests run
Mutants (each in its own process, code restored after each)
All five were killed. States that read the federal schedule
CINot run: CI skips drafts. Proposed follow-upAdd the 2015-2016 basic standard deduction (and check 2017). Generated by Claude Code |
Section 3.01 of each Rev. Proc. starts on the linked page: Rev. Proc. 2016-55 on page 7, and 2015-53 and 2014-61 on page 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hub review in progress Superseded: the final review is at #10637 (comment) Generated by Claude Code |
|
Ready for hub review The PR head is still I found no P1 or P2. The PR fixes a defect that reproduces on main. Each 2015-2017 result I built moves toward the law, and nothing in 2018 or later can move. The findings below are P3s on docs, test strength and hand-off claims. FindingsP3-1. Three docstrings and comments still say "top of the 24 percent bracket", which is now wrong for 2015-2017
The code reads In The PR has no test of this path before 2018. Suggested YAML case for - name: 2017 single filer, the pre-2018 schedule taxes income below 25 percent only to the 15 percent bracket top
period: 2017
input:
filing_status: SINGLE
taxable_income_plus_section_911_exclusion: 43_950
output:
taxable_income_taxed_below_25_percent: 37_950P3-2. The property test can't detect threshold data errors
@pytest.mark.parametrize("year", YEARS)
@pytest.mark.parametrize("status", FILING_STATUSES)
def test_thresholds_and_rates_strictly_rise(year, status):
bracket = bracket_node(year)
keys = list(bracket.rates.__iter__())
t = [float(bracket.thresholds[b][status]) for b in keys]
r = rates(bracket)
assert all(a < b for a, b in zip(t, t[1:])), (year, status, t)
assert all(a < b for a, b in zip(r, r[1:])), (year, r)P3-3. The Rev. Proc. reference titles leave out their multi-page ranges
- title: "2017 IRS data release, Rev. Proc. 2016-55 section 3.01 #page 7-8"
href: https://www.irs.gov/pub/irs-drop/rp-16-55.pdf#page=7
- title: "2016 IRS data release, Rev. Proc. 2015-53 section 3.01 #page 5-7"
href: https://www.irs.gov/pub/irs-drop/rp-15-53.pdf#page=5
- title: "2015 IRS data release, Rev. Proc. 2014-61 section 3.01 #page 5-6"
href: https://www.irs.gov/pub/irs-drop/rp-14-61.pdf#page=5P3-4. The hand-off's list of states that read federal tax misses two, and the PR body has no listThe brief asked for this list. I grepped
Suggested body line: "States that read a federal tax variable, and so can move in 2015-2017: AL, CA, IA, LA, MO, MT, NE, NM, NY, OK, OR, WV." P3-5. One mutant count in the hand-off is wrongThe hand-off says M2 fails 2 YAML cases. Rerun at this head, it fails 1 (31 pass). Surviving spouse has its own Checked and soundThe defect reproduces on main ( The rule. I wrote my own seven-rate calculator from the 90 thresholds and seven rates, which the hub compared against the Rev. Procs, before reading the test comments. It matches all 25 pre-2018 YAML expectations to the cent (0 mismatches). The code ( Uprating. I dumped all 7 rates and 35 thresholds at 2015-2019, 2025, 2026, 2030 and 2036, at main and at this head. 105 of 378 values differ, all in 2015-2017. The values for 2018 and later are identical (e.g. 2036 single bracket 1 15,225, 2036 joint bracket 6 944,150 on both sides). Fixed-date readers.
I found no prior-year reads of federal tax variables. Impact. Nothing other than 2015-2017 periods can move, so impact is 2017 witnesses (TX, main → head; standard deduction 12,000/24,000 on both):
The law values use my hand calculation with the 2017 standard deduction (6,350 single, 12,700 joint) and the 2017 exemption of 4,050 per person, which main already has. Results for 2015 move the same way: single +749.25, couple +1,498.50. The partly fixed year isn't made worse. At this head the tax is the pre-TCJA schedule applied to the model's taxable income. That income is at or below the true figure, because the model's standard deduction (the 2018 value) is larger, and the exemption and the uncapped pre-2018 SALT are already pre-TCJA. So the head's tax lies between main's and the law's wherever the 2018 schedule taxes less. Where the 2018 schedule taxes more (single taxable income of about $385k-$432k, as in the itemizer row), the head is lower and exact for itemizers. In every case I built, the head is closer to the law than main. For a typical filer the head still understates the 2017 tax by about 1,012.50 (single, $50k) and 2,025 (couple, $100k), against 1,755.25 and 3,510.50 on main. Landing order with #10595. #10595 adds only 2017 standard deduction values (6,350 / 12,700 / 9,350), which I confirmed in its diff. The two PRs touch different files, and each moves 2017 toward the law alone, so either order works. Capital gains. AMT. The AMT exemption has 2014-2017 values ( Tax-limited credits. In the two-child witness, CTC (2,000) isn't limited on either side. Tests run at this head, one file per process:
Existing 2015-2017 tests. I count 130 YAML files with a 2015-2017 period (496 such periods, 471 excluding the 25 new ones). That matches the hand-off. I sampled 12 tax-heavy files and all pass at this head: AR 2014-2020 (116), MT elderly credit (50), refundable CTC (15), itemized reduction (14), MS joint (12), IN refundable (9), AMT income less exemptions (8), MO itemized (8), itemized limitation (7), VA itemized (6), Medicare 2015-2016 integration (2), NC credit order (1). Python tests also pass: Mutants, each in its own worktree and process. All six are killed:
I removed the worktrees afterward, and Partner tests. The periods under Scope and overlap. The PR changes 4 files. Searching open PRs for Body. The CI at this head, Pull request run 41553424628, checked while it was still running. Succeeded: Quick Feedback, PR validation, Package and compatibility, Household API Partners, all 7 Rest Python shards, and all 11 Baseline shards (irs, reform, states 1-4, household, ssa-usda, rest-a, rest-b, contrib-hhs). In progress: Microsimulation and the 9 Contrib shards. The hub should confirm their conclusions. What I couldn't check
Generated by Claude Code |
Three comments said the amount taxed below 25 percent ends at the top of the 24 percent bracket; before 2018 it ends at the top of the 15 percent bracket. A 2017 case now pins that amount, a test checks that each threshold and rate exceeds the one before (the monotone-tax property can't catch a threshold typo), and the three Rev. Proc. titles give their page ranges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
US + core hub merge audit, us#10637 at
|
Adds the 2015-2017 federal income tax rates and bracket thresholds.
gov.irs.income.bracketstarted in 2018.backdate_parameters(policyengine_us/tools/parameters.py) copies each parameter's earliest value back toFIRST_MODELED_YEAR(2015), so 2015-2017 ran on the 2018 TCJA schedule. For example, a 2017 single filer with $43,950 of taxable income got $5,608.50 at the main rates; under the 2017 schedule it's $6,726.25.Changes
parameters/gov/irs/income/bracket.yaml: pre-TCJA rates from 2015-01-01 (10/15/25/28/33/35/39.6%) and the 2015, 2016 and 2017 thresholds for each filing status, from Rev. Procs. 2014-61, 2015-53 and 2016-55 (section 3.01). All 90 thresholds (six brackets, five statuses, three years) and the seven rates match those tables as printed. 2015 is the earliest year becauseFIRST_MODELED_YEARis 2015, so parameters have no values before it.income_tax_main_rates.yaml: 25 hand-computed cases. Every filing status at $100k and $500k for 2017 and 2015, and at $500k for 2016. Each comment shows the amount and tax in every bracket.test_main_rate_schedule_monotone.py: a property test that the main-rates tax never falls as taxable income rises, and rises by at most the top rate per dollar. It runs every filing status, 2015-2030, on a $250 grid plus ±$1 around each threshold, and also pins the pre-2018 and 2018 rate lists. It reads parameters from the package's shared system and callstax_at_main_ratesdirectly, so it builds no simulation: 81 cases in about 7 s after the import. A second test checks that each threshold and each rate exceeds the one before:amounts_in_bracketsclamps each bracket to the one below, so a threshold typo leaves the tax monotone and only this test catches it.taxable_income_taxed_below_25_percent,dwks19andamt_tax_including_cg: the comments put the end of the amount taxed below 25 percent at the top of the 15 percent bracket before 2018 and of the 24 percent bracket from 2018. A 2017 case intaxable_income_taxed_below_25_percent.yamlpins it: $37,950 for a single filer with $43,950 of taxable income.Not in this PR
The basic standard deduction still starts in 2018 on
main, so a 2017 run uses $12,000 for a single filer, not $6,350. End-to-end pre-2018income_taxstays wrong until that's fixed. #10595 adds a 2017 value only, which backdating would then copy to 2015 and 2016 ($6,300).programs.yamlkeepsverified_start_year: 2018.Twelve states read a federal tax variable, so their 2015-2017 results can move too: AL, CA, IA, LA, MO, MT, NE, NM, NY, OK, OR and WV.
impact: none from 2018 on. The PR changes only 2015-2017 values: every rate and threshold for 2018, 2019, 2025, 2026, 2030 and 2036 matches main.
axiom: TheAxiomFoundation/rulespec-us#2016 queued
🤖 Generated with Claude Code