Repository navigation
Count dependents' medical bills in Montana's separate-filing medical deduction - #10276
Conversation
Montana's 2021-2023 separate-filing medical deduction read each spouse's own other_medical_expenses, so a dependent's bills (recorded on the person cared for) reached neither spouse. Add mt_medical_and_dental_expenses_indiv, Itemized Deductions Schedule line 1a in each column: the spouse's own expenses plus an equal share of the dependents' (spouse_allocation_rate, the model's convention for itemized deductions not clearly attributable to one spouse; ARM 42.15.524(3)(b) and the Form 2 instructions let the couple divide them in any proportion). An unmarried filer's column holds all of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
YAML cases fail on main: a couple with a child's expenses under both head/spouse labellings, a head of household, and the 2024 reform branch. The invariants test states the line 1a closed form and conservation (the head's and spouse's columns sum to every member's other medical expenses), uses it in the separate-variant accounting identity, and adds a head-of-household differential against the joint variant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document that line 1a divides every member other than the head and spouse (so the columns hold every member's expenses, as the joint line 1 does) and test it with a member who is neither spouse nor tax unit dependent. Cite the 2022 instructions' pages 11 and 32-33, state ARM 42.15.524(3)'s federal-return precondition, and name the charitable split the equal division follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hand-off to the US + core hub (from session local_bb242a7d, 2026-10-19)
|
PR Review: Count dependents' medical bills in Montana's separate-filing medical deductionSummaryThe PR adds
Critical (must fix)None. Should address1. Known tax increase for some status 2a couples in claimed years; fix how the itemize election is chosen, or track it before merge. Files: What's wrong. Moving a dependent's bills into the columns can tip the summed comparison toward itemizing even when the couple's tax goes up. The PR body's TY2022 witness: head wages $60,000, spouse $15,000, a child with $10,250 of other medical expenses.
The hand arithmetic agrees. The head's column gives up 1,680.20 of deductions at the 6.75% top rate (about +$113). The spouse's column gains 1,696.20 in the 3–4% brackets (about −$55). The net is about +$58, against the body's $58.62. Why it's wrong under the law. Montana lets the couple choose the standard deduction. The 2022 Form 2 instructions say "Spouses must either both claim the standard deduction, or both itemize their deductions" (PDF p. 11). MCA 2021 § 15-30-2132(1) says the standard deduction "is allowed if elected by the taxpayer." So this couple would owe at most $2,929.68 under the standard deduction, or $2,789.30 with all of the child's bills in the head's column, which the "any proportional amount" rule allows. The model elsewhere assumes the couple makes the cheaper choice:
Why this is not critical.
Even so, merging as is knowingly makes some modeled TY2021–2023 liabilities worse. Fix (preferred). Make Fallback, if the hub prefers to merge first.
Suggestions
Verified correct
Axiom line assessmentThe line is
The line is valid. CI statusAll 27 checks pass at I did not rerun anything locally. CI covers the PR's tests at this head, and the witness figures above are the PR's own, checked against hand arithmetic. Branch statusThe branch is 4 commits ahead of main and 34 behind. Verdict: REQUEST_CHANGES |
DTrim99
left a comment
There was a problem hiding this comment.
The law checks out: line 1 includes dependents under IRC 213, the floor is on each column's own MT AGI, and an equal split is a legal joint-account allocation. One should-address item: the equal split plus the summed test in mt_tax_unit_itemizes makes some couples itemize at a higher tax than the standard deduction the law lets them take (e.g. $2,929.68 -> $2,988.30). Choose itemizing by comparing tax, here or in a stacked PR, or file a tracked issue. Four suggestions. Full report in the comment above.
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The split of dependents' medical bills and the itemize election need the couple's tax under other deductions, without simulation branches. Moving the column formulas (dependent exemption allocation, rate schedule, capital gains tax, medical deduction) into tools/mt_separate_return.py lets the variables and those choices run the same code. No change in results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under status 2a the couple may divide jointly paid bills in any proportion. mt_dependent_medical_expense_head_share evaluates the couple's line 20 in closed form at the equal split, all to either spouse, and the two shares where a column's floor starts to bind, and takes the lowest, with ties going to the equal split. Every dependent bill is treated as jointly paid, since the model has no payer data. The non-medical itemized deductions become their own variable so the choice can hold them fixed. Property tests check the chosen tax against every candidate, the standard deduction and the old result, label swaps, and more bills. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spouses filing on the same form must both itemize or both take the standard deduction, which the taxpayer may elect, so the election now compares the couple's combined line 20 under each, instead of summed deductions; ties keep the old rule. The joint path takes the larger deduction and is unchanged. One existing case flips: itemizing lowers that couple's tax although its summed itemized deductions are smaller. A downstream mt_income_tax case pins the flow through the itemized deductions and the election. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the same form the rule defers to the form's any-proportion allocation, so 0.5 is a convention (the charitable split and the medical tie-break). The ARM reference now points at archived text, and Pub. 502 anchors the equal split of jointly paid bills. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The split into non-medical and medical parts dropped the head-or-spouse mask on the medical part, so a medical deduction supplied for a dependent reached that dependent's itemized deductions. Restores the mask. Also updates the childcare boundary case: its summed deductions tie, but itemizing lowers the couple's tax, so the election now itemizes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review Head: What was asked and how it's resolved1. Tax-minimizing split in closed form, no branches (
2. Every dependent bill treated as jointly paid. Documented at 3. Tests expect the minimizing allocation; witness under the final code. TY2022, head wages $60,000, spouse $15,000, child's bills $10,250, computed with the model at this head:
No differences. Swapping the labels gives $2,789.30 too. The chosen share is 1.0 and the couple itemizes. Updated YAML in
The zero-income cases in 4. Invariants as Hypothesis property tests.
The requested "deduction monotone non-decreasing in the bills" is stated and tested as (5), on the tax. Each candidate gives each column at least as much line 1a when the bills grow, so the tax form holds by construction. A monotone summed deduction isn't guaranteed and isn't tested; I didn't look for a counterexample. 2021 uses line 20, not 5. Body readings. The body has a Readings table (the candidate-set split per the ruling, with the full search logged as an optimization; the jointly paid assumption; the election). The DTrim99 Should 1: itemize election by tax (
Suggestion 2: Suggestion 3: downstream case (
Fix after the first push ( Suggestion 4: body housekeeping. Local paths, queue ids and fleet terms are removed. The follow-up is now in this PR. Not in this PR: splitting the other jointly paid itemized deductions to minimize tax. The body logs it as a later optimization, along with the full search over medical splits. The body has a Round 2 table and the witness under the final code. Tests (one file per process, at the head's tree)
Mutants (each in its own process, restored after)
CI
Judgment calls
Follow-ups to propose
Generated by Claude Code |
|
Hub review in progress Superseded by the final review: #10276 (comment) Generated by Claude Code |
|
Ready for hub review The head hasn't moved since the hand-off. This is a delta review of The code does what it says:
Two things need changing before merge:
P2-3 is a missing FindingsP2-1. The candidate set misses the minimum where the columns' incomes can be balancedWhere: Why the set misses it. Let s be the head's share.
Witness (TY2022), with Montana AGI as input so the federal return doesn't enter: head $25,000, spouse $15,000, other itemized deductions 0, one child with $25,000 of other medical expenses.
Fix: add the balancing share as a candidate. On the segment between the kinks, the head's column income is # The share at which the two columns' incomes before the dependent
# exemptions are equal: between the floor kinks the total deduction is
# fixed and the couple's tax is convex in the share, lowest where the
# columns balance.
kink_deduction = mt_medical_deduction(
person, period, parameters, own_expense + room
)
level = (
person("mt_agi_indiv", period)
- person("mt_personal_exemptions_indiv", period)
- other_itemized
- kink_deduction
+ room
)
gap = tax_unit.sum(level * head) - tax_unit.sum(level * spouse)
balance = np.clip(0.5 + gap / (2 * safe_pool), 0, 1)Then add What the fix does:
Also do these:
The tests don't detect the gap:
P2-2. TY2021: the couple's post-rebate tax can rise against main, and the body doesn't say soWhere: the body's Impact, Sweep and Invariants sections. The mechanism is in What happens. This PR lowers the separate path's line 20. That moves some couples from joint to separate, where a low-income spouse's column has little line 20 for the rebate to refund. Witness (TY2021): head wages $60,000, spouse $5,000, child's bills $8,000.
On a 240-couple TY2021 grid against main (heads $5k–$60k, spouses $0–$30k, bills $0–$25k), 10 couples pay more, by up to $1,228.45, and 12 pay less. On the same grid in TY2022 and TY2023, 0 pay more: 75 and 68 pay less, by up to $754.38 and $807.88. This agrees with the body for those years. Whether line 20 is the right objective in 2021 is a methodology call (see the table below), so this finding is about disclosure:
The line-20 choice in P2-3. The body has no
|
| Mutant | Property file (4 tests) | YAML (deduction, tax unit itemizes, income tax) |
|---|---|---|
| M1: the share is always 0.5 (hand-off) | 3 fail: test_seeded_population[2021/2022/2023]; the Hypothesis test passes |
not run |
| M2: the election by summed deductions (hand-off) | 3 fail, the same three; the Hypothesis test passes | not run |
| M3 (mine): drop both kink candidates | 4 pass | 13, 4, 3 pass. Survives. |
M4 (mine): the closed form ignores mt_non_refundable_credits |
4 pass | 13, 4, 3 pass. Survives. |
M4 matters. The hand-off says "the property tests confirm it against forced inputs". They don't compare the closed form with the columns, and they never generate capital gains, so the 2021–2023 capital gain credit is always 0.
- I checked equality directly: with employment income and long-term capital gains, over 60 random couples per year, |closed form − Σ
mt_income_tax_before_refundable_credits_indiv| is at most $0.0008. The credit is non-zero in 47, 43 and 43 units. - So the code is right today, but nothing guards it.
- Fix: add one test asserting
mt_separate_tax_before_refundable_credits(tax_unit, period, parameters, mt_deductions_indiv)equals the summed column line 20, on couples with capital gains. Raise Hypothesismax_examples, or drop the Hypothesis test, since the seeded tests do all the killing.
P3-2. spouse_allocation_rate states a convention as law
- Where:
policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/spouse_allocation_rate.yaml:1. - Problem: "Montana allocates this share …" contradicts the comment below it, which says the share is a modeling convention. The comment also omits two other readers:
mt_child_dependent_care_expense_deduction, where an equal split is statutory, andmt_itemized_deductions_for_federal_itemization_indiv. - Fix:
description: Montana itemized deductions paid jointly are split by this share between the spouses' columns when married couples file separately on the same form; this is a modeling convention, not a rate in law.Then add both readers to the comment.
P3-3. Two broken links in the body
- Where: the body's "Law" bullets.
- Problem:
([2022 instructions, PDF p. 32](``https://…#page=32)):``` and(```https://…#page=11);````. The stray backticks break both links. - Fix: remove the backticks so each reads
([2022 instructions, PDF p. 32](https://…#page=32)).
P3-4. Coordinate with open PRs that edit the moved code
- Where:
mt_itemized_deductions_indiv.py. - Problem: Cap Montana's 2021–2023 state and local tax deduction once per joint return and at $5,000 per separate column #10002 (per-column SALT cap) conflicts with this head in that file. Count a Montana casualty loss once per joint return and only for the spouse who had it #9888 (casualty loss) edits the same itemized path; it conflicts elsewhere with this head. Both predate the split into
mt_non_medical_itemized_deductions_indiv. - Whoever resolves them must move their changes into the new variable. Otherwise the split and the election compare a tax that leaves those changes out. Use the federal itemized total and deduction type for Montana from 2024 #10206 and Count only filer-paid premiums in Schedule A medical expenses #10270 also conflict with this head (
merge-treeexits 1), but not in the moved formula. - Command:
git merge-tree --write-tree pr-10276 pr-<n>exits 1 for all four. For Cap Montana's 2021–2023 state and local tax deduction once per joint return and at $5,000 per separate column #10002 the conflict isCONFLICT (content): Merge conflict in …/itemized/general/mt_itemized_deductions_indiv.py.
P3-5. Carried over from the earlier round
mt_files_separately's strict < lets float noise flag unmarried filers as separate. It's still at the head. The hand-off lists it as a follow-up; I couldn't confirm an issue exists.
Methodology choices
| Provision | PR pick | Alternative | Numbers |
|---|---|---|---|
| Status 2a line 1a split of dependents' bills (ARM 42.15.524(3)(b); the form allows "any proportional amount") | Minimize line 20 over {0.5, 0, 1, two kinks}; ties go to 0.5 | Exact minimum (add the balancing share, P2-1) | Witness $187.35 against $131.40; up to $55.95 on the low-income grid; 0 gap on the high-income grid |
| Objective in TY2021 | Line 20, rebate left out | Line 20 net of the 2021 rebate | $185.20 (main) against $1,413.65 (head) for one couple. The repo's own source for the rebate is Ch. 44, L. 2023, which supports line 20 as what couples actually minimized when they filed their 2021 returns. That's Max's call |
| Who paid a dependent's bill | All paid from a joint account | Payer attribution | No data; documented on the variable |
| Election | By couple's line 20; tie keeps summed-deduction rule | By summed deductions (main) | Flips two existing cases; YAML verified |
DTrim99's review, item by item
| Item | Status at head | Evidence |
|---|---|---|
| Should 1: itemize election by tax | Resolved | mt_tax_unit_itemizes.py:41-55 compares the couple's line 20, and a tie keeps the old rule. The joint path doesn't read the flag (its only readers are mt_deductions_indiv and the tool). The new YAML case (8,325 itemized > 8,090 standard, yet 2,973.66 > 2,929.675) passes. M2 is killed. The 2022 and 2023 grids show 0 increases against main; 2021 is under P2-2 |
| Suggestion 1: tax-minimizing split | Mostly done | Candidate set, see P2-1 |
Suggestion 2: spouse_allocation_rate metadata |
Done, with a wording slip | Archived ARM, 2022 instructions p. 11 and Pub. 502 are cited; see P3-2 |
Suggestion 3: downstream mt_income_tax case |
Done | I recomputed every figure by hand: 2,774.625 chosen; 2,903.80 all to the spouse; 2,973.662 equal split; 2,997.10 and 2,850.5625 at the kinks; 2,929.675 standard; joint 3,224.075 |
| Suggestion 4: body housekeeping | Done | No local paths or queue terms; the follow-ups are listed. Two links are broken (P3-3) |
Every should-address item is resolved. The hub can dismiss DTrim99's CHANGES_REQUESTED with a note pointing to 8accee23e7 and the table above.
Checked and sound
- The defect, at main (
6145ad4513), TY2022, head wages $60,000, spouse $15,000, child's bills $10,250:mt_medical_expense_deduction_indiv= [0, 0, 0] and the joint deduction is 4,625 (10,250 − 7.5% × 75,000);mt_income_tax= 2,929.675.
- The witness, at the head:
- chosen share 1.0; line 20 = 2,505.55 + 283.75 = $2,789.30;
- standard $2,929.68; equal split $2,988.30; all to the spouse $2,918.36. On 201 forced shares the worst is $3,011.36 at 0.44;
- I didn't rerun the label swap on the witness. Label invariance is property 4, which passes. A note for anyone re-running this: Core treats a formula variable supplied for some tax units as supplied for all of them, so run forced shares in a separate simulation from free ones.
- The refactor:
mt_taxable_income_indiv,mt_regular_income_tax_indiv,mt_capital_gains_tax_indiv,mt_capital_gains_tax_applicable_threshold_indivandmt_medical_expense_deduction_indivcall functions whose code is line-for-line main's. I diffed against merge base97b6d5135c. The only change is that line 1a replacesother_medical_expenses.- Closed form = columns to $0.0008, as under P3-1.
- No cycle, and the right objective for 2022–2023:
- Montana's non-refundable credits are only
mt_capital_gain_credit(2% of net gains, independent of deductions). - Its refundable credits (EITC, the elderly homeowner/renter credit, the 2021 rebate) don't depend on the deductions except through the rebate's line-20 cap.
- Montana's non-refundable credits are only
- The candidate kinks:
room / pooland1 − room / poolmatch the test's independent_kinks. - The tie-break: a tie within $0.01 keeps the share closest to
spouse_allocation_rate. - The tests, one process per file:
mt_childcare_itemization_boundary.yaml2 passed;mt_medical_and_dental_expenses_indiv.yaml4;mt_medical_expense_deduction_indiv.yaml13;mt_tax_unit_itemizes.yaml4;mt_income_tax.yaml3;test_mt_dependent_medical_split_invariants.py4 passed (105 s, peak 1.9 GB RSS);test_mt_medical_premium_deduction_invariants.py5 passed (98 s);- command:
python -m policyengine_core.scripts.policyengine_command test <file> -c policyengine_usandpytest -q -p no:cacheprovider <file>.
- Readers of the changed variables:
mt_dependent_medical_expense_head_share→mt_medical_and_dental_expenses_indiv→mt_medical_expense_deduction_indiv→mt_itemized_deductions_indivandmt_itemized_deductions_for_federal_itemization_indiv(which feedsstate_itemized_deductions);mt_tax_unit_itemizes→mt_deductions_indiv→ columns →mt_files_separately,mt_income_tax,mt_income_tax_before_refundable_credits_unit,mt_income_tax_before_2021_rebate/mt_income_tax_rebate;mt_itemized_deductions_indivis also listed in_generate_state_mfs_variables.py.- None is made worse in 2022–2023 on my grids. 2021 is under P2-2.
- Impact: baseline outputs move only for TY2021–2023 Montana tax units on the status 2a path. The variables that move are line 1a, the medical and itemized deductions, the election, column taxes,
mt_files_separately,mt_income_tax, the 2021 rebate, andstate_income_taxand net income downstream. That calls for a Montana microsim for TY2021, 2022 and 2023, split by year, counting increases separately in 2021. - Scope: nothing under
.github/ortests/policy/baseline/partners/. - CI: run 41416732622 at the head: 34 of 34 jobs succeeded.
What I couldn't check
-
Sources: this session's network refused every source host:
- revenuefiles.mt.gov, mca.legmt.gov and archive.legmt.gov;
- web.archive.org and law.cornell.edu;
- govinfo.gov, uscode.house.gov, ecfr.gov, leg.mt.gov and irs.gov.
So I didn't verify MCA 2021 § 15-30-2132(1), the 2021–2023 Form 2 instructions' quoted phrases and page numbers (pp. 10, 11, 12–13, 32), ARM 42.15.524 or Pub. 502. The new MCA 2132 URL uses the
bills/2022/mca/path, labelled MCA 2021, like the repo's existing § 2131 citation; I couldn't open it. -
Population impact: no microdata here.
-
Axiom: rulespec-us#1520 is outside this session's GitHub scope, so I couldn't check whether the queued extension still matches the tax-minimizing split and the election.
-
Follow-up issues: I couldn't confirm the issues the hand-off proposes exist.
-
The author's grid: the body's 1,452-couple sweep script isn't public. My grids are my own, so their counts don't match the body's.
Generated by Claude Code
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Between the two floor kinks the total medical deduction is fixed and the couple's tax is convex in the head's share, so its minimum is where the columns' incomes before the dependent exemptions balance. The candidate set missed that point and left some lower-income couples up to about $56 above the minimum. Adds the review's TY2022 witness (131.40) on mt_income_tax and a test that the closed form equals the columns' line 20 when the capital gain credit is non-zero. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The description said Montana allocates this share, which the comment below it contradicts. Name all four readers, including the dependent care deduction, where the equal split is the form's rule rather than a convention. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Property 1 compared the chosen tax only with the chooser's own candidates, so dropping a candidate that finds the minimum failed no test. Without the capital gain credit the minimum over all shares is at a candidate, so the chosen tax must also be at most the tax at shares 0.1 to 0.9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where a column is below its 7.5% floor, bills moved into it are wasted, so the tax is monotone there and a kink share can only tie all-to-one-spouse. Removing both kinks changed no tax or chosen share on a 270-couple grid and no test result, so the chooser evaluates four candidates instead of six. The Hypothesis test killed none of the mutants even at 30 examples, while the seeded and closed-form tests kill them all, so it is dropped. The hand computations in the YAML cases now note where the balancing share falls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review Round 3 of #10276, answering the round 2 review at Commits
Items1. Pick-up and merge. Pick-up comment posted. Main merged with a merge commit, no conflicts. All seven of this PR's test files were rerun after the merge (counts below). 2. P2-1, the balancing share (
3. P2-2, the 2021 objective. No code change. The body's Impact section has the review's TY2021 sentence verbatim. A methodology paragraph ("TY2021 objective", under Readings) says the 2021 choice minimizes line 20 and why: the rebate law is HB 192, Ch. 44, L. 2023, which I checked is the repo's source. It also notes that main's new 4. P3-1, guarding the closed form.
5. P3-2,
6. P3-3, broken links: partly blocked. The body file I sent had clean links. But both
Other URLs in the body are untouched. So I removed those two URLs from the body. It now cites "2022 Form 2 instructions, PDF p. 32 / p. 11" and points to 7. P3-4, overlaps. New section "Overlaps with open PRs" in the body:
I didn't touch those PRs. 8. Left for the hub. P2-3 ( 9. Mutants. Each ran in its own process across all seven test files. Each was restored after its run, and
Tests at
|
| File | Result |
|---|---|
mt_childcare_itemization_boundary.yaml |
2 passed |
mt_medical_and_dental_expenses_indiv.yaml |
4 passed |
mt_medical_expense_deduction_indiv.yaml |
13 passed (peak 5.3 GB) |
mt_tax_unit_itemizes.yaml |
4 passed |
mt_income_tax.yaml |
4 passed |
test_mt_dependent_medical_split_invariants.py |
6 passed |
test_mt_medical_premium_deduction_invariants.py |
5 passed |
Commands: uv run python -m policyengine_core.scripts.policyengine_command test <file> -c policyengine_us and uv run pytest -q -p no:cacheprovider <file>. No full suites or microsimulations were run.
CI
Pull request run 41746413835 at cad9f36902: completed, success, 36 of 36 jobs (finished by 01:31Z; this line was updated after the run finished).
Judgment calls
- Kinks dropped, not kept. The brief allowed either. The monotonicity argument plus the identical grid and test results favour four candidates over six.
- Hypothesis test dropped, not raised. At 30 examples it still missed MB and cost about 65 s.
- Property 1's interior grid (0.1–0.9) relies on the no-credit setting of the seeded population. The closed-form test, which has credits, checks equality, not optimality.
- No PR links for the two instruction pages (item 6).
- Dependent care split. Called "the law's rule … Form 2441-M line 7", not "statutory", because I couldn't read the MCA.
Proposed follow-ups
- Montana status 2a: minimize the dependent medical split where the capital gain credit binds. When a column's line 20 is held at zero by the credit, the minimum can sit between the floor kinks away from the balance point. A one-dimensional search on that segment, or a credit-aware balance point, would close it.
mt_files_separately: compare with a tolerance. Its strict<lets float noise flag unmarried filers as filing separately (round 2 review P3-5). A $0.01 tolerance, as inmt_dependent_medical_expense_head_share, would match.
Generated by Claude Code
|
Hub review in progress Superseded by the final review: #10276 (comment) Generated by Claude Code |
|
Ready for hub review The head hasn't moved since the hand-off. This is a delta review of The round 2 fixes hold:
I found no P1 or P2. Four P3s follow. The largest is the credit-bound exception the body already discloses: it moves line 20 by up to $60.30 on my grid, but final The FindingsP3-1. Where the capital gain credit holds a column at zero, the candidate set misses the minimum. A cheap exact fix existsWhere: Why the set misses it. Between the floor kinks, each column's line 20 is Witness (TY2022): head $10,000 wages and $10,000 of long-term gains, spouse $20,000 wages, one child with $25,000 of other medical expenses.
Grid, a script of my own:
Final tax. The joint path absorbs most of the gap in final
Fix, for a follow-up. Search the convex middle segment. I tested this as a scratch edit, since reverted. Insert it before # Between the floor kinks the couple's tax is convex in the share,
# also when a column's capital gain credit holds its line 20 at
# zero, so a ternary search there reaches the minimum.
head_room = tax_unit.sum(room * head)
spouse_room = tax_unit.sum(room * spouse)
low = np.clip(head_room / safe_pool, 0, 1)
high = np.maximum(np.clip(1 - spouse_room / safe_pool, 0, 1), low)
for _ in range(40):
third = (high - low) / 3
left, right = low + third, high - third
left_lower = couple_tax(left) < couple_tax(right)
high = where(left_lower, right, high)
low = where(left_lower, low, left)
searched = (low + high) / 2What the fix does:
Property 1 can't detect this: its seeded population has no capital gains, as its docstring says. Follow-up title: "Montana status 2a: reach the minimum when the capital gain credit holds a column at zero". Use the witness above and the fix code. P3-2. The balancing share's premium term is untested (mutant M6 survives)
P3-3. The body's overlap section is out of date
P3-4. The body still has no
|
| Provision | PR pick | Alternative | Numbers |
|---|---|---|---|
| Status 2a line 1a split of dependents' bills (the form allows "any proportional amount", per the body's quotes; I couldn't fetch the source) | Lowest line 20 over {0.5, 0, 1, balancing share}; ties go to 0.5 | Add a search on the middle segment (P3-1) | Line 20 up to $60.30 above the minimum; final tax up to $14.10 (4 of 432 couples) |
| Objective in TY2021 | Line 20, rebate left out. The reason: couples filed their 2021 returns before the rebate law (Ch. 44, L. 2023, the repo's cited source) | Line 20 net of the rebate | $185.20 at main against $1,413.65 at the head for one couple; disclosed in Impact |
| Who paid a dependent's bill | All paid from a joint account | Payer attribution | No data; documented on the variable |
| Itemize election | By the couple's line 20; a tie keeps the summed-deduction rule | Summed deductions (main) | Unchanged since round 2 |
Checked and sound
-
Head and range. The head is
cad9f36902and the PR is open and not a draft. The PR's own commits after the merge are9e2bc13d17,dca8b29047,c6cdfe2df7andcad9f36902.git diff bda0215efd^1 bda0215efd -- <PR files>is empty. -
The balancing share, derived by hand from
mt_separate_return.py:- 2021–2023.
room = 0.075·AGI − own, somt_medical_deduction(own + room)is the premiums alone, andlevel = AGI − exemptions − other − premiums + 0.075·AGI − own. Above its floor, a column's deduction isown + c·pool − 0.075·AGI + premiums. So its income before the dependent exemptions islevel − c·pool, with c = s for the head and 1 − s for the spouse. Setting the two equal givess = 0.5 + (level_h − level_s) / (2·pool). That is the code. - 2024 branch.
roomalso subtracts the premiums, the kink deduction ismax(0, premiums + own + room − floor) = 0, and the same identity follows.
- 2021–2023.
-
A column at or below its floor. On
[0, head_kink]the head's deduction is fixed, and the spouse's falls as s rises. Each column's tax is non-decreasing in its income, and so is the exemption allocation. So the couple's tax is non-decreasing there, and s = 0 weakly beatshead_kink. The same holds for s = 1 on[spouse_kink, 1]. If the computed balance falls outside the middle segment, the middle minimum is at a kink, which 0 or 1 dominates. So an out-of-range balance is a harmless candidate. If the kinks cross (room_h + room_s > pool), the middle segment has both columns below their floors and the tax is flat there. -
Pool = 0.
safe_poolavoids a division by zero, and:121returns the equal split, which has no effect. -
Dropping the kinks. The argument above holds. On my grid, couples without gains or with gains in both columns sit at $0.00 against 101 forced shares in every year.
-
2024+ reform check. I ran the TY2024 path under a reform that sets
married_filing_separately_on_same_return_allowedto true. On 24 couples, gains included, the gap is $0.00. The standard deduction wins for most of them, so this check is weak. -
The witness case (
mt_income_tax.yaml:107-160), recomputed in a script from the 2022 separate schedule, independent of the model:- share 0.715; 6,290 in each column before the child's exemption and 4,935 after; 65.70 per column; 131.40 in total;
- equal split 178.20 + 9.15 = 187.35; all to the head 203.20; all to the spouse 720.80;
- standard deduction 429 + 191.60 = 620.60; joint return 214.80.
The model gives the same at the head. On a 1,001-point grid the minimum is 131.40, flat for shares 0.627 to 0.803.
-
The closed-form guard (
test_mt_dependent_medical_split_invariants.py:233-297):- It uses
assert_allclose(closed_form, columns, atol=0.01). - It requires
mt_non_refundable_credits > 0in at least half the units. - Its couples have wages and long-term gains.
- M4 fails it in all three years at line 297, and only there.
- It uses
-
Mutants, each a scratch edit in a separate worktree, one process per file, restored afterwards.
git statusis clean in both trees.Mutant Killed by MB: drop balancemt_income_tax.yaml(1 of 4 fails, the witness);test_seeded_population[2022],[2023]. The other four YAML files and the premium property file passM4: closed form ignores mt_non_refundable_creditstest_closed_form_matches_columns[2021/2022/2023]only; all five YAML files and the premium property file passM5 (mine): sign flip, 0.5 - gap / (2 * safe_pool)mt_income_tax.yaml(1 fails);test_seeded_population[2022],[2023](line 193)M6 (mine): drop - kink_deductionSurvives; see P3-2 -
The defect, at main (
25c4253483), TY2022: head wages $60,000, spouse $15,000, child's bills $10,250.- At main,
mt_medical_expense_deduction_indiv= [0, 0, 0] andmt_income_tax= 2,929.68. - At the head, the share is 1.0, line 20 is [2,505.55, 283.75] and
mt_income_taxis 2,789.30. - The balancing witness gives 214.80 (joint) at main and 131.40 at the head.
- At main,
-
The 2021 objective.
mt_income_tax_rebateon main documents that "the separate-versus-joint filing election is determined before this rebate by assumption". Its reference is HB 192, Ch. 44, L. 2023, so the body's reasoning matches the repo's source.mt_separate_tax_before_refundable_creditsleaves the rebate out, as its docstring says.- The body's Impact section carries the TY2021 disclosure word for word. My rerun of its witness gives $185.20 at main and $1,413.65 at the head.
-
spouse_allocation_rate:- The description is the round 2 text.
- The comment names all four readers, which I confirmed by grep:
mt_non_medical_itemized_deductions_indiv:31,mt_itemized_deductions_for_federal_itemization_indiv:31,mt_dependent_medical_expense_head_share:56andmt_child_dependent_care_expense_deduction:46.
-
Readers. The delta adds none.
mt_dependent_medical_expense_head_shareis read bymt_medical_and_dental_expenses_indiv, and through the tool bymt_tax_unit_itemizes.mt_non_medical_itemized_deductions_indivis read bymt_itemized_deductions_indivand the share.mt_tax_unit_itemizesis read only bymt_deductions_indiv. The round 2 reader list stands. -
Body statements checked against the code:
- the four candidates;
- the dropped kinks and the Hypothesis test;
- the 40-couple seeded population and the 30-couple guard;
- property 1's interior shares 0.1–0.9;
- the "Credit-bound columns" caveat, which my grid confirms: every gap is in a one-sided-gains couple;
- the TY2021 objective paragraph.
The two instruction links are still absent; the hub restores them.
-
Tests at the head, one process each:
File Result Wall Peak RSS mt_childcare_itemization_boundary.yaml2 passed 129 s 2.21 GB mt_medical_and_dental_expenses_indiv.yaml4 passed 175 s 3.13 GB mt_medical_expense_deduction_indiv.yaml13 passed 252 s 5.43 GB mt_tax_unit_itemizes.yaml4 passed 132 s 2.53 GB mt_income_tax.yaml4 passed 131 s 2.54 GB test_mt_dependent_medical_split_invariants.py6 passed (32.5 s pytest) 109 s 1.87 GB test_mt_medical_premium_deduction_invariants.py5 passed (39.4 s pytest) 113 s 1.84 GB Wall times are inflated because a grid ran alongside. The premium file needs
hypothesis, a dev dependency thatuv syncleft out here, so I installed it before rerunning. -
CI: Pull request run 41746413835 at
cad9f36902: completed, success, 36 of 36 jobs. -
Scope: nothing under
.github/orpolicyengine_us/tests/policy/baseline/partners/. The changelog fragment is a top-levelchangelog.d/fix-mt-separate-medical-dependents.fixed.md. -
Open PRs on the changed files (search API): Count a Montana casualty loss once per joint return and only for the spouse who had it #9888, Cap Montana's 2021–2023 state and local tax deduction once per joint return and at $5,000 per separate column #10002, Use the federal itemized total and deduction type for Montana from 2024 #10206, Count only filer-paid premiums in Schedule A medical expenses #10270 and Count dependents' other medical expenses in Idaho's claimant medical worksheet #10278. See P3-3.
What I couldn't check
- Sources. This session's network refused leg.mt.gov, archive.legmt.gov, mca.legmt.gov, revenuefiles.mt.gov, revenue.mt.gov, web.archive.org, govinfo.gov, uscode.house.gov, ecfr.gov and rules.mt.gov. So I didn't verify:
- HB 192 / Ch. 44, L. 2023 and its enactment date, which the 2021 reasoning rests on;
- MCA 2021 § 15-30-2132(1);
- the 2021–2023 Form 2 instructions' quoted phrases and page numbers;
- ARM 42.15.524;
- IRS Pub. 502's joint-account statement in the
spouse_allocation_ratecomment.
- Population impact: no microdata here. See P3-4.
- Axiom: rulespec-us#1520 is outside this session's GitHub scope.
- The author's grids: the body's 1,452-couple sweep and 270-couple grid scripts aren't public. My grids are my own.
- Mutant runs: the first run hit the session's 30-minute background limit after M4's fourth YAML file. I reran the rest of M4 (the income tax YAML and both property files), plus M5 and M6, on those three files only. M5 and M6 didn't run against the other four YAML files.
Generated by Claude Code
No case had medical insurance premiums, so dropping the kink deduction (the 2021-2023 premiums) from each column's level passed every test. With 3,000 of premiums on the head of the balancing witness, the share moves from 0.715 to 0.655, which this case asserts with both columns' line 1a and line 20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpcqEDJ2tmDgRbDecMcN7j
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
|
Ready for hub review Head:
|
| # | Item | Resolution |
|---|---|---|
| 12 | P3-2: premium term untested | New case "The balancing share counts the head's medical insurance premiums", mt_income_tax.yaml:162-203. It is the balancing witness plus mt_eligible_medical_insurance_premiums: 3_000 on the head. It asserts share 0.655, mt_medical_and_dental_expenses_indiv [16_375, 8_625, 0] and mt_income_tax_before_refundable_credits_indiv [35.70, 35.70, 0], with the review's arithmetic in comments. It passes at the head, and M6 fails it (below). |
| 13 | P3-3: overlap bullet | I reran git merge-tree --write-tree --name-only (below); the review's result holds, so the bullet is now the hub's sentence. |
| 14 | Runs | All pass; see below. |
The case's margin. I used absolute_error_margin: 0.01 rather than the balancing case's 0.05, so the share assertion separates shares 0.01 apart.
Body. Besides the overlap bullet, I added a line to "Review" and a "Round 4" table. The table records P3-1 as a follow-up and P3-4 (impact:) as left for the hub. The impact: line is still absent, as the brief says.
Hand computation (TY2022, 2022 separate schedule)
| Step | Head column | Spouse column |
|---|---|---|
| Level | 25,000 − 2,710 − 3,000 + 1,875 = 21,165 | 13,415 |
| Line 1a | 16,375 | 8,625 |
| Deduction | 14,500 + 3,000 = 17,500 | 7,500 |
| Income | 25,000 − 2,710 − 17,500 = 4,790 | 15,000 − 2,710 − 7,500 = 4,790 |
| After half the child's 2,710 | 3,435 | 3,435 |
| Tax (line 20) | 33 + 135 × 2% = 35.70 | 35.70 |
The share is s = 0.5 + (21,165 − 13,415) / 50,000 = 0.655, and the total is 71.40.
Mutant M6 (delete - kink_deduction from level)
M6 ran in its own process on a scratch worktree, which I removed afterwards; git status is clean.
mt_income_tax.yaml: 1 failed, 4 passed. The new case fails withmt_dependent_medical_expense_head_share0.715 against 0.655, and the runner stops at that first mismatch.- Direct run of the same household under M6: share 0.715, line 1a [17,875, 7,125], line 20 [32.90, 38.60],
mt_income_tax71.50. At the head: 0.655, [16,375, 8,625], [35.70, 35.70], 71.40. - So the per-column line 20 also kills M6. Only the total, 71.50 against 71.40, is close.
git merge-tree --write-tree --name-only
Run at cad9f36902 and at the new head; both give the same result.
| Pair | Result |
|---|---|
this PR vs #10206 (c1b942f276) |
exit 0, clean |
this PR vs #10270 (2e5fccfd1f) |
exit 1, conflict only in policyengine_us/tests/core/test_medical_expense_premium_conservation.py |
#10270 vs main (25c4253483) |
exit 1, the same file |
this PR vs main |
exit 0, clean |
#10206 vs main |
exit 0, clean |
This PR doesn't touch test_medical_expense_premium_conservation.py; #10270 does, and main changed it since (e.g. #10109).
Tests at the head (one process per file)
| File | Result |
|---|---|
mt_income_tax.yaml |
5 passed |
test_mt_dependent_medical_split_invariants.py |
6 passed (33.5 s pytest, 108.7 s wall, 1.79 GiB peak RSS) |
test_mt_medical_premium_deduction_invariants.py |
5 passed (45.9 s pytest, 121.9 s wall, 1.76 GiB peak RSS) |
The premium file needs Hypothesis, so I installed the dev extras first. The two property files ran at the same time, so their wall times are somewhat inflated. No Python file changed in this round, so there was nothing for ruff to check.
CI
Not finished at the 60-minute mark; the runners were congested. This is the only run at this head: the Pull request run 41861893133 at c08dd183f6.
At 16:50Z, 60 minutes after my 15:50Z push, the run was in_progress:
- 34 of 36 jobs had succeeded: all eleven Baseline shards (the four state shards included), Household API Partners, PR validation, Package and compatibility, Quick Feedback, Microsimulation, the Rest Python planning and check jobs, all nine Rest Python shards, and seven Contrib shards.
- 2 were still running: Contrib other-shard-1 and other-shard-3.
- None had failed or been cancelled.
The hub needs to read the run's conclusion.
Judgment calls
- The 0.01 margin (above).
- The Round 4 table and the Review line, which follow the body's existing per-round record.
- The links: I reported them rather than working around the rewrite.
Proposed follow-up
None beyond the review's own P3-1, "Montana status 2a: reach the minimum when the capital gain credit holds a column at zero", which the body already logs.
Generated by Claude Code
|
Hub review in progress Round 4 delta review of So far:
Generated by Claude Code |
|
Ready for hub review This is the round 4 delta review of The round 3 items are resolved:
I found no P1 or P2. There are two P3s. The methodology choice is the same as in round 3: the share with the lowest line 20, chosen from a candidate set. FindingsP3-1. The premium case sets a derived variable, so its joint path leaves the premiums out
P3-2. The body still has no
|
| Mutant | mt_income_tax.yaml |
Split property file | Premium property file |
|---|---|---|---|
| none (head) | 5 passed | 6 passed | 5 passed |
M6: delete - kink_deduction from level |
1 failed (the new case: share 0.715 vs 0.655), 4 passed | 6 passed in round 3 | 5 passed in round 3 |
M7 (mine): count premiums under the floor room in every year, dropping the if not p.state_specific_deduction_applies guard in mt_medical_floor_room |
1 failed (the new case: share 0.595 vs 0.655), 4 passed | 6 passed (survives) | 5 passed (survives) |
- M7 by hand: the head's room is 1,875 − 3,000 = −1,125, so its level is 18,165 and s = 0.5 + 4,750 / 50,000 = 0.595. A direct run under M7 gives share 0.595, line 1a [14,875, 10,125] and line 20 [38.60, 32.90]. That is 71.50 in total, as predicted.
- So the new case is the only test that guards the premium term in either form.
- I removed the worktrees afterwards;
git statusis clean.
The defect at main (25c4253483), TY2022: head wages $60,000, spouse $15,000, child's other medical expenses $10,250.
- At main:
mt_medical_expense_deduction_indiv[0, 0, 0],mt_tax_unit_itemizesfalse,mt_income_tax2,929.68. - At the head: share 1.0, deduction [5,750, 0, 0] (10,250 − 7.5% × 60,000), line 20 [2,505.55, 283.75],
mt_income_tax2,789.30. These match the body's witness table.
Overlap, with git merge-tree --write-tree --name-only:
| Pair | Result |
|---|---|
this head vs Use the federal itemized total and deduction type for Montana from 2024 #10206 (c1b942f276) |
exit 0 |
this head vs Count only filer-paid premiums in Schedule A medical expenses #10270 (2e5fccfd1f) |
conflicts only in policyengine_us/tests/core/test_medical_expense_premium_conservation.py |
Count only filer-paid premiums in Schedule A medical expenses #10270 vs main (25c4253483) |
the same file |
| this head vs main | exit 0 |
| Use the federal itemized total and deduction type for Montana from 2024 #10206 vs main | exit 0 |
this head vs Count a Montana casualty loss once per joint return and only for the spouse who had it #9888 (e352ed05bb) |
conflicts in 16 files, including mt_itemized_deductions_indiv.py |
this head vs Cap Montana's 2021–2023 state and local tax deduction once per joint return and at $5,000 per separate column #10002 (0a4846c7b1) |
conflicts in 3 files, including mt_itemized_deductions_indiv.py |
So both overlap bullets in the body hold. The search API finds no other open PR on the changed variables or files.
The body's links. In the API's body_html, both revenuefiles.mt.gov instruction links (PDF p. 32 and p. 11) render as <a href> anchors, with no backticks in the source markdown.
The body's round 4 claims: "M6 now fails it", the case's values, and the Count only filer-paid premiums in Schedule A medical expenses #10270 conflict "only in test_medical_expense_premium_conservation.py, as it does against main". All three match my runs.
Readers. The delta changes no variable or parameter, so it adds no reader. The round 3 reader list stands.
CI. The Pull request run 41861893133 at c08dd183f6 completed with success: 36 of 36 jobs succeeded, none cancelled or skipped. That covers the Baseline shards 11/11, the Contrib shards 9/9, Python Rest 9/9 plus plan and check, Microsimulation, Household API Partners, PR validation, Package and compatibility, and Quick Feedback.
Style. ruff format --check reports "13 files already formatted" and ruff check reports "All checks passed!" on the PR's 13 Python files.
Scope. No file is under .github/ or policyengine_us/tests/policy/baseline/partners/.
Open since round 3, logged as follow-ups in the body: the credit-bound columns (round 3 P3-1) and the mt_files_separately strict comparison.
What I couldn't check
- Sources. This session can't reach revenuefiles.mt.gov, govinfo.gov, uscode.house.gov, ecfr.gov, rules.mt.gov or web.archive.org: the proxy answered 403, and WebFetch couldn't resolve the hosts. So I confirmed none of the legal quotes in the body: IRC 213(a), the 2021–2023 Form 2 instructions and their page numbers, ARM 42.15.524, and MCA 15-30-2132. No reading in this delta depends on them, because the delta is a test of the existing formula.
- Population impact: no microdata here (P3-2).
- Axiom: rulespec-us#1520 is outside this session's GitHub scope.
- Peak memory:
/usr/bin/timeisn't installed here. Wall times were 131 s formt_income_tax.yaml, 92 s for the split property file (31.3 s pytest) and 100 s for the premium property file (40.9 s pytest), with other processes running at the same time.
Generated by Claude Code
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
The case set mt_eligible_medical_insurance_premiums, a derived variable that mt_premium_deduction_joint doesn't read, so its joint return had no premiums. Setting medical_expense_health_insurance_premiums makes both paths see them; the case now also pins the joint tax (115.10), the separate-filing election and the final tax (71.40). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6K4hbmpvP2YevFmELw
|
Ready for hub review Head: This round builds on Items
Tests at the head (one file per process)
No Python file changed, so ruff had nothing to check. Mutant
Hand computation (TY2022 joint, from the case comment and the review)40,000 − 3 × 2,710 = 31,870. Deductions: the child's 25,000 − 7.5% × 40,000 = 22,000 of medical, plus 3,000 of premiums, for 25,000. Taxable income is 31,870 − 25,000 = 6,870, and the tax is 33 + 50 + 1,070 × 3% = 115.10. The separate total of 71.40 is lower, so Body links: needs the hubThe two revenuefiles.mt.gov links under "Law, and the choice it leaves" are broken again. Something on this session's outbound path rewrites those two URLs, wrapping each in backticks ( CIPull request run 41946770211 at The body has no CI status sentence to update. Its CI mentions are local timings. Judgment calls
What I couldn't check
Generated by Claude Code |
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
Main now carries #10038, which added mt_misc_deductions_subject_to_floor_indiv to the separate-filing sum in mt_itemized_deductions_indiv. This branch moved that sum into mt_non_medical_itemized_deductions_indiv, so the conflict keeps this branch's mt_itemized_deductions_indiv (the non-medical deductions plus the head-or-spouse medical deduction). The next commit carries the floor deduction into the new variable. test_mt_medical_premium_deduction_invariants.py merged without conflict and keeps both sides' changes: this branch's property 10 and 11 checks and main's long-term care premium checks (#10265). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#10038 added mt_misc_deductions_subject_to_floor_indiv to the separate-filing sum in mt_itemized_deductions_indiv. This branch moved that sum into mt_non_medical_itemized_deductions_indiv, so the merge dropped it. Listing it there again puts it back in each column's itemized deductions. The balancing share and the itemize election now also see it, because both read the non-medical sum. The new TY2017 case in mt_income_tax.yaml needs both changes: a child's medical bills split between the columns and a floor deduction in each column. Without the floor variable in the list, the share moves from 0.679 to 0.715 and the couple's tax from 99.20 to 143.20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review This round merges main after #10038 and carries #10038's floor deduction into the new non-medical sum. The head started at 1. Merge of main
2. The floor deduction and the component lists
3. The case that needs both changes
The year. TY2017 is the only year in which the code applies both paths:
Inputs. Head: Montana AGI 25,000 and 2,500 of unreimbursed employee expenses. Spouse: 15,000 and 500 of tax preparation fees. Child: 25,000 of other medical expenses. Hand computation, from the code before I ran the case:
The case also asserts the floor variable, the non-medical sum, the share, line 1a,
4. MutantI removed
5. Tests at
|
| File | Result |
|---|---|
mt_childcare_itemization_boundary.yaml |
2 passed |
mt_medical_and_dental_expenses_indiv.yaml |
4 passed |
mt_medical_expense_deduction_indiv.yaml |
13 passed |
mt_tax_unit_itemizes.yaml |
4 passed |
mt_income_tax.yaml |
6 passed |
test_mt_dependent_medical_split_invariants.py |
6 passed |
test_mt_medical_premium_deduction_invariants.py |
5 passed |
Main's mt_claim_of_right_deduction.yaml |
35 passed |
Main's mt_taxable_income_joint.yaml |
8 passed |
Main's test_mt_taxable_income_properties.py |
1 passed |
Main's test_mt_mortgage_interest_federal_limit_properties.py |
6 passed |
The first runs of the two Hypothesis files stopped at collection, because my uv sync had left out the dev extra. I ran uv sync --extra dev, as CI does, and reran both. ruff format --check and ruff check pass on the changed Python file.
6. Body
I applied the update with gh api -X PATCH and read it back.
- "Changes" gains the floor variable in
mt_non_medical_itemized_deductions_indiv. - A new section, "Main merge (Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038) and tests at this head", gives the merge, the new case, the mutant, the test counts and the CI line ("pending").
- "Sweep" and "Impact" now say their figures were measured before the merge at
7001052730. I left their numbers unchanged. - "Overlaps with open PRs":
- The Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038 bullet now says it merged as
0296cdbd13and that this merge carries its floor deduction. - I reran
git merge-treeat the new head. Cap Montana's 2021–2023 state and local tax deduction once per joint return and at $5,000 per separate column #10002 still conflicts in 3 files and Count a Montana casualty loss once per joint return and only for the spouse who had it #9888 in 16,mt_itemized_deductions_indiv.pyamong them, so that bullet stands. - Count only filer-paid premiums in Schedule A medical expenses #10270 now conflicts in 7 files, exactly the 7 it conflicts with on main (Count eligible long-term care premiums in Schedule A medical expenses #10265 touched the same federal medical files), so this PR adds no conflict of its own. I rewrote that bullet. Use the federal itemized total and deduction type for Montana from 2024 #10206 closed without merging, so the bullet says so.
- The Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038 bullet now says it merged as
- The
axiom:line and the review-round sections are unchanged.
Needs the hub: the two Form 2 instruction links in "Law, and the choice it leaves" (body lines 15-16) are broken. This session's GitHub write path wraps every mt.gov URL in backticks: I tried six forms, and every one came back wrapped. The github.com and web.archive.org links came through intact. The body at the start had the trailing half of an earlier wrap (stray backticks after both links); I removed those. Please restore both links from a local session: PDF pages 32 and 11 of the 2022 Form 2 instructions on revenuefiles. I didn't try to encode around the rewrite.
7. CI
Pull request run 42636963243 at 96d1045eeb is in progress. I didn't wait for it.
Judgment calls
- TY2017 for the new case. It is the only year in which the code applies both paths without a parameter change. The alternative is TY2022 with
misc_floor_appliesset true by a reform, which runs real 2022 parameters but tests a counterfactual. I chose the year the code reaches by itself and said in the case that it pins the model's arithmetic. - The floor inputs. I used unreimbursed employee expenses and tax preparation fees, which feed Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038's floor pool, rather than a claim of right repayment. That avoids a pre-2018 section 1341 calculation that Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038's own 2017 cases stub out with inputs.
- The case's file. It sits in
mt_income_tax.yamlbeside this PR's three other status 2a cases, so the five-file list in the brief still covers it. - Extra assertions. The case asserts the intermediate variables as well as the brief's two. So the mutant's first mismatch is the non-medical sum. The direct run above shows the headline outputs fail as well.
- Run order. Before the hand computation, I ran one feasibility simulation of a variant without the spouse's fees, to see whether 2017 computes at all. I worked the final case's values by hand before running it.
- Body scope. Beyond what the brief listed, I rewrote the Count only filer-paid premiums in Schedule A medical expenses #10270 bullet (the merge changed it) and noted that Use the federal itemized total and deduction type for Montana from 2024 #10206 closed.
Follow-up issue proposed
- Montana's 2021 parameters stand in for earlier years. The rates, exemptions, standard deduction and status 2a switch first appear in 2021, so core extends them back. Any pre-2021 Montana result, including Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038's 2017 floor years, uses 2021 law unless a case supplies its own inputs.
Generated by Claude Code
|
David, here's where each item of your review stands at
An independent AI review of these changes agreed at |
The commits since this review answer each of its items, and my reply above maps each item to its commit and file.
|
US + core hub merge audit, us#10276 at
🤖 Generated with Claude Code |
Summary
From 2021 to 2023, Montana let married couples file separately on the same form (filing status 2a, columns A and B).
mt_medical_expense_deduction_indivmodels each column's medical deduction. It readperson("other_medical_expenses")for each spouse.other_medical_expensessits on the person cared for, including amounts other household members paid on that person's behalf (CPS ASEC PMED_VAL; #10270, still open, documents this on the variable). So a dependent's medical bills reached neither column. The joint variant counts every member's.This PR:
mt_medical_and_dental_expenses_indiv, line 1a of the Itemized Deductions Schedule in each column: each spouse's own other medical expenses plus their share of every dependent's; an unmarried filer's column holds all of them;mt_dependent_medical_expense_head_share, the head's share of the dependents' bills, chosen to minimize the couple's Montana tax over a small closed-form candidate set;mt_tax_unit_itemizeschoose the separate path's election (both itemize or both take the standard deduction) by the couple's tax, not by summed deductions.mt_medical_expense_deduction_indivfloors line 1a at 7.5% of the column's Montana AGI. Lines 2 and 3 (premiums) don't change.Law, and the choice it leaves
Readings
The comparisons use each column's tax before refundable credits (Form 2 line 20). Refundable credits don't depend on the deductions, except the 2021 income tax rebate. That rebate can't exceed line 20, and the choices leave it out.
TY2021 objective. In 2021 the choices minimize line 20, not tax net of the 2021 rebate. Couples chose how to file their 2021 returns before the rebate law existed (the repo cites Ch. 44, L. 2023 for the rebate), so line 20 is the tax they could minimize.
mt_income_tax_rebateon main documents the same assumption: couples make the separate-versus-joint election before the rebate.Changes
tools/mt_separate_return.py(new): the status 2a column formulas as functions (dependent exemption allocation, rate schedule, capital gains tax, medical deduction, and the couple's line 20 for given deductions).mt_taxable_income_indiv,mt_regular_income_tax_indiv,mt_capital_gains_tax_indivand its threshold now call them, unchanged in result, so the choices evaluate the model's own formulas in closed form, with no simulation branches.mt_dependent_medical_expense_head_share(new, TaxUnit): evaluates the couple's line 20 at four candidates (equal split, 0, 1, the balancing share) and takes the lowest.mt_non_medical_itemized_deductions_indiv(new): the itemized deductions other than medical, so the choice can hold them fixed;mt_itemized_deductions_indivadds the medical deduction to it. Since the merge of main it also lists Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038'smt_misc_deductions_subject_to_floor_indiv, the 2017 pool after its 2% Montana-AGI floor, which main lists inmt_itemized_deductions_indiv.mt_medical_and_dental_expenses_indiv(new): line 1a from the chosen share.mt_tax_unit_itemizes: the election by tax when the model allows status 2a. The joint path doesn't read it: a single return takes the larger deduction, which can't raise its tax, so the joint path doesn't change.spouse_allocation_rate: its description now calls it a modeling convention for status 2a, not a rate in law. Its comment names its four readers: the charitable split in two variables, the medical tie-break and first candidate, and the dependent care deduction, where the equal split is the form's rule. The ARM reference points at archived text, with the 2022 instructions p. 11 and IRS Pub. 502 "Separate returns".Witness (TY2022: head wages $60,000, spouse $15,000, child's bills $10,250)
We computed these with the model at the round 2 head. Swapping the labels gives $2,789.30 too.
Invariants
Property tests in
tests/unit/test_mt_dependent_medical_split_invariants.pyrun on a seeded population of 40 couples in each of 2021–2023. I dropped an earlier Hypothesis test because it killed none of the mutants. For every couple:test_closed_form_matches_columnschecks thatmt_separate_tax_before_refundable_creditswithmt_deductions_indivequals the columns' summed line 20 within $0.01, so the choices minimize the tax the columns compute. It runs 30 couples with wages and long-term capital gains in 2021–2023, so most have a non-zero capital gain credit.Properties 3–5 use
mt_income_taxin 2022–2023 and line 20 in 2021 (the rebate). Each candidate's allocation gives each column at least as much line 1a when the bills grow, so property 5 holds by construction for the candidate set. The tests state and check monotonicity on the tax (5), not on the summed deduction. The chosen allocation can move between columns as bills grow, so nothing guarantees a rise in the summed medical deduction, and no test checks one.test_mt_medical_premium_deduction_invariants.pyproperty 10 now reads the chosen share (between 0 and 1) instead of assuming 0.5, and its conservation and accounting checks stay as they were.Sweep (household-level, no microdata)
We measured these figures before the merge of main at
7001052730. We report the population run at the final head under "Microsimulation impact" below.The sweep covers 1,452 TY2022 couples: head wages $40,000–$90,000 (step $5,000), spouse wages $8,000–$30,000 (step $2,000), and one child with $0–$30,000 of bills (step $3,000).
Impact
We measured the figures below before the merge of main at
7001052730.mt_tax_unit_itemizes.yaml(head AGI $20,000, spouse $0) now itemizes ($189.20 against $243.20), although its itemized deductions sum to less; andmt_childcare_itemization_boundary.yamlcase 1, whose sums tie, now itemizes ($180.80 against $243.20).mt_income_taxcan rise (grid witness: $185.20 → $1,413.65).mt_itemized_deductions_indivandmt_tax_unit_itemizesinto each column's tax, thenmt_files_separately,mt_income_tax,mt_income_tax_before_refundable_credits_unitand the 2021 rebate's cap base.test_mt_dependent_medical_split_invariants.pyruns 6 tests in 23.5 s of pytest time (72 s wall with imports, peak RSS 1.83 GB) in one process. With the earlier Hypothesis test, the file took 36.7 s (90 s wall, 1.95 GB).test_mt_medical_premium_deduction_invariants.pyruns 5 tests in 26 s (75 s wall, 1.80 GB). We timed both locally on a 4-vCPU VM, not on Linux CI. The choices add four closed-form evaluations of the columns' line 20 per tax unit, and the election two; no simulation branches.Not covered
filing_status, and its head's column then holds all of the dependents' expenses.spouse_allocation_rate. This PR doesn't split them jointly with the medical bills to minimize tax.Main merge (#10038) and tests at this head
7001052730merges main atba9bfdea75. I resolved its only conflict, inmt_itemized_deductions_indiv.py, by keeping this PR's version: the non-medical deductions plus the head-or-spouse medical deduction.test_mt_medical_premium_deduction_invariants.pymerged without a conflict and keeps both sides: properties 10 and 11 here, and Count eligible long-term care premiums in Schedule A medical expenses #10265's long-term care checks.96d1045eeblistsmt_misc_deductions_subject_to_floor_indivinmt_non_medical_itemized_deductions_indiv, besidemt_misc_deductions, as main had it. The balancing share reads that sum, and the itemize election readsmt_itemized_deductions_indiv, which includes it, so both hold the floor deduction fixed. The joint and federal-itemization sums already carry main's floor variables, andtools/mt_separate_return.pybuilds no list of its own.mt_income_tax.yaml, hand-computed before the run. TY2017 is the only year in which the code applies both paths:misc_floor_appliesis true only in 2017, and status 2a eligibility returnsmarried_filing_separately_on_same_return_allowed, whose 2021 value extends back. The model's 2017 exemption (2,580), schedule and standard deduction likewise equal its 2021 values, so the case pins the model's arithmetic.mt_income_taxis 99.20.mt_income_tax.yamlgives 1 failed, 5 passed. A direct run gives share 0.715, itemized deductions [16,000, 6,000] andmt_income_tax143.20, matching the hand computation.96d1045eeb), one file per process:mt_childcare_itemization_boundary.yamlmt_medical_and_dental_expenses_indiv.yamlmt_medical_expense_deduction_indiv.yamlmt_tax_unit_itemizes.yamlmt_income_tax.yamltest_mt_dependent_medical_split_invariants.pytest_mt_medical_premium_deduction_invariants.pymt_claim_of_right_deduction.yamlmt_taxable_income_joint.yamltest_mt_taxable_income_properties.pytest_mt_mortgage_interest_federal_limit_properties.py96d1045eebpassed all 36 jobs on GitHub's merge with main833c28bb47.Overlaps with open PRs
mt_non_medical_itemized_deductions_indiv. Whichever lands second must move its change into the new variable; otherwise the split and the election compare a tax that leaves the change out.test_mt_medical_premium_deduction_invariants.py, is also in this PR, which adds no conflict of its own. Use the federal itemized total and deduction type for Montana from 2024 #10206 closed without merging.0296cdbd13. It addedmt_misc_deductions_subject_to_floor_indiv, the 2017 pool after its 2% Montana-AGI floor, to the list inmt_itemized_deductions_indiv. This PR's merge of main carries it intomt_non_medical_itemized_deductions_indiv(96d1045eeb).Review
01fd6e5c6a: REQUEST_CHANGES, no code defects; body, Axiom draft and documentation findings, fixed inb352a51acb.b352a51acb: REQUEST_CHANGES on two prose claims, then APPROVE.a358eb61d0: APPROVE, with one P2: measure the split in tax. Its witness and grid led to the ruling.a358eb61d0: REQUEST_CHANGES, one should-address item and four suggestions (Round 2 below).3146e8b86f: REQUEST_CHANGES, three P2s and five P3s (Round 3 below).cad9f36902: APPROVE, four P3s (Round 4 below).c08dd183f6: APPROVE, two P3s (Round 5 below).96d1045eeb: agrees; no P1 or P2; four P3s on this description, the Axiom note and the TY2017 case's years (Round 6 below).Round 2
65036f1615,3dfd4a7333mt_tax_unit_itemizescompares the couple's line 20; joint path unchanged; properties 2 and 38accee23e73dfd4a7333spouse_allocation_ratemetadata7102775bc3mt_income_tax.yaml: a status 2a couple whose child's bills lower the tax ($2,774.63 against $2,929.68), hand-computed8accee23e73146e8b86fI merged main in at
07d9af6696.Round 3
mt_income_tax.yaml($131.40 at share 0.715). Low-income grid (90 couples per year, heads $10k–$40k, spouses $5k–$25k, bills $4k/$12k/$25k, employment income, TY2021–2023, against 51 forced shares and the standard deduction): largest gap $51.79 before, $0.00 after9e2bc13d17,cad9f36902impact:linetest_closed_form_matches_columnswith capital gains; property 1 also checks shares 0.1–0.9; Hypothesis test dropped9e2bc13d17,c6cdfe2df7,cad9f36902spouse_allocation_ratewordingdca8b29047mt_files_separatelystrict comparisonI merged main again at
bda0215efd(no conflicts).Round 4
mt_income_tax.yaml: the balancing witness with $3,000 of premiums on the head; share 0.655, line 1a [16,375, 8,625], line 20 [35.70, 35.70], hand-computed. M6 now fails itc08dd183f6git merge-treererun at this head: #10206 clean; #10270 conflicts only intest_medical_expense_premium_conservation.py, as it does against main (superseded at96d1045eeb; see Overlaps)impact:lineRound 5
mt_income_tax.yaml: the balancing witness now setsmedical_expense_health_insurance_premiums: 3_000and also assertsmt_income_tax_joint115.10,mt_files_separatelytrue andmt_income_tax71.40. M6 still fails it (share 0.715 vs 0.655)4c615b1c79impact:linegit merge-treeagainst #10038's head conflicts only inmt_itemized_deductions_indiv.py; bullet added aboveRound 6
impact:line; the sweep and impact figures predate the mergeMicrosimulation impact
We estimate that this PR changes no output in 2026 on the default dataset: Montana income tax stays at $2.32bn, and no sampled tax unit, person or household changes. We compared
mainatba9bfdea75with this branch at96d1045eeb.This PR changes only the separate path, which the model turns off from 2024, when
married_filing_separately_on_same_return_allowedturns false. So in 2026 the three separate-column outputs below come to $0 on main and on this branch. No record changes in any output we compared, so the largest single change comes to $0. State and federal income tax, household net income and household benefits don't change.The default dataset's records start in 2024. We also ran 2021, 2022 and 2023 on it, and every Montana total came to $0 on main and on this branch, so it can't estimate the years this PR changes. The sweep above covers TY2022 household by household.
mt_income_taxmt_medical_expense_deduction_indivmt_itemized_deductions_indivmt_capital_gains_tax_indivstate_income_taxincome_taxhousehold_net_incomehousehold_benefitsThis PR adds
mt_medical_and_dental_expenses_indiv,mt_non_medical_itemized_deductions_indivandmt_dependent_medical_expense_head_share, so main has no total to compare.impact: no output changes in 2026 on the default dataset (0 sampled records; Montana income tax stays at $2.32bn); the separate path ends after 2023, and the dataset starts in 2024, so TY2021–2023 has no estimate
axiom: TheAxiomFoundation/rulespec-us#1520 queued, extended with two comments (the status 2a line 1a allocation, then the tax-minimizing share at
96d1045eebwith rows for the three TY2022 cases added since). At corpus pin437198d6, its open prerequisites include the historical ARM 42.15.524, MCA 2021 text for §§ 15-30-2110 and 15-30-2132, and a TY2021–2023 version of the IRC 213 floor rate.🤖 Generated with Claude Code