From 77307b21cd115ec8fde30b1e8fb79a59fd1f141c Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Tue, 6 Oct 2026 11:54:34 -0400 Subject: [PATCH 1/3] Apply Massachusetts Schedule B capital losses to dividends before long-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 --- changelog.d/ma-schedule-b-loss-order.fixed.md | 1 + .../ma_schedule_b_capital_losses.yaml | 140 ++++++++++++++++++ .../ma_long_term_loss_against_dividends.py | 38 +++++ .../ma_part_a_adjusted_dividends.py | 27 ++++ .../adjusted_gross_income/ma_part_a_agi.py | 44 ++---- .../adjusted_gross_income/ma_part_c_agi.py | 20 ++- .../ma_short_term_loss_against_dividends.py | 27 ++++ .../ma_part_a_cg_excess_exemption.py | 3 +- .../ma_part_a_div_excess_exemption.py | 3 +- .../ma_part_a_taxable_capital_gains_income.py | 3 +- .../ma_part_a_taxable_dividend_income.py | 3 +- 11 files changed, 269 insertions(+), 40 deletions(-) create mode 100644 changelog.d/ma-schedule-b-loss-order.fixed.md create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml create mode 100644 policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py create mode 100644 policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py create mode 100644 policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py diff --git a/changelog.d/ma-schedule-b-loss-order.fixed.md b/changelog.d/ma-schedule-b-loss-order.fixed.md new file mode 100644 index 00000000000..b3484a3378c --- /dev/null +++ b/changelog.d/ma-schedule-b-loss-order.fixed.md @@ -0,0 +1 @@ +Apply Massachusetts Schedule B short-term and long-term losses to dividends before long-term gains, and tax Part A on the dividends left (lines 20, 22 and 33). diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml new file mode 100644 index 00000000000..852b6da8283 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml @@ -0,0 +1,140 @@ +# Massachusetts Schedule B applies a short-term loss first against up to +# $2,000 of dividends (line 20), then against long-term gains (line 22). +# Expected tax values match the NBER TAXSIM-35 MA tax before credits +# (staxbc) for the same returns. + +- name: Short-term loss below the dividends leaves the net dividends as Part A income + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + qualified_dividend_income: 1_205 + short_term_capital_gains: -1_106 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + ma_short_term_loss_against_dividends: 1_106 + ma_part_a_agi: 99 + ma_part_a_adjusted_dividends: 99 + ma_part_a_taxable_dividend_income: 99 + ma_part_c_agi: 0 + # 5% x (49,200 Part B + 99 Part A). + ma_income_tax_before_credits: 2_464.95 + +- name: The short-term loss applied against dividends is capped at $2,000 + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + qualified_dividend_income: 3_000 + short_term_capital_gains: -2_500 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + ma_short_term_loss_against_dividends: 2_000 + ma_part_a_taxable_dividend_income: 1_000 + ma_income_tax_before_credits: 2_510 + +- name: A long-term loss also reduces the dividends taxed in Part A + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + qualified_dividend_income: 1_205 + long_term_capital_gains: -1_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + ma_long_term_loss_against_dividends: 1_000 + ma_part_a_taxable_dividend_income: 205 + ma_income_tax_before_credits: 2_470.25 + +- name: Only the short-term loss left after the dividends offsets long-term gains + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + qualified_dividend_income: 10_000 + short_term_capital_gains: -5_000 + long_term_capital_gains: 50_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + ma_short_term_loss_against_dividends: 2_000 + ma_part_a_agi: 8_000 + # The $8,800 exemption is unused in Part B: $8,000 goes to the dividends + # and $800 to the long-term gains. + ma_part_a_taxable_dividend_income: 0 + ma_part_a_cg_excess_exemption: 800 + # 50,000 - (5,000 - 2,000) + ma_part_c_agi: 47_000 + ma_part_c_taxable_income: 46_200 + ma_income_tax_before_credits: 2_310 + +- name: Massachusetts bank interest does not absorb the short-term loss + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + taxable_interest_income: 10_000 + qualified_dividend_income: 500 + short_term_capital_gains: -1_500 + long_term_capital_gains: 5_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + ma_short_term_loss_against_dividends: 500 + ma_part_a_agi: 0 + # 5,000 - (1,500 - 500) + ma_part_c_agi: 4_000 + # 5% x (59,000 Part B + 0 Part A + 4,000 Part C). + ma_income_tax_before_credits: 3_150 diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py new file mode 100644 index 00000000000..3df14b05c65 --- /dev/null +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py @@ -0,0 +1,38 @@ +from policyengine_us.model_api import * + + +class ma_long_term_loss_against_dividends(Variable): + value_type = float + entity = TaxUnit + label = "MA long-term capital losses applied against interest and dividends" + documentation = ( + "Massachusetts Schedule B, line 32: long-term losses left after " + "short-term gains, up to $2,000 of interest and dividends less the " + "short-term losses already applied in line 20." + ) + unit = USD + definition_period = YEAR + reference = ( + "https://taxsim.nber.org/historical_state_tax_forms/MA/2024/dor-2024-inc-sch-b-(form-1).pdf#page=2", + "https://www.mass.gov/doc/2024-form-1-instructions/download#page=25", + ) + defined_for = StateCode.MA + + def formula(tax_unit, period, parameters): + short_term_capital_gains = add(tax_unit, period, ["short_term_capital_gains"]) + nonnegative_short_term_capital_gains = max_(0, short_term_capital_gains) + long_term_capital_gains = add(tax_unit, period, ["long_term_capital_gains"]) + long_term_capital_loss = max_(0, -long_term_capital_gains) + # Schedule B, line 25: long-term losses first offset short-term gains. + remaining_long_term_loss = max_( + 0, long_term_capital_loss - nonnegative_short_term_capital_gains + ) + dividends = add(tax_unit, period, ["dividend_income"]) + short_term_loss_applied = tax_unit( + "ma_short_term_loss_against_dividends", period + ) + cap = parameters( + period + ).gov.states.ma.tax.income.capital_gains.deductible_against_interest_dividends + remaining_cap = max_(0, min_(dividends, cap) - short_term_loss_applied) + return min_(remaining_cap, remaining_long_term_loss) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py new file mode 100644 index 00000000000..6ac29dccc78 --- /dev/null +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class ma_part_a_adjusted_dividends(Variable): + value_type = float + entity = TaxUnit + label = "MA Part A adjusted interest and dividends" + documentation = ( + "Massachusetts Schedule B, line 33: dividends less the short-term " + "(line 20) and long-term (line 32) losses applied against them." + ) + unit = USD + definition_period = YEAR + reference = "https://taxsim.nber.org/historical_state_tax_forms/MA/2024/dor-2024-inc-sch-b-(form-1).pdf#page=2" + defined_for = StateCode.MA + + def formula(tax_unit, period, parameters): + dividends = add(tax_unit, period, ["dividend_income"]) + losses_applied = add( + tax_unit, + period, + [ + "ma_short_term_loss_against_dividends", + "ma_long_term_loss_against_dividends", + ], + ) + return max_(0, dividends - losses_applied) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_agi.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_agi.py index 2120a051cec..1ab652847db 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_agi.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_agi.py @@ -13,23 +13,7 @@ class ma_part_a_agi(Variable): def formula(tax_unit, period, parameters): part_a_gross_income = tax_unit("ma_part_a_gross_income", period) short_term_capital_gains = add(tax_unit, period, ["short_term_capital_gains"]) - short_term_capital_loss = max_(0, -short_term_capital_gains) nonnegative_short_term_capital_gains = max_(0, short_term_capital_gains) - interest_and_dividends = add( - tax_unit, period, ["taxable_interest_income", "dividend_income"] - ) - - tax = parameters(period).gov.states.ma.tax.income - interest_dividends_deduction_cap = ( - tax.capital_gains.deductible_against_interest_dividends - ) - short_term_loss_against_interest_dividends = min_( - interest_dividends_deduction_cap, - min_( - interest_and_dividends, - short_term_capital_loss, - ), - ) long_term_capital_gains = add(tax_unit, period, ["long_term_capital_gains"]) long_term_capital_loss = max_(0, -long_term_capital_gains) @@ -37,24 +21,17 @@ def formula(tax_unit, period, parameters): long_term_capital_loss, nonnegative_short_term_capital_gains, ) - remaining_long_term_loss = ( - long_term_capital_loss - long_term_loss_against_short_term_gain - ) - remaining_interest_dividends = ( - interest_and_dividends - short_term_loss_against_interest_dividends - ) - remaining_interest_deduction_cap = ( - interest_dividends_deduction_cap - - short_term_loss_against_interest_dividends - ) - long_term_loss_against_interest_dividends = min_( - remaining_interest_deduction_cap, - min_( - remaining_long_term_loss, - remaining_interest_dividends, - ), + # Schedule B, lines 20 and 32. + losses_against_dividends = add( + tax_unit, + period, + [ + "ma_short_term_loss_against_dividends", + "ma_long_term_loss_against_dividends", + ], ) + tax = parameters(period).gov.states.ma.tax.income long_term_capital_gains_on_collectibles = add( tax_unit, period, ["long_term_capital_gains_on_collectibles"] ) @@ -67,8 +44,7 @@ def formula(tax_unit, period, parameters): ) deductions = ( - short_term_loss_against_interest_dividends - + long_term_loss_against_interest_dividends + losses_against_dividends + long_term_loss_against_short_term_gain + long_term_gains_on_collectibles_deduction ) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py index c5e5172f09c..ccda2bf2df5 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py @@ -7,7 +7,23 @@ class ma_part_c_agi(Variable): label = "MA Part C AGI" unit = USD definition_period = YEAR - reference = "https://www.mass.gov/info-details/mass-general-laws-c62-ss-2" + reference = ( + "https://www.mass.gov/info-details/mass-general-laws-c62-ss-2", + "https://taxsim.nber.org/historical_state_tax_forms/MA/2024/dor-2024-inc-sch-b-(form-1).pdf#page=2", + ) defined_for = StateCode.MA - adds = ["ma_part_c_gross_income"] + def formula(tax_unit, period, parameters): + # Part C gross income nets all short-term losses against long-term + # gains. Schedule B applies a short-term loss first against up to + # $2,000 of dividends (line 20), so only the rest offsets long-term + # gains (line 22). + part_c_gross_income = tax_unit("ma_part_c_gross_income", period) + long_term_capital_gains = add(tax_unit, period, ["long_term_capital_gains"]) + losses_against_dividends = tax_unit( + "ma_short_term_loss_against_dividends", period + ) + return min_( + max_(0, long_term_capital_gains), + part_c_gross_income + losses_against_dividends, + ) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py new file mode 100644 index 00000000000..70e8edd29d1 --- /dev/null +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class ma_short_term_loss_against_dividends(Variable): + value_type = float + entity = TaxUnit + label = "MA short-term capital losses applied against interest and dividends" + documentation = ( + "Massachusetts Schedule B, line 20. Interest is treated as interest " + "from Massachusetts banks (5.0% income), so line 9 is the dividends." + ) + unit = USD + definition_period = YEAR + reference = ( + "https://taxsim.nber.org/historical_state_tax_forms/MA/2024/dor-2024-inc-sch-b-(form-1).pdf#page=2", + "https://www.mass.gov/doc/2024-form-1-instructions/download#page=25", + ) + defined_for = StateCode.MA + + def formula(tax_unit, period, parameters): + short_term_capital_gains = add(tax_unit, period, ["short_term_capital_gains"]) + short_term_capital_loss = max_(0, -short_term_capital_gains) + dividends = add(tax_unit, period, ["dividend_income"]) + cap = parameters( + period + ).gov.states.ma.tax.income.capital_gains.deductible_against_interest_dividends + return min_(cap, min_(dividends, short_term_capital_loss)) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_cg_excess_exemption.py b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_cg_excess_exemption.py index 66b8ebc6a89..843b1f8273d 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_cg_excess_exemption.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_cg_excess_exemption.py @@ -12,7 +12,8 @@ class ma_part_a_cg_excess_exemption(Variable): def formula(tax_unit, period, parameters): part_a_agi = tax_unit("ma_part_a_agi", period) - dividends = add(tax_unit, period, ["dividend_income"]) + # Schedule B, line 33: dividends after the capital losses applied. + dividends = tax_unit("ma_part_a_adjusted_dividends", period) stcg_agi = part_a_agi - dividends div_excess_exemption = tax_unit("ma_part_a_div_excess_exemption", period) return max_(0, div_excess_exemption - stcg_agi) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_div_excess_exemption.py b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_div_excess_exemption.py index e2f70b2a180..5868e84da5f 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_div_excess_exemption.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_div_excess_exemption.py @@ -11,6 +11,7 @@ class ma_part_a_div_excess_exemption(Variable): defined_for = StateCode.MA def formula(tax_unit, period, parameters): - dividends = add(tax_unit, period, ["dividend_income"]) + # Schedule B, line 33: dividends after the capital losses applied. + dividends = tax_unit("ma_part_a_adjusted_dividends", period) partb_excess_exemption = tax_unit("ma_part_b_excess_exemption", period) return max_(0, partb_excess_exemption - dividends) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_taxable_capital_gains_income.py b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_taxable_capital_gains_income.py index 6a172d727ad..81a62e90f68 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_taxable_capital_gains_income.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_taxable_capital_gains_income.py @@ -12,7 +12,8 @@ class ma_part_a_taxable_capital_gains_income(Variable): def formula(tax_unit, period, parameters): part_a_agi = tax_unit("ma_part_a_agi", period) - dividends = add(tax_unit, period, ["dividend_income"]) + # Schedule B, line 33: dividends after the capital losses applied. + dividends = tax_unit("ma_part_a_adjusted_dividends", period) stcg_agi = part_a_agi - dividends div_excess_exemption = tax_unit("ma_part_a_div_excess_exemption", period) return max_(0, stcg_agi - div_excess_exemption) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_taxable_dividend_income.py b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_taxable_dividend_income.py index 3de2a5accfb..45c35479ce4 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_taxable_dividend_income.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_a_taxable_dividend_income.py @@ -11,6 +11,7 @@ class ma_part_a_taxable_dividend_income(Variable): defined_for = StateCode.MA def formula(tax_unit, period, parameters): - dividends = add(tax_unit, period, ["dividend_income"]) + # Schedule B, line 33: dividends after the capital losses applied. + dividends = tax_unit("ma_part_a_adjusted_dividends", period) part_b_excess_exemption = tax_unit("ma_part_b_excess_exemption", period) return max_(0, dividends - part_b_excess_exemption) From 1c61403b5790e2c80e3e9982e3a22a2f827ae768 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Tue, 6 Oct 2026 19:37:13 -0400 Subject: [PATCH 2/3] Apply only the remaining Massachusetts short-term loss to Part C gains 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 --- .../ma_schedule_b_capital_losses.yaml | 139 +++++++++++++++++- .../ma_long_term_loss_against_dividends.py | 8 +- .../ma_part_a_adjusted_dividends.py | 2 +- .../adjusted_gross_income/ma_part_c_agi.py | 21 ++- .../ma_short_term_loss_against_dividends.py | 4 +- 5 files changed, 156 insertions(+), 18 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml index 852b6da8283..a3f80b83773 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml @@ -1,7 +1,10 @@ # Massachusetts Schedule B applies a short-term loss first against up to # $2,000 of dividends (line 20), then against long-term gains (line 22). -# Expected tax values match the NBER TAXSIM-35 MA tax before credits -# (staxbc) for the same returns. +# Expected tax values in the first five cases match the NBER TAXSIM-35 MA +# tax before credits (staxbc) for the same returns. The later cases are +# computed line by line from the 2022 and 2023 Schedules B and D. +# With 60,000 of wages and no other 5.0% income, Part B taxable income is +# 60,000 - 2,000 (Form 1, line 11a) - 8,800 (joint exemption) = 49,200. - name: Short-term loss below the dividends leaves the net dividends as Part A income period: 2022 @@ -138,3 +141,135 @@ ma_part_c_agi: 4_000 # 5% x (59,000 Part B + 0 Part A + 4,000 Part C). ma_income_tax_before_credits: 3_150 + +- name: A short-term loss above the long-term gain leaves only the rest of the gain in Part C + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + qualified_dividend_income: 3_000 + short_term_capital_gains: -5_000 + long_term_capital_gains: 4_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + # Schedule B: line 19c = -5,000; line 20 = min(3,000, 5,000, 2,000) + # = 2,000; line 21 = -5,000 + 2,000 = -3,000. + ma_short_term_loss_against_dividends: 2_000 + # Schedule B, line 33 = 3,000 - 2,000 = 1,000 (line 38 = 1,000). + ma_part_a_taxable_dividend_income: 1_000 + # Schedule D: line 13 = 4,000; line 14 = Schedule B, line 22 + # = min(3,000, 4,000) = 3,000; line 15 = 4,000 - 3,000 = 1,000. + ma_part_c_agi: 1_000 + # 5% x (49,200 + 1,000) + 5% x 1,000 = 2,510 + 50. + ma_income_tax_before_credits: 2_560 + +- name: A short-term loss above a smaller long-term gain leaves no Part C income + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + qualified_dividend_income: 3_000 + short_term_capital_gains: -5_000 + long_term_capital_gains: 1_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + # Schedule B: line 20 = 2,000; line 21 = -3,000. + ma_short_term_loss_against_dividends: 2_000 + ma_part_a_taxable_dividend_income: 1_000 + # Schedule D: line 14 = Schedule B, line 22 = min(3,000, 1,000) + # = 1,000; line 15 = 1,000 - 1,000 = 0. Schedule B, line 23 + # carries over -3,000 + 1,000 = -2,000. + ma_part_c_agi: 0 + # 5% x (49,200 + 1,000). + ma_income_tax_before_credits: 2_510 + +- name: Short-term and long-term losses share the $2,000 cap against dividends + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + qualified_dividend_income: 3_000 + short_term_capital_gains: -1_500 + long_term_capital_gains: -1_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + # Schedule B: line 20 = min(3,000, 1,500, 2,000) = 1,500; line 21 = 0. + ma_short_term_loss_against_dividends: 1_500 + # Schedule D, line 15 = -1,000. Line 32 worksheet: line 2 = + # min(3,000, 2,000) = 2,000; line 4 = 2,000 - 1,500 = 500; + # line 5 = 1,000; line 6 = min(500, 1,000) = 500. + ma_long_term_loss_against_dividends: 500 + # Schedule B, line 33 = 3,000 - 1,500 - 500 = 1,000. + ma_part_a_adjusted_dividends: 1_000 + ma_part_a_taxable_dividend_income: 1_000 + ma_part_c_agi: 0 + # 5% x (49,200 + 1,000). + ma_income_tax_before_credits: 2_510 + +- name: Short-term gains in 2023 are taxed at 8.5% and the long-term gain stays in Part C + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + qualified_dividend_income: 1_000 + short_term_capital_gains: 2_000 + long_term_capital_gains: 4_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + # Schedule B: line 19c = 2,000, so lines 20 through 23 are omitted. + ma_short_term_loss_against_dividends: 0 + ma_long_term_loss_against_dividends: 0 + # Line 33 = 1,000; line 34 = line 28 = 2,000; line 37 = 3,000; + # line 38 = line 9 = 1,000; line 39 = 3,000 - 1,000 = 2,000. + ma_part_a_adjusted_dividends: 1_000 + ma_part_a_taxable_dividend_income: 1_000 + ma_part_a_taxable_capital_gains_income: 2_000 + # Schedule D: line 14 = 0; line 15 = 4,000; line 21 = 4,000. + ma_part_c_agi: 4_000 + # 5% x (49,200 + 1,000) + 8.5% x 2,000 + 5% x 4,000 + # = 2,510 + 170 + 200. + ma_income_tax_before_credits: 2_880 diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py index 3df14b05c65..6052768c677 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py @@ -4,15 +4,17 @@ class ma_long_term_loss_against_dividends(Variable): value_type = float entity = TaxUnit - label = "MA long-term capital losses applied against interest and dividends" + label = "MA long-term capital losses applied against dividends" documentation = ( "Massachusetts Schedule B, line 32: long-term losses left after " - "short-term gains, up to $2,000 of interest and dividends less the " - "short-term losses already applied in line 20." + "short-term gains, up to $2,000 of dividends less the short-term " + "losses already applied in line 20." ) unit = USD definition_period = YEAR reference = ( + # (c)(2)(b) and (c)(4) + "https://malegislature.gov/Laws/GeneralLaws/PartI/TitleIX/Chapter62/Section2", "https://taxsim.nber.org/historical_state_tax_forms/MA/2024/dor-2024-inc-sch-b-(form-1).pdf#page=2", "https://www.mass.gov/doc/2024-form-1-instructions/download#page=25", ) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py index 6ac29dccc78..5e28ac28c6f 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py @@ -4,7 +4,7 @@ class ma_part_a_adjusted_dividends(Variable): value_type = float entity = TaxUnit - label = "MA Part A adjusted interest and dividends" + label = "MA Part A adjusted dividends" documentation = ( "Massachusetts Schedule B, line 33: dividends less the short-term " "(line 20) and long-term (line 32) losses applied against them." diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py index ccda2bf2df5..4ee6a1cedf2 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py @@ -10,20 +10,19 @@ class ma_part_c_agi(Variable): reference = ( "https://www.mass.gov/info-details/mass-general-laws-c62-ss-2", "https://taxsim.nber.org/historical_state_tax_forms/MA/2024/dor-2024-inc-sch-b-(form-1).pdf#page=2", + "https://taxsim.nber.org/historical_state_tax_forms/MA/2023/dor-2023-inc-sch-d-(form-1).pdf", ) defined_for = StateCode.MA def formula(tax_unit, period, parameters): - # Part C gross income nets all short-term losses against long-term - # gains. Schedule B applies a short-term loss first against up to - # $2,000 of dividends (line 20), so only the rest offsets long-term - # gains (line 22). - part_c_gross_income = tax_unit("ma_part_c_gross_income", period) - long_term_capital_gains = add(tax_unit, period, ["long_term_capital_gains"]) - losses_against_dividends = tax_unit( + # Schedule B applies a short-term loss first against up to $2,000 of + # dividends (line 20). Only the rest (line 21) offsets long-term + # gains (line 22 and Schedule D, lines 14 and 15). + short_term_capital_gains = add(tax_unit, period, ["short_term_capital_gains"]) + short_term_capital_loss = max_(0, -short_term_capital_gains) + loss_against_dividends = tax_unit( "ma_short_term_loss_against_dividends", period ) - return min_( - max_(0, long_term_capital_gains), - part_c_gross_income + losses_against_dividends, - ) + remaining_short_term_loss = short_term_capital_loss - loss_against_dividends + long_term_capital_gains = add(tax_unit, period, ["long_term_capital_gains"]) + return max_(0, long_term_capital_gains - remaining_short_term_loss) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py index 70e8edd29d1..b480d00f788 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py @@ -4,7 +4,7 @@ class ma_short_term_loss_against_dividends(Variable): value_type = float entity = TaxUnit - label = "MA short-term capital losses applied against interest and dividends" + label = "MA short-term capital losses applied against dividends" documentation = ( "Massachusetts Schedule B, line 20. Interest is treated as interest " "from Massachusetts banks (5.0% income), so line 9 is the dividends." @@ -12,6 +12,8 @@ class ma_short_term_loss_against_dividends(Variable): unit = USD definition_period = YEAR reference = ( + # (c)(2)(a) and (c)(4) + "https://malegislature.gov/Laws/GeneralLaws/PartI/TitleIX/Chapter62/Section2", "https://taxsim.nber.org/historical_state_tax_forms/MA/2024/dor-2024-inc-sch-b-(form-1).pdf#page=2", "https://www.mass.gov/doc/2024-form-1-instructions/download#page=25", ) From bae9b4ce9315eee555e38e76c513c393d7b56535 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Wed, 7 Oct 2026 11:12:44 -0400 Subject: [PATCH 3/3] Count capital gain distributions in the Schedule B loss order 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 --- .../ma_schedule_b_capital_losses.yaml | 31 +++++++++++++++++++ .../ma_long_term_loss_against_dividends.py | 6 +++- .../adjusted_gross_income/ma_part_c_agi.py | 6 +++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml index a3f80b83773..e60beb1e9ba 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_schedule_b_capital_losses.yaml @@ -273,3 +273,34 @@ # 5% x (49,200 + 1,000) + 8.5% x 2,000 + 5% x 4,000 # = 2,510 + 170 + 200. ma_income_tax_before_credits: 2_880 + +- name: Capital gain distributions that cover a long-term loss leave no loss for the dividends + period: 2023 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + qualified_dividend_income: 1_000 + non_sch_d_capital_gains: 5_000 + long_term_capital_gains: -3_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MA + output: + # Schedule D: line 1 = -3,000; line 6 = 5,000; line 9 = 2,000 = line 13 + # = line 15. Line 15 is not a loss, so Schedule B, line 32 is omitted. + ma_long_term_loss_against_dividends: 0 + # Schedule B, line 33 = line 31 = 1,000 = line 38. + ma_part_a_adjusted_dividends: 1_000 + ma_part_a_taxable_dividend_income: 1_000 + ma_part_c_agi: 2_000 + # 5% x (49,200 + 1,000) + 5% x 2,000 = 2,510 + 100. + ma_income_tax_before_credits: 2_610 diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py index 6052768c677..73a8e2a1ab9 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py @@ -23,7 +23,11 @@ class ma_long_term_loss_against_dividends(Variable): def formula(tax_unit, period, parameters): short_term_capital_gains = add(tax_unit, period, ["short_term_capital_gains"]) nonnegative_short_term_capital_gains = max_(0, short_term_capital_gains) - long_term_capital_gains = add(tax_unit, period, ["long_term_capital_gains"]) + # Massachusetts Schedule D, line 13 includes capital gain + # distributions reported without a federal Schedule D (line 6). + long_term_capital_gains = add( + tax_unit, period, ["long_term_capital_gains", "non_sch_d_capital_gains"] + ) long_term_capital_loss = max_(0, -long_term_capital_gains) # Schedule B, line 25: long-term losses first offset short-term gains. remaining_long_term_loss = max_( diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py index 4ee6a1cedf2..a214438fb47 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py @@ -24,5 +24,9 @@ def formula(tax_unit, period, parameters): "ma_short_term_loss_against_dividends", period ) remaining_short_term_loss = short_term_capital_loss - loss_against_dividends - long_term_capital_gains = add(tax_unit, period, ["long_term_capital_gains"]) + # Massachusetts Schedule D, line 13 includes capital gain + # distributions reported without a federal Schedule D (line 6). + long_term_capital_gains = add( + tax_unit, period, ["long_term_capital_gains", "non_sch_d_capital_gains"] + ) return max_(0, long_term_capital_gains - remaining_short_term_loss)