Skip to content

Cap the Montana 2021 income tax rebate at liability (MCA 15-30-2191(2)(b)) and scope it to 2021#8970

Open
PavelMakarchuk wants to merge 1 commit into
mainfrom
fix-mt-rebate-wiring
Open

Cap the Montana 2021 income tax rebate at liability (MCA 15-30-2191(2)(b)) and scope it to 2021#8970
PavelMakarchuk wants to merge 1 commit into
mainfrom
fix-mt-rebate-wiring

Conversation

@PavelMakarchuk

Copy link
Copy Markdown
Collaborator

Summary

mt_income_tax_rebate returned the flat filing-status amount ($1,250/$2,500) for every MT filer in every year ≥ 2021, ignoring both the statutory liability cap — MCA 15-30-2191(2)(b): the rebate is the lesser of the amount or the taxpayer's income tax liability — and the rebate's 2021-only scope.

Montana state_income_tax results are unchanged (verified): the non-refundable credit application already floored the applied amount, and the credits list already restricts application to 2021. The defect was the variable's reported value — wrong for low-liability filers (e.g. a $20k joint couple: reported $2,500, statutory rebate $252.40) and nonzero in 2022+ — which mis-feeds any consumer of the variable (it produced phantom amounts in the TAXSIM-comparison srebate work, PolicyEngine/policyengine-taxsim#1070/#1068).

Changes

  • Formula caps at the smaller of the joint/separate-column pre-credit bases (the mt_files_separately election is post-credit and would create a computation cycle; the smaller base never exceeds the elected one).
  • rebate/amount.yaml zero-scoped from 2022-01-01.
  • Unit tests rewritten: the previous period-2023, no-income tests asserted the uncapped flat amounts (they encoded the bug); new tests cover above-cap single/joint, the capped low-liability case ($252.40), non-MT, and 2022 = 0.

Testing

Full Montana baseline suite: 536 passed, 0 failed. The TAXSIM-parity integration tests are unaffected (they input-override the rebate).

Fixes #8958.

🤖 Generated with Claude Code

