Skip to content

Scope the Montana 2021 income tax rebate to its eligibility year - #8970

Open
PavelMakarchuk wants to merge 4 commits into
mainfrom
fix-mt-rebate-wiring
Open

Scope the Montana 2021 income tax rebate to its eligibility year#8970
PavelMakarchuk wants to merge 4 commits 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 (724e795) to head (dd53545).
⚠️ Report is 29 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #8970   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         1           
  Lines           35        16   -19     
  Branches         2         0    -2     
=========================================
- Hits            35        16   -19     
Flag Coverage Δ
unittests 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.

…d liability cap

PRs #9018/#9019 landed after this branch was opened and moved
mt_income_tax_rebate to a Person-entity variable that splits the per-return
cap across each spouse's column. That is the correct architecture, so this
branch no longer carries its own TaxUnit formula:

- Take main's Person-entity formula unchanged. Porting the liability cap into
  it would under-pay a one-earner couple electing the joint column, which the
  statute and the DOR rebate report grant min($2,500, joint line 20).
- Document instead, on that formula, that MCA 15-30-2191(2)'s lesser-of rule
  is already enforced by the ordered non-refundable credit application, which
  floors each path at zero. Verified: a $20k joint couple has $252.40 of
  pre-credit liability and lands at exactly $0 before refundable credits.
- Keep amount.yaml's 2022 zeroing. This is the behavioural fix: without it the
  2021 amounts backdate forward and every Montana filer gets a phantom rebate.
- Rework the tests onto period 2021, add 2022/2023 year-scoping guards that
  assert mt_income_tax end to end, and re-express the low-liability case as
  the credit floor in action rather than a capped rebate value.
- Rename the changelog fragment to match the branch and rescope its wording.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PavelMakarchuk PavelMakarchuk changed the title Cap the Montana 2021 income tax rebate at liability (MCA 15-30-2191(2)(b)) and scope it to 2021 Scope the Montana 2021 income tax rebate to its eligibility year Aug 25, 2026
@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

Reworked in 0a6a95f along the lines @hua7450 laid out on 2026-07-17. Re-requesting review from @hua7450 and @MaxGhenis.

What changed

PRs #9018/#9019 landed after this branch was opened and moved mt_income_tax_rebate to a Person-entity variable that splits the per-return cap across each spouse's column. That is the correct architecture, so this branch no longer carries its own TaxUnit formula or the liability cap:

  1. Dropped the liability cap; took main's formula unchanged. Porting the cap into the person-level formula would under-pay a one-earner couple electing the joint column — capping each spouse's $1,250 against their own column yields $1,250, where the statute and the DOR rebate report (May 2024, pp. 6, 10–11) grant min($2,500, joint line 20).
  2. Documented why the cap is unnecessary, on main's formula, citing MCA 15-30-2191(2) — the chapeau/(2)(a), not (2)(b) as the old title and changelog said (thanks @MaxGhenis). The lesser-of-line-20 rule is already enforced by the ordered non-refundable credit application: mt_income_tax_before_refundable_credits_{indiv,joint} floors at zero.
    Verified on the branch — a $20k joint couple: pre-credit liability $252.40, pooled credits $2,500, before-refundable $0.00. (mt_income_tax reads −$33.81 for that household, entirely from the refundable Montana EITC, not the rebate.)
  3. Kept the 2022 zeroing in amount.yaml — the genuinely behavioural piece. Without it the 2021 amounts backdate forward and every Montana filer receives a phantom rebate credit. Also dropped the trailing space @MaxGhenis flagged.
  4. Reworked the tests. Active-year cases moved to period 2021 as person-level arrays; the low-liability case is now expressed as the credit floor in action rather than a capped rebate value; added 2022 and 2023 year-scoping guards that assert mt_income_tax end to end. The 2022 guard is discriminating: without the zeroing the leaked $1,250 would cut that filer's tax from $2,246.00 to $996.00.
    Note this is why main's existing period-2023 cases had to move — they only passed because of the backdating leak.
  5. Changelog renamed to match the branch (fix-mt-rebate-wiring.fixed.md) and rescoped to the year-scoping fix. PR title updated likewise.

