Skip to content

Apply Massachusetts Schedule B capital losses to dividends before long-term gains - #9906

Merged
PavelMakarchuk merged 5 commits into
mainfrom
ma-schedule-b-loss-order
Oct 8, 2026
Merged

PavelMakarchuk merged 5 commits into
mainfrom
ma-schedule-b-loss-order

Conversation

@PavelMakarchuk

@PavelMakarchuk PavelMakarchuk commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #9905.

What changes

Massachusetts Schedule B applies a short-term capital loss first against up to $2,000 of dividends (line 20), and only the rest against long-term gains (line 22). A long-term loss left after short-term gains also reduces the dividends (line 32). Part A then taxes the dividends left (line 33).

  • New ma_short_term_loss_against_dividends (line 20) and ma_long_term_loss_against_dividends (line 32). ma_part_a_agi now uses them; its value does not change.
  • New ma_part_a_adjusted_dividends (line 33). ma_part_a_taxable_dividend_income, ma_part_a_div_excess_exemption, ma_part_a_cg_excess_exemption and ma_part_a_taxable_capital_gains_income use it in place of gross dividends.
  • ma_part_c_agi subtracts only the short-term loss left after line 20. ma_part_c_gross_income is unchanged; since Keep capital gains and losses out of Massachusetts Part B income #9954, ma_part_b_gross_income does not depend on it.
  • ma_part_c_agi and ma_long_term_loss_against_dividends read long_term_capital_gains plus non_sch_d_capital_gains (Schedule D, line 6), as ma_part_a_agi does since Keep capital gains and losses out of Massachusetts Part B income #9954.

Taxable interest stays Massachusetts bank interest (Form 1, line 5), so it is not Schedule B line 9 income and does not absorb the line 20 loss.

Tests

ma_schedule_b_capital_losses.yaml has ten returns. In the first five (2022), each expected ma_income_tax_before_credits equals the NBER TAXSIM-35 staxbc for the same return:

Return Main This PR = TAXSIM
Wages 60,000, dividends 1,205, short-term loss 1,106 2,520.25 2,464.95
Wages 60,000, dividends 3,000, short-term loss 2,500 2,610.00 2,510.00
Wages 60,000, dividends 1,205, long-term loss 1,000 2,520.25 2,470.25
Dividends 10,000, short-term loss 5,000, long-term gain 50,000 2,210.00 2,310.00
Wages 60,000, bank interest 10,000, dividends 500, short-term loss 1,500, long-term gain 5,000 3,125.00 3,150.00

The other five are computed line by line from the 2022 and 2023 Schedules B and D. The last one has capital gain distributions of 5,000 that cover a 3,000 long-term loss, so Schedule D, line 15 is a gain and Schedule B, line 32 is omitted (tax 2,610); it fails without the non_sch_d_capital_gains reads.

All 678 tests under gov/states/ma and 746 other tests with Massachusetts households pass.

Overlap

#9954 has merged. This branch merges main (resolving ma_part_a_agi) and adds the distributions in bae9b4c; without them, #9954's four distribution cases fail.

#9991 adds ma_long_term_capital_gains for the long-term Schedule D amount (with Form 4797 amounts). Whichever of #9991 and this PR merges second switches ma_part_a_agi, ma_part_c_agi and ma_long_term_loss_against_dividends to read it.

#9894 (MaxGhenis) changes add to tax_unit_non_dep_add in several of the same files. The changes are independent; whichever PR merges second needs a small rebase.

🤖 Generated with Claude Code

…g-term gains

Schedule B line 20 applies a short-term loss first against up to $2,000 of
dividends, and line 22 applies only the rest against long-term gains. Part A
then taxes the dividends left (line 33). Part A taxable income used the gross
dividends, and Part C subtracted the full short-term loss.

Fixes #9905

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PavelMakarchuk
PavelMakarchuk requested a review from hua7450 October 6, 2026 15:54

@hua7450 hua7450 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.

The Schedule B ordering (line 20 first, then line 22, line 32 after line 25, combined $2,000 cap) now matches c.62 s.2(c)(2) and (4), and all five 2022 cases recompute line by line against the form. One formula gap, so requesting changes:

