diff --git a/changelog.d/mt-elderly-credit-disability-unemployment.fixed.md b/changelog.d/mt-elderly-credit-disability-unemployment.fixed.md new file mode 100644 index 00000000000..00d4cb4a5c6 --- /dev/null +++ b/changelog.d/mt-elderly-credit-disability-unemployment.fixed.md @@ -0,0 +1 @@ +Count employment disability benefits and unemployment compensation excluded from federal AGI (the 2020 IRC 85(c) exclusion) in Montana's elderly homeowner/renter credit gross household income. diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/gross_income_sources.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/gross_income_sources.yaml index fb84aef0128..e58384f3072 100644 --- a/policyengine_us/parameters/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/gross_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/gross_income_sources.yaml @@ -18,17 +18,23 @@ metadata: href: https://rules.mt.gov/api/policy-library-public/collections/aec52c46-128e-4279-9068-8af5d5432d74/policies/c90ea93a-03a2-4605-a0f8-f2f3760198bf/document/297c7a8d-f6f4-4b70-a7b7-fe1cedee53e7 - title: ARM 42.4.301(2) as in effect from 9/7/2024 ("federal refundable tax credits received" and "any state refundable tax credits received") href: https://rules.mt.gov/api/policy-library-public/collections/aec52c46-128e-4279-9068-8af5d5432d74/policies/c90ea93a-03a2-4605-a0f8-f2f3760198bf/document/768c8344-24ee-4950-9d5e-9d8fc97ec0f7 - - title: 2016 Montana Form 2EC instructions, Part II, disability benefits including SSI payments and workers' compensation, and cash public assistance and relief + - title: "2014 Montana Form 2EC instructions, Part II, disability benefits including SSI payments and workers' compensation; Income Source Worksheet, line 7, alimony, cash public assistance, unemployment #page 53-54" + href: https://taxsim.nber.org/historical_state_tax_forms/MT/2014/Form_2_booklet_2014.pdf#page=53 + - title: "2016 Montana Form 2EC instructions, Part II, disability benefits including SSI payments and workers' compensation, and cash public assistance and relief; Gross Household Income Worksheet, line 7, alimony, cash public assistance, unemployment #page 74-75" href: https://taxsim.nber.org/historical_state_tax_forms/MT/2016/Form_2_booklet_2016.pdf#page=74 - - title: "2017 Montana Form 2EC instructions, Part II, disability benefits including SSI payments and workers' compensation, and cash public assistance and relief; Gross Household Income Worksheet, items 4 and 5 #page 64-65" + - title: "2017 Montana Form 2EC instructions, Part II, disability benefits including SSI payments and workers' compensation, and cash public assistance and relief; Gross Household Income Worksheet, items 4 and 5 (alimony, cash public assistance, unemployment) #page 64-65" href: https://taxsim.nber.org/historical_state_tax_forms/MT/2017/Form%202%202017%20Booklet.pdf#page=64 - - title: "2022 Montana Form 2 instructions, Elderly Homeowner/Renter Credit Schedule, gross household income; line 8, disability benefits including SSI payments and workers' compensation #page 47-48" + - title: 2020 Montana Form 2 instructions, Elderly Homeowner/Renter Credit Schedule, line 1, federal adjusted gross income; line 8, other income not listed above, including disability benefits + href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2020_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=45 + - title: 2021 Montana Form 2 instructions, Elderly Homeowner/Renter Credit Schedule, line 8, other income not listed above, including disability benefits + href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2021_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=49 + - title: "2022 Montana Form 2 instructions, Elderly Homeowner/Renter Credit Schedule, gross household income; line 8, other income, including disability benefits, SSI payments and workers' compensation #page 47-48" href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=47 - - title: "2023 Montana Form 2 instructions, Elderly Homeowner/Renter Credit Schedule, line 7, refundable credits received; line 8, disability benefits including SSI payments and workers' compensation #page 51-52" + - title: "2023 Montana Form 2 instructions, Elderly Homeowner/Renter Credit Schedule, line 7, refundable credits received; line 8, other income, including disability benefits, SSI payments and workers' compensation #page 51-52" href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2023_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=51 - - title: "2024 Montana Form 2 instructions, Schedule 2EC, gross household income; line 8, refundable credits received; line 16, government assistance #page 46-48" + - title: "2024 Montana Form 2 instructions, Schedule 2EC, gross household income; line 1, wages; line 8, refundable credits received; line 14, unemployment compensation; line 15, other income; line 16, government assistance #page 46-48" href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2024_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=46 - - title: "2025 Montana Form 2 instructions, Schedule 2EC, line 8, refundable credits received; line 16, government assistance #page 45-46" + - title: "2025 Montana Form 2 instructions, Schedule 2EC, line 8, refundable credits received; line 14, unemployment compensation; line 15, other income; line 16, government assistance #page 45-46" href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2025_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=45 values: @@ -60,6 +66,21 @@ values: # (9)(a) counts all nontaxable income; a government-funded pilot is also # "any other government assistance" (Schedule 2EC line 16). - gi_cash_assistance + # Disability benefits from employment other than workers' compensation + # (2014-2023 instructions: "Disability benefits including, but not + # limited to, veteran's disability, SSI payments, and workers' + # compensation"; from 2024, Schedule 2EC line 1 for taxable sick pay and + # disability pay reported as wages, line 5 for a disability pension after + # minimum retirement age (IRS Pub. 525), otherwise line 15). The model + # leaves them out of federal AGI (irs_gross_income) and + # dependent_gross_income, so all of them count here, once. + - disability_benefits + # Unemployment compensation that neither federal AGI nor a dependent's + # own gross income counts, such as the 2020 IRC 85(c) exclusion (2020 + # line 8, "all items of income ... not reported on lines 1 through 7"; + # 2014-2017 worksheets, "Alimony, cash public assistance, + # unemployment"; Schedule 2EC line 14 from 2024, the total received). + - mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback # Refundable credits received (2023 schedule line 7, Schedule 2EC line 8 # from 2024): Montana's refundable credits other than this credit, # and this credit as received during the year (ARM 42.4.301(2)(c)). @@ -81,6 +102,8 @@ values: - tanf_person - workers_compensation - gi_cash_assistance + - disability_benefits + - mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback - mt_refundable_credits_before_renter_credit - mt_elderly_homeowner_or_renter_credit_received - mt_elderly_homeowner_or_renter_credit_federal_refundable_credits @@ -101,6 +124,8 @@ values: - tanf_person - workers_compensation - gi_cash_assistance + - disability_benefits + - mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback - mt_refundable_credits_before_renter_credit - mt_elderly_homeowner_or_renter_credit_received - mt_elderly_homeowner_or_renter_credit_federal_refundable_credits diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_disability_and_unemployment.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_disability_and_unemployment.yaml new file mode 100644 index 00000000000..9a474ec7a5a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_disability_and_unemployment.yaml @@ -0,0 +1,336 @@ +# Employment disability benefits and unemployment compensation outside +# federal AGI in gross household income. § 15-30-2337(9)(a) counts federal +# AGI "plus all nontaxable income". The 2014-2023 instructions list +# "Disability benefits including, but not limited to, veteran's disability, +# SSI payments, and workers' compensation"; the 2014-2017 worksheets list +# "Alimony, cash public assistance, unemployment"; 2024 Schedule 2EC line 14 +# counts "the total of unemployment compensation received". SSI is pinned to +# zero so that each case tests only the item it names. + +- name: Case 1, 2016 Form 2EC, the claimant's disability benefits count. + period: 2016 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 80 + taxable_pension_income: 12_000 + disability_benefits: 2_500 + rent: 6_000 + ssi: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # Gross Household Income Worksheet: line 8 pensions 12_000; line 9 + # disability benefits 2_500; line 10 = 14_500, Form 2EC line 1. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 14_500 + # Line 2 6_300; line 3 = 8_200; line 4 = 0.039; line 5 = 319.80. + mt_elderly_homeowner_or_renter_credit_net_household_income: 319.80 + # Line 7 rent 6_000; line 8 = 900; line 9 = 900; line 10 = 900 - 319.80 + # = 580.20; line 11 = 580.20; line 12 = 100%; line 13 = 580.20. Without + # the disability benefits, line 1 = 12_000, line 5 = 5_700 x 0.028 = + # 159.60 and the credit would be 740.40. + mt_elderly_homeowner_or_renter_credit: 580.20 + +- name: Case 2, 2017 Form 2EC, a household member's disability benefits count. + period: 2017 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 72 + taxable_pension_income: 15_000 + real_estate_taxes: 1_200 + ssi: 0 + person2: + age: 45 + disability_benefits: 5_000 + ssi: 0 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # The claimant's adult son files his own return. Gross Household Income + # Worksheet: line 6 pensions 15_000; line 7 his disability benefits + # 5_000; line 8 = 20_000, Form 2EC line 1. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [15_000, 5_000] + # Line 2 6_300; line 3 = 13_700; line 4 = 0.05; line 5 = 685. + mt_elderly_homeowner_or_renter_credit_net_household_income: [685, 0] + # Line 6 = 1_200; line 9 = 1_200; line 10 = 515; line 11 = 515; line 12 + # = 100%; line 13 = 515. Without his benefits, line 1 = 15_000, line 5 = + # 8_700 x 0.039 = 339.30 and the credit would be 860.70. + mt_elderly_homeowner_or_renter_credit: [515, 0] + +- name: Case 3, 2023 schedule line 8, a spouse's disability benefits count. + period: 2023 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + taxable_pension_income: 18_000 + real_estate_taxes: 1_000 + ssi: 0 + person2: + age: 64 + disability_benefits: 4_000 + ssi: 0 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # Joint return. Line 1 federal AGI 18_000; line 8 disability benefits + # 4_000; line 10 = 22_000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [18_000, 4_000] + # Line 11 12_600; line 12 = 9_400; line 13 = 0.042; line 14 = 394.80. + mt_elderly_homeowner_or_renter_credit_net_household_income: [394.80, 0] + # Line 15 = 1_000; line 18 = 1_000; line 19 = 605.20; line 20 = 605.20; + # line 21 = 100%; line 22 = 605.20. Without the benefits, line 10 = + # 18_000, line 14 = 5_400 x 0.028 = 151.20 and the credit would be + # 848.80. + mt_elderly_homeowner_or_renter_credit: [605.20, 0] + +- name: Case 4, 2024 Schedule 2EC line 17, a household member's disability benefits count. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 75 + taxable_pension_income: 20_000 + rent: 9_600 + ssi: 0 + person2: + age: 45 + disability_benefits: 7_000 + ssi: 0 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # The claimant's adult son files his own return. Line 5 pensions 20_000; + # line 17 his disability benefits (line 1 or line 15 items for another + # member) 7_000; line 18 = 27_000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 7_000] + # Line 19 12_600; line 20 = 14_400; line 21 = 0.05; line 22 = 720. + mt_elderly_homeowner_or_renter_credit_net_household_income: [720, 0] + # Line 24 rent 9_600; line 25 = 1_440; line 26 = 1_440; line 27 = 720; + # line 28 = 720; line 29 = 100%; line 30 = 720. Without his benefits, + # line 18 = 20_000, line 22 = 7_400 x 0.035 = 259 and the credit would + # be the 1_150 cap. + mt_elderly_homeowner_or_renter_credit: [720, 0] + +- name: Case 5, 2025 Schedule 2EC, the claimant's own disability benefits count. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 64 + taxable_pension_income: 16_000 + disability_benefits: 6_000 + real_estate_taxes: 1_300 + ssi: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # Line 5 pensions 16_000; disability benefits 6_000 on line 1 if taxable + # and reported as wages, line 5 if a disability pension after minimum + # retirement age, line 15 if nontaxable; line 18 = 22_000 either way. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 22_000 + # Line 19 12_600; line 20 = 9_400; line 21 = 0.042; line 22 = 394.80. + mt_elderly_homeowner_or_renter_credit_net_household_income: 394.80 + # Line 23 = 1_300; line 26 = 1_300; line 27 = 905.20; line 28 = 905.20; + # line 29 = 100%; line 30 = 905.20. Without the benefits, line 18 = + # 16_000, line 22 = 3_400 x 0.016 = 54.40 and the credit would be the + # 1_150 cap. + mt_elderly_homeowner_or_renter_credit: 905.20 + +- name: Case 6, 2020 schedule line 8, the claimant's unemployment compensation excluded from federal AGI counts. + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + taxable_pension_income: 16_000 + unemployment_compensation: 9_000 + real_estate_taxes: 1_500 + ssi: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # IRC 85(c) excludes the 9_000 from 2020 federal AGI (AGI without it is + # 16_000, under 150_000). Line 1 federal AGI 16_000; line 7 refundable + # credits received, the 2020 recovery rebate credit 1_200 + 600 = 1_800; + # line 8 other income not listed above, the excluded 9_000; line 10 = + # 26_800. + adjusted_gross_income_person: 16_000 + mt_elderly_homeowner_or_renter_credit_federal_refundable_credits: 1_800 + mt_elderly_homeowner_or_renter_credit_gross_household_income: 26_800 + # Line 11 6_300; line 12 = 20_500; line 13 = 0.05; line 14 = 1_025. + mt_elderly_homeowner_or_renter_credit_net_household_income: 1_025 + # Line 15 = 1_500; line 18 = 1_500; line 19 = 475; line 20 = 475; line 21 + # = 100%; line 22 = 475. Without line 8, line 10 = 17_800, line 14 = + # 11_500 x 0.048 = 552 and the credit would be 948. + mt_elderly_homeowner_or_renter_credit: 475 + +- name: Case 7, 2020 schedule line 8, a household member's compensation above the exclusion limit counts in full. + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 72 + taxable_pension_income: 14_000 + rent: 12_000 + ssi: 0 + person2: + age: 40 + unemployment_compensation: 14_000 + ssi: 0 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # The claimant's daughter files her own return. Of her 14_000, 10_200 is + # excluded and 14_000 - 10_200 = 3_800 is in her federal AGI. Line 1 + # federal AGI 14_000 + 3_800 = 17_800; line 7 each return's 2020 + # recovery rebate credit, 1_800 + 1_800 = 3_600; line 8 her excluded + # 10_200; line 10 = 31_600. + adjusted_gross_income_person: [14_000, 3_800] + mt_elderly_homeowner_or_renter_credit_gross_household_income: [15_800, 15_800] + # Line 11 6_300; line 12 = 25_300; line 13 = 0.05; line 14 = 1_265. + mt_elderly_homeowner_or_renter_credit_net_household_income: [1_265, 0] + # Line 16 rent 12_000; line 17 = 1_800; line 18 = 1_800; line 19 = 535; + # line 20 = 535; line 21 = 100% (under 35_000); line 22 = 535. Without + # line 8, line 10 = 21_400, line 14 = 15_100 x 0.05 = 755 and the credit + # would be the 1_000 cap. + mt_elderly_homeowner_or_renter_credit: [535, 0] + +- name: Case 8, 2020 a dependent's compensation counts once, in their own gross income. + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 15_000 + real_estate_taxes: 1_400 + ssi: 0 + person2: + age: 20 + is_full_time_student: true + is_tax_unit_dependent: true + unemployment_compensation: 4_000 + ssi: 0 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # The claimant's grandson, a student, is her dependent. All of his 4_000 + # is excluded from federal AGI, and his own gross income counts it in + # full, so nothing more is added for it. Line 1 federal AGI 15_000; line + # 7 the recovery rebate credit 1_800; line 8 his 4_000; line 10 = 20_800. + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: [0, 0] + mt_elderly_homeowner_or_renter_credit_gross_household_income: [16_800, 4_000] + # Line 12 = 14_500; line 13 = 0.05; line 14 = 725. + mt_elderly_homeowner_or_renter_credit_net_household_income: [725, 0] + # Line 15 = 1_400; line 19 = 675; line 20 = 675; line 22 = 675. + mt_elderly_homeowner_or_renter_credit: [675, 0] + +- name: Case 9, 2021 taxable unemployment compensation counts once, in federal AGI. + period: 2021 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + taxable_pension_income: 16_000 + unemployment_compensation: 9_000 + real_estate_taxes: 1_500 + ssi: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # No exclusion in 2021. Line 1 federal AGI 25_000; line 7 the 2021 + # recovery rebate credit 1_400; line 10 = 26_400. + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: 0 + mt_elderly_homeowner_or_renter_credit_gross_household_income: 26_400 + # Line 12 = 20_100; line 13 = 0.05; line 14 = 1_005. + mt_elderly_homeowner_or_renter_credit_net_household_income: 1_005 + # Line 19 = 1_500 - 1_005 = 495; line 20 = 495; line 22 = 495. + mt_elderly_homeowner_or_renter_credit: 495 + +- name: Case 10, 2024 Schedule 2EC line 14, unemployment compensation counts once. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + taxable_pension_income: 15_000 + unemployment_compensation: 6_000 + real_estate_taxes: 1_200 + ssi: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # Line 5 pensions 15_000; line 14 unemployment compensation 6_000, all + # in federal AGI; line 18 = 21_000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 21_000 + # Line 20 = 8_400; line 21 = 0.039; line 22 = 327.60. + mt_elderly_homeowner_or_renter_credit_net_household_income: 327.60 + # Line 27 = 1_200 - 327.60 = 872.40; line 28 = 872.40; line 30 = 872.40. + mt_elderly_homeowner_or_renter_credit: 872.40 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback.yaml new file mode 100644 index 00000000000..21e4ee2e4f4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback.yaml @@ -0,0 +1,218 @@ +# Unemployment compensation that neither federal AGI nor a dependent's own +# gross income counts. § 15-30-2337(9)(a) adds "all nontaxable income" to +# federal AGI. In 2020, IRC 85(c) excluded up to 10_200 of unemployment +# compensation from gross income when AGI without it was under 150_000. + +- name: Case 1, 2020 compensation under the exclusion limit is all nontaxable. + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + taxable_pension_income: 20_000 + unemployment_compensation: 8_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # AGI without the compensation is 20_000, under 150_000, and 8_000 is + # under 10_200, so none of it is in federal AGI. + taxable_unemployment_compensation: 0 + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: 8_000 + +- name: Case 2, 2020 compensation above the exclusion limit. + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + taxable_pension_income: 20_000 + unemployment_compensation: 15_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # 15_000 - 10_200 = 4_800 is in federal AGI; the excluded 10_200 is not. + taxable_unemployment_compensation: 4_800 + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: 10_200 + +- name: Case 3, 2020 income above the 150,000 cutoff gets no exclusion. + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + taxable_pension_income: 160_000 + unemployment_compensation: 8_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # AGI without the compensation is 160_000, not less than 150_000, so all + # of it is in federal AGI. + taxable_unemployment_compensation: 8_000 + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: 0 + +- name: Case 4, 2021 compensation is all taxable. + period: 2021 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + taxable_pension_income: 20_000 + unemployment_compensation: 8_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + taxable_unemployment_compensation: 8_000 + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: 0 + +- name: Case 5, 2024 compensation is all taxable. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + taxable_pension_income: 20_000 + unemployment_compensation: 8_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + taxable_unemployment_compensation: 8_000 + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: 0 + +- name: Case 6, 2020 joint return, the spouse who received the compensation. + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + taxable_pension_income: 20_000 + person2: + age: 64 + unemployment_compensation: 12_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # 12_000 - 10_200 = 1_800 is in the spouse's share of federal AGI. + taxable_unemployment_compensation: [0, 1_800] + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: [0, 10_200] + +- name: Case 7, 2020 a dependent's compensation is already in their own gross income. + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + taxable_pension_income: 20_000 + taxable_unemployment_compensation: 0 + person2: + age: 64 + unemployment_compensation: 8_000 + taxable_unemployment_compensation: 1_200 + person3: + age: 20 + is_full_time_student: true + is_tax_unit_dependent: true + unemployment_compensation: 4_000 + taxable_unemployment_compensation: 600 + tax_units: + tax_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_name: MT + output: + # The taxable allocation is an input, so the case does not depend on how + # the federal exclusion is shared. With 1_200 of the spouse's 8_000 and + # 600 of the dependent's 4_000 taxable, the spouse's 8_000 counts as + # 1_200 in federal AGI plus 6_800 here. The dependent's 4_000 counts in + # full in their own gross income (dependent_gross_income), so none of it + # is added here. + adjusted_gross_income_person: [20_000, 1_200, 0] + dependent_gross_income: [0, 0, 4_000] + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: [0, 6_800, 0] + # Each person's compensation once. The head also carries the joint + # return's 2020 recovery rebate credit, 1_200 x 2 + 600 x 2 = 3_600. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [23_600, 8_000, 4_000] + +- name: Case 8, a person recorded as both a dependent and the head. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: true + unemployment_compensation: 5_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # Federal AGI counts people who are not dependents and + # dependent_gross_income people who are not the head or spouse, so + # neither counts this person's compensation; all of it is added here. + adjusted_gross_income_person: 0 + dependent_gross_income: 0 + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: 5_000 + # The 2023 list carries it into gross household income. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 5_000 + +- name: Case 9, the same person in 2016 under the 2009 list. + period: 2016 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_tax_unit_dependent: true + unemployment_compensation: 5_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback: 5_000 + mt_elderly_homeowner_or_renter_credit_gross_household_income: 5_000 diff --git a/policyengine_us/tests/test_mt_elderly_credit_nontaxable_income.py b/policyengine_us/tests/test_mt_elderly_credit_nontaxable_income.py new file mode 100644 index 00000000000..be9b2281fed --- /dev/null +++ b/policyengine_us/tests/test_mt_elderly_credit_nontaxable_income.py @@ -0,0 +1,293 @@ +"""Invariant tests: disability benefits and unemployment compensation in the +Montana elderly homeowner/renter credit. + +MCA 15-30-2337(9)(a) counts federal adjusted gross income "plus all +nontaxable income", and (4) counts "all income received by all individuals +of a household". 2024 Schedule 2EC line 14 counts "the total of unemployment +compensation received during the year"; the 2014-2023 instructions list +disability benefits. The model leaves employment disability benefits out of +federal AGI, and in 2020 IRC 85(c) excluded up to 10_200 of unemployment +compensation from it. Federal AGI counts the taxable part of a filer's +compensation and a dependent's own gross income all of a dependent's. + +For a claimant aged 66 (taxable pension, unemployment compensation, +disability benefits, property tax) living with one other adult (unemployment +compensation, disability benefits), in 2019, 2020, 2021 and 2024, in three +arrangements: + + dependent: one return, the other adult a 20-year-old student dependent; + own_return: two returns, the other adult (45) filing their own; + joint: one joint return, the other adult (60) the claimant's spouse; + +over a grid of amounts that puts the compensation under and over the 10_200 +limit, and income under and over the 150_000 cutoff: + +1. The household's gross household income is the pension plus every + member's unemployment compensation and disability benefits, each once, + plus the refundable credits the household received (read from the model; + no one has earnings, so these are the recovery rebate credits). +2. The credit equals an independent reading of the schedule (standard + exclusion, Household Income Reduction Table, cap, Credit Multiplier + Table), lies between zero and the cap, and is zero at 45_000 or more. +3. More unemployment compensation or disability benefits never raise the + credit while the refundable credits stay the same. + +The YAML cases pin worked examples from each year's form. This test adds +what they cannot: every arrangement and year at once against a reference +written from the statute rather than the model's income paths, so a double +count through federal AGI, dependent_gross_income or the 2020 exclusion +shows up. It builds one vectorized Simulation. +""" + +import gc +import itertools + +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEARS = [2019, 2020, 2021, 2024] +ARRANGEMENTS = ["dependent", "own_return", "joint"] +PENSIONS = [12_000, 160_000] +CLAIMANT_UC = [0, 6_000, 15_000] +MEMBER_UC = [0, 4_000, 12_000] +CLAIMANT_DISABILITY = [0, 3_000] +MEMBER_DISABILITY = [0, 5_000] +PROPERTY_TAX = 1_500 +INCOME_LIMIT = 45_000 +TOLERANCE = 0.01 +# Standard exclusion and cap: $6,300 and $1,000 through 2021; HB 191 (2021) +# raised them to $12,600 and $1,150 from 2022. +EXCLUSION = {2019: 6_300, 2020: 6_300, 2021: 6_300, 2024: 12_600} +CAP = {2019: 1_000, 2020: 1_000, 2021: 1_000, 2024: 1_150} +# Household Income Reduction Table: lower bounds and rates. +REDUCTION_TABLE = [ + (0, 0), + (2_000, 0.006), + (3_000, 0.016), + (4_000, 0.024), + (5_000, 0.028), + (6_000, 0.032), + (7_000, 0.035), + (8_000, 0.039), + (9_000, 0.042), + (10_000, 0.045), + (11_000, 0.048), + (12_000, 0.05), +] +# Credit Multiplier Table: lower bounds of gross household income. +MULTIPLIER_TABLE = [ + (0, 1), + (35_000, 0.4), + (37_501, 0.3), + (40_001, 0.2), + (42_501, 0.1), + (45_000, 0), +] +CASES = [ + dict( + arrangement=arrangement, + pension=pension, + claimant_uc=claimant_uc, + member_uc=member_uc, + claimant_disability=claimant_disability, + member_disability=member_disability, + ) + for ( + arrangement, + pension, + claimant_uc, + member_uc, + claimant_disability, + member_disability, + ) in itertools.product( + ARRANGEMENTS, + PENSIONS, + CLAIMANT_UC, + MEMBER_UC, + CLAIMANT_DISABILITY, + MEMBER_DISABILITY, + ) +] + + +def build_situation(): + def every_year(value): + return {year: value for year in YEARS} + + people, tax_units, marital_units, households = {}, {}, {}, {} + for i, c in enumerate(CASES): + claimant, member = f"claimant_{i}", f"member_{i}" + arrangement = c["arrangement"] + people[claimant] = { + "age": every_year(66), + "taxable_pension_income": every_year(c["pension"]), + "unemployment_compensation": every_year(c["claimant_uc"]), + "disability_benefits": every_year(c["claimant_disability"]), + "real_estate_taxes": every_year(PROPERTY_TAX), + "ssi": every_year(0), + "is_tax_unit_dependent": every_year(False), + } + people[member] = { + "age": every_year( + {"dependent": 20, "own_return": 45, "joint": 60}[arrangement] + ), + "is_full_time_student": every_year(arrangement == "dependent"), + "unemployment_compensation": every_year(c["member_uc"]), + "disability_benefits": every_year(c["member_disability"]), + "ssi": every_year(0), + "is_tax_unit_dependent": every_year(arrangement == "dependent"), + } + if arrangement == "own_return": + tax_units[f"tax_unit_{i}"] = {"members": [claimant]} + tax_units[f"tax_unit_{i}_member"] = {"members": [member]} + else: + tax_units[f"tax_unit_{i}"] = {"members": [claimant, member]} + if arrangement == "joint": + marital_units[f"marital_unit_{i}"] = {"members": [claimant, member]} + else: + marital_units[f"marital_unit_{i}"] = {"members": [claimant]} + marital_units[f"marital_unit_{i}_member"] = {"members": [member]} + households[f"household_{i}"] = { + "members": [claimant, member], + "state_code": every_year("MT"), + } + return { + "people": people, + "tax_units": tax_units, + "marital_units": marital_units, + "households": households, + } + + +@pytest.fixture(scope="module") +def results(): + """Household totals by year: gross household income, the credit, and + the refundable credits received.""" + simulation = Simulation(situation=build_situation()) + + def by_household(variable, year): + return np.asarray(simulation.calculate(variable, year, map_to="household")) + + results = { + year: { + "gross_household_income": by_household( + "mt_elderly_homeowner_or_renter_credit_gross_household_income", + year, + ), + "credit": by_household("mt_elderly_homeowner_or_renter_credit", year), + "refundable_credits": by_household( + "mt_elderly_homeowner_or_renter_credit_federal_refundable_credits", + year, + ) + + by_household("mt_refundable_credits_before_renter_credit", year), + } + for year in YEARS + } + # Keep only the arrays: free the simulation before the rest of the + # process's tests run. + del simulation + gc.collect() + return results + + +def lookup(table, amount): + rate = 0 + for lower, value in table: + if amount >= lower: + rate = value + return rate + + +def expected_credit(year, gross_household_income): + household_income = max(gross_household_income - EXCLUSION[year], 0) + net_household_income = household_income * lookup(REDUCTION_TABLE, household_income) + uncapped = max(PROPERTY_TAX - net_household_income, 0) + return min(uncapped, CAP[year]) * lookup(MULTIPLIER_TABLE, gross_household_income) + + +def tolerance(amount): + return max(TOLERANCE, 8 * float(np.spacing(np.float32(abs(amount))))) + + +@pytest.mark.parametrize("year", YEARS) +def test_each_amount_counts_once(results, year): + r = results[year] + for i, c in enumerate(CASES): + expected = ( + c["pension"] + + c["claimant_uc"] + + c["member_uc"] + + c["claimant_disability"] + + c["member_disability"] + + r["refundable_credits"][i] + ) + assert r["gross_household_income"][i] == pytest.approx( + expected, abs=tolerance(expected) + ), (year, c) + + +@pytest.mark.parametrize("year", YEARS) +def test_credit_follows_the_schedule(results, year): + r = results[year] + for i, c in enumerate(CASES): + ghi = float(r["gross_household_income"][i]) + credit = float(r["credit"][i]) + tol = tolerance(ghi) + assert credit == pytest.approx(expected_credit(year, ghi), abs=tol), ( + year, + c, + ) + assert -tol <= credit <= CAP[year] + tol, (year, c) + if ghi >= INCOME_LIMIT: + assert credit == 0, (year, c) + + +@pytest.mark.parametrize("year", YEARS) +def test_more_income_never_raises_the_credit(results, year): + r = results[year] + index = {tuple(sorted(c.items())): i for i, c in enumerate(CASES)} + axes = { + "claimant_uc": CLAIMANT_UC, + "member_uc": MEMBER_UC, + "claimant_disability": CLAIMANT_DISABILITY, + "member_disability": MEMBER_DISABILITY, + } + compared = 0 + for i, c in enumerate(CASES): + for key, values in axes.items(): + position = values.index(c[key]) + if position + 1 == len(values): + continue + j = index[tuple(sorted({**c, key: values[position + 1]}.items()))] + if r["refundable_credits"][j] != r["refundable_credits"][i]: + continue + compared += 1 + tol = tolerance(r["gross_household_income"][j]) + assert r["credit"][j] <= r["credit"][i] + tol, (year, c, key) + assert compared > 0 + + +def test_worked_example(results): + """2020 Elderly Homeowner/Renter Credit Schedule, own_return arrangement: + the claimant's pension 12_000 and unemployment compensation 6_000, all + excluded from federal AGI; the other adult's 12_000 of compensation, + 1_800 of it in federal AGI; each return's recovery rebate credit 1_800. + Line 1 = 12_000 + 1_800; line 7 = 3_600; line 8 = 6_000 + 10_200; line + 10 = 33_600; line 12 = 27_300; line 14 = 1_365; line 19 = 135; line 22 = + 135 at the 100% multiplier.""" + i = CASES.index( + dict( + arrangement="own_return", + pension=12_000, + claimant_uc=6_000, + member_uc=12_000, + claimant_disability=0, + member_disability=0, + ) + ) + r = results[2020] + assert r["refundable_credits"][i] == pytest.approx(3_600, abs=TOLERANCE) + assert r["gross_household_income"][i] == pytest.approx(33_600, abs=TOLERANCE) + assert r["credit"][i] == pytest.approx(135, abs=TOLERANCE) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback.py new file mode 100644 index 00000000000..407163fa0d5 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback.py @@ -0,0 +1,48 @@ +from policyengine_us.model_api import * + + +class mt_elderly_homeowner_or_renter_credit_unemployment_compensation_addback(Variable): + value_type = float + entity = Person + label = "Montana elderly homeowner/renter credit unemployment compensation add-back" + documentation = ( + "Unemployment compensation a household member received that neither " + "federal adjusted gross income nor a dependent's own gross income " + "counts. For a filer this is the part excluded from 2020 federal " + "gross income under IRC 85(c), which gross household income counts as " + "nontaxable income. For a person recorded as both a dependent and the " + "head or spouse, whom neither income path counts, it is all of their " + "compensation." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.MT + reference = ( + # § 15-30-2337(9)(a): federal adjusted gross income "plus all + # nontaxable income" + "https://mca.legmt.gov/bills/mca/title_0150/chapter_0300/part_0230/section_0370/0150-0300-0230-0370.html", + # IRC 85(c), added by ARPA § 9042: up to $10,200 of 2020 + # unemployment compensation excluded from gross income + "https://www.law.cornell.edu/uscode/text/26/85#c", + # 2020 Form 2 instructions, line 8: "Enter all items of income + # received by the members of the household that were not reported on + # lines 1 through 7" + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2020_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=45", + # 2024 Schedule 2EC, line 14: "Enter the total of unemployment + # compensation received during the year" + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2024_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=48", + ) + + def formula(person, period, parameters): + # Gross household income counts all the unemployment compensation a + # member received, taxable or not. Federal AGI already counts the + # taxable part of a filer's (irs_gross_income counts people who are + # not dependents), and a dependent's own gross income counts all of a + # dependent's (dependent_gross_income counts people who are not the + # head or spouse, without the IRC 85(c) exclusion). Add the rest. + received = max_(0, person("unemployment_compensation", period)) + is_dependent = person("is_tax_unit_dependent", period) + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + taxable = max_(0, person("taxable_unemployment_compensation", period)) + counted = where(is_dependent, ~head_or_spouse * received, taxable) + return max_(received - counted, 0)