Verification on the merged head: MT rebate tests 8 passed; full Montana suite 558 passed; partner contract suite 630 passed (no partner test references MT, so there is no partner-facing impact); test_parameter_files.py 7 passed; ruff format --check clean.

Still open, unchanged by this PR: the per-person projection tracked in #9012 is resolved on main by #9019, so the note I left on 2026-07-13 about a joint couple netting 2 × $2,500 no longer applies here. The branch was 908 commits behind and is now merged up to date.

@DTrim99

DTrim99 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Heads up @PavelMakarchuk — this has a sibling in the same subsystem: the Montana property tax rebate (mt_property_tax_rebate, up to $675) has an analogous mis-wiring. It's listed in credits/non_refundable.yaml (2022 entry), so it reduces income tax, and since mt_non_refundable_credits is person-level it gets projected onto every household member ($675 × 3 = $2,025 for a family of three), understating MT tax. Unlike the income tax rebate you're handling here, the property tax rebate is a separate direct-payment program (own DOR application, not on Form 2), so I removed it from the credit list rather than capping/scoping it.

Filed as #9347, PR up at #9348. No file overlap with this one (I don't touch rebate/amount.yaml or the rebate variable/tests; you don't touch non_refundable.yaml). Flagging so the MT-rebate treatment stays deliberately consistent across the two — happy to fold it in here instead if you'd prefer.

@DTrim99

DTrim99 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Program Review — PR #8970: Montana 2021 income tax rebate (year-scoping + liability cap)

PR head: 0a6a95f3770ef91a1404f62e9c351900198dda1b
Baseline / merge-base: 5117f076515d263299dfe3b70492c37bf5a7517d
CI: 33/33 green.

Overview

The PR has two advertised purposes:

  1. Scope the rebate to the 2021 tax year (add 2022-01-01: 0 to amount.yaml so the
    one-time 2021 amounts stop leaking into 2022+). Delivered and correct.
  2. Cap the rebate variable at the smaller of the filing-status amount and the 2021 line-20
    liability
    (per the PR body). Not delivered — the .py change is comment-only.

The delivered half is well-sourced and genuinely guarded by tests. The un-delivered half is
the variable-level cap the PR body advertises; net-income results are nonetheless correct
because the cap is enforced downstream. Details below.

