Skip to content

Fix MT income tax applying preferential LTCG rates when net CG is negative#7862

Open
PavelMakarchuk wants to merge 3 commits intomainfrom
fix-mt-ltcg-negative-net-gains
Open

Fix MT income tax applying preferential LTCG rates when net CG is negative#7862
PavelMakarchuk wants to merge 3 commits intomainfrom
fix-mt-ltcg-negative-net-gains

Conversation

@PavelMakarchuk
Copy link
Collaborator

Summary

Fixes two interacting bugs in Montana's income tax calculation that caused $0 tax when it should be ~$20,837.

Closes #7859

Bug 1: Missing net capital gain check

mt_regular_income_tax_joint.py subtracted all positive LTCG from taxable income even when STCG losses exceeded LTCG. Per MT Form 2 and TaxAct, preferential LTCG rates should not apply when the overall net capital position is a loss.

Fix: Only subtract LTCG from taxable income when net_capital_gains (STCG + LTCG) > 0. Applied to both _joint and _indiv variants.

Bug 2: Person-level vs tax-unit mismatch in CG tax

mt_capital_gains_tax_joint was a Person-level variable, but for joint filers, LTCG and taxable income can be on different persons (taxable income is assigned to head via is_head *). Neither person had both LTCG and taxable income, so both got $0 CG tax.

Fix: Convert mt_capital_gains_tax_joint and mt_capital_gains_tax_applicable_threshold_joint from Person to TaxUnit entity, aggregating LTCG and taxable income across persons.

Test case