ma_part_c_agi overstates Part C AGI when the short-term loss exceeds the long-term gain. The add-back min_(max_(0, ltcg), part_c_gross_income + losses_against_dividends) starts from ma_part_c_gross_income, which is already floored at zero (max_(0, ltcg + min_(0, stcg))), so the part of the loss that was never absorbed by the gain gets added back anyway. The form applies only the remaining loss: "If Schedule B, line 21 is a loss and Schedule D, line 13 is greater than 0, enter the smaller of Schedule B, line 21 (considered as a positive amount) or Schedule D, line 13 in Schedule B, line 22 and in Schedule D, line 14", and Schedule D line 15: "If line 13 is greater than 0, subtract line 14 from line 13."

Example, 2022 joint, wages 60,000, dividends 3,000, short-term -5,000, long-term 4,000: line 20 = 2,000, line 21 = -3,000, line 22 = 3,000, Schedule D line 15 = 1,000. The branch gives Part C AGI = min(4,000, 0 + 2,000) = 2,000 (tax 2,610 vs form 2,560). With long-term 1,000 the form has Part C = 0 (the remaining 2,000 carries over) but the branch gives 1,000; main gives 0 there, so this input regresses. Suggested: max_(0, long_term_capital_gains - (short_term_capital_loss - losses_against_dividends)), with the short-term loss as a positive amount. ma_agi (No Tax Status, Limited Income Credit) inherits whatever Part C AGI says, so the fix matters beyond the 5% line.

Nits: add a test with 0 < long-term gain < short-term loss (it would have caught this), a combined-cap case (short-term 1,500 plus long-term 1,000 against 3,000 of dividends: line 20 = 1,500, line 32 = 500), and one 2023+ case at the 8.5% rate; the labels on the two new loss variables say "interest and dividends" while the base is dividends only; consider citing s.2(c)(2) and (4) on the new variables.

Limits: formulas replayed by hand and with a small grid rather than by running the suite; the 2023 form itself and the TAXSIM outputs were not reproduced; the line 38 exemption split, collectibles and carryovers are pre-existing and out of scope here.

Part C AGI added the short-term loss applied against dividends back to
a Part C gross income that was already floored at zero, so a short-term
loss larger than the long-term gain left too much Part C income.
Schedule B, line 22 and Schedule D, lines 14 and 15 apply only the line
21 remainder against the long-term gain.

Add cases with a long-term gain below the short-term loss, a combined
$2,000 cap, and a 2023 case at the 8.5% rate. Label the new loss
variables as applied against dividends and cite c.62 s.2(c)(2) and (4).

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

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. Addressed in 1c61403.

1. ma_part_c_agi overstates Part C (blocking). Confirmed. ma_part_c_gross_income is max_(0, ltcg + min_(0, stcg)), so it is already 0 when the short-term loss is larger than the gain, and the old add-back put back loss that the gain never absorbed. The form applies only the line 21 remainder:

2022 Schedule B, line 21: "Available short-term losses. Combine lines 19c and 20."

2022 Form 1 instructions, p. 25 (Schedule B, line 22): "If Schedule B, line 21 is a loss and Schedule D, line 13 is greater than 0, enter the smaller of Schedule B, line 21 (considered as a positive amount) or Schedule D, line 13 in Schedule B, line 22 and in Schedule D, line 14."

2022 Schedule D, line 15: "Subtotal. If line 13 is greater than 0, subtract line 14 from line 13. If line 13 is less than 0, combine lines 13 and 14."

The statute says the same, in c.62 s.2(c)(2)(a): "any remaining excess of the Part A net capital loss for the year shall be applied against capital gains included in Part C gross income."

The formula is now your suggestion: max_(0, long_term_capital_gains - (short_term_capital_loss - ma_short_term_loss_against_dividends)). ma_part_c_gross_income is not changed, because ma_part_b_gross_income uses it.