Source Documents

  • Statute: MCA 15-30-2191 (Temporary), "Individual income tax rebate" — the rebate equals
    the lesser of (a) the taxpayer's 2021 individual income tax liability as properly
    reported on line 20 of the 2021 Montana return, or (b) a flat cap of $1,250
    (single / head of household / married-filing-separate, at (1)(b)(i)) / $2,500 (married
    filing jointly, at (1)(b)(ii)). Subsection (2) restates the ceiling: the department "may
    not issue a rebate … that exceeds the taxpayer's individual income tax liability as properly
    reported on line 20." One-time, 2021 tax year only; issued by December 31, 2023; statute
    terminates December 31, 2025.
    • Live-URL termination (important): the cited href
      (archive.legmt.gov/.../0150-0300-0210-0910.html) now 301-redirects to mca.legmt.gov
      and renders only "15-30-2191. Terminated. Sec. 9, Ch. 44, L. 2023, sec. 10, Ch. 764, L.
      2023."
      A 2023 enactment reused the section number, so the live page no longer shows
      the 2021 rebate text or the $1,250/$2,500 amounts.
  • DOR report: MT DOR Income Tax and Property Tax Rebate Report, May 2024. Confirms both
    caps in plain English and that a joint couple receives min($2,500, joint line-20 liability) — the whole-return line 20 governs, not a per-spouse column ("whichever was
    lower"; "If a taxpayer's line 20 was less than $1,250, that amount was the rebate").
    Enacting bills: HB 192 (Ch. 44, L. 2023) + HB 816.

Branch Status

The branch is 32 commits behind main. This is noted for context only; it is not raised
as a review finding. CI is green on the current head.


Critical

C1 — Cited statute URL no longer corroborates the parameter values

Files: parameters/gov/states/mt/tax/income/credits/rebate/amount.yaml:8;
variables/gov/states/mt/tax/income/credits/rebate/mt_income_tax_rebate.py:10

Both the parameter and the variable cite the same href, which now resolves (verified live) to
a "Terminated" section because a 2023 enactment reused section number 15-30-2191. Clicking
the link does not display the $1,250/$2,500 amounts or any (1)(b)/(2) subsection — it fails
the core corroboration test ("when someone clicks the link, they should see the value"). A
reference that no longer corroborates the value provides false confidence.

Fix (choose one value-containing source): cite HB 192 (2023 Regular Session, Ch. 44,
Laws of 2023)
, or a Wayback Machine snapshot of MCA 15-30-2191 captured in 2023
(pre-termination), or (secondary) the Montana DOR "2021 Income Tax Rebate" guidance page. Apply
the same fix to both the parameter reference and the variable's bare-URL reference.

C2 — Subsection citation is imprecise

File: amount.yaml:7 — cited as "…Section 15-30-2191 (b)".

The bare "(b)" drops the (1) and the (i)/(ii) that actually pin the dollar figures: the amounts
live at 15-30-2191(1)(b)(i) ($1,250 single/HoH/separate) and (1)(b)(ii) ($2,500 joint),
and the standalone line-20 ceiling is at (2) — not cited at all. Since the implementation
relies on that ceiling (enforced downstream), the reference should name (1)(b)(i)–(ii) and (2).

Recommended title: "Montana Code Annotated (2023), 15-30-2191(1)(b)(i)–(ii) — rebate amounts
($1,250 single/HoH/separate; $2,500 joint); (2) — rebate capped at line 20 income tax
liability" (pointing at a value-containing source per C1).

Verification caveat: the live section is confirmed "Terminated" by direct fetch, but the full
2023 statute text could not be independently re-opened (Justia/FindLaw/Casetext blocked; HB 192
PDF path 404'd). The (1)(b)(i)/(ii) and (2) mapping is corroborated by the DOR report and the
Wayback snapshot used in the source audit; a maintainer should confirm exact subsection numbers
against the Wayback snapshot or the HB 192 enrolled text before finalizing the corrected cite.


Should Address

S1 — PR body claims a variable-level cap the code does not implement (central finding)

File: variables/gov/states/mt/tax/income/credits/rebate/mt_income_tax_rebate.py

The .py change is comment-only: the formula body is byte-for-byte identical to
baseline (5117f07). No min_() / min() cap was added. The variable still returns the flat
filing-status amount:

per_person_amount = where(
    filing_status == statuses.JOINT,
    p.amount["SEPARATE"],   # $1,250 per spouse for a JOINT return
    p.amount[filing_status],
)
return head_or_spouse * per_person_amount

Consequences:

  • mt_income_tax_rebate still reports the uncapped flat amount ($1,250 / $2,500) for
    low-liability filers. In the PR's own worked example (a ~$20k one-earner joint couple with a
    true pre-credit liability of $252.40) the variable reports [1_250, 1_250] while only $252.40
    of benefit survives. So the variable's reported value is not the statutory
    min(amount, line-20) for any filer whose liability is below the cap.
  • Net tax liability is nonetheless correct. The rebate enters as a member of the
    non-refundable credit list (non_refundable.yaml 2021 list →
    mt_non_refundable_credits.py addsmt_income_tax_before_refundable_credits_* applies
    max_(income − credits, 0)). That ordered floor is the only thing realizing the
    "lesser-of-liability" rule, and it is correct; mt_income_tax / state_income_tax are
    unchanged. mt_income_tax_rebate is referenced by no non-test formula other than that credit
    path (verified by grep).
  • The PR body is therefore inaccurate on two points: (a) it says "Formula caps at the
    smaller of the joint/separate-column pre-credit bases," which the formula does not do; and
    (b) its stated motivation — that the uncapped reported value mis-feeds a taxsim srebate
    output — is not resolved, because the reported value is unchanged. (No srebate /
    state_rebate consumer of this variable exists in the model today, so the un-capped value is
    currently latent/cosmetic, not an active net-income miscalculation — but it is a real
    deviation from the statute at the variable level and will mislead any future direct/srebate
    consumer.)

The added .py comment is candid ("that cap is NOT applied here"); the defect is that the PR
body overstates it
. Not applying the cap in the variable is a defensible design (capping
against a post-credit liability could create a computation cycle), but the PR body and code must
be reconciled.

Options (either resolves it): (1) correct the PR body to state the cap is emergent from
downstream credit ordering, not a formula-level cap, and drop/soften the srebate motivation;
or (2) apply min(per_person_amount, <person-level line-20 share>) in the formula so the
reported value is self-consistent with the statute. Also correct two PR-body-only citation
errors: "HB 3" → HB 192/816, and "(2)(b)" → (1)(b)/(2) (neither error appears in the
code or parameters — checked clean).

S2 — Liability-cap test coverage is partial

File: tests/.../mt_income_tax_rebate.yaml (post-PR, 6 cases)

Both sides of the cap are present (above-cap full amount in cases 1–2; below-cap partial
realization in case 3 at $252.40 < $2,500), so a gross regression to the downstream floor would
be caught. But the boundary is not probed, and every case asserts the uncapped rebate on
the variable — the cap only shows via downstream tax variables (these are integration
assertions, not unit assertions that the rebate equals min(amount, line 20)). Missing:

  • Exact-at-cap case (liability == $1,250 / == $2,500) and a cap ± 1 / ± epsilon pair —
    the single most valuable gap for "the crux"; a small near-threshold mis-set would go
    uncaught.
  • Single-filer low-liability case — only the JOINT low-liability path is tested; the indiv
    path uses a different variable (mt_income_tax_before_refundable_credits_indiv) and its own
    max_(…, 0) floor, currently unexercised.
  • SEPARATE (MFS-on-same-return) case — the formula's JOINT-vs-SEPARATE branch
    (p.amount["SEPARATE"]) is never exercised for an actual SEPARATE filer, which is exactly
    the configuration the new variable comment reasons about.
  • (Lower value) HEAD_OF_HOUSEHOLD and SURVIVING_SPOUSE 2022 zero-outs are unguarded.

Note: the year-scoping is genuinely guarded — cases 5 (2022) and 6 (2023) assert
mt_income_tax_rebate: [0] and would FAIL under the old amount.yaml (which backfilled $1,250
into 2022/2023). That half of the fix is regression-proof.


Suggestions

G1 — Trailing whitespace

amount.yaml:26 — the last value line 2022-01-01: 0 has a trailing space. make format /
prettier strips it; harmless (a carried-over nit, present one line up pre-PR) but clean before
merge.

G2 — SURVIVING_SPOUSE = $2,500 is inferred; add a clarifying comment

The statute and DOR report do not name a surviving-spouse / qualifying-widow(er) status.
Repo maps it to $2,500 (the JOINT maximum). This is defensible and the better choice
Montana derives filing status from the federal return, where a qualifying surviving spouse uses
the MFJ rate schedule and standard deduction, so line 20 is computed on the joint schedule and
the $2,500 cap is the internally consistent reading. But it is an inference, not stated in
either source; recommend a code comment noting the statute's silence and the joint-schedule
rationale.

G3 — Reconcile the 2022 test comment's "996.00" counterfactual

The 2022 case comment says "without the 2022 zeroing the leaked $1,250 would cut this to
996.00." But mt_income_tax_rebate is already de-listed from the 2022 non-refundable credit
list (non_refundable.yaml 2022 lists mt_capital_gain_credit + mt_property_tax_rebate,
not mt_income_tax_rebate), so the backfilled amount would only surface via a direct query
of the variable, not through mt_non_refundable_credits. The asserted 2_246.00 is likely
correct on its own; it is the causal story that looks wrong. Recommend the author confirm what
actually drives the phantom credit (direct-query vs credit list) so the comment/changelog is
accurate. (The amount.yaml 2022 zero-out is thus belt-and-suspenders for the direct-query
path — still worth keeping.)

G4 — Point the variable comment at its guarding test

mt_income_tax_rebate.py:17-27 — the comment asserts the rebate is "line-20-capped" but no cap
exists in this variable; it is realized by downstream credit ordering. A one-line pointer to the
guarding test would harden the implied cap against a future refactor of the credit chain that
could silently break it.


Source / Value Audit summary

All amounts match the statute — no mismatches. No value differs from the statutory maxima by
more than 0.3 for any enumerated status.

Filing status 2021-01-01 2022-01-01 Statute
SINGLE 1,250 0 (1)(b)(i) $1,250 — MATCH
HEAD_OF_HOUSEHOLD 1,250 0 (1)(b)(i) $1,250 — MATCH
JOINT 2,500 0 (1)(b)(ii) $2,500 — MATCH
SURVIVING_SPOUSE 2,500 0 not named; inferred from MFJ schedule — defensible (G2)
SEPARATE 1,250 0 (1)(b)(i) $1,250 — MATCH
  • One-time 2021 scope: correctly encoded via 2022-01-01: 0 (statute "(Temporary)",
    "one-time", "By December 31, 2023"; DOR "one-time only occurrence").
  • Liability cap: a real, twice-stated statutory requirement (15-30-2191(1)(a) and (2);
    DOR "whichever was lower"). Enforced correctly downstream for net income; not applied at
    the variable level (S1).
  • $252.40 low-liability figure: plausible and internally consistent (< the $2,500 cap;
    equals the joint pre-credit liability the test asserts); not independently recomputed to the
    cent — it functions as a regression anchor.

Validation Summary

Check Result
CI PASS (33/33)
Parameter values vs statute PASS (all 5 amounts + 5 zero-outs match)
One-time 2021 scoping (2022+ zero-out) PASS — delivered, tested (S2 note)
Variable-level liability cap (per PR body) FAIL — not delivered (comment-only); net income still correct (S1)
Net tax liability / state_income_tax correctness PASS (downstream max_(…,0) floor)
References corroborate values (live link) FAIL — terminated/reused section (C1)
Subsection citation precision FAIL — "(b)" vs (1)(b)(i)–(ii)/(2) (C2)
Test coverage — year-scoping PASS (regression-guarded)
Test coverage — liability cap boundary PARTIAL — no exact-boundary/single-low/SEPARATE (S2)
Period usage PASS (bare YYYY; 2021 active, 2022/2023 zero)
Changelog fragment PASS (fix-mt-rebate-wiring.fixed.md)
Formatting Minor — trailing whitespace (G1)
PR body accuracy FAIL — claims cap not in code; "HB 3"/"(2)(b)" errors (S1)

Review Severity: REQUEST_CHANGES

Two CRITICAL reference findings (dead-for-purpose statute link; imprecise subsection cite) are
both squarely in the "must-fix" category under the review rules (non-corroborating references
and incorrect section citations are CRITICAL). Neither blocks the delivered year-scoping fix,
which is correct and well-tested, but they must be corrected before merge. The central S1
finding — the PR body advertises a variable-level cap the code does not implement, and the
srebate motivation is unresolved — should be reconciled (correct the body or add the cap) so
the PR's description matches its behavior.

Next Steps

Run /fix-pr 8970 to:

  1. (C1) Swap the terminated statute href for a value-containing source (HB 192 session law or a
    2023 Wayback snapshot) in both amount.yaml and mt_income_tax_rebate.py.
  2. (C2) Correct the subsection citation to 15-30-2191(1)(b)(i)–(ii) and (2).
  3. (S1) Reconcile PR body ↔ code: either correct the body (cap is emergent downstream, drop the
    srebate claim) or apply min(per_person_amount, line-20 share) in the formula; fix the
    "HB 3" → HB 192/816 and "(2)(b)" → (1)(b)/(2) PR-body citations.
  4. (S2) Add exact-at-cap and cap±1 cases, a single-filer low-liability case, and a SEPARATE
    case.
  5. (G1–G4) Strip the trailing whitespace, add the SURVIVING_SPOUSE rationale comment, reconcile
    the 2022 "996.00" comment, and point the variable comment at its guarding test.

🤖 Generated with Claude Code /review-program

PavelMakarchuk and others added 2 commits August 27, 2026 01:12
…t the tax

The earlier comment claimed the leaked $1,250 would cut 2022 Montana tax
from $2,246 to $996. That is wrong, and verified so by counterfactual:
credits/non_refundable.yaml already drops mt_income_tax_rebate from the
list at 2022-01-01, so removing the amount.yaml sunset leaves
mt_non_refundable_credits at 0 and mt_income_tax at 2,246 unchanged - only
mt_income_tax_rebate itself moves, 0 -> 1,250.

The discriminating assertion is therefore the rebate variable alone; the
tax and credit-aggregate pins are unchanged controls, and the changelog
now scopes the fix to the reported value that mis-feeds consumers such as
the TAXSIM srebate comparison.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PavelMakarchuk

Copy link
Copy Markdown
Collaborator Author

Correction to my previous comment — one claim in it was wrong, pushed as dd53545.

I wrote that the 2022 guard was discriminating because "without the zeroing the leaked $1,250 would cut this filer's tax from $2,246.00 to $996.00." That is not true. I verified it by counterfactual — stripping the 2022-01-01: 0 entries and recomputing:

with the sunset without it
mt_income_tax_rebate 0 1,250
mt_non_refundable_credits 0 0
mt_income_tax 2,246.00 2,246.00

credits/non_refundable.yaml already drops mt_income_tax_rebate from the list at 2022-01-01, so the amount never reaches the credit aggregate in 2022 regardless. Montana tax is unaffected either way.

What this changes. The fix is still correct and worth landing, but its scope is narrower than I described, and it is the scope @PavelMakarchuk originally stated in the PR description: the defect is the variable's reported value, which mis-feeds consumers of it — the TAXSIM srebate comparison in PolicyEngine/policyengine-taxsim#1070/#1068 — not the tax outcome. It also means @hua7450's framing that "every MT filer in a 2022+ computation receives a phantom rebate credit" is a shade too strong: the phantom is in the variable, not in the credit that gets applied.

Applied: the misleading test comment is replaced with the accurate one (the rebate variable is the discriminating assertion, 0 vs 1,250; mt_income_tax and mt_non_refundable_credits are pinned as unchanged controls), and the changelog is rescoped to the reported value. Full Montana suite still 558 passed.

Everything else in my previous comment stands — in particular the verified $20k-couple decomposition ($252.40 pre-credit → $0.00 before refundable credits) showing the ordered credit application already enforces MCA 15-30-2191(2)'s lesser-of rule, which is why the liability cap is correctly dropped.

Heads-up @DTrim99: your #9348 edits the same credits/non_refundable.yaml (removing mt_property_tax_rebate from the 2022 list). No file overlap with this PR, and no semantic conflict that I can see — but the two touch adjacent parts of the same Montana credit wiring, so worth being aware of the ordering.

@DTrim99

DTrim99 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

PR #8970 review — Montana 2021 income-tax rebate (scope to eligibility year)

Re-review note: This is a re-review after the author corrected the framing following an earlier review. The prior review's core contradiction — the PR body/comments/changelog claimed the formula caps the rebate at the smaller of liability or the statutory amount, while the .py implemented no such cap — is now RESOLVED in-repo: the code comment, changelog, and tests have been rewritten to honestly state that the lesser-of-liability cap is realized downstream by the ordered non-refundable-credit flooring, not inside the variable. The remaining items are a dead source link, a stale GitHub PR description, and test-coverage gaps.

Program Review

MCA 15-30-2191 establishes a one-time Montana 2021 individual income-tax rebate equal to the lesser of (a) the taxpayer's 2021 line-20 income-tax liability, or (b) a filing-status amount: $1,250 (single / head of household / married filing separately) or $2,500 (married filing jointly). It applies to tax year 2021 only (paid out in 2023).

PR #8970 scopes the rebate to its eligibility year. amount.yaml sets the 2021 amounts and adds 2022-01-01: 0 for all five filing statuses, sunsetting the rebate. The mt_income_tax_rebate.py change is comment-only (formula byte-identical to baseline; no min_ cap added) — the variable intentionally reports the uncapped statutory maximum, and the lesser-of-liability cap is realized downstream when the rebate is applied as an ordered non-refundable credit that floors net tax at zero.

Source Documents

  • MCA 15-30-2191(1)(b)(i)/(ii) (rebate amounts) and (2) (line-20 cap) — verified verbatim via Wayback snapshot.
  • MT DOR "Income Tax & Property Tax Rebate Report" (May 2024) — independently confirms the $1,250/$2,500 lesser-of amounts.
  • Enacting bills HB 192 (Ch. 44, L. 2023) and HB 816 (Ch. 764, L. 2023).

Values are fully and independently corroborated.

Branch Status

Branch is 8 commits behind main. CI is green (33/33). This is informational only and is not a review finding.

Critical

None.

After dropping a false-positive raised by the tests agent (see Validation Summary), there are no true criticals. The tax outcome is correct, the sunset is present and complete, per-return splitting is correct, and no variables were reinvented. The reviewer judgment call on the dead link is stated under Should Address #1 below.

Should Address

  1. Cited statute URL is dead-for-purpose (repeat from prior review, unaddressed). The archive.legmt.gov href in both amount.yaml (reference href) and mt_income_tax_rebate.py now 301-redirects to a reused mca.legmt.gov section rendering only "15-30-2191. Terminated." — with no rebate text, no $1,250/$2,500 amounts, and no lesser-of/line-20 language. A reader clicking the link today does not see the parameter values.

    • Severity call: the values are independently corroborated (Wayback snapshot + MT DOR May-2024 report), so this is a link-durability defect, not a mis-valuation. Given full independent corroboration, SHOULD ADDRESS is the fair classification rather than critical. It was flagged in the prior review and remains unfixed.
    • Recommended fix: replace the terminated href with a durable, value-containing source — the Wayback snapshot of the pre-termination section, or enacting bill HB 192 (Ch. 44, L. 2023, Sec. 2) — and keep the MT DOR rebate report (page-anchored, e.g. #page=8) as secondary corroboration. Apply in both files.
  2. GitHub PR description still contains the false "formula caps" claim. The in-repo artifacts (code comment, changelog, tests) were corrected, but the GitHub PR description body still reads "Formula caps at the smaller of the joint/separate-column pre-credit bases…" — the exact stale claim the prior review flagged, now contradicting the corrected code. Ask the author to update the PR description to match the corrected in-repo framing: the delivered fix is the 2022+ sunset (amount.yaml 2022-01-01: 0), and the lesser-of/line-20 cap is realized downstream by the ordered non-refundable-credit flooring, not inside the variable.

  3. Variable reports the statutory maximum, not the lesser-of rebate (design note). mt_income_tax_rebate intentionally returns the uncapped ceiling ($1,250 / $2,500), and the lesser-of-liability cap is realized downstream by credit-flooring. This is now honestly documented in the variable comment and tests. The consideration: a consumer reading the variable directly (e.g. TAXSIM srebate, distributional tallies, or a future refundable use) receives the maximum, not the actual rebate — e.g. a $20k joint couple reads $2,500 when the statutory rebate is $252.40. The MT state_income_tax outcome is provably unaffected (the ordered credit application floors net tax at zero and the rebate is restricted to 2021), so this is not a wrong tax outcome and not an undocumented trap. Carried at the regulatory agent's assigned severity of Should Address: the reported value does not equal the statutory rebate for filers with liability below the cap, and the PR's own stated motivation is a direct consumer of this variable. If the cap is deliberately deferred, the current mitigation (document + accept the known overstatement) is acceptable, but the direct-consumer value remains inaccurate by design.

  4. Trailing whitespace in amount.yaml. The added SEPARATE sunset line ( 2022-01-01: 0, amount.yaml:26) carries a trailing space. make format runs ruff on Python only and will not strip YAML whitespace, so it persists. Strip it.

  5. Test-coverage gaps. (Tests agent's valid points, distinct from its rejected critical.)

    • No direct HEAD_OF_HOUSEHOLD ($1,250) or SURVIVING_SPOUSE ($2,500) variable case — a swap of those two parameter values would go undetected. Only SINGLE and JOINT are directly tested.
    • No single-type low-liability / partial-cap case (2021 line-20 liability strictly between $0 and the statutory max, e.g. ~$600 for a single filer). This is the decisive scenario for the downstream-cap design — variable reports $1,250 while net tax floors at $0 with only part of the rebate usable. The design's core assertion is currently proven only for JOINT at full liability.
    • No 2021-vs-2022 boundary test on delivered net tax. A 2022 clone of the 2021 joint household — asserting the rebate is no longer consumed and net tax is correspondingly higher — would guard the phantom-2022 regression end-to-end.

Suggestions

  1. Reference title precision. amount.yaml's reference title cites "Section 15-30-2191 (b)" but the amounts live at (1)(b)(i)/(ii) and the cap at (2). Consider "MCA 15-30-2191(1)(b)(i)-(ii) (rebate amounts) and (2) (line-20 cap)". Not misdirecting, merely incomplete.
  2. Consider trimming the 16-line inline comment block relative to the 8-line formula (the "under-pay a one-earner couple" rationale could live in the PR description), keeping the formula scannable. Non-blocking.

Validation Summary

Confirmed good:

  • 2022 sunset present and complete — amount.yaml has 2022-01-01: 0 for all five filing statuses (SINGLE, HEAD_OF_HOUSEHOLD, JOINT, SURVIVING_SPOUSE, SEPARATE). Verified.
  • Per-return split correct — joint $2,500 is split $1,250 per spouse (p.amount["SEPARATE"] gated on is_tax_unit_head_or_spouse), summing to $2,500 (not $5,000); dependents get $0.
  • Integration net-tax guard is non-tautological — the 2021 joint case independently recomputes mt_income_tax: 6_319.02 from the tax base and pins the downstream flooring.
  • defined_for = StateCode.MT tested (AR → 0).
  • TAXSIM rows honestly input mt_income_tax_rebate: 0 (documented "not in TAXSIM35"), so they don't spuriously fail on the uncapped value.
  • No reinvented variables — reuses is_tax_unit_head_or_spouse, filing_status, and the existing mt_income_tax_before_*_joint chain plus the credits-list flooring.
  • .py change is comment-only (formula byte-identical; no min_ cap) — the intended design, now honestly documented.

Dropped false positive: The tests agent raised a CRITICAL that amount.yaml has no 2022-01-01: 0 and that the sunset lives only in non_refundable.yaml. This is a false positive — it conflated this PR with a different MT PR (#9348, which edits non_refundable.yaml; that file is not in #8970's diff). The orchestrator confirmed via git show that post-PR amount.yaml HAS 2022-01-01: 0 for all five filing statuses. The critical is dropped in full; only the tests agent's legitimate coverage-gap points are retained (Should Address #5).

Review Severity

COMMENT.

The prior review's blocking contradiction (body claimed a cap the code did not deliver) is resolved in-repo. There are no true criticals after dropping the tests-agent false positive. The remaining items are a dead-for-purpose (but corroborated) source link, a stale GitHub PR description, a documented design tradeoff, YAML trailing whitespace, and test-coverage gaps — all addressable without blocking the correct tax outcome. (REQUEST_CHANGES would be warranted only if the dead link were elevated to critical; given full independent corroboration, it is not.)

Next Steps

Run /fix-pr 8970 to address: replace the dead statute link in both files, strip the YAML trailing whitespace, and add the missing test cases (HoH/QSS variable cases, a single-type partial-cap case, and a 2021-vs-2022 net-tax boundary case). Separately, ask the author to update the GitHub PR description to match the corrected in-repo framing.

🤖 Generated with Claude Code /review-program

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix minor nits, then ready

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)

4 participants