From TaxAct (policyengine-taxsim#755):

Variable Before fix After fix TaxAct
MT Taxable Income $361,508 $361,508 $361,508
MT Income Tax $0 $20,837 $20,837

Test plan

  • policyengine-core test policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/capital_gains/ -c policyengine_us

🤖 Generated with Claude Code

…ative

Two bugs fixed:
1. mt_regular_income_tax: only subtract LTCG from taxable income when
   net capital gains (STCG + LTCG) are positive. When STCG losses
   exceed LTCG, all income should be taxed at ordinary rates.
2. mt_capital_gains_tax_joint: convert from Person to TaxUnit entity
   to fix mismatch where LTCG and taxable income are on different
   persons in joint filing, causing both to get $0 CG tax.

Closes #7859

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Mar 25, 2026

Codecov Report

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

Additional details and impacted files
@@             Coverage Diff              @@
##             main     #7862       +/-   ##
============================================
+ Coverage   71.81%   100.00%   +28.18%     
============================================
  Files        4084         6     -4078     
  Lines       58886       138    -58748     
  Branches      288         4      -284     
============================================
- Hits        42288       138    -42150     
+ Misses      16583         0    -16583     
+ Partials       15         0       -15     
Flag Coverage Δ
unittests 100.00% <100.00%> (+28.18%) ⬆️

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

☔ View full report in Codecov by Sentry.
📢 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.

Integration tests:
- Positive net CG with STCG losses: confirms preferential rates still apply
- LTCG on head, wages on spouse: confirms TaxUnit aggregation works cross-person
- Both spouses with LTCG: confirms combined LTCG aggregation

Unit tests:
- Negative net CG: confirms $0 CG tax when STCG losses exceed LTCG
- Positive net CG with STCG losses: confirms full LTCG gets preferential rates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@PavelMakarchuk PavelMakarchuk marked this pull request as ready for review March 25, 2026 18:07
@hua7450
Copy link
Collaborator

hua7450 commented Mar 25, 2026

Review: Verified against MT Form 2 (2024) and MCA 15-30-2103

The core fix (no preferential LTCG rates when net CG <= 0) is correct per Form 2 Line 1: "If you do not have a net long-term capital gain, skip lines 2 through 10 and enter 0 (zero) on line 11."

Two issues found:

1. "Lesser of line 15 or line 16" not fully implemented

The Form 2 instructions (p. 11) say Line 2 should be:

"the lesser of federal Schedule D, line 15 or Schedule D, line 16"

  • Schedule D Line 15 = net LTCG only
  • Schedule D Line 16 = net STCG + net LTCG (combined)

The PR currently uses max_(ltcg, 0) (i.e., full net LTCG) when net_cg > 0. This doesn't match the "lesser of" rule when there are partial short-term losses:

Scenario Line 15 (net LTCG) Line 16 (combined) Form 2 Line 2 should be PR uses
LTCG=50K, STCG=-30K 50,000 20,000 20,000 (lesser) 50,000

Suggested fix: min_(max_(ltcg, 0), max_(net_cg, 0)) instead of max_(ltcg, 0) in all three files (mt_regular_income_tax_joint, mt_regular_income_tax_indiv, mt_capital_gains_tax_joint).

2. Same bug in mt_capital_gains_tax_indiv.py

The _indiv variant still uses LTCG directly without the net_cg > 0 check. Impact is limited to married-filing-separately-on-same-return, but it's the same logical bug. Worth fixing here or filing a follow-up.

Sources:

@DTrim99
Copy link
Collaborator

DTrim99 commented Mar 25, 2026

Program Review: PR #7862 - Fix MT income tax applying preferential LTCG rates when net CG is negative

Source Documents

  • MT Form 2 Instructions (2023): Page 6
  • MT Form 2 (2024): Page 2
  • Validation: TaxAct comparison confirms expected MT Income Tax = $20,837

Bugs Fixed

Bug 1: Missing net capital gain check

  • mt_regular_income_tax_joint.py subtracted all positive LTCG from taxable income even when STCG losses exceeded LTCG
  • Per MT Form 2, preferential LTCG rates should not apply when the overall net capital position is a loss
  • Fix: Only subtract LTCG when net_capital_gains (STCG + LTCG) > 0

Bug 2: Person-level vs TaxUnit mismatch in CG tax

  • mt_capital_gains_tax_joint was a Person-level variable
  • For joint filers, LTCG and taxable income can be on different persons
  • Neither person had both values, so both got $0 CG tax
  • Fix: Convert to TaxUnit entity, aggregate LTCG and taxable income across persons

Critical (Must Fix)

None identified.

Should Address

None identified.

Suggestions

None - this is a well-implemented bug fix.

Files Changed

Type Files
Variables mt_capital_gains_tax_applicable_threshold_joint.py, mt_capital_gains_tax_joint.py, mt_regular_income_tax_indiv.py, mt_regular_income_tax_joint.py
Tests mt_capital_gains_tax_applicable_threshold_joint.yaml, mt_capital_gains_tax_joint.yaml, integration.yaml
Changelog fix-mt-ltcg-negative-net-gains.fixed.md

Test Coverage

New test scenarios added:

  1. Net CG negative: LTCG=50K, STCG=-80K → no preferential treatment, $0 CG tax
  2. Positive net CG with STCG losses: LTCG=50K, STCG=-20K → preferential rates apply
  3. Cross-person aggregation: LTCG on head, wages on spouse → proper TaxUnit aggregation
  4. Both spouses with LTCG: Combined LTCG aggregation at TaxUnit level
  5. Issue MT income tax incorrectly applies preferential LTCG rates when net capital gains are negative #7859 reproduction: Exact scenario from bug report validates fix

Validation Summary

Check Result
Regulatory Accuracy ✅ Logic matches MT Form 2 instructions
Code Patterns ✅ Proper use of add(), where(), entity levels
Test Coverage ✅ Comprehensive (4 new integration tests, 2 new unit tests)
CI Status ✅ All checks passing
Changelog ✅ Present

Review Severity: APPROVE

This is a well-implemented bug fix with:

  • Proper validation against TaxAct ($20,837 expected vs actual)
  • Comprehensive test coverage for edge cases
  • Correct entity-level changes for joint filing scenarios
  • Clear comments explaining the logic

🤖 Generated with Claude Code

@DTrim99
Copy link
Collaborator

DTrim99 commented Mar 25, 2026

Minor Code Quality Suggestion

The code validator found that an existing variable net_capital_gains (in policyengine_us/variables/household/income/tax_unit/net_capital_gains.py) already calculates long_term_capital_gains + short_term_capital_gains.

Optional improvement: Replace the inline calculation:

ltcg = add(tax_unit, period, ["long_term_capital_gains"])
stcg = add(tax_unit, period, ["short_term_capital_gains"])
net_cg = ltcg + stcg

With:

ltcg = add(tax_unit, period, ["long_term_capital_gains"])
net_cg = tax_unit("net_capital_gains", period)

This applies to mt_capital_gains_tax_joint.py and mt_regular_income_tax_joint.py. The Person-level files (_indiv) would need to stay as-is since net_capital_gains is TaxUnit-level.

This is not blocking - just a maintainability improvement. The PR is approved as-is.

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.

MT income tax incorrectly applies preferential LTCG rates when net capital gains are negative

4 participants