2. Test with 0 < long-term gain < short-term loss. I added both of your examples. I computed them line by line from the 2022 forms. The arithmetic is in the YAML comments.

  • Long-term gain 4,000: line 20 = 2,000, line 21 = -3,000, line 22 = 3,000, Schedule D line 15 = 1,000. Part C AGI = 1,000 and tax = 2,560.
  • Long-term gain 1,000: line 22 = 1,000, Schedule D line 15 = 0, and 2,000 carries over. Part C AGI = 0 and tax = 2,510.

With the old formula, these two cases fail (2 failed, 7 passed) and the other cases pass.

3. Combined-cap case. Added it: short-term -1,500 and long-term -1,000 against 3,000 of dividends. Line 20 = 1,500, line 32 = 500, line 33 = 1,000, and tax = 2,510. The code did not need a change for this case. The line 32 worksheet takes line 30 out of the $2,000 cap:

2022 Form 1 instructions, p. 26 (Schedule B, line 32 and Schedule D, line 16 worksheet): "2. Enter the lesser of line 1 or $2,000 ... 3. Enter the amount from Schedule B, line 30. 4. Subtract line 3 from line 2."

Section 2(c)(4) makes the cap an "aggregate amount of $2,000 in Part A capital loss and Part C capital loss". One note: the Schedule D line 15 instruction (2022 instructions, p. 28) says "If Schedule D, line 15 is a loss, and Schedule B, line 21 is 0 or less, omit Schedule D, line 16". Read literally, that skips line 32 when line 21 is exactly 0. The test follows the worksheet and s.2(c)(4), which give 500.

4. 2023+ case at the 8.5% rate. Added a 2023 case with dividends 1,000, short-term 2,000 and long-term 4,000. Line 39 = 2,000 at 8.5% (2023 Form 1, line 23a, "× .085"), Part C = 4,000, and tax = 2,510 + 170 + 200 = 2,880. 8.5% income is 0 whenever line 20 or line 32 applies. Line 20 needs a line 19c loss, and line 32 needs line 25 to absorb all of line 24. So this case checks that the new Part C formula and the line 33 split leave the short-term-gain path unchanged.

I did not use a short-term gain with a long-term loss (line 25) for the 2023 case, because that input shows a separate issue that exists before this PR. ma_part_b_gross_income subtracts Part A gross income, which has the gross short-term gain, from MA gross income, which nets the long-term loss. With wages 60,000, dividends 1,000, short-term 3,000 and long-term -1,000, Part B gross income is 59,000, but Form 1 has 60,000. This PR does not change it.

5. Labels. Changed to "... applied against dividends" on both loss variables. I also changed ma_part_a_adjusted_dividends to "MA Part A adjusted dividends", for the same reason.

6. Citations. Both new loss variables now cite M.G.L. c.62 s.2: (c)(2)(a) and (c)(4) on the short-term variable, and (c)(2)(b) and (c)(4) on the long-term variable. ma_part_c_agi also cites the 2023 Schedule D.

Sources. The NBER 2022 folder has only Form 1 and the instructions. For the 2022 Schedules B and D, I used the archived mass.gov copies (Schedule B, Schedule D). For 2023, I used the NBER Form 1 and Schedules B and D. I extracted all of them with pdftotext -layout.

Tests. policyengine_us/tests/policy/baseline/gov/states/ma: 662 passed (the 9 cases in ma_schedule_b_capital_losses.yaml are included). These also pass: the MA partner edge cases, contrib/states/ma, gov/states/tax/income/state_agi.yaml and contrib/state_dependent_exemptions (80 passed). ruff format and ruff check pass on the changed files. The changelog fragment is unchanged, because the scope is the same.

@hua7450 hua7450 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.

Approving. Read all 11 files at 1c61403 plus ma_part_a_gross_income, ma_part_c_gross_income, ma_gross_income, ma_agi and the capital gain inputs, against 2022 Schedule B lines 19c to 22, Schedule D lines 13 to 15 and 19, the Form 1 instructions (pages 25 and 28) and c.62 s.2(c)(2)(a).