The variable returned the flat filing-status amount (1,250/2,500) for every
MT filer in every year >= 2021, ignoring the statutory cap (lesser of the
amount or the taxpayer's income tax liability) and the rebate's 2021-only
scope. Montana income tax results are unchanged - the credit application
already floored the applied amount - but the variable's reported value was
wrong for low-liability filers and for 2022+ (it produced phantom amounts
in the TAXSIM-comparison srebate work).

Cap at the smaller of the joint and separate-column pre-credit bases (the
filing election itself is post-credit and would create a computation
cycle), zero-scope the amounts from 2022, and replace the period-2023
no-income unit tests that encoded the uncapped values.

Fixes #8958.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (ee36ad1) to head (bdcd44f).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #8970   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            5         1    -4     
  Lines           86        16   -70     
=========================================
- Hits            86        16   -70     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MaxGhenis

Copy link
Copy Markdown
Contributor

Review: cap the Montana 2021 income tax rebate at liability

Verified the parameter values and cap logic against the enacted statute and reproduced the behavior on the PR branch. Verdict: approve. The change is correct, well-tested, and provably tax-neutral, with two minor accuracy notes and one nit.

Verified

  • Statute. MCA 15-30-2191 (HB 192, 2023) sets the rebate as the lesser of (i) the 2021 individual income tax liability reported on line 20 of the 2021 Form 2, or (ii) the filing-status amount — $1,250 (single / head of household / married filing separately) or $2,500 (joint / surviving spouse). The flat amounts in amount.yaml match.
  • Vectorization. The formula uses min_/max_/add with no array-level branching. The if p.capital_gains.in_effect in the base variables is a scalar parameter branch. Clean.
  • Tests pin the boundary. All 5 rebate tests pass. Independently reproduced the capped case: a $20,000 joint couple in 2021 has a joint pre-credit base of $252.40, and mt_income_tax_rebate returns $252.40 = min($2,500, $252.40). Above-cap single ($1,250) and joint ($2,500), non-MT ($0), and 2022 ($0) are all covered.
  • Tax-neutrality holds. Reproduced state_income_tax for the capped couple on both this branch and main: −33.81 in both cases (unchanged). The 2022 single case confirms the fix removes a phantom reported $1,250 (main reports 1,250; this branch reports 0) that was never actually applied.
  • 2022 zero-scoping is correct. mt_income_tax_rebate appears only in the 2021 non_refundable credit list, so the flat amount persisting into 2022+ was a reported-value artifact; zeroing it is right.

Minor: the cap base is line 18, but the statute specifies line 20

The statute caps at line 20 of the 2021 Form 2, which is "Tax after nonrefundable credits" (line 18 − line 19). This PR caps at mt_income_tax_before_non_refundable_credits, which is line 18 ("Tax liability before credits"). For a 2021 filer with other nonrefundable credits — in the model, the capital gains credit, which is applied before the rebate in the 2021 non_refundable list — the reported rebate can exceed the statutory line-20 cap by the amount of that credit.

This has no effect on state_income_tax (the credit application already floors the applied amount — verified), so it affects only the reported mt_income_tax_rebate value that downstream consumers see. The cycle-avoidance rationale for using pre-credit bases is sound with respect to the mt_files_separately election, but the capital gains credit is applied before the rebate and does not depend on it, so netting it out would not create a cycle. Consider capping at the pre-credit base net of the non-rebate nonrefundable credits, or documenting the line-18-vs-line-20 deviation in the comment.

Minor: citation precision

The "lesser of ... tax liability" cap is subsection (2)(a); (2)(b) is the flat filing-status amount. The PR title, changelog, and code comment attribute the cap to "(2)(b)"; more precisely, the "lesser of" is in the chapeau of (2), with (a) = liability and (b) = amount. The amount.yaml reference (citing the "(b)" amounts) is correct as written.

Nit

amount.yaml has a trailing space on the new 2022-01-01: 0 line under SEPARATE.

Out of scope (pre-existing, not introduced here)

For low-liability couples that elect separate filing, MT income tax can go slightly negative (for example −33.81 for the $20k couple) because the tax-unit-level rebate is projected onto persons in the separate-column allocation (mt_non_refundable_credits adds the TaxUnit rebate at Person level). This is identical on main and this branch, so it is not a regression — noting only for awareness.

@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

Heads-up from taxsim triage (PolicyEngine/policyengine-taxsim#1078): this cap is one of three legs on that record, but it does not fix the per-person projection filed as #9012mt_non_refundable_credits (Person entity) adds the TaxUnit-level rebate once per spouse, so a joint couple still nets 2 × $2,500 = $5,000 with this PR applied. The two can land independently; noting it here so the cap isn't assumed to close out the #1078 record.

@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

@MaxGhenis your review above concludes "Verdict: approve" but was posted as a comment, so the PR still shows as awaiting review — would you mind submitting it as a formal approval when you get a chance? Thanks!

@hua7450

hua7450 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Heads-up: #9019 merged (along with #9018), which changes the ground under this PR. git pull upstream main now conflicts on both mt_income_tax_rebate.py and mt_income_tax_rebate.yaml, and the conflict is semantic, not just textual: mt_income_tax_rebate is now a Person-entity variable that gives each spouse a $1,250 column share (the #9012 fix @MaxGhenis flagged as out-of-scope in his review above).

That interacts with this PR's two components differently:

The liability cap no longer belongs in the formula. Two reasons, one old and one new:

  1. (Max's point, sharpened by the DOR record) The statutory cap is line 20 of the 2021 Form 2 — tax after nonrefundable credits — and the DOR rebate report (May 2024, pp. 6, 10–11) confirms it was administered per filing configuration: MFJ couples got min($2,500, joint line 20), while married-filing-separately spouses were "treated as separate individuals," each capped at min($1,250, own column line 20). PolicyEngine's ordered credit application already reproduces exactly this: the rebate applies after the capital gains credit and each path floors at zero, so the effective rebate is line-20-capped per configuration in both the indiv and joint paths, with no formula change.
  2. (New, post-Apply MT 2021 income tax rebate once per return, not per spouse (#9012) #9019) Porting the cap into the now-Person-level formula would regress the joint path: mt_income_tax_before_refundable_credits_joint sums the person-level rebate via add(), so capping each spouse's $1,250 at their own column liability would deliver only $1,250 to a one-earner couple electing the joint column, where the statute (and DOR practice) grants min($2,500, joint line 20). The uncapped-per-person + per-path-floor design now on main is the correct architecture. Notably, mt_files_separately elects on final per-path tax including the rebate, which also captures the real-world amend-to-maximize behavior the DOR report describes (HB 816 had to cut off rebate amendments at May 1, 2023).

The 2022 zeroing in amount.yaml should definitely land. It's the genuinely behavioral piece: with only 2021-01-01 values, the flat amounts backfill forward and every MT filer in a 2022+ computation receives a phantom rebate credit (the existing integration tests carry mt_income_tax_rebate: 0 overrides precisely to suppress this). Zeroing from 2022 fixes it — and it also means the current period-2023 unit tests on main (single $1,250, joint [1_250, 1_250]) only pass because of the leak, so this PR's move of the unit tests to period 2021 (with the "No rebate in 2022" case) is needed regardless.

Suggested rework:

  • Keep main's (Apply MT 2021 income tax rebate once per return, not per spouse (#9012) #9019) formula; add a comment citing MCA 15-30-2191(2) noting the lesser-of-line-20 rule is enforced by the ordered per-path credit flooring.
  • Keep the amount.yaml zeroing (and fix the trailing space Max flagged).
  • Port this PR's realistic-income tests to period 2021 as person-level arrays, keep "No rebate in 2022," and re-express the capped case ($20k couple) as an mt_income_tax integration assertion — the floor in action — rather than a rebate-variable assertion.
  • Retitle/reword changelog around the year-scoping (and per Max, the lesser-of cite is § 2191(2) chapeau/(2)(a), not (2)(b)).

Auto-merge is disabled on this PR until the rework lands, since a formal approval of the current head would merge a stale TaxUnit formula over #9019.

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.

Montana 2021 income tax rebate variable ignores the MCA 15-30-2191 liability cap and pays in all years >= 2021 (reporting; state_income_tax unaffected)

3 participants