Repository navigation
Use the married-filing-separately breakpoint on Form 6251 Part III line 18 - #9780
Conversation
Line 18 applied the 26%/28% AMT scale to line 17 with the full breakpoint ($239,100 in 2025) for every filing status. 26 U.S.C. 55(b)(1)(C) halves the dollar amount for a married individual filing a separate return, and 55(b)(3)(A) figures line 18 "at the rates and in the same manner" as 55(b)(1)(A); the 2025 Form 6251 line 18 reads "$239,100 or less ($119,550 or less if married filing separately) ... subtract $4,782 ($2,391 if married filing separately)". Lines 7 and 39 (amt_lower_base_tax and amt_higher_base_tax) already used the multiplier. The scale is now applied with the filing-status multiplier as its threshold factor. Filers other than married filing separately have a multiplier of 1, which is the scale's default factor, so their line 18 is unchanged bit for bit. Tests: YAML cases with Form 6251 line comments (2025 and 2026, both sides of the separate breakpoint, unrecaptured section 1250 gain, a full-chain household), and Hypothesis property and differential tests against a transcription of 2025 Form 6251 lines 12 to 40. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new test file skips itself through pytest.importorskip when hypothesis is missing, and main's dev extras did not include it. Same line as #9761; locked with uv 0.12.13, the version CI uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9780 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 4 1 -3
Lines 76 42 -34
Branches 2 0 -2
=========================================
- Hits 76 42 -34
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Building the whole parameter tree at each of the 23 years made the test take 195 s in CI; the gov.irs.income.amt subtree takes well under a second. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reuse the shared tax-benefit system instead of building a second one, compare the bitwise line 18 check over the same array, derandomize the Hypothesis tests so unrelated pull requests draw the same examples, and ignore the .hypothesis/ example database. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…8-separate # Conflicts: # policyengine_us/tests/policy/baseline/gov/irs/tax/federal_income/alternative_minimum_tax/amt_tax_including_cg.yaml # policyengine_us/tests/policy/baseline/gov/irs/tax/federal_income/alternative_minimum_tax/base_tax/amt_base_tax.yaml
The worksheet comparison skipped the AMT of married-filing-separately filers with dividends or gains because Form 6251 line 18 used the full breakpoint. With line 18 fixed, those filers match the transcribed Form 6251 too, so the skip and its docstring note go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Derandomizing the Part III tests meant the properties test no longer drew the 2018 separate filer $5 above the $95,550 breakpoint that caught the full-breakpoint line 18. Pin it as an explicit example (it fails on the pre-fix formula). The 911(f) worksheet tests now compare separate filers' AMT too, so give them fixed examples as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hand-off to the US + core hub. Head Reviews (all independent Subfleet runs, all APPROVE, no blocking findings; files in
Passed locally at the current head:
Pending: CI on 2cd124f (run 37334027809, queued). The earlier "Full Suite - Rest" failure predates main's #9782/#9787 fixes. Depends on: nothing. #9761 is merged and its carve-out is removed here. #9781 (Form 4952 election, Schedule D lines 3–10) also touches the dwks/Part III inputs, so whichever lands second may need a re-merge. Hold / decision: none. This is a bug fix to the law, and its aggregate impact (+$21.9M in 2025, +$22.6M in 2026, from one record) is stated in the PR body. Follow-ups owed (not merge conditions):
|
|
Landed by the US + core hub, after session 37df6349 handed it off, at head Gates:
#9792 is stacked on this PR; the hub merges main into it next. Follow-ups noted in the hand-off: rulespec-us#1487, and chip task_f9763819 (line 27, which #9792 addresses). |
…nded #9780 (which this branch was stacked on) squash-merged, so this merge takes main and re-applies only this PR's own changes (2cd124f..eb4e9c0). Test-file conflicts with the squashed #9780 are resolved by taking main plus this PR's diff, which applies with identical +/- lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What was wrong
amt_tax_including_cg(Form 6251 Part III, line 38) computed line 18 asp.income.amt.brackets.calc(excess_income), so the 28% rate started at the full breakpoint ($239,100 in 2025) for every filing status. For a married individual filing a separate return it starts at half that.Line 39 (
amt_lower_base_tax+amt_higher_base_tax) already usedp.multiplier[filing_status].alternative_minimum_taxtakesmin(amt_base_tax, amt_tax_including_cg)when Part III is required, so for a separate filer with qualified dividends or long-term gain and line 17 above the separate breakpoint, the AMT came out too low. The shortfall was 2% of the part of line 17 between the two breakpoints, up to $2,391 in 2025. Example: a 2025 separate filer with $104,000 of wages, $25,000 of qualified dividends and $900,000 of long-term gain has line 17 = $136,162.50; line 18 should be $35,734.50, but the model gave $35,402.25, which is $332.25 short.Fix
MarginalRateTaxScale.calc(tax_base, factor)multiplies the thresholds byfactor. The default is1.0, and every status exceptSEPARATEhas a multiplier of exactly1.0, so for them the computation is the same floating-point operations as before, bit for bit.Invariants
These hold for every input:
amt_tax_including_cgequalsamt_base_tax(within float32 rounding) for every filing status and year.calc(x, factor=multiplier)is bitwise equal tocalc(x).(28% − 26%) × clip(line 17 − T/2, 0, T/2), where T is the full breakpoint.Tests
amt_tax_including_cg.yaml,base_tax/amt_base_tax.yaml,integration.yaml), with Form 6251 line-by-line comments:amt_base_taxcase (line 39 already used the separate breakpoint).policyengine_us/tests/test_form_6251_part_iii.py(Hypothesis):amt_tax_including_cg,amt_base_taxandalternative_minimum_taxfor all five filing statuses (grid and random households). Line 13 comes from the household's own dividends and gains, not from the model's worksheet variables;The Hypothesis file has 5 tests. Three are property or differential tests over generated inputs; two are deterministic (the grid and the $136,162.50 household). All Hypothesis tests run with
derandomize=True, so CI on an unrelated PR always draws the same examples. On the pre-fix formula, the three differential tests fail (checked at d9cb630). Before derandomizing, the properties test also caught the bug there, by shrinking to the 2018 example above. That case is now pinned as an@hypothesis.example, so the test still catches the bug with fixed draws; I re-checked that it fails on the pre-fix formula. The 911(f) file's Hypothesis tests are derandomized too, now that they compare separate filers.Main already has
hypothesis>=6.100.0in thedevextras, so CI runs the new file. This PR no longer changespyproject.tomloruv.lock.Microsim
Default dataset (
populace_us_2024.h5@populace-us-2024-spm-20260909), policyengine-core 3.32.12. Upstream/main 6b0e773 and this branch were each run in full, one at a time, under a 24 GB watchdog (peak footprint 14.5 GB). I compared 15 outputs per tax unit (Part I–III amounts, the worksheet inputs, regular tax,alternative_minimum_tax,income_tax_before_credits,income_tax,state_income_tax) plushousehold_net_income.amt_tax_including_cgchangesalternative_minimum_taxandincome_taxhousehold_net_incomestate_income_taxFor 12 of the 13 Part III units, the regular tax is above line 40 both before and after the fix, so their AMT stays $0. In the remaining unit, line 17 is above the full breakpoint, so its AMT rises by the maximum amount (2% of the separate breakpoint). That single record carries the whole aggregate, so read the dollar total as one sampled record, not a precise population estimate.
Section 911(f) carve-out removed
#9761 (merged as 94c4453) made
test_section_911_tax_stacking.pyskip the AMT comparison for separate filers with dividends or gains, because of this bug. This PR merges main and removes that skip and its docstring note (7c90a98), so separate filers are now compared with the transcribed Form 6251 too. Before main was merged, I checked this on a local merge of #9761's head e962c91: with the skip removed,test_grid_matches_the_2025_worksheetsfails without this fix and passes with it.After the merge, at 7c90a98:
test_form_6251_part_iii.pypasses, 5/5;test_section_911_tax_stacking.pypasses, 10/10.axiom: TheAxiomFoundation/rulespec-us#1487 queued (us/statutes/26/55 takes the 55(b)(3) amount as a completed input
amt_tax_including_capital_gains, so line 18 is not encoded; its Part II breakpoint,amt_twenty_eight_percent_threshold, is already $122,250 for separate filers)🤖 Generated with Claude Code