The blocking item is fixed in the code: ma_part_c_agi is now max_(0, long_term_capital_gains - (short_term_capital_loss - ma_short_term_loss_against_dividends)), which is Schedule B line 21 applied through line 22 and Schedule D line 14, and ma_part_c_gross_income is no longer read by it, so the floored amount is not double-applied. Walked the full Schedule B ordering at head (line 20, line 22, line 25, the page 26 worksheet with the combined 2,000 cap under s.2(c)(4), line 33 into the four taxable-income files, excess exemption into Schedule D line 20) and the loss-plus-gain, both-loss, zero-dividend and line-21-zero edge cases: every one matches the form, and against main the head fixes the short-term-loss-above-gain cases and regresses none. All nine cases in ma_schedule_b_capital_losses.yaml recompute line by line, including the two added ones (Part C 1,000 and 0, tax 2,560 and 2,510) and the 2023 8.5% case. The earlier nits are addressed.

Merge interaction with PR 9954, which I approved earlier today: both rewrite ma_part_a_agi.py (one conflicting hunk), and 9954 moves non_sch_d_capital_gains onto Schedule D. Whichever lands second needs the distributions added to the long_term_capital_gains reads in ma_part_c_agi and ma_long_term_loss_against_dividends, otherwise they drop out of Part C. Neither PR changes the other's expected values.

Nit, not blocking: a one-line comment in ma_part_c_agi that Schedule D line 15 is read as "combine lines 13 and 14 only when line 21 is a loss" would record the interpretation the formula relies on.

Pre-existing, for follow-ups: the Part B residual (PR 9954), the line 38 excess-exemption split between dividends and gains, collectibles, and non-bank interest on line 9.

PavelMakarchuk and others added 2 commits October 7, 2026 12:18
# Conflicts:
#	policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_agi.py
After #9954, Massachusetts Schedule D, line 13 includes capital gain
distributions reported without a federal Schedule D (line 6). Read them
in ma_part_c_agi and ma_long_term_loss_against_dividends too, so
distributions stay in Part C and a long-term loss they cover is not
applied against dividends on Schedule B, line 32.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PavelMakarchuk
PavelMakarchuk requested a review from hua7450 October 7, 2026 16:24

@hua7450 hua7450 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.

Re-approving at bae9b4c. The new commit does exactly what the 9954 merge needed: ma_part_c_agi and ma_long_term_loss_against_dividends now read ["long_term_capital_gains", "non_sch_d_capital_gains"], so Schedule D line 13 carries the line 6 distributions in both places, and the added case recomputes by hand: line 13 = -3,000 + 5,000 = 2,000, no loss left for line 32, Part A dividends 1,000, Part C AGI 2,000, tax 5% x (49,200 + 1,000) + 5% x 2,000 = 2,610. Nothing else in the MA files changed; the rest of the diff is the merge of main.

The states-shard-3 failure is not this PR: it is ms_health_savings_account_adjustment.yaml ("ms_agi@2025: [-2000, 50000] differs from [0, 50000]"), the interaction between the merged 9865 and 9885 that PR 9992 fixed on main at 17:26 UTC, after this branch's 16:18 UTC merge of main. A re-merge clears it; the "Unpublished release wheel" failure is the same root cause.

One more read to switch when PR 9991 (Form 4797 on Schedule D) lands: it introduces ma_long_term_capital_gains as the Schedule D long-term amount, so whichever of the two merges second should point these two reads at that variable instead of the two-item list.

@PavelMakarchuk
PavelMakarchuk merged commit adcfb40 into main Oct 8, 2026
35 checks passed
@PavelMakarchuk
PavelMakarchuk deleted the ma-schedule-b-loss-order branch October 8, 2026 13:02
PavelMakarchuk pushed a commit that referenced this pull request Oct 8, 2026
Resolve ma_part_a_agi with main's #9906 structure (Schedule B loss order
variables), and read Schedule D line 13 from ma_long_term_capital_gains
in ma_part_a_agi, ma_part_c_agi and ma_long_term_loss_against_dividends,
so Form 4797 Part II amounts reach every line 13 use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Massachusetts Part A taxes gross dividends after Schedule B applies capital losses against them

2 participants