Repository navigation
Apply Massachusetts Schedule B capital losses to dividends before long-term gains - #9906
Conversation
…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>
hua7450
left a comment
There was a problem hiding this comment.
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>
|
Thanks for the review. Addressed in 1c61403. 1.
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: 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.
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:
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. 5. Labels. Changed to "... applied against dividends" on both loss variables. I also changed 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. 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 Tests. |
hua7450
left a comment
There was a problem hiding this comment.
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.
# 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>
hua7450
left a comment
There was a problem hiding this comment.
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.
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>
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).
ma_short_term_loss_against_dividends(line 20) andma_long_term_loss_against_dividends(line 32).ma_part_a_aginow uses them; its value does not change.ma_part_a_adjusted_dividends(line 33).ma_part_a_taxable_dividend_income,ma_part_a_div_excess_exemption,ma_part_a_cg_excess_exemptionandma_part_a_taxable_capital_gains_incomeuse it in place of gross dividends.ma_part_c_agisubtracts only the short-term loss left after line 20.ma_part_c_gross_incomeis unchanged; since Keep capital gains and losses out of Massachusetts Part B income #9954,ma_part_b_gross_incomedoes not depend on it.ma_part_c_agiandma_long_term_loss_against_dividendsreadlong_term_capital_gainsplusnon_sch_d_capital_gains(Schedule D, line 6), asma_part_a_agidoes 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.yamlhas ten returns. In the first five (2022), each expectedma_income_tax_before_creditsequals the NBER TAXSIM-35staxbcfor the same return: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_gainsreads.All 678 tests under
gov/states/maand 746 other tests with Massachusetts households pass.Overlap
#9954 has merged. This branch merges
main(resolvingma_part_a_agi) and adds the distributions in bae9b4c; without them, #9954's four distribution cases fail.#9991 adds
ma_long_term_capital_gainsfor the long-term Schedule D amount (with Form 4797 amounts). Whichever of #9991 and this PR merges second switchesma_part_a_agi,ma_part_c_agiandma_long_term_loss_against_dividendsto read it.#9894 (MaxGhenis) changes
addtotax_unit_non_dep_addin several of the same files. The changes are independent; whichever PR merges second needs a small rebase.🤖 Generated with Claude Code