diff --git a/changelog.d/fix-mt-ehrc-dependent-adjustments.fixed.md b/changelog.d/fix-mt-ehrc-dependent-adjustments.fixed.md new file mode 100644 index 00000000000..b1437bd7a98 --- /dev/null +++ b/changelog.d/fix-mt-ehrc-dependent-adjustments.fixed.md @@ -0,0 +1 @@ +Montana elderly homeowner/renter credit household income includes dependents' own allowable non-loss adjustments to federal gross income. diff --git a/changelog.d/fix-mt-ehrc-household-member-income.fixed.md b/changelog.d/fix-mt-ehrc-household-member-income.fixed.md new file mode 100644 index 00000000000..0b6584283d6 --- /dev/null +++ b/changelog.d/fix-mt-ehrc-household-member-income.fixed.md @@ -0,0 +1 @@ +Count household members' income in the Montana elderly homeowner/renter credit: a tax unit dependent's gross income, and the income of household members who file their own returns. diff --git a/changelog.d/fix-mt-ehrc-refundable-credits.fixed.md b/changelog.d/fix-mt-ehrc-refundable-credits.fixed.md new file mode 100644 index 00000000000..67ba4eb92f0 --- /dev/null +++ b/changelog.d/fix-mt-ehrc-refundable-credits.fixed.md @@ -0,0 +1 @@ +Count federal refundable credits, the elderly homeowner/renter credit received during the year, and actual property tax rebate receipts (including in 2023) in Montana's elderly homeowner/renter credit gross household income; date the Montana property tax rebate ($400 for tax year 2024, none after). diff --git a/changelog.d/fix-mt-federal-tax-zero-allocation.fixed.md b/changelog.d/fix-mt-federal-tax-zero-allocation.fixed.md new file mode 100644 index 00000000000..ca01c310d03 --- /dev/null +++ b/changelog.d/fix-mt-federal-tax-zero-allocation.fixed.md @@ -0,0 +1 @@ +Preserve zero Montana federal income tax deduction allocation when neither filer has positive federal adjusted gross income. diff --git a/changelog.d/fix-mt-separate-filing-allocations.fixed.md b/changelog.d/fix-mt-separate-filing-allocations.fixed.md new file mode 100644 index 00000000000..c7d3be16f20 --- /dev/null +++ b/changelog.d/fix-mt-separate-filing-allocations.fixed.md @@ -0,0 +1 @@ +Montana's federal income tax deduction for spouses filing separately on the same form now attributes federal tax to the spouse who earned the income and caps each spouse at 5,000; the child and dependent care expense deduction phases out on the couple's combined Montana AGI and is halved only for spouses filing separately on the same form; and the elderly homeowner/renter credit counts income without regard to losses. 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 c949e5c54fe..7c3c2184cdf 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 @@ -4,12 +4,28 @@ metadata: period: year label: Montana elderly homeowner or renter credit gross income sources reference: + - title: HB191 (2021), section 1(9), all nontaxable income + href: https://archive.legmt.gov/bills/2021/HB0199/HB0191_X.pdf#page=2 - title: Montana 2022 Form2 Individual Income Tax Instructions, Page47, Gross Household Income href: https://revenuefiles.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: 2022 Montana Code Annotated, Title 15, Chapter 30, Part 23, 15-30-2337 (9) href: https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-23/section-15-30-2337/ - title: 2024 Montana Individual Income Tax Return href: https://revenuefiles.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: ARM 42.4.301(2) as in effect 8/20/2004 to 11/9/2017 ("elderly homeowner/renter credit refunds received") + href: https://rules.mt.gov/api/policy-library-public/collections/aec52c46-128e-4279-9068-8af5d5432d74/policies/c90ea93a-03a2-4605-a0f8-f2f3760198bf/document/788508cc-8418-41c8-926d-3dfb11af3fd6 + - title: ARM 42.4.301(2) as in effect 11/10/2017 to 9/6/2024 ("federal refundable credits received" and "any state refundable credits received") + 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: 2017 Montana Form 2EC instructions, Gross Household Income Worksheet, item 4 ("Any federal or state refundable credits including the 2EC credit") + href: https://taxsim.nber.org/historical_state_tax_forms/MT/2017/Form%202%202017%20Booklet.pdf#page=65 + - title: 2023 Montana Form 2 instructions, Elderly Homeowner/Renter Credit Schedule, line 7, refundable credits received + 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, line 8, refundable credits received + 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=47 + - title: 2025 Montana Form 2 instructions, Schedule 2EC, line 8, refundable credits received + 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: 2009-01-01: @@ -27,8 +43,45 @@ values: - child_support_received # § 15-30-2337(9)(a)(iv) – support money - strike_benefits # § 15-30-2337(9)(a)(v) – nontaxable strike benefits - general_assistance # § 15-30-2337(9)(a)(vi) – cash public assistance - - mt_refundable_credits_before_renter_credit # Line 7: Refundable credits + # 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)). + - mt_refundable_credits_before_renter_credit + - mt_elderly_homeowner_or_renter_credit_received + # ARM 42.4.301(2)(b), amended effective 11/10/2017, counts "federal + # refundable credits received"; the 2017 Form 2EC worksheet counts "any + # federal or state refundable credits". + 2017-01-01: + - adjusted_gross_income_person + - tax_exempt_interest_income + - tax_exempt_ira_distributions + - tax_exempt_pension_income + - veterans_benefits + - child_support_received + - strike_benefits + - general_assistance + - mt_refundable_credits_before_renter_credit + - mt_elderly_homeowner_or_renter_credit_received + - mt_elderly_homeowner_or_renter_credit_federal_refundable_credits + # Actual rebate receipts count under the statutory nontaxable-income + # definition and ARM 42.4.301(2)(c). The 2023 form's illustrative list + # establishes no exemption for 2022 rebates received in 2023. The + # 2024/2025 schedules explicitly name the prior property-year rebates. + 2023-01-01: + - adjusted_gross_income_person + - tax_exempt_interest_income + - tax_exempt_ira_distributions + - tax_exempt_pension_income + - veterans_benefits + - child_support_received + - strike_benefits + - general_assistance + - mt_refundable_credits_before_renter_credit + - mt_elderly_homeowner_or_renter_credit_received + - mt_elderly_homeowner_or_renter_credit_federal_refundable_credits + - mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received # Social Security under § 15-30-2337(9)(a)(viii) is added separately in # mt_elderly_homeowner_or_renter_credit_gross_household_income.py because the # untaxed portion is computed as social_security - taxable_social_security. -# The renter credit itself is excluded to avoid a circular reference. +# This year's elderly homeowner/renter credit is excluded to avoid a circular +# reference; the credit received during the year counts instead. diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/credits/rebate/property/amount.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/credits/rebate/property/amount.yaml index 4ec487b6cb1..f7c074a2c6d 100644 --- a/policyengine_us/parameters/gov/states/mt/tax/income/credits/rebate/property/amount.yaml +++ b/policyengine_us/parameters/gov/states/mt/tax/income/credits/rebate/property/amount.yaml @@ -1,7 +1,13 @@ description: Montana provides the following property tax rebate amount. values: + # Tax years 2022 and 2023: $500 (HB 222, Ch. 47, L. 2023) plus the $175 + # bonus (HB 816, Ch. 764, L. 2023). 2022-01-01: 675 - # The property tax rebate was only offered in the 2022 and 2023 tax years + # Tax year 2024: $400 (SB 542, Ch. 767, L. 2025, sec. 3). + 2024-01-01: 400 + # No rebate has been enacted for tax year 2025 or later; the tax year 2024 + # rebate terminates June 30, 2026 (Ch. 767, L. 2025, sec. 29(2)). + 2025-01-01: 0 metadata: period: year unit: currency-USD @@ -16,3 +22,9 @@ metadata: href: https://docs.legmt.gov/download-ticket?ticketId=ff6faf83-515f-4472-a079-c41a4a75e827#page=1 - title: 2024 Montana Form 2 Instructions href: https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2024_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=3 + - title: Montana Department of Revenue, Income tax and property tax rebate report, May 2024 (maximum rebate $675 in each of tax years 2022 and 2023) + href: https://archive.legmt.gov/content/Committees/Interim/2023-2024/Revenue/Meetings/May-2024/5.1-DOR-rebate-report.pdf#page=13 + - title: 2025 Montana Session Laws, Chapter 767 (SB 542), sec. 3(1), property tax rebate for tax year 2024 + href: https://archive.legmt.gov/content/Sessions/69th/Contractor_index/CH0767.pdf#page=2 + - title: 2025 Montana Session Laws, Chapter 767 (SB 542), sec. 29(2), termination of the tax year 2024 rebate + href: https://archive.legmt.gov/content/Sessions/69th/Contractor_index/CH0767.pdf#page=44 diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml index 1eeb0351b1a..c4744269768 100644 --- a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml +++ b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml @@ -1,8 +1,11 @@ description: Montana counts these sources as miscellaneous deductions. +# The child and dependent care expense deduction is also a miscellaneous +# deduction (Form 2 Itemized Deductions Schedule, line 14). It is added +# separately because its income limit uses the return's own Montana AGI, which +# differs between a joint return and separate columns (Form 2441-M line 3). values: 2021-01-01: - - mt_child_dependent_care_expense_deduction - - casualty_loss_deduction + - casualty_loss_deduction 2024-01-01: [] metadata: diff --git a/policyengine_us/tests/policy/baseline/gov/states/household/state_property_tax_credits/mt_property_tax_rebate_in_aggregate.yaml b/policyengine_us/tests/policy/baseline/gov/states/household/state_property_tax_credits/mt_property_tax_rebate_in_aggregate.yaml index 3abf3612f58..3c15dbf23d6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/household/state_property_tax_credits/mt_property_tax_rebate_in_aggregate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/household/state_property_tax_credits/mt_property_tax_rebate_in_aggregate.yaml @@ -24,3 +24,45 @@ # contributor to the aggregate is the $675 property tax rebate. mt_property_tax_rebate: 675 taxsim_state_property_tax_credit: 675 + +- name: The tax year 2024 MT property tax rebate of 400 flows into the aggregate + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 50_000 + real_estate_taxes: 2_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MT + output: + # Ch. 767, L. 2025 (SB 542), sec. 3(1): $400 for tax year 2024. Before + # the rebate amount was dated, the model carried 675 into 2024. + mt_property_tax_rebate: 400 + taxsim_state_property_tax_credit: 400 + +- name: No MT property tax rebate in the aggregate for tax year 2025 + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 50_000 + real_estate_taxes: 2_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MT + output: + mt_property_tax_rebate: 0 + taxsim_state_property_tax_credit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml index 9e4f2540b71..472a0218dd0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml @@ -200,8 +200,16 @@ members: [person1] state_code: MT output: - # (863.08 - (gross_hh - 6,300) x 0.05) x 1.0, capped at $1,000 = $133. - mt_elderly_homeowner_or_renter_credit: 133.33 + # (863.08 - (gross_hh - 6,300) x 0.05) x 1.0, capped at $1,000. + # 2021 instructions, line 7: "Refundable credits include the federal and + # Montana Earned Income Tax Credit, the federal Child Tax Credits, + # Economic Impact Payments". Federal EITC (ARPA, no qualifying child) + # 1_502 - 15.3% x (20_892.60 - 11_610) = 81.76; Montana EITC 3% = 2.45; + # Economic Impact Payment 1_400. Line 10 = 20_892.60 + 1_484.21 = + # 22_376.81; line 12 = 16_076.81; line 14 = 803.84; line 19 = 863.08 - + # 803.84 = 59.24. The earlier expected value, 133.33, counted only the + # Montana EITC. + mt_elderly_homeowner_or_renter_credit: 59.24 - name: MT EHRC uses the $12,600 exclusion from 2022 (HB 191) period: 2022 @@ -223,3 +231,352 @@ # Same household, larger exclusion ($12,600) leaves a smaller expected # contribution, so the credit is higher: $539.67. mt_elderly_homeowner_or_renter_credit: 539.67 + +- name: 2023 Elderly Homeowner/Renter Credit Schedule worked case, a capital loss does not reduce gross household income + period: 2023 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + long_term_capital_gains: -3_000 + social_security_retirement: 10_000 + real_estate_taxes: 2_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # Line 1 federal AGI 17_000 (Social Security untaxed: 17_000 + 5_000 is + # under the 25_000 base); line 5 untaxed Social Security 10_000; line 9 + # losses 3_000; line 10 gross household income 30_000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 30_000 + # Line 12 = 30_000 - 12_600 = 17_400; line 13 multiplier 0.05; + # line 14 = 870. + mt_elderly_homeowner_or_renter_credit_net_household_income: 870 + # Line 18 = 2_000; line 19 = 1_130; line 20 = min(1_130, 1_150); + # line 21 = 100% (under 35_000); line 22 = 1_130. + mt_elderly_homeowner_or_renter_credit: 1_130 + +- name: 2024 Schedule 2EC worked case, a dependent's wages count toward gross household income + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + person2: + age: 16 + employment_income: 10_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # Line 5 pensions 20_000; line 17 other household members' income + # 10_000 (the dependent's wages); line 18 gross household income 30_000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 10_000] + # Line 19 12_600; line 20 = 30_000 - 12_600 = 17_400; line 21 = 0.05; + # line 22 = 870. + mt_elderly_homeowner_or_renter_credit_net_household_income: [870, 0] + # Line 23 = 2_000; line 26 = 2_000; line 27 = 2_000 - 870 = 1_130; + # line 28 = min(1_130, 1_150) = 1_130; line 29 = 100% (line 18 is under + # 35_000); line 30 = 1_130. Without the dependent's wages, gross + # household income would be 20_000 and the credit the 1_150 cap. + mt_elderly_homeowner_or_renter_credit: [1_130, 0] + +- name: 2024 Schedule 2EC worked case, a household member filing their own return counts + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + person2: + age: 45 + taxable_interest_income: 10_000 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # The claimant's adult daughter lives in the household and files her + # own return. § 15-30-2337(7)(a): a household is "an association of + # persons who live in the same dwelling"; the 2024 instructions: "You do + # not need to be related to be in the same household." Line 5 = 20_000; + # line 17 = 10_000 (her interest); line 18 = 30_000; line 22 = 870; + # line 27 = 2_000 - 870 = 1_130; line 30 = 1_130. Her tax unit has no + # one aged 62 or older, so it claims nothing. + mt_elderly_homeowner_or_renter_credit_net_household_income: [870, 0] + mt_elderly_homeowner_or_renter_credit: [1_130, 0] + +- name: 2024 Schedule 2EC worked case, a dependent's wages move the credit into the 40 percent band + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + person2: + age: 17 + employment_income: 16_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # Line 18 = 20_000 + 16_000 = 36_000; line 20 = 23_400; line 22 = + # 23_400 x 0.05 = 1_170; line 27 = 2_000 - 1_170 = 830; line 28 = 830; + # line 29 = 40% (36_000 is in the 35_000 to 37_500 band); + # line 30 = 830 x 0.4 = 332. + mt_elderly_homeowner_or_renter_credit_net_household_income: [1_170, 0] + mt_elderly_homeowner_or_renter_credit: [332, 0] + +- name: 2024 Schedule 2EC, a dependent's wages bring gross household income to 45,000 + period: 2024 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + person2: + age: 17 + employment_income: 25_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # Line 18 = 45_000. The claimant must have gross household income "less + # than $45,000"; line 29 = 0% at 45_000 and above. + mt_elderly_homeowner_or_renter_credit: [0, 0] + +- name: 2023 Elderly Homeowner/Renter Credit Schedule worked case, a non-filing dependent's wages count + period: 2023 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + person2: + age: 16 + employment_income: 10_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # Line 1 federal AGI 20_000 (the head's return). The dependent's 10_000 + # of wages is under the 13_850 filing threshold for dependents; the + # 2023 instructions say members of the household not filing a Form 2 or + # federal Form 1040 "must report all wages" on line 8. Line 10 = 30_000; + # line 12 = 17_400; line 13 = 0.05; line 14 = 870; line 15 = 2_000; + # line 18 = 2_000; line 19 = 1_130; line 20 = 1_130; line 21 = 100%; + # line 22 = 1_130. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 10_000] + mt_elderly_homeowner_or_renter_credit_net_household_income: [870, 0] + mt_elderly_homeowner_or_renter_credit: [1_130, 0] + +- name: 2024 Schedule 2EC worked case, a household member's federal EITC counts as a refundable credit received + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + person2: + age: 40 + employment_income: 10_000 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # The claimant's adult son lives in the household and files his own + # return. Line 5 pensions 20_000. Line 8, refundable credits received by + # all household members: his federal EITC 632 (Rev. Proc. 2023-34, no + # qualifying child, wages between 8_260 and 10_330) and Montana EITC + # 10% x 632 = 63.20, 695.20 in all. Line 17 his wages 10_000. Line 18 + # gross household income 30_695.20; line 20 = 30_695.20 - 12_600 = + # 18_095.20; line 21 = 0.05; line 22 = 904.76; line 27 = 2_000 - 904.76 + # = 1_095.24; line 28 = 1_095.24 (under 1_150); line 29 = 100%; line 30 = + # 1_095.24. Counting only his Montana EITC gave 1_126.84. + mt_elderly_homeowner_or_renter_credit_net_household_income: [904.76, 0] + mt_elderly_homeowner_or_renter_credit: [1_095.24, 0] + +- name: 2024 Schedule 2EC worked case, a parent's federal EITC and refundable child tax credit count + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + person2: + age: 30 + employment_income: 10_000 + person3: + age: 5 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2, person3] + households: + household: + members: [person1, person2, person3] + state_name: MT + output: + # The claimant's daughter and grandchild live in the household; the + # daughter files her own return. Line 8: federal EITC 34% x 10_000 = + # 3_400, refundable part of the federal child tax credit + # min(1_700, 15% x (10_000 - 2_500)) = 1_125, Montana EITC 340; 4_865 in + # all. Line 17 her wages 10_000. Line 18 = 20_000 + 4_865 + 10_000 = + # 34_865; line 20 = 22_265; line 22 = 22_265 x 0.05 = 1_113.25; line 27 = + # 2_000 - 1_113.25 = 886.75; line 29 = 100% (under 35_000); line 30 = + # 886.75. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 14_865, 0] + mt_elderly_homeowner_or_renter_credit_net_household_income: [1_113.25, 0, 0] + mt_elderly_homeowner_or_renter_credit: [886.75, 0, 0] + +- name: 2024 Schedule 2EC worked case, federal credits move gross household income into the 30 percent band + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + person2: + age: 30 + employment_income: 12_000 + person3: + age: 5 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2, person3] + households: + household: + members: [person1, person2, person3] + state_name: MT + output: + # Line 8: federal EITC 34% x 12_000 = 4_080 (under the 12_390 earned + # income amount), refundable child tax credit + # min(1_700, 15% x 9_500) = 1_425, Montana EITC 408; 5_913 in all. + # Line 18 = 20_000 + 5_913 + 12_000 = 37_913; line 20 = 25_313; + # line 22 = 1_265.65; line 27 = 734.35; line 29 = 30% (37_501 to 40_000); + # line 30 = 734.35 x 0.3 = 220.31. Counting only the Montana EITC, line + # 18 was 32_408 and the credit 1_009.60. + mt_elderly_homeowner_or_renter_credit: [220.31, 0, 0] + +- name: 2025 Schedule 2EC worked case, the credit and the property tax rebate received in 2025 count + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 72 + taxable_pension_income: 20_000 + real_estate_taxes: + 2024: 1_000 + 2025: 1_000 + mt_elderly_homeowner_or_renter_credit_received: 1_150 + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 400 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: + 2024: MT + 2025: MT + output: + # Line 5 pensions 20_000. Line 8: "your elderly homeowner/renter credit + # received in 2025", 1_150 (the 2024 credit), and "the 2024 Montana + # property tax rebate", min(400, 1_000) = 400; 1_550 in all. Line 18 = + # 21_550; line 20 = 8_950; line 21 = 0.039 (8_000 to 9_000); line 22 = + # 349.05; line 27 = 1_000 - 349.05 = 650.95; line 30 = 650.95. Without + # line 8, line 22 would be 7_400 x 0.035 = 259 and the credit 741. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 21_550 + mt_elderly_homeowner_or_renter_credit_net_household_income: 349.05 + mt_elderly_homeowner_or_renter_credit: 650.95 + +- name: 2023 Elderly Homeowner/Renter Credit Schedule worked case, a household member's federal EITC counts on line 7 + period: 2023 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: + 2022: 2_000 + 2023: 2_000 + person2: + age: 40 + employment_income: 10_000 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + households: + household: + members: [person1, person2] + state_code: + 2022: MT + 2023: MT + output: + # Line 1 federal AGI of all household members 30_000. Line 7, the + # refundable credits received by the members of the household: the + # son's federal EITC, Rev. Proc. 2022-38, no qualifying child, + # 600 - 7.65% x (10_000 - 9_800) = 584.70, and Montana EITC 3%, 17.54; + # 602.24 in all. No actual property-rebate receipt is supplied, so + # none counts. Line 10 = 30_602.24; line 12 = 18_002.24; line 13 = + # 0.05; line 14 = 900.11; line 19 = 2_000 - 900.11 = 1_099.89; line 20 + # = 1_099.89; line 21 = 100%; line 22 = 1_099.89. + mt_elderly_homeowner_or_renter_credit_net_household_income: [900.11, 0] + mt_elderly_homeowner_or_renter_credit: [1_099.89, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_dependent_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_dependent_deductions.yaml new file mode 100644 index 00000000000..a0bd2ad43a7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_dependent_deductions.yaml @@ -0,0 +1,195 @@ +- name: Dependent Schedule C income keeps the dependent's half-SE-tax deduction + period: 2023 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_tax_unit_dependent: false + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + dependent: + age: 16 + is_tax_unit_dependent: true + self_employment_income: 15_000 + tax_units: + tax_unit: + members: [claimant, dependent] + households: + household: + members: [claimant, dependent] + state_name: MT + output: + # 2023 Schedule SE p.1: 15,000 * .9235 * .153 / 2 = 1,059.71625. + # 2023 MT instructions p.51, line 1: include each member's federal AGI. + # Dependent AGI without losses = 15,000 - 1,059.71625 = 13,940.28375; + # GHI = 20,000 + 13,940.28375 = 33,940.28375. + self_employment_tax_ald_person: [0, 1_059.71625] + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 13_940.28375] + # 2,000 - (33,940.28375 - 12,600) * .05 = 932.9858125. + # GHI is below 35,000, so the credit multiplier is 100%. + mt_elderly_homeowner_or_renter_credit: [932.9858125, 0] + +- name: The statutory AGI definition also preserves dependent deductions in 2024 + period: 2024 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_tax_unit_dependent: false + taxable_pension_income: 20_000 + real_estate_taxes: 2_000 + dependent: + age: 16 + is_tax_unit_dependent: true + self_employment_income: 15_000 + tax_units: + tax_unit: + members: [claimant, dependent] + households: + household: + members: [claimant, dependent] + state_name: MT + output: + # HB191 section 1(9), PDF p.2: federal AGI without losses remains the + # income definition. 15,000 - 15,000 * .9235 * .153 / 2 = 13,940.28375. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 13_940.28375] + # 2,000 - (20,000 + 13,940.28375 - 12,600) * .05 = 932.9858125. + mt_elderly_homeowner_or_renter_credit: [932.9858125, 0] + +- name: Own personal deductions count once and parental HSA deductions stay with the parent + period: 2024 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_tax_unit_dependent: false + taxable_pension_income: 20_000 + mt_refundable_credits_before_renter_credit: 0 + dependent: + age: 16 + is_tax_unit_dependent: true + employment_income: 10_000 + self_employment_income: 15_000 + traditional_ira_contributions: 1_000 + early_withdrawal_penalty: 200 + self_employed_health_insurance_premiums: 500 + self_employed_pension_contributions: 600 + long_term_capital_gains: -3_000 + rental_income: -2_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [claimant, dependent] + health_savings_account_ald: 1_000 + income_tax_refundable_credits: 0 + households: + household: + members: [claimant, dependent] + state_name: MT + output: + # Parent AGI: 20,000 - own HSA deduction 1,000 = 19,000. + # Dependent: 10,000 + 15,000 - 1,059.71625 - 1,000 - 200 - 500 - 600 + # = 21,640.28375. Capital/rental losses do not reduce MT income. + # The parent's HSA deduction must not be subtracted from the child again. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [19_000, 21_640.28375] + +- name: Parent-owned exclusions recorded on a dependent are not deducted twice + period: 2024 + input: + people: + claimant: + age: 70 + is_tax_unit_dependent: false + taxable_pension_income: 20_000 + mt_refundable_credits_before_renter_credit: 0 + dependent: + age: 16 + is_tax_unit_dependent: true + employment_income: 10_000 + qualified_adoption_assistance_expense: 500 + us_bonds_for_higher_ed: 700 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [claimant, dependent] + income_tax_refundable_credits: 0 + households: + household: + members: [claimant, dependent] + state_name: MT + output: + # The established filer_amounts_recorded_on_dependents convention puts + # 500 + 700 = 1,200 on the parent's return: 20,000 - 1,200 = 18,800. + # Neither is the dependent's own deduction: their income stays 10,000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [18_800, 10_000] + +- name: Dependent educator and IRA deductions retain their statutory annual caps + period: 2023 + input: + people: + claimant: + age: 70 + is_tax_unit_dependent: false + taxable_pension_income: 20_000 + mt_refundable_credits_before_renter_credit: 0 + dependent: + age: 19 + is_tax_unit_dependent: true + employment_income: 10_000 + traditional_ira_contributions: 10_000 + educator_expense: 1_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [claimant, dependent] + income_tax_refundable_credits: 0 + households: + household: + members: [claimant, dependent] + state_name: MT + output: + # https://www.irs.gov/pub/irs-prior/p590a--2023.pdf#page=11: + # IRA deduction = min(10,000 contributions, 10,000 compensation, + # 6,500 annual limit) = 6,500; supplied actual excess is not deductible. + traditional_ira_deduction: [0, 6_500] + # https://www.irs.gov/pub/irs-prior/i1040gi--2023.pdf#page=89: + # Eligible educator deduction = min(1,000 expenses, 300 cap) = 300. + educator_expense_ald_person: [0, 300] + # Dependent AGI without losses = 10,000 - 6,500 - 300 = 3,200. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 3_200] + +- name: Own compensation caps the IRA deduction without flooring valid negative AGI + period: 2024 + input: + people: + claimant: + age: 70 + is_tax_unit_dependent: false + taxable_pension_income: 20_000 + mt_refundable_credits_before_renter_credit: 0 + dependent: + age: 19 + is_tax_unit_dependent: true + employment_income: 2_000 + traditional_ira_contributions: 10_000 + educator_expense: 1_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [claimant, dependent] + income_tax_refundable_credits: 0 + households: + household: + members: [claimant, dependent] + state_name: MT + output: + # Pub.590-A (2023) pp.2,11: 2024 annual limit is 7,000; compensation + # is only 2,000, so min(10,000, 2,000, 7,000) = 2,000 is deductible. + traditional_ira_deduction: [0, 2_000] + # The $300 educator deduction is valid separately from compensation: + # 2,000 - 2,000 - 300 = -300. This is an adjustment, not a loss. + educator_expense_ald_person: [0, 300] + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, -300] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_federal_refundable_credits.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_federal_refundable_credits.yaml new file mode 100644 index 00000000000..5f2f32ff9fe --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_federal_refundable_credits.yaml @@ -0,0 +1,95 @@ +- name: Case 1, a single parent's federal EITC and refundable CTC are counted once, on the head. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 10_000 + person2: + age: 5 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Rev. Proc. 2023-34, one qualifying child: 34% x 10_000 = 3_400 (under + # the 12_390 earned income amount). Refundable CTC: no tax before credits + # (10_000 is under the standard deduction), so + # min(1_700, 15% x (10_000 - 2_500)) = 1_125. + eitc: 3_400 + refundable_ctc: 1_125 + # 3_400 + 1_125 = 4_525, on the head only. + mt_elderly_homeowner_or_renter_credit_federal_refundable_credits: [4_525, 0] + +- name: Case 2, a joint return's federal refundable credits go to the head, not the spouse or child. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + employment_income: 15_000 + person2: + age: 33 + person3: + age: 4 + tax_units: + tax_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # Rev. Proc. 2023-34, one child: 15_000 is above the 12_390 earned income + # amount and under the 29_640 joint phaseout threshold, so the maximum + # 4_213. Refundable CTC: min(1_700, 15% x (15_000 - 2_500) = 1_875) + # = 1_700. Total 5_913. + mt_elderly_homeowner_or_renter_credit_federal_refundable_credits: [5_913, 0, 0] + +- name: Case 3, the 2021 Economic Impact Payment counts with the federal EITC. + period: 2021 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 63 + employment_income: 20_892.60 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # 2021 Elderly Homeowner/Renter Credit Schedule instructions, line 7: + # "Refundable credits include the federal and Montana Earned Income Tax + # Credit, the federal Child Tax Credits, Economic Impact Payments". + # 2021 childless EITC (ARPA): 1_502 - 15.3% x (20_892.60 - 11_610) + # = 81.76. Recovery rebate credit (third-round payment): 1_400. + eitc: 81.76 + mt_elderly_homeowner_or_renter_credit_federal_refundable_credits: 1_481.76 + +- name: Case 4, zero outside Montana. + period: 2024 + input: + people: + person1: + age: 30 + employment_income: 10_000 + person2: + age: 5 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: ID + output: + mt_elderly_homeowner_or_renter_credit_federal_refundable_credits: [0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml index cee870a82af..ec00f8c2fad 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml @@ -37,3 +37,302 @@ # § 15-30-2337(9)(a)(viii) requires all SS to be counted, even the # portion excluded from federal AGI. mt_elderly_homeowner_or_renter_credit_gross_household_income: 30_000 + +- name: A capital loss deducted in federal AGI is added back + period: 2023 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + long_term_capital_gains: -3_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # § 15-30-2337(9)(a): federal AGI "without regard to loss". 2023 Elderly + # Homeowner/Renter Credit Schedule: + # line 1 federal AGI 20_000 - 3_000 = 17_000; line 9 adds back the 3_000 + # capital loss; line 10 = 20_000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 20_000 + +- name: A business loss deducted in federal AGI is added back once for the return + period: 2024 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + mt_refundable_credits_before_renter_credit: 0 + person2: + age: 68 + self_employment_income: -5_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # Federal AGI 20_000 - 5_000 = 15_000; the 5_000 loss is added back, so + # gross household income is 20_000 (2024 Schedule 2EC line 18). Net household + # income: (20_000 - 12_600) * 0.035 = 259, allocated to the head. + mt_elderly_homeowner_or_renter_credit_net_household_income: [259, 0] + +- name: A dependent's wages count in gross household income + period: 2024 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + person2: + age: 16 + employment_income: 10_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # § 15-30-2337(4): "all income received by all individuals of a + # household". The dependent's wages are on their own return, not in the + # head's federal AGI. 2024 Schedule 2EC: line 5 pensions 20_000 (head); + # line 17 income of other household members 10_000 (dependent's wages); + # line 18 gross household income 30_000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 10_000] + +- name: A dependent's Social Security counts once, in full + period: 2024 + input: + people: + person1: + age: 70 + person2: + age: 17 + employment_income: 25_000 + social_security_survivors: 10_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # On the dependent's own return, provisional income is + # 25_000 + 10_000 / 2 = 30_000, so 50% x (30_000 - 25_000) = 2_500 of the + # benefits is taxable (IRC 86) and is part of dependent_gross_income. + dependent_taxable_social_security: [0, 2_500] + # § 15-30-2337(9)(a)(viii) counts all Social Security, and 2024 + # Schedule 2EC line 6 takes "the total taxable and nontaxable" benefits: + # wages 25_000 + Social Security 10_000 = 35_000, not 37_500. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [0, 35_000] + +- name: A dependent's capital loss does not reduce gross household income + period: 2024 + input: + people: + person1: + age: 70 + person2: + age: 16 + employment_income: 10_000 + long_term_capital_gains: -3_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # § 15-30-2337(9)(a): income "without regard to loss". 2024 Schedule 2EC + # line 7: "do not include any capital losses". Wages 10_000 count; the + # 3_000 loss does not reduce them. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [0, 10_000] + +- name: A household member's federal and Montana EITC count once, on their own return + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + person2: + age: 40 + employment_income: 10_000 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # 2024 Schedule 2EC line 8: "Combine all the refundable credits received + # by all household members in 2024. Refundable credits include the + # federal and Montana earned income tax credits". Rev. Proc. 2023-34, no + # qualifying child: 10_000 is above the 8_260 earned income amount and + # under the 10_330 phaseout threshold, so the maximum 632; the Montana + # EITC is 10% of it, 63.20. The member's 10_000 + 632 + 63.20. + mt_elderly_homeowner_or_renter_credit_federal_refundable_credits: [0, 632] + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 10_695.20] + +- name: A parent's federal EITC and refundable child tax credit count in gross household income + period: 2024 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + person2: + age: 30 + employment_income: 10_000 + person3: + age: 5 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2, person3] + households: + household: + members: [person1, person2, person3] + state_name: MT + output: + # 2024 Schedule 2EC line 8 counts "the refundable portion of the federal + # child tax credit". Federal EITC 34% x 10_000 = 3_400; refundable CTC + # min(1_700, 15% x 7_500) = 1_125; Montana EITC 340. The parent: + # 10_000 + 3_400 + 1_125 + 340 = 14_865. + mt_elderly_homeowner_or_renter_credit_gross_household_income: [20_000, 14_865, 0] + +- name: The elderly homeowner/renter credit received during the year counts + period: 2024 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + mt_elderly_homeowner_or_renter_credit_received: 1_150 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # 2024 Schedule 2EC line 8: "including your elderly homeowner/renter + # credit received in 2024". 20_000 + 1_150. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 21_150 + +- name: The rebate for 2023 property taxes, received in 2024, counts + period: 2024 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: + 2023: 2_000 + 2024: 2_000 + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 675 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: + 2023: MT + 2024: MT + output: + # 2024 Schedule 2EC line 8 lists "the 2023 Montana property tax rebate": + # min(675, 2_000) = 675. 20_000 + 675. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 20_675 + +- name: Actual rebate cash received in 2023 counts under the broad income definition + period: 2023 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: + 2022: 2_000 + 2023: 2_000 + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 675 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: + 2022: MT + 2023: MT + output: + # HB191 section1(9), PDFp2: all nontaxable income; ARM42.4.301(2)(c): + # state refundable credits received. The illustrative 2023 list does + # not exempt the rebate. Pension 20_000 + actual cash 675 = 20_675. + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 675 + mt_elderly_homeowner_or_renter_credit_gross_household_income: 20_675 + +- name: Federal refundable credits count from 2017 + period: 2017 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 10_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # ARM 42.4.301(2)(b), effective 11/10/2017: "federal refundable credits + # received"; 2017 Form 2EC worksheet item 4: "Any federal or state + # refundable credits". Rev. Proc. 2016-55, no qualifying child: + # 510 - 7.65% x (10_000 - 8_340) = 383.01. + eitc: 383.01 + mt_elderly_homeowner_or_renter_credit_gross_household_income: 10_383.01 + +- name: Federal refundable credits do not count before 2017 + period: 2016 + input: + people: + person1: + age: 40 + employment_income: 10_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # The 2004-2017 ARM 42.4.301(2)(b) counted federal refunds only "to the + # extent that the amount recovered reduced the claimant's Montana income + # tax in a prior year". + mt_elderly_homeowner_or_renter_credit_gross_household_income: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_net_household_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_net_household_income.yaml index dcc4e975a04..052498f9036 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_net_household_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_net_household_income.yaml @@ -40,3 +40,29 @@ output: # (48,000 - 6,300) x 0.05 with the 2021 exclusion of $6,300. mt_elderly_homeowner_or_renter_credit_net_household_income: [2_085, 0] + +- name: Gross household income includes members outside the claimant's tax unit + period: 2024 + input: + people: + person1: + mt_elderly_homeowner_or_renter_credit_gross_household_income: 20_000 + mt_elderly_homeowner_or_renter_credit_eligible: true + person2: + mt_elderly_homeowner_or_renter_credit_gross_household_income: 10_000 + mt_elderly_homeowner_or_renter_credit_eligible: false + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # 2024 Schedule 2EC line 18 = 20_000 + 10_000 (line 17, the other member) + # = 30_000; line 20 = 30_000 - 12_600 = 17_400; line 21 = 0.05; + # line 22 = 870, on the claimant's row. person2's tax unit has no + # claimant, so 0. + mt_elderly_homeowner_or_renter_credit_net_household_income: [870, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received.yaml new file mode 100644 index 00000000000..dc2ddf161cf --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received.yaml @@ -0,0 +1,139 @@ +- name: Case 1, the head reports actual rebate cash received in 2024. + period: 2024 + input: + people: + person1: + age: 70 + real_estate_taxes: + 2023: 2_000 + 2024: 2_100 + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 675 + person2: + age: 68 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: + 2023: MT + 2024: MT + output: + # 2024 Schedule 2EC line 8: "the 2023 Montana property tax rebate", + # min(675, 2_000) = 675. + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: [675, 0] + +- name: Case 2, the person reports a smaller actual rebate receipt. + period: 2024 + input: + people: + person1: + age: 70 + real_estate_taxes: + 2023: 500 + 2024: 900 + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: + 2023: MT + 2024: MT + output: + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 500 + +- name: Case 3, the 2024 rebate of 400 is received in 2025. + period: 2025 + input: + people: + person1: + age: 70 + real_estate_taxes: + 2024: 2_000 + 2025: 2_000 + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 400 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: + 2024: MT + 2025: MT + output: + # 2025 Schedule 2EC line 8: "the 2024 Montana property tax rebate"; + # Ch. 767, L. 2025, sec. 3(1): $400 or the property taxes paid, if less. + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 400 + +- name: Case 4, no actual cash receipt reported in 2026 defaults to zero. + period: 2026 + input: + people: + person1: + age: 70 + real_estate_taxes: + 2025: 2_000 + 2026: 2_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: + 2025: MT + 2026: MT + output: + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 0 + +- name: Case 5, no reported actual receipt defaults to zero. + period: 2024 + input: + people: + person1: + age: 70 + real_estate_taxes: 2_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # No actual cash receipt was reported; default zero. + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 0 + +- name: Case 6, a prior tax bill does not establish rebate eligibility or receipt. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + real_estate_taxes: + 2024: 1_000 + 2025: 1_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: + 2024: MT + 2025: MT + output: + # Reviewer: six months owning/occupying in 2024 fails SB542 section1(3), + # PDFp2 (minimum seven months); section3(5), PDFp3 also requires a claim. + # No actual receipt: 1_000 - (20_000 - 12_600) * .035 = 741. + # Inferring 400 from the old tax bill incorrectly gave 727. + mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received: 0 + mt_elderly_homeowner_or_renter_credit_gross_household_income: 20_000 + mt_elderly_homeowner_or_renter_credit: 741 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/rebate/mt_property_tax_rebate.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/rebate/mt_property_tax_rebate.yaml index b2c717f534e..ea970786fd0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/rebate/mt_property_tax_rebate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/rebate/mt_property_tax_rebate.yaml @@ -21,3 +21,29 @@ state_code: MT output: mt_property_tax_rebate: 675 + +- name: The tax year 2024 rebate is 400 + period: 2024 + input: + real_estate_taxes: 2_000 + state_code: MT + output: + # Ch. 767, L. 2025 (SB 542), sec. 3(1): "$400 or the amount of total + # property taxes paid, whichever is less, for tax year 2024". + mt_property_tax_rebate: 400 + +- name: The tax year 2024 rebate is the property tax when that is less than 400 + period: 2024 + input: + real_estate_taxes: 300 + state_code: MT + output: + mt_property_tax_rebate: 300 + +- name: No rebate for tax year 2025 + period: 2025 + input: + real_estate_taxes: 2_000 + state_code: MT + output: + mt_property_tax_rebate: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml index df49679c68b..4cdfcd2395d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml @@ -19,8 +19,8 @@ state_code: MT output: # One qualifying individual caps expenses at 2_400. - # Head receives half: 2_400 * 0.5 = 1_200. - mt_child_dependent_care_expense_deduction: [1_200, 0] + # A single filer enters the full line 7 amount: 2_400. + mt_child_dependent_care_expense_deduction: [2_400, 0] - name: Care expense greater than maximum amount with moderate AGI and 1 child period: 2021 @@ -43,8 +43,8 @@ state_code: MT output: # 2_400 - 0.5 * (20_000 - 18_000) = 1_400. - # Head receives half: 1_400 * 0.5 = 700. - mt_child_dependent_care_expense_deduction: [700, 0] + # A single filer enters the full line 7 amount: 1_400. + mt_child_dependent_care_expense_deduction: [1_400, 0] - name: Care expense greater than maximum amount with high AGI and 1 child period: 2021 @@ -92,8 +92,8 @@ state_code: MT output: # Two qualifying individuals cap expenses at 3_600. - # Head receives half: 3_600 * 0.5 = 1_800. - mt_child_dependent_care_expense_deduction: [1_800, 0, 0] + # A single filer enters the full line 7 amount: 3_600. + mt_child_dependent_care_expense_deduction: [3_600, 0, 0] - name: Care expense greater than maximum amount with moderate AGI and 2 children period: 2021 @@ -119,8 +119,8 @@ state_code: MT output: # 3_600 - 0.5 * (22_000 - 18_000) = 1_600. - # Head receives half: 1_600 * 0.5 = 800. - mt_child_dependent_care_expense_deduction: [800, 0, 0] + # A single filer enters the full line 7 amount: 1_600. + mt_child_dependent_care_expense_deduction: [1_600, 0, 0] - name: Care expense greater than maximum amount with high AGI and 2 children period: 2021 @@ -174,8 +174,8 @@ state_code: MT output: # Three qualifying individuals cap expenses at 4_800. - # Head receives half: 4_800 * 0.5 = 2_400. - mt_child_dependent_care_expense_deduction: [2_400, 0, 0, 0] + # A single filer enters the full line 7 amount: 4_800. + mt_child_dependent_care_expense_deduction: [4_800, 0, 0, 0] - name: Care expense greater than maximum amount with moderate AGI and 3 children period: 2021 @@ -204,8 +204,8 @@ state_code: MT output: # 4_800 - 0.5 * (24_000 - 18_000) = 1_800. - # Head receives half: 1_800 * 0.5 = 900. - mt_child_dependent_care_expense_deduction: [900, 0, 0, 0] + # A single filer enters the full line 7 amount: 1_800. + mt_child_dependent_care_expense_deduction: [1_800, 0, 0, 0] - name: Care expense greater than maximum amount with high AGI and 3 children period: 2021 @@ -255,8 +255,8 @@ members: [person1, person2] state_code: MT output: - # Head receives half: 2_000 * 0.5 = 1_000. - mt_child_dependent_care_expense_deduction: [1_000, 0] + # A single filer enters the full line 7 amount: 2_000. + mt_child_dependent_care_expense_deduction: [2_000, 0] - name: Care expense less than maximum amount with high AGI and 1 child period: 2021 @@ -303,8 +303,8 @@ members: [person1, person2, person3] state_code: MT output: - # Head receives half: 3_000 * 0.5 = 1_500. - mt_child_dependent_care_expense_deduction: [1_500, 0, 0] + # A single filer enters the full line 7 amount: 3_000. + mt_child_dependent_care_expense_deduction: [3_000, 0, 0] - name: Care expense less than maximum amount with high AGI and 2 children period: 2021 @@ -357,8 +357,8 @@ members: [person1, person2, person3, person4] state_code: MT output: - # Head receives half: 4_000 * 0.5 = 2_000. - mt_child_dependent_care_expense_deduction: [2_000, 0, 0, 0] + # A single filer enters the full line 7 amount: 4_000. + mt_child_dependent_care_expense_deduction: [4_000, 0, 0, 0] - name: Care expense less than maximum amount with high AGI and 3 children period: 2021 @@ -441,8 +441,8 @@ state_code: MT output: # One qualifying individual caps expenses at 2_400 (Form 2441-M line 2). - # Head receives half: 2_400 * 0.5 = 1_200. - mt_child_dependent_care_expense_deduction: [1_200, 0] + # A single filer enters the full line 7 amount: 2_400. + mt_child_dependent_care_expense_deduction: [2_400, 0] - name: Case 18, capable disabled adult care expenses are excluded. period: 2021 @@ -470,8 +470,8 @@ state_code: MT output: # Only the child qualifies, so 2_000 of adult care expenses are excluded. - # Head receives half of the 1_000 childcare expense deduction. - mt_child_dependent_care_expense_deduction: [500, 0, 0] + # A single filer enters the full 1_000 childcare expense deduction. + mt_child_dependent_care_expense_deduction: [1_000, 0, 0] - name: Case 19, a child and an incapacitated adult dependent both count toward the tier. period: 2021 @@ -500,8 +500,8 @@ output: # Two qualifying individuals cap expenses at 3_600 (Form 2441-M line 2). # Childcare 3_000 + adult care 1_000 = 4_000, capped at 3_600. - # Head receives half: 3_600 * 0.5 = 1_800. - mt_child_dependent_care_expense_deduction: [1_800, 0, 0] + # A single filer enters the full line 7 amount: 3_600. + mt_child_dependent_care_expense_deduction: [3_600, 0, 0] - name: Case 20, adult care expenses cannot exceed the one-qualifying-individual tier cap. period: 2021 @@ -525,8 +525,8 @@ state_code: MT output: # One qualifying individual caps expenses at 2_400 (Form 2441-M line 2). - # Head receives half: 2_400 * 0.5 = 1_200. - mt_child_dependent_care_expense_deduction: [1_200, 0] + # A single filer enters the full line 7 amount: 2_400. + mt_child_dependent_care_expense_deduction: [2_400, 0] - name: Case 21, incapacitated spouse care expenses qualify. period: 2021 @@ -606,8 +606,8 @@ output: # One qualifying individual caps expenses at 2_400 (Form 2441-M line 2). # Childcare 1_500 + care expenses 1_000 = 2_500, capped at 2_400. - # Head receives half: 2_400 * 0.5 = 1_200. - mt_child_dependent_care_expense_deduction: [1_200, 0] + # A single filer enters the full line 7 amount: 2_400. + mt_child_dependent_care_expense_deduction: [2_400, 0] - name: Case 24, combined childcare and adult care expenses are capped by the three-qualifying-individual tier. period: 2021 @@ -643,3 +643,109 @@ # Childcare 4_000 + adult care 2_000 = 6_000, capped at 4_800. # The deduction is divided equally between spouses: 4_800 * 0.5 = 2_400. mt_child_dependent_care_expense_deduction: [2_400, 2_400, 0, 0] + +- name: Case 25, the phase-out uses the couple's combined Montana AGI, then each column gets half + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + is_tax_unit_head: true + mt_agi_indiv: 20_000 + person2: + is_tax_unit_spouse: true + mt_agi_indiv: 0 + person3: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + tax_unit_childcare_expenses: 3_000 + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # Form 2441-M (2022): line 2 = min(3_000, 2_400) = 2_400; line 3 adds + # columns A and B of Form 2 line 14 = 20_000 + 0 = 20_000; line 5 = 2_000; + # line 6 = 1_000; line 7 = 1_400. Filing separately on the same form, each + # spouse enters one-half of line 7 (700). Applying the phase-out to each + # spouse's own AGI would give 700 and 1_200. + mt_child_dependent_care_expense_deduction_eligible_expenses: 2_400 + mt_child_dependent_care_expense_deduction: [700, 700, 0] + +- name: Case 26, a joint return uses the return's Montana AGI and claims the whole deduction + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + is_tax_unit_head: true + person2: + is_tax_unit_spouse: true + person3: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + tax_unit_childcare_expenses: 3_000 + mt_agi_joint: 20_000 + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # Form 2441-M lines 2-7 with line 3 = 20_000: 2_400 - 0.5 * 2_000 = 1_400, + # entered in full on the joint return. + mt_child_dependent_care_expense_deduction_joint: 1_400 + +- name: Case 27, the joint deduction is repealed beginning in 2024 + period: 2024 + input: + people: + person1: + is_tax_unit_head: true + person2: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + tax_unit_childcare_expenses: 3_000 + mt_agi_joint: 10_000 + households: + household: + members: [person1, person2] + state_code: MT + output: + mt_child_dependent_care_expense_deduction_joint: 0 + +- name: Case 28, a married person filing on a separate form cannot take the deduction + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + mt_agi_indiv: 15_000 + person2: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + filing_status: SEPARATE + tax_unit_childcare_expenses: 2_000 + mt_agi_joint: 15_000 + households: + household: + members: [person1, person2] + state_code: MT + output: + # MCA 15-30-2131(1)(c)(vi)(A): "married couples shall file a joint return + # or file separately on the same form". 2022 instructions, line 14: "If + # you are married and filing separately on separate forms, or your spouse + # is not filing, you cannot qualify for this deduction." + mt_child_dependent_care_expense_deduction: [0, 0] + mt_child_dependent_care_expense_deduction_joint: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_childcare_itemization_boundary.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_childcare_itemization_boundary.yaml new file mode 100644 index 00000000000..f6f0e87b609 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_childcare_itemization_boundary.yaml @@ -0,0 +1,88 @@ +# 2022 Form 2441-M, lines 2-7 and separate-spouse allocation (PDF p. 1): +# https://revenue.mt.gov/files/Forms/Child-and-Dependent-Care-Expense-Deduction-Form-2441-M/2022_Child_and_Dependent_Care_Expense_Deduction_Form_2441-M.pdf#page=1 +# Form 2, Itemized Deductions Schedule lines 8 and 14, and Standard Deduction +# Worksheet lines 1-6 (PDF p. 7): +# https://revenue.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2/2022_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=7 +# Instructions for Form 2 line 15 require both spouses to use the same method +# (PDF p. 16); the model retains the standard deduction when totals tie: +# 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=16 + +- name: Combined-AGI care deduction leaves standard deductions at the itemization boundary + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + is_tax_unit_dependent: false + mt_agi_indiv: 20_000 + home_mortgage_interest: 4_860 + spouse: + age: 39 + is_tax_unit_dependent: false + mt_agi_indiv: 0 + child: + age: 5 + is_tax_unit_dependent: true + mt_agi_indiv: 0 + tax_units: + tax_unit: + members: [head, spouse, child] + filing_status: JOINT + state_filing_status_if_married_filing_separately_on_same_return: SEPARATE + tax_unit_childcare_expenses: 3_000 + income_tax_before_refundable_credits: 0 + households: + household: + members: [head, spouse, child] + state_code: MT + output: + # Care = min(3_000, 2_400) - 0.5 * (20_000 + 0 - 18_000) + # = 1_400; each spouse gets 700. Itemized total = 4_860 + 700 + 700 + # = 6_260. Standard = max(20% * 20_000, 2_260) + 2_260 = 6_260. + # The unchanged tie rule selects standard deductions. Phasing out on + # each spouse's own AGI would incorrectly give 700 + 1_200 care and + # itemized total 6_760, crossing the choice boundary. + mt_child_dependent_care_expense_deduction: [700, 700, 0] + mt_itemized_deductions_indiv: [5_560, 700, 0] + mt_standard_deduction_indiv: [4_000, 2_260, 0] + mt_tax_unit_itemizes: false + mt_deductions_indiv: [4_000, 2_260, 0] + +- name: One more dollar of mortgage interest crosses the derived childcare itemization boundary + period: 2022 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + is_tax_unit_dependent: false + mt_agi_indiv: 20_000 + home_mortgage_interest: 4_861 + spouse: + age: 39 + is_tax_unit_dependent: false + mt_agi_indiv: 0 + child: + age: 5 + is_tax_unit_dependent: true + mt_agi_indiv: 0 + tax_units: + tax_unit: + members: [head, spouse, child] + filing_status: JOINT + state_filing_status_if_married_filing_separately_on_same_return: SEPARATE + tax_unit_childcare_expenses: 3_000 + income_tax_before_refundable_credits: 0 + households: + household: + members: [head, spouse, child] + state_code: MT + output: + # The same 1_400 care deduction gives itemized total + # 4_861 + 700 + 700 = 6_261, exceeding the unchanged 6_260 standard. + mt_child_dependent_care_expense_deduction: [700, 700, 0] + mt_itemized_deductions_indiv: [5_561, 700, 0] + mt_standard_deduction_indiv: [4_000, 2_260, 0] + mt_tax_unit_itemizes: true + mt_deductions_indiv: [5_561, 700, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.yaml index e0fed58cbeb..941a60e6c1b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.yaml @@ -1,39 +1,46 @@ -- name: Deductions split evenly +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 4 (PDF p. 33): +# withholding is reported by the spouse who earned the income, and each +# spouse filing separately is limited to 5,000. + +- name: Each spouse deducts their own withholding estimate period: 2021 input: people: person1: - mt_agi_indiv: 500 + mt_withheld_income_tax: 3_000 + adjusted_gross_income_person: 50_000 person2: - mt_agi_indiv: 500 + mt_withheld_income_tax: 1_000 + adjusted_gross_income_person: 50_000 tax_units: tax_unit: members: [person1, person2] - mt_federal_income_tax_deduction_for_federal_itemization: 3_000 state_filing_status_if_married_filing_separately_on_same_return: SEPARATE households: household: members: [person1, person2] state_code: MT output: - mt_federal_income_tax_deduction_for_federal_itemization_indiv: [1_500, 1_500] + # Prorating the couple's 4_000 by Montana AGI would give 2_000 / 2_000. + mt_federal_income_tax_deduction_for_federal_itemization_indiv: [3_000, 1_000] -- name: Deductions capped at max amount individually +- name: Each spouse is capped at 5,000 on their own amount period: 2021 input: people: person1: - mt_agi_indiv: 100 + mt_withheld_income_tax: 7_000 + adjusted_gross_income_person: 60_000 person2: - mt_agi_indiv: 900 + mt_withheld_income_tax: 1_000 + adjusted_gross_income_person: 40_000 tax_units: tax_unit: members: [person1, person2] - mt_federal_income_tax_deduction_for_federal_itemization: 10_000 state_filing_status_if_married_filing_separately_on_same_return: SEPARATE households: household: members: [person1, person2] state_code: MT output: - mt_federal_income_tax_deduction_for_federal_itemization_indiv: [1_000, 5_000] + mt_federal_income_tax_deduction_for_federal_itemization_indiv: [5_000, 1_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml index 85519179428..5c2836f99a1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml @@ -7,7 +7,7 @@ mt_salt_deduction: 5_000 mt_medical_expense_deduction_joint: 1_000 casualty_loss_deduction: 400 - mt_child_dependent_care_expense_deduction: 123 + mt_child_dependent_care_expense_deduction_joint: 123 mt_federal_income_tax_deduction_for_federal_itemization: 2_000 state_code: MT output: @@ -22,7 +22,7 @@ mt_salt_deduction: 10_000 mt_medical_expense_deduction_joint: 3_000 casualty_loss_deduction: 0 - mt_child_dependent_care_expense_deduction: 0 + mt_child_dependent_care_expense_deduction_joint: 0 mt_federal_income_tax_deduction_for_federal_itemization: 2_000 state_code: MT output: @@ -37,7 +37,7 @@ mt_salt_deduction: 1_035 mt_medical_expense_deduction_joint: 3_000 casualty_loss_deduction: 745 - mt_child_dependent_care_expense_deduction: 1_004 + mt_child_dependent_care_expense_deduction_joint: 1_004 mt_federal_income_tax_deduction_for_federal_itemization: 2_000 state_code: MT output: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_federal_income_tax_deduction_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_federal_income_tax_deduction_indiv.yaml index 4b0d588ce5e..a6633222517 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_federal_income_tax_deduction_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_federal_income_tax_deduction_indiv.yaml @@ -1,39 +1,150 @@ -- name: Deductions split evenly +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 4 (PDF p. 33): +# "If you are married filing separately with your spouse, the federal income +# tax withheld should be reported by the spouse who earned the income." and +# "If your filing status is Married Filing Separately, you are each limited to +# the $5,000 federal tax deduction and you cannot arbitrarily allocate this +# deduction between spouses." + +- name: Federal income tax is attributed by each spouse's share of federal AGI period: 2021 + absolute_error_margin: 0.01 input: people: person1: - mt_agi_indiv: 500 + adjusted_gross_income_person: 60_000 person2: - mt_agi_indiv: 500 + adjusted_gross_income_person: 40_000 tax_units: tax_unit: members: [person1, person2] - mt_federal_income_tax_deduction_unit: 3_000 + income_tax_before_refundable_credits: 3_000 state_filing_status_if_married_filing_separately_on_same_return: SEPARATE households: household: members: [person1, person2] state_code: MT output: - mt_federal_income_tax_deduction_indiv: [1_500, 1_500] + mt_federal_income_tax_deduction_share: [0.6, 0.4] + mt_federal_income_tax_deduction_indiv: [1_800, 1_200] -- name: Deductions capped at max amount individually - period: 2021 +- name: Each spouse's own federal tax is capped at 5,000, not a share of the joint cap + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + adjusted_gross_income_person: 80_000 + person2: + adjusted_gross_income_person: 20_000 + tax_units: + tax_unit: + members: [person1, person2] + income_tax_before_refundable_credits: 30_000 + state_filing_status_if_married_filing_separately_on_same_return: SEPARATE + households: + household: + members: [person1, person2] + state_code: MT + output: + # Federal tax attributed 24_000 / 6_000; each spouse is limited to 5_000. + # Capping the couple at 10_000 first and then splitting 80% / 20% would + # give 5_000 / 2_000. + mt_federal_income_tax_deduction_indiv: [5_000, 5_000] + +- name: Montana-only subtractions do not shift federal tax between spouses + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + adjusted_gross_income_person: 50_000 + person2: + adjusted_gross_income_person: 50_000 + mt_subtractions: 20_000 + tax_units: + tax_unit: + members: [person1, person2] + income_tax_before_refundable_credits: 6_000 + state_filing_status_if_married_filing_separately_on_same_return: SEPARATE + households: + household: + members: [person1, person2] + state_code: MT + output: + # The federal tax was paid on 50_000 of federal income each. Prorating by + # Montana AGI (50_000 / 30_000) would give 3_750 / 2_250. + mt_federal_income_tax_deduction_indiv: [3_000, 3_000] + +- name: A dependent's income does not draw a share of the parents' federal tax + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + adjusted_gross_income_person: 40_000 + person2: + adjusted_gross_income_person: 0 + person3: + is_tax_unit_dependent: true + adjusted_gross_income_person: 10_000 + tax_units: + tax_unit: + members: [person1, person2, person3] + income_tax_before_refundable_credits: 2_000 + state_filing_status_if_married_filing_separately_on_same_return: SEPARATE + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # Only the parents' 40_000 + 0 AGI allocates their federal tax: + # 2_000 * 40_000 / 40_000 = 2_000 for the head. Including the + # dependent's 10_000 would incorrectly give 1_600 / 0 / 400. + mt_federal_income_tax_deduction_share: [1, 0, 0] + mt_federal_income_tax_deduction_indiv: [2_000, 0, 0] + +- name: No positive filer income retains main's zero federal tax allocation + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + adjusted_gross_income_person: 0 + person2: + adjusted_gross_income_person: 0 + tax_units: + tax_unit: + members: [person1, person2] + income_tax_before_refundable_credits: 6_000 + state_filing_status_if_married_filing_separately_on_same_return: SEPARATE + households: + household: + members: [person1, person2] + state_code: MT + output: + # Form 2 line 4a (source cited above) does not specify how to allocate + # federal tax when neither spouse has income. Preserve main's zero + # allocation: 6_000 * 0 = 0 for each spouse. Assigning all tax to the head + # would invent a new fallback and produce min(6_000, 5_000) = 5_000. + mt_federal_income_tax_deduction_share: [0, 0] + mt_federal_income_tax_deduction_indiv: [0, 0] + +- name: The deduction is repealed from 2024 + period: 2024 input: people: person1: - mt_agi_indiv: 100 + adjusted_gross_income_person: 60_000 person2: - mt_agi_indiv: 900 + adjusted_gross_income_person: 40_000 tax_units: tax_unit: members: [person1, person2] - mt_federal_income_tax_deduction_unit: 10_000 + income_tax_before_refundable_credits: 3_000 state_filing_status_if_married_filing_separately_on_same_return: SEPARATE households: household: members: [person1, person2] state_code: MT output: - mt_federal_income_tax_deduction_indiv: [1_000, 5_000] + mt_federal_income_tax_deduction_indiv: [0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml index 8289ef0c24c..2e4c3acee9a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml @@ -7,7 +7,7 @@ mt_salt_deduction: 5_000 mt_medical_expense_deduction_joint: 1_000 casualty_loss_deduction: 400 - mt_child_dependent_care_expense_deduction: 123 + mt_child_dependent_care_expense_deduction_joint: 123 mt_federal_income_tax_deduction_unit: 2_000 state_code: MT output: @@ -22,7 +22,7 @@ mt_salt_deduction: 10_000 mt_medical_expense_deduction_joint: 3_000 casualty_loss_deduction: 0 - mt_child_dependent_care_expense_deduction: 0 + mt_child_dependent_care_expense_deduction_joint: 0 mt_federal_income_tax_deduction_unit: 2_000 state_code: MT output: @@ -37,7 +37,7 @@ mt_salt_deduction: 1_035 mt_medical_expense_deduction_joint: 3_000 casualty_loss_deduction: 745 - mt_child_dependent_care_expense_deduction: 1_004 + mt_child_dependent_care_expense_deduction_joint: 1_004 mt_federal_income_tax_deduction_unit: 2_000 state_code: MT output: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml index 1427235ceaa..687a3ecabbf 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml @@ -564,9 +564,12 @@ output: # $2,500 per-return cap split $1,250 per spouse (was double-counted to $5,000) mt_income_tax_rebate: [1_250, 1_250] - # Files separately: 5,099.51 - 1,250 + 3,719.51 - 1,250 = 6,319.02 + # Each spouse deducts their own federal income tax up to $5,000 (2021 + # federal tax 21,175 attributed 90/160 and 70/160), so both itemize 5,000 + # over the 4,830 standard deduction. Taxable income 82,420 and 62,420. + # Files separately: 5,087.78 - 1,250 + 3,707.78 - 1,250 = 6,295.56 # (old double-count subtracted $2,500 per column, understating tax by $2,500) - mt_income_tax: 6_319.02 + mt_income_tax: 6_295.56 - name: MT property tax rebate is not a Form 2 credit and is not tripled (issue 9347) absolute_error_margin: 0.01 diff --git a/policyengine_us/tests/test_mt_elderly_credit_dependent_deductions.py b/policyengine_us/tests/test_mt_elderly_credit_dependent_deductions.py new file mode 100644 index 00000000000..90736451eca --- /dev/null +++ b/policyengine_us/tests/test_mt_elderly_credit_dependent_deductions.py @@ -0,0 +1,209 @@ +"""Own valid adjustments reduce Montana household income once, losses never do. + +2023 Form 2 instructions p.51 line 1 uses each household member's federal +AGI. HB191 section 1(9), PDF p.2, retains federal AGI without regard to loss. +These properties compare a dependent with a member on a separate return, +holding receipts fixed at zero to isolate their non-loss deductions. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings, strategies as st + +from policyengine_us import Simulation + + +def build_situation(cases, year): + people, tax_units, marital_units, spm_units, families, households = ( + {}, + {}, + {}, + {}, + {}, + {}, + ) + for index, case in enumerate(cases): + # A: dependent; B: separate filer; C: no losses; D: extra own penalty. + for arrangement in "ABCD": + key = f"{arrangement}{index}" + claimant, member = f"claimant_{key}", f"member_{key}" + people[claimant] = { + "age": {year: 70}, + "taxable_pension_income": {year: case["pension"]}, + "early_withdrawal_penalty": {year: case["parent_penalty"]}, + "is_tax_unit_dependent": {year: False}, + "mt_refundable_credits_before_renter_credit": {year: 0}, + "mt_elderly_homeowner_or_renter_credit_received": {year: 0}, + "mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received": { + year: 0 + }, + } + people[member] = { + "age": {year: 19}, + "is_tax_unit_dependent": {year: arrangement != "B"}, + "employment_income": {year: case["wages"]}, + "self_employment_income": {year: case["self_employment"]}, + "taxable_interest_income": {year: case["interest"]}, + "traditional_ira_contributions": {year: case["ira"]}, + "educator_expense": {year: case["educator"]}, + "early_withdrawal_penalty": { + year: case["penalty"] + + (case["extra_penalty"] if arrangement == "D" else 0) + }, + "self_employed_health_insurance_premiums": {year: case["health"]}, + "self_employed_pension_contributions": { + year: case["pension_contribution"] + }, + "long_term_capital_gains": { + year: -case["capital_loss"] if arrangement != "C" else 0 + }, + "rental_income": { + year: -case["rental_loss"] if arrangement != "C" else 0 + }, + "mt_refundable_credits_before_renter_credit": {year: 0}, + "mt_elderly_homeowner_or_renter_credit_received": {year: 0}, + "mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received": { + year: 0 + }, + } + tax_units[f"tax_unit_{key}"] = { + "members": [claimant] if arrangement == "B" else [claimant, member], + "health_savings_account_ald": {year: case["parent_hsa"]}, + "income_tax_refundable_credits": {year: 0}, + } + if arrangement == "B": + tax_units[f"member_tax_unit_{key}"] = { + "members": [member], + "health_savings_account_ald": {year: 0}, + "income_tax_refundable_credits": {year: 0}, + } + marital_units[f"claimant_marital_unit_{key}"] = {"members": [claimant]} + marital_units[f"member_marital_unit_{key}"] = {"members": [member]} + spm_units[f"spm_unit_{key}"] = {"members": [claimant, member]} + families[f"family_{key}"] = {"members": [claimant, member]} + households[f"household_{key}"] = { + "members": [claimant, member], + "state_code": {year: "MT"}, + } + return { + "people": people, + "tax_units": tax_units, + "marital_units": marital_units, + "spm_units": spm_units, + "families": families, + "households": households, + } + + +def assert_properties(cases, year): + simulation = Simulation(situation=build_situation(cases, year)) + income = np.asarray( + simulation.calculate( + "mt_elderly_homeowner_or_renter_credit_gross_household_income", year + ) + ).reshape(len(cases), 4, 2) + for index, case in enumerate(cases): + # Schedule SE p.1: net earnings x .9235 x (.124 + .029) / 2. + # Generated positive profits exceed the $400 net-earnings threshold + # and remain below the Social Security wage base. + self_employment_deduction = case["self_employment"] * 0.9235 * 0.153 / 2 + # Pub.590-A (2023) p.11: the deduction cannot exceed contributions, + # compensation, or the annual limit ($6,500 in 2023; $7,000 in + # 2024–25). These single members' MAGI is below the phase-out start. + # Compensation excludes the half-SE-tax and SE retirement deduction. + compensation = case["wages"] + max( + 0, + case["self_employment"] + - self_employment_deduction + - case["pension_contribution"], + ) + ira_deduction = min(case["ira"], compensation, 6_500 if year == 2023 else 7_000) + # 2023 Form1040 instructions p.89: the per-educator cap is $300; + # the cap remains $300 in the other generated years. + educator_deduction = min(case["educator"], 300) + own_deductions = ( + self_employment_deduction + + case["health"] + + case["pension_contribution"] + + ira_deduction + + educator_deduction + + case["penalty"] + ) + parent = case["pension"] - case["parent_hsa"] - case["parent_penalty"] + member = ( + case["wages"] + case["self_employment"] + case["interest"] - own_deductions + ) + tol = max(0.01, 8 * float(np.spacing(np.float32(abs(parent + member))))) + # Each person's non-loss deductions are used once, on that person. + assert income[index, :, 0] == pytest.approx([parent] * 4, abs=tol) + assert income[index, :3, 1] == pytest.approx([member] * 3, abs=tol) + # The filing arrangement leaves total household income unchanged. + assert income[index, 0].sum() == pytest.approx(income[index, 1].sum(), abs=tol) + # Removing capital/rental losses never changes this statutory total. + assert income[index, 0].sum() == pytest.approx(income[index, 2].sum(), abs=tol) + # Another dollar of the dependent's valid penalty is deducted once. + assert income[index, 3, 1] == pytest.approx( + member - case["extra_penalty"], abs=tol + ) + + +def test_worked_deductions_and_losses(): + case = { + "pension": 20_000, + "parent_hsa": 1_000, + "parent_penalty": 50, + "wages": 10_000, + "self_employment": 15_000, + "interest": 500, + "ira": 1_000, + "educator": 100, + "penalty": 200, + "health": 500, + "pension_contribution": 600, + "capital_loss": 3_000, + "rental_loss": 2_000, + "extra_penalty": 300, + } + # Parent = 20,000 - 1,000 - 50 = 18,950. Member = 25,500 - 1,059.71625 + # - 1,000 - 100 - 200 - 500 - 600 = 22,040.28375; extra penalty -> + # 21,740.28375. Neither of the two losses affects these amounts. + assert_properties([case], 2023) + + +@st.composite +def case_strategy(draw): + # Keep health/pension limits nonbinding, including after half-SE-tax: + # at the minimum $5,000 profit, half-SE-tax = $353.23875. With pension + # $500, Form7206 lines7-10,14 allow health up to $4,146.76125; the + # SEP 20% ceiling is ($5,000 - $353.23875) * .2 = $929.35225. + # https://www.irs.gov/pub/irs-prior/f7206--2023.pdf#page=1 + # https://www.irs.gov/pub/irs-prior/p560--2023.pdf#page=35 (PDFpp35-36) + return { + "pension": draw(st.integers(10_000, 35_000)), + "parent_hsa": draw(st.integers(0, 3_000)), + "parent_penalty": draw(st.integers(0, 500)), + "wages": draw(st.integers(10_000, 20_000)), + "self_employment": draw(st.integers(5_000, 15_000)), + "interest": draw(st.integers(0, 10_000)), + "ira": draw(st.integers(0, 10_000)), + "educator": draw(st.integers(0, 1_000)), + "penalty": draw(st.integers(0, 1_000)), + "health": draw(st.integers(0, 500)), + "pension_contribution": draw(st.integers(0, 500)), + "capital_loss": draw(st.integers(0, 20_000)), + "rental_loss": draw(st.integers(0, 20_000)), + "extra_penalty": draw(st.integers(1, 2_000)), + } + + +@settings( + max_examples=15, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) +@given( + cases=st.lists(case_strategy(), min_size=1, max_size=4), + year=st.sampled_from([2023, 2024, 2025]), +) +def test_own_deductions_and_losses_are_independent_of_filing_arrangement(cases, year): + assert_properties(cases, year) diff --git a/policyengine_us/tests/test_mt_elderly_credit_household_members.py b/policyengine_us/tests/test_mt_elderly_credit_household_members.py new file mode 100644 index 00000000000..d1c1fff15ef --- /dev/null +++ b/policyengine_us/tests/test_mt_elderly_credit_household_members.py @@ -0,0 +1,241 @@ +"""Property tests: household members' income in the Montana elderly +homeowner/renter credit. + +MCA 15-30-2337(4) defines gross household income as "all income received by +all individuals of a household while they are members of the household", and +(9)(a) counts federal adjusted gross income "without regard to loss" plus +nontaxable income, including all Social Security. The 2024 Schedule 2EC lists +the claimant's income by category (line 7: "do not include any capital +losses"; line 6: "the total taxable and nontaxable Social Security benefits") +and puts the income of "other members of your household" on line 17. + +The model reaches the same quantity by two paths. A member who is the +claimant's tax unit dependent is counted through dependent_gross_income and +dependent_taxable_social_security; a member who files their own return is +counted through federal AGI, the loss add-back and taxable_social_security. +For a claimant aged 62 or older (taxable pension, Social Security, property +tax, rent) and one other adult in the household (taxable interest, a +long-term capital gain or loss, Social Security): + +1. The household's gross household income is the claimant's pension and + Social Security plus the other member's interest, positive capital gain + and Social Security, whichever path counts the other member. +2. So the claimant's credit is the same in both arrangements, and the other + member's own tax unit, with no one aged 62 or older, claims nothing. +3. The credit is between zero and the cap, and zero when gross household + income is $45,000 or more (line 29). +4. More interest for the other member never raises the credit: gross + household income rises, so net household income (line 22) cannot fall + and the credit multiplier (line 29) cannot rise. + +No one has earned income, so no refundable credit enters line 8. The model +computes in single precision, so comparisons allow one cent or eight float32 +spacings at the household's total income, whichever is larger. +""" + +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEAR = 2024 +CAP = 1_150 +INCOME_LIMIT = 45_000 +TOLERANCE = 0.01 + + +def build_situation(cases): + """Three Montana households per case: (A) the other member is the + claimant's dependent, (B) the other member files their own return and + (C) as A with the other member's extra interest added.""" + people, tax_units, marital_units, spm_units, families, households = ( + {}, + {}, + {}, + {}, + {}, + {}, + ) + for i, c in enumerate(cases): + for arrangement in "ABC": + key = f"{arrangement}{i}" + claimant, member = f"claimant_{key}", f"member_{key}" + interest = c["interest"] + ( + c["extra_interest"] if arrangement == "C" else 0 + ) + people[claimant] = { + "age": {YEAR: c["claimant_age"]}, + "taxable_pension_income": {YEAR: c["pension"]}, + "social_security_retirement": {YEAR: c["claimant_ss"]}, + "real_estate_taxes": {YEAR: c["property_tax"]}, + "rent": {YEAR: c["rent"]}, + } + people[member] = { + "age": {YEAR: c["member_age"]}, + "taxable_interest_income": {YEAR: interest}, + "long_term_capital_gains": {YEAR: c["capital_gain"]}, + "social_security_survivors": {YEAR: c["member_ss"]}, + } + if arrangement == "B": + tax_units[f"tax_unit_{key}"] = {"members": [claimant]} + tax_units[f"tax_unit_{key}_member"] = {"members": [member]} + else: + tax_units[f"tax_unit_{key}"] = {"members": [claimant, member]} + # Set for everyone: an input for some people gives the others + # its default (false), not its formula. Without it the model + # takes an adult in the claimant's tax unit to be the spouse. + people[claimant]["is_tax_unit_dependent"] = {YEAR: False} + people[member]["is_tax_unit_dependent"] = {YEAR: arrangement != "B"} + marital_units[f"marital_unit_{key}"] = {"members": [claimant]} + marital_units[f"marital_unit_{key}_member"] = {"members": [member]} + spm_units[f"spm_unit_{key}"] = {"members": [claimant, member]} + families[f"family_{key}"] = {"members": [claimant, member]} + households[f"household_{key}"] = { + "members": [claimant, member], + "state_code": {YEAR: "MT"}, + } + return { + "people": people, + "tax_units": tax_units, + "marital_units": marital_units, + "spm_units": spm_units, + "families": families, + "households": households, + } + + +def calculate(cases): + """Household totals of gross household income and the credit, and the + other member's credit, as arrays indexed [arrangement, case].""" + simulation = Simulation(situation=build_situation(cases)) + gross_household_income = np.asarray( + simulation.calculate( + "mt_elderly_homeowner_or_renter_credit_gross_household_income", + YEAR, + map_to="household", + ) + ) + credit = np.asarray( + simulation.calculate( + "mt_elderly_homeowner_or_renter_credit", YEAR, map_to="household" + ) + ) + person_credit = np.asarray( + simulation.calculate("mt_elderly_homeowner_or_renter_credit", YEAR) + ) + dependent = np.asarray(simulation.calculate("is_tax_unit_dependent", YEAR)) + # Households and people are in situation order: A0, B0, C0, A1, ...; + # the member follows the claimant. + n = len(cases) + return { + "gross_household_income": gross_household_income.reshape(n, 3).T, + "credit": credit.reshape(n, 3).T, + "member_credit": person_credit[1::2].reshape(n, 3).T, + "member_is_dependent": dependent[1::2].reshape(n, 3).T, + } + + +def expected_gross_household_income(c, extra_interest=0): + """2024 Schedule 2EC line 18: lines 5 and 6 for the claimant, and line + 17 for the other member's interest, gains and Social Security.""" + claimant = c["pension"] + c["claimant_ss"] + member = c["interest"] + extra_interest + max(0, c["capital_gain"]) + return claimant + member + c["member_ss"] + + +def tolerance(amount): + return max(TOLERANCE, 8 * float(np.spacing(np.float32(abs(amount))))) + + +def assert_properties(cases): + result = calculate(cases) + a, b, c_ = 0, 1, 2 + # The other member is a dependent in A and C and their own filer in B. + assert result["member_is_dependent"].tolist() == [ + [True] * len(cases), + [False] * len(cases), + [True] * len(cases), + ] + for i, c in enumerate(cases): + expected = expected_gross_household_income(c) + tol = tolerance(expected + c["extra_interest"]) + ghi = result["gross_household_income"] + credit = result["credit"] + # 1. Both paths give the Schedule 2EC total. + assert ghi[a, i] == pytest.approx(expected, abs=tol), c + assert ghi[b, i] == pytest.approx(expected, abs=tol), c + assert ghi[c_, i] == pytest.approx(expected + c["extra_interest"], abs=tol), c + # 2. The claimant's credit does not depend on the arrangement, and + # the other member claims nothing. + assert credit[a, i] == pytest.approx(credit[b, i], abs=tol), c + assert np.all(result["member_credit"][:, i] == 0), c + for arrangement in (a, b, c_): + # 3. Bounds and the income limit. + assert -tol <= credit[arrangement, i] <= CAP + tol, c + if ghi[arrangement, i] >= INCOME_LIMIT + tol: + assert credit[arrangement, i] == 0, c + # 4. More income never raises the credit. + assert credit[c_, i] <= credit[a, i] + tol, c + + +def test_worked_example(): + """2024 Schedule 2EC: line 5 pension 20,000; line 17 the other member's + interest 10,000; line 18 = 30,000; line 22 = (30,000 - 12,600) x 0.05 = + 870; line 27 = 2,000 - 870 = 1,130; line 30 = 1,130 in both + arrangements. With 5,000 more interest, line 18 = 35,000; line 22 = + 1,120; line 28 = 880; line 29 = 40%; line 30 = 352.""" + case = { + "claimant_age": 70, + "pension": 20_000, + "claimant_ss": 0, + "property_tax": 2_000, + "rent": 0, + "member_age": 40, + "interest": 10_000, + "capital_gain": 0, + "member_ss": 0, + "extra_interest": 5_000, + } + result = calculate([case]) + assert result["gross_household_income"][:, 0] == pytest.approx( + [30_000, 30_000, 35_000], abs=TOLERANCE + ) + assert result["credit"][:, 0] == pytest.approx([1_130, 1_130, 352], abs=TOLERANCE) + assert_properties([case]) + + +try: + import hypothesis + import hypothesis.strategies as st +except ImportError: # pragma: no cover + hypothesis = None + +if hypothesis is not None: + # Each example builds a Simulation; on a loaded runner input generation + # can trip the too_slow health check, which says nothing about the model. + SLOW = [hypothesis.HealthCheck.too_slow] + + def amount(high): + return st.one_of(st.just(0), st.integers(min_value=1, max_value=high)) + + @st.composite + def case(draw): + return { + "claimant_age": draw(st.integers(min_value=62, max_value=90)), + "pension": draw(amount(40_000)), + "claimant_ss": draw(amount(30_000)), + "property_tax": draw(st.integers(min_value=0, max_value=4_000)), + "rent": draw(amount(12_000)), + "member_age": draw(st.integers(min_value=15, max_value=61)), + "interest": draw(amount(40_000)), + "capital_gain": draw( + st.one_of(st.just(0), st.integers(min_value=-20_000, max_value=40_000)) + ), + "member_ss": draw(amount(20_000)), + "extra_interest": draw(st.integers(min_value=1, max_value=20_000)), + } + + @hypothesis.settings(max_examples=15, deadline=None, suppress_health_check=SLOW) + @hypothesis.given(st.lists(case(), min_size=1, max_size=8)) + def test_properties(cases): + assert_properties(cases) diff --git a/policyengine_us/tests/test_mt_elderly_credit_rebate_receipts.py b/policyengine_us/tests/test_mt_elderly_credit_rebate_receipts.py new file mode 100644 index 00000000000..9f44d62671b --- /dev/null +++ b/policyengine_us/tests/test_mt_elderly_credit_rebate_receipts.py @@ -0,0 +1,117 @@ +"""Actual cash receipts enter Montana household income once, without inference. + +SB542 sections 1(3),3(5) require qualifying occupancy and a claim, so a prior +property-tax bill alone cannot establish a receipt. Statutory gross household +income includes all nontaxable income (HB191 section1(9), PDFp2), including +rebate cash in2023 despite omission from that year's illustrative form list. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings, strategies as st + +from policyengine_us import Simulation + + +def build_situation(cases, year, with_receipts): + people, tax_units, households = {}, {}, {} + for i, c in enumerate(cases): + # No receipt input is supplied anywhere in A/B's simulation. Core + # defaults omitted person rows once any row supplies an input, so C + # must be in another simulation to exercise absence of that input. + for arrangement in "C" if with_receipts else "AB": + key = f"{arrangement}{i}" + members = [f"claimant{key}", f"member{key}"] + for j, name in enumerate(members): + people[name] = { + "age": {year: 70 if j == 0 else 40}, + "taxable_pension_income": {year: c["pension"] if j == 0 else 0}, + "is_tax_unit_dependent": {year: False}, + "real_estate_taxes": { + year: c["property_tax"] if j == 0 else 0, + year - 1: c["prior_tax"] + if arrangement == "A" and j == 0 + else 0, + }, + "mt_refundable_credits_before_renter_credit": {year: 0}, + "mt_elderly_homeowner_or_renter_credit_received": {year: 0}, + } + if arrangement == "C": + people[name][ + "mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received" + ] = {year: c["receipts"][j]} + tax_units[f"{name}_return"] = { + "members": [name], + "income_tax_refundable_credits": {year: 0}, + } + households[f"household{key}"] = { + "members": members, + "state_code": {year: "MT", year - 1: "MT"}, + } + return {"people": people, "tax_units": tax_units, "households": households} + + +def calculate(cases, year, with_receipts): + sim = Simulation(situation=build_situation(cases, year, with_receipts)) + shape = (len(cases), 1 if with_receipts else 2) + return { + name: np.asarray(sim.calculate(variable, year, map_to="household")).reshape( + shape + ) + for name, variable in { + "income": "mt_elderly_homeowner_or_renter_credit_gross_household_income", + "credit": "mt_elderly_homeowner_or_renter_credit", + "receipts": "mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received", + }.items() + } + + +def assert_receipts(cases, year): + absent = calculate(cases, year, with_receipts=False) + supplied = calculate(cases, year, with_receipts=True) + for i, c in enumerate(cases): + cash = sum(c["receipts"]) + assert absent["receipts"][i] == pytest.approx([0, 0], abs=0.01) + assert supplied["receipts"][i, 0] == pytest.approx(cash, abs=0.01) + assert absent["income"][i] == pytest.approx([c["pension"]] * 2, abs=0.01) + assert supplied["income"][i, 0] == pytest.approx(c["pension"] + cash, abs=0.01) + assert absent["credit"][i, 0] == pytest.approx(absent["credit"][i, 1], abs=0.01) + assert supplied["credit"][i, 0] <= absent["credit"][i, 0] + 0.01 + return absent["credit"], supplied["credit"] + + +def test_reviewer_unreceived_rebate(): + # No receipt: 1000-(20000-12600)*.035=741. The old assumed400 gave727. + absent, supplied = assert_receipts( + [ + { + "pension": 20000, + "property_tax": 1000, + "prior_tax": 1000, + "receipts": [400, 0], + } + ], + 2025, + ) + assert absent[0] == pytest.approx([741, 741], abs=0.01) + assert supplied[0, 0] == pytest.approx(727, abs=0.01) + + +@settings(max_examples=10, deadline=None, suppress_health_check=[HealthCheck.too_slow]) +@given( + cases=st.lists( + st.fixed_dictionaries( + { + "pension": st.integers(0, 45000), + "property_tax": st.integers(0, 4000), + "prior_tax": st.integers(0, 4000), + "receipts": st.lists(st.integers(0, 675), min_size=2, max_size=2), + } + ), + min_size=1, + max_size=4, + ), + year=st.sampled_from([2023, 2024, 2025]), +) +def test_actual_receipts_count_once_and_prior_tax_does_not_imply_receipt(cases, year): + assert_receipts(cases, year) diff --git a/policyengine_us/tests/test_mt_elderly_credit_refundable_credits.py b/policyengine_us/tests/test_mt_elderly_credit_refundable_credits.py new file mode 100644 index 00000000000..a946da6f053 --- /dev/null +++ b/policyengine_us/tests/test_mt_elderly_credit_refundable_credits.py @@ -0,0 +1,201 @@ +"""Property tests: refundable credits in the Montana elderly homeowner/renter +credit's gross household income. + +ARM 42.4.301(2)(b)-(c) counts "federal refundable tax credits received" and +"any state refundable tax credits received" in gross household income, and +the 2024 Schedule 2EC line 8 combines "all the refundable credits received by +all household members". The model sums a person-level gross household income +over the household, so each tax unit's federal refundable credits are put on +its head once. These tests check that allocation for households of a claimant +aged 62 or older and a younger member who files their own return with up to +two children, so that the member can have a federal EITC and a refundable +child tax credit. + +YAML cases cannot compare two simulations or draw household structures, so +this file does both. Each example runs the households twice: as given (W) and +with the federal refundable credits in gross household income set to zero +(Z). Properties, for every household: + +1. Counted once: the household sum of the person-level federal credits + equals the sum of income_tax_refundable_credits over its tax units, read + from the tax-unit array. +2. Accounting: gross household income in W exceeds Z by exactly those + credits. +3. More income never raises the credit: the credit in W is at most Z's. +4. The credit is between zero and the cap, and zero when gross household + income is $45,000 or more. +5. Montana tax changes only through the credit: Montana refundable credits + other than this one are the same in W and Z, and the household's Montana + income tax rises by exactly the fall in its credit. + +The model computes in single precision, so comparisons allow one cent or +eight float32 spacings at the amount compared, whichever is larger. +""" + +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEAR = 2024 +CAP = 1_150 +INCOME_LIMIT = 45_000 +TOLERANCE = 0.01 +FEDERAL = "mt_elderly_homeowner_or_renter_credit_federal_refundable_credits" +GROSS = "mt_elderly_homeowner_or_renter_credit_gross_household_income" +CREDIT = "mt_elderly_homeowner_or_renter_credit" + + +def build_situation(cases, zero_federal_credits): + """One Montana household per case: the claimant files alone; the member + files with their children. Also returns each person's and tax unit's + case index.""" + people, tax_units, households, case_of = {}, {}, {}, {} + for i, c in enumerate(cases): + claimant, member = f"claimant_{i}", f"member_{i}" + children = [f"child_{i}_{j}" for j in range(len(c["child_ages"]))] + people[claimant] = { + "age": {YEAR: c["claimant_age"]}, + "taxable_pension_income": {YEAR: c["pension"]}, + "real_estate_taxes": {YEAR: c["property_tax"]}, + "rent": {YEAR: c["rent"]}, + } + people[member] = { + "age": {YEAR: c["member_age"]}, + "employment_income": {YEAR: c["wages"]}, + } + for child, age in zip(children, c["child_ages"]): + people[child] = {"age": {YEAR: age}} + tax_units[f"claimant_unit_{i}"] = {"members": [claimant]} + tax_units[f"member_unit_{i}"] = {"members": [member, *children]} + for name in [claimant, member, *children, *list(tax_units)[-2:]]: + case_of[name] = i + households[f"household_{i}"] = { + "members": [claimant, member, *children], + "state_code": {YEAR: "MT"}, + } + if zero_federal_credits: + # Set for everyone: an input for some people gives the others its + # default, not its formula. + for person in people.values(): + person[FEDERAL] = {YEAR: 0} + situation = {"people": people, "tax_units": tax_units, "households": households} + return situation, case_of + + +def calculate(cases, zero_federal_credits): + """Household totals of person- and tax-unit-level variables, summed by + entity id rather than array position.""" + situation, case_of = build_situation(cases, zero_federal_credits) + simulation = Simulation(situation=situation) + + def by_household(variable, entity): + values = np.asarray(simulation.calculate(variable, YEAR), dtype=float) + totals = np.zeros(len(cases)) + for name, value in zip(simulation.populations[entity].ids, values): + totals[case_of[name]] += value + return totals + + return { + "federal_person": by_household(FEDERAL, "person"), + "federal_tax_unit": by_household("income_tax_refundable_credits", "tax_unit"), + "gross": by_household(GROSS, "person"), + "credit": by_household(CREDIT, "person"), + "mt_other_credits": by_household( + "mt_refundable_credits_before_renter_credit", "person" + ), + "mt_income_tax": by_household("mt_income_tax", "tax_unit"), + } + + +def tolerance(amount): + return max(TOLERANCE, 8 * float(np.spacing(np.float32(abs(amount))))) + + +def assert_properties(cases): + w = calculate(cases, zero_federal_credits=False) + z = calculate(cases, zero_federal_credits=True) + for i, c in enumerate(cases): + tol = tolerance(w["gross"][i]) + federal = w["federal_tax_unit"][i] + # 1. Each tax unit's federal refundable credits count once. + assert w["federal_person"][i] == pytest.approx(federal, abs=tol), c + assert z["federal_person"][i] == 0, c + # 2. They are the whole difference in gross household income. + assert w["gross"][i] - z["gross"][i] == pytest.approx(federal, abs=tol), c + # 3. More income never raises the credit. + assert w["credit"][i] <= z["credit"][i] + tol, c + for result in (w, z): + # 4. Bounds and the income limit. + assert -tol <= result["credit"][i] <= CAP + tol, c + if result["gross"][i] >= INCOME_LIMIT + tol: + assert result["credit"][i] == 0, c + # 5. Montana tax moves only through this credit. + assert w["mt_other_credits"][i] == pytest.approx( + z["mt_other_credits"][i], abs=TOLERANCE + ), c + assert w["mt_income_tax"][i] - z["mt_income_tax"][i] == pytest.approx( + z["credit"][i] - w["credit"][i], abs=tolerance(z["credit"][i]) + ), c + + +def test_worked_example(): + """2024 Schedule 2EC: the claimant's line 5 pension 20,000; the + daughter's wages 10,000 (line 17) and her line 8 credits: federal EITC + 34% x 10,000 = 3,400, refundable child tax credit min(1,700, 15% x + 7,500) = 1,125 and Montana EITC 340. Line 18 = 34,865; line 22 = + (34,865 - 12,600) x 0.05 = 1,113.25; line 27 = 2,000 - 1,113.25 = + 886.75; line 30 = 886.75. Without the federal credits, line 18 = + 30,340, line 22 = 887 and line 30 = 1,113.""" + case = { + "claimant_age": 70, + "pension": 20_000, + "property_tax": 2_000, + "rent": 0, + "member_age": 30, + "wages": 10_000, + "child_ages": [5], + } + w = calculate([case], zero_federal_credits=False) + z = calculate([case], zero_federal_credits=True) + assert w["federal_tax_unit"][0] == pytest.approx(4_525, abs=TOLERANCE) + assert w["gross"][0] == pytest.approx(34_865, abs=TOLERANCE) + assert w["credit"][0] == pytest.approx(886.75, abs=TOLERANCE) + assert z["gross"][0] == pytest.approx(30_340, abs=TOLERANCE) + assert z["credit"][0] == pytest.approx(1_113, abs=TOLERANCE) + assert_properties([case]) + + +try: + import hypothesis + import hypothesis.strategies as st +except ImportError: # pragma: no cover + hypothesis = None + +if hypothesis is not None: + # Each example builds two Simulations; on a loaded runner input + # generation can trip the too_slow health check, which says nothing about + # the model. + SLOW = [hypothesis.HealthCheck.too_slow] + + def amount(high): + return st.one_of(st.just(0), st.integers(min_value=1, max_value=high)) + + @st.composite + def case(draw): + return { + "claimant_age": draw(st.integers(min_value=62, max_value=90)), + "pension": draw(amount(40_000)), + "property_tax": draw(st.integers(min_value=0, max_value=4_000)), + "rent": draw(amount(12_000)), + "member_age": draw(st.integers(min_value=19, max_value=61)), + "wages": draw(amount(30_000)), + "child_ages": draw( + st.lists(st.integers(min_value=0, max_value=16), max_size=2) + ), + } + + @hypothesis.settings(max_examples=10, deadline=None, suppress_health_check=SLOW) + @hypothesis.given(st.lists(case(), min_size=1, max_size=6)) + def test_properties(cases): + assert_properties(cases) diff --git a/policyengine_us/tests/test_mt_federal_income_tax_deduction_allocation.py b/policyengine_us/tests/test_mt_federal_income_tax_deduction_allocation.py new file mode 100644 index 00000000000..6f26c9dfea1 --- /dev/null +++ b/policyengine_us/tests/test_mt_federal_income_tax_deduction_allocation.py @@ -0,0 +1,209 @@ +"""Invariants of Montana's separate-spouse federal tax allocation. + +The 2022 Form 2 instructions, Itemized Deductions Schedule line 4a (PDF +page 33), attribute federal withholding to the spouse earning the income; +line 4 limits each separately filing spouse's deduction to $5,000: +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=33 + +The model's positive federal-AGI shares approximate spouse-specific payments +because withholding inputs are unavailable. The form does not prescribe an +allocation when neither filer has positive income; the model retains main's +zero allocation in that case rather than inventing a head-only fallback. For +every eligible tax unit, the method must: + +1. With positive filer AGI, allocate all of the couple's federal tax across + head and spouse before each spouse's cap, without any dependent share. +2. Treat negative filer AGI identically to zero AGI and disregard changes + to dependent AGI. +3. Allocate no tax when neither filer has positive AGI, as main does when + its allocation denominator is nonpositive. +4. Keep deductions nonnegative and at most $5,000 per person; conserve + nonnegative federal tax when filer AGI is positive and each spouse's + uncapped deduction is at most $5,000. + +Each generated case is simulated alongside clipped-income, changed-dependent, +and zero-filer-income variants. Comparisons allow one cent for float32 tax +amounts and eight float32 spacings for dimensionless shares. +""" + +import hypothesis +import hypothesis.strategies as st +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEAR = 2022 +CAP = 5_000 +SHARE_TOLERANCE = 8 * np.finfo(np.float32).eps +AMOUNT_TOLERANCE = 0.01 +ARRANGEMENTS = ("original", "clipped", "dependent", "zero") + + +def build_situation(cases): + people, tax_units, households = {}, {}, {} + for i, case in enumerate(cases): + for arrangement in ARRANGEMENTS: + key = f"{arrangement}_{i}" + head, spouse, dependent = ( + f"head_{key}", + f"spouse_{key}", + f"dependent_{key}", + ) + filer_incomes = [case["head_agi"], case["spouse_agi"]] + if arrangement == "clipped": + filer_incomes = [max(income, 0) for income in filer_incomes] + elif arrangement == "zero": + filer_incomes = [0, 0] + dependent_income = case["dependent_agi"] + if arrangement in ("dependent", "zero"): + dependent_income = abs(dependent_income) + 25_000 + for name, age, income, is_dependent in zip( + (head, spouse, dependent), + (50, 49, 16), + (*filer_incomes, dependent_income), + (False, False, True), + ): + people[name] = { + "age": {YEAR: age}, + "adjusted_gross_income_person": {YEAR: income}, + "is_tax_unit_dependent": {YEAR: is_dependent}, + } + tax_units[f"tax_unit_{key}"] = { + "members": [head, spouse, dependent], + "income_tax_before_refundable_credits": {YEAR: case["federal_tax"]}, + "state_filing_status_if_married_filing_separately_on_same_return": { + YEAR: "SEPARATE" + }, + } + households[f"household_{key}"] = { + "members": [head, spouse, dependent], + "state_code": {YEAR: "MT"}, + } + return {"people": people, "tax_units": tax_units, "households": households} + + +def calculate(cases): + simulation = Simulation(situation=build_situation(cases)) + shape = (len(cases), len(ARRANGEMENTS), 3) + shares = np.asarray( + simulation.calculate("mt_federal_income_tax_deduction_share", YEAR) + ).reshape(shape) + deductions = np.asarray( + simulation.calculate("mt_federal_income_tax_deduction_indiv", YEAR) + ).reshape(shape) + return shares, deductions + + +def assert_properties(cases): + shares, deductions = calculate(cases) + for i, case in enumerate(cases): + case_shares, case_deductions = shares[i], deductions[i] + # Each array is indexed [arrangement, head/spouse/dependent]. + has_positive_filer_income = case["head_agi"] > 0 or case["spouse_agi"] > 0 + assert np.all(case_shares >= 0), case + assert np.all(case_shares <= 1), case + assert case_shares.sum(axis=1) == pytest.approx( + [int(has_positive_filer_income)] * 3 + [0], abs=SHARE_TOLERANCE + ), case + assert np.all(case_shares[:, 2] == 0), case + # Negative income and dependent income cannot shift tax to or away + # from either filer. + for arrangement in (1, 2): + assert case_shares[arrangement] == pytest.approx( + case_shares[0], abs=SHARE_TOLERANCE + ), case + assert case_deductions[arrangement] == pytest.approx( + case_deductions[0], abs=AMOUNT_TOLERANCE + ), case + # The source is silent on zero filer income; preserve main's zero + # allocation, even with a positive-income dependent. + assert case_shares[3] == pytest.approx([0, 0, 0], abs=SHARE_TOLERANCE), case + federal_tax = max(case["federal_tax"], 0) + assert case_deductions[3] == pytest.approx([0, 0, 0], abs=AMOUNT_TOLERANCE), ( + case + ) + assert np.all(case_deductions >= 0), case + assert np.all(case_deductions <= CAP), case + assert np.all(case_deductions[:, 2] == 0), case + if not has_positive_filer_income: + assert np.all(case_deductions == 0), case + elif np.all(case_shares[:3] * federal_tax <= CAP): + assert case_deductions[:3].sum(axis=1) == pytest.approx( + [federal_tax] * 3, abs=AMOUNT_TOLERANCE + ), case + + +def test_worked_allocations(): + cases = [ + # 2_000 * 40_000 / (40_000 + 0) = 2_000; the dependent's + # 10_000 is excluded. An unmasked allocator gives 1_600 / 0 / 400. + { + "head_agi": 40_000, + "spouse_agi": 0, + "dependent_agi": 10_000, + "federal_tax": 2_000, + }, + # Negative head AGI contributes zero; all 2_000 goes to the spouse. + { + "head_agi": -5_000, + "spouse_agi": 10_000, + "dependent_agi": 25_000, + "federal_tax": 2_000, + }, + # Neither filer has positive AGI: main's zero allocation is retained. + { + "head_agi": 0, + "spouse_agi": 0, + "dependent_agi": 10_000, + "federal_tax": 2_000, + }, + { + "head_agi": -5_000, + "spouse_agi": -2_000, + "dependent_agi": 10_000, + "federal_tax": 2_000, + }, + # 9_000 * 40_000 / (40_000 + 40_000) = 4_500 per spouse. + # Neither individual cap binds, so the full 9_000 is conserved. + { + "head_agi": 40_000, + "spouse_agi": 40_000, + "dependent_agi": 10_000, + "federal_tax": 9_000, + }, + ] + shares, deductions = calculate(cases) + assert shares[:, 0] == pytest.approx( + np.array([[1, 0, 0], [0, 1, 0], [0, 0, 0], [0, 0, 0], [0.5, 0.5, 0]]), + abs=SHARE_TOLERANCE, + ) + assert deductions[:, 0] == pytest.approx( + np.array( + [[2_000, 0, 0], [0, 2_000, 0], [0, 0, 0], [0, 0, 0], [4_500, 4_500, 0]] + ), + abs=AMOUNT_TOLERANCE, + ) + + +@hypothesis.settings( + max_examples=10, + deadline=None, + suppress_health_check=[hypothesis.HealthCheck.too_slow], +) +@hypothesis.given( + st.lists( + st.fixed_dictionaries( + { + "head_agi": st.integers(min_value=-100_000, max_value=100_000), + "spouse_agi": st.integers(min_value=-100_000, max_value=100_000), + "dependent_agi": st.integers(min_value=-100_000, max_value=100_000), + "federal_tax": st.integers(min_value=-5_000, max_value=30_000), + } + ), + min_size=1, + max_size=6, + ) +) +def test_allocation_properties(cases): + assert_properties(cases) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.py index d233f4ad92c..f2cbf8e8162 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.py @@ -8,16 +8,23 @@ class mt_elderly_homeowner_or_renter_credit(Variable): unit = USD definition_period = YEAR defined_for = "mt_elderly_homeowner_or_renter_credit_eligible" + reference = ( + "https://mca.legmt.gov/bills/mca/title_0150/chapter_0300/part_0230/section_0370/0150-0300-0230-0370.html", + # 2024 Form 2, Schedule 2EC + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2/2024_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=10", + ) def formula(person, period, parameters): p = parameters( period ).gov.states.mt.tax.income.credits.elderly_homeowner_or_renter - # § 15-30-2337(4) defines gross household income at the household - # (tax-unit) level, so the multiplier phase-out must be applied to - # the aggregated amount rather than each person's share. + # § 15-30-2337(4) defines gross household income as "all income + # received by all individuals of a household", including members + # outside the claimant's return (2024 Schedule 2EC line 17), so the + # multiplier phase-out applies to the household's total income rather + # than each person's share. gross_household_income = add( - person.tax_unit, + person.household, period, ["mt_elderly_homeowner_or_renter_credit_gross_household_income"], ) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_federal_refundable_credits.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_federal_refundable_credits.py new file mode 100644 index 00000000000..25342808b61 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_federal_refundable_credits.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class mt_elderly_homeowner_or_renter_credit_federal_refundable_credits(Variable): + value_type = float + entity = Person + label = "Federal refundable credits in Montana elderly homeowner/renter credit gross household income" + unit = USD + definition_period = YEAR + defined_for = StateCode.MT + reference = ( + # ARM 42.4.301(2)(b): "federal refundable tax credits received" + "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", + # 2023 instructions, line 7: "the federal and Montana Earned Income Tax + # Credit, the federal Child Tax Credits" + "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", + # 2024 Schedule 2EC, line 8: "the federal and Montana earned income tax + # credits, the refundable portion of the federal child tax credit" + "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=47", + ) + + def formula(person, period, parameters): + # Federal refundable credits are claimed on the tax unit's return. + # Gross household income sums this person-level amount over the + # household, so count each return's credits once, on its head. + head = person("is_tax_unit_head", period) + return head * person.tax_unit("income_tax_refundable_credits", period) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py index e4a6976f246..c6f1065035f 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.income.taxable_income.adjusted_gross_income.irs_gross_income.social_security.dependent_taxable_ss_magi import ( + PERSON_ABOVE_THE_LINE_DEDUCTIONS, +) class mt_elderly_homeowner_or_renter_credit_gross_household_income(Variable): @@ -8,16 +11,63 @@ class mt_elderly_homeowner_or_renter_credit_gross_household_income(Variable): unit = USD definition_period = YEAR defined_for = StateCode.MT - reference = "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-23/section-15-30-2337/" + reference = ( + "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-23/section-15-30-2337/", + "https://mca.legmt.gov/bills/mca/title_0150/chapter_0300/part_0230/section_0370/0150-0300-0230-0370.html", + # 2023 Form 2 instructions, PDF pages 51-52: each household member's + # return (line 1), nonfilers' wages (line 8), no losses (line 9). + "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", + # 2024 Schedule 2EC, line 17: income received by other members of + # the household + "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", + # The statutory federal-AGI definition remains after the 2024 form + # began displaying separate income categories: HB191 § 1(9), p.2. + "https://archive.legmt.gov/bills/2021/HB0199/HB0191_X.pdf#page=2", + ) def formula(person, period, parameters): p = parameters( period ).gov.states.mt.tax.income.credits.elderly_homeowner_or_renter sources = add(person, period, p.gross_income_sources) - # AGI only captures the taxable portion of Social Security; add the - # untaxed portion so all SS is counted per § 15-30-2337(9)(a)(viii). + # Gross household income counts every member's federal AGI without + # losses (§ 15-30-2337(4), (9)(a)). The dependent's gross income is + # omitted from this return's AGI, but their own valid non-loss + # adjustments still belong on their own return (2023 instructions, + # line 1). Do not subtract the parent's TaxUnit-level deductions. + ald = parameters(period).gov.irs.ald + deduction_variables = [ + PERSON_ABOVE_THE_LINE_DEDUCTIONS[name] + for name in ald.deductions + if name in PERSON_ABOVE_THE_LINE_DEDUCTIONS + and name not in ald.filer_amounts_recorded_on_dependents + ] + # The shared mapping uses capped/eligible person-level deductions, + # including the dependent's own IRA deduction. It excludes loss_ald: + # business/capital losses never reduce Montana household income. + # Parent-owned adoption/bond exclusions recorded on dependents + # remain with the filer under the existing convention. + own_deductions = add(person, period, deduction_variables) + is_dependent = person("is_tax_unit_dependent", period) + dependent_income = is_dependent * ( + person("dependent_gross_income", period) - own_deductions + ) + # The income above counts only the taxable portion of Social + # Security: a filer's in federal AGI, a dependent's in their own + # gross income. Add the untaxed portion so all SS is counted per + # § 15-30-2337(9)(a)(viii). social_security = person("social_security", period) - taxable_social_security = person("taxable_social_security", period) + taxable_social_security = add( + person, + period, + ["taxable_social_security", "dependent_taxable_social_security"], + ) untaxed_social_security = max_(social_security - taxable_social_security, 0) - return sources + untaxed_social_security + # Income is federal AGI "without regard to loss" (§ 15-30-2337(9)(a)), + # so the business and capital losses deducted in computing federal + # AGI are added back (2023 Elderly Homeowner/Renter Credit Schedule, + # line 9: "The gross household income cannot be reduced by any + # losses."). The return's losses are added once, to the head. + head = person("is_tax_unit_head", period) + losses = head * person.tax_unit("loss_ald", period) + return sources + dependent_income + untaxed_social_security + losses diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_net_household_income.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_net_household_income.py index 4c996aded7b..a46ccf6f5c6 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_net_household_income.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_net_household_income.py @@ -8,6 +8,11 @@ class mt_elderly_homeowner_or_renter_credit_net_household_income(Variable): unit = USD definition_period = YEAR defined_for = "mt_elderly_homeowner_or_renter_credit_eligible" + reference = ( + "https://mca.legmt.gov/bills/mca/title_0150/chapter_0300/part_0230/section_0370/0150-0300-0230-0370.html", + # 2024 Form 2, Schedule 2EC + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2/2024_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=10", + ) def formula(person, period, parameters): p = parameters( @@ -16,10 +21,12 @@ def formula(person, period, parameters): # Only one claim is allowed per household # married taxpayer who are living apart may qualify for only one credit per year standard_exclusion = p.net_household_income.standard_exclusion - # Allocate the income to the head + # Gross household income counts every member of the household, + # including those outside the claimant's return (§ 15-30-2337(4); + # 2024 Schedule 2EC line 17). Allocate it to the head. head = person("is_tax_unit_head", period) gross_household_income = add( - person.tax_unit, + person.household, period, ["mt_elderly_homeowner_or_renter_credit_gross_household_income"], ) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received.py new file mode 100644 index 00000000000..d359822c279 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class mt_elderly_homeowner_or_renter_credit_property_tax_rebate_received(Variable): + value_type = float + entity = Person + label = "Montana property tax rebate received, in elderly homeowner/renter credit gross household income" + documentation = ( + "Montana property tax rebate cash actually received by this person " + "during the year. Defaults to zero: prior-year property taxes or a " + "calculated entitlement do not establish eligibility, a claim, or receipt." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.MT + reference = ( + # SB542 eligibility and claim requirements, PDF pages 2-3. + "https://archive.legmt.gov/content/Sessions/69th/Contractor_index/CH0767.pdf#page=2", + "https://archive.legmt.gov/bills/2021/HB0199/HB0191_X.pdf#page=2", + # 2024 Schedule 2EC, line 8: "the 2023 Montana property tax rebate" + "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=47", + # 2025 Schedule 2EC, line 8: "the 2024 Montana property tax rebate" + "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", + ) + + # Actual receipt input: SB542 sections 1(3) and 3(5) require qualifying + # ownership/occupancy, paid taxes, and a claim. The model's separate + # mt_property_tax_rebate amount calculation omits those requirements. diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_received.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_received.py new file mode 100644 index 00000000000..5bfbabeb8b0 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_received.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class mt_elderly_homeowner_or_renter_credit_received(Variable): + value_type = float + entity = Person + label = "Montana elderly homeowner/renter credit received" + documentation = ( + "Montana elderly homeowner/renter credit refunds received during the " + "year, normally the credit claimed on the prior year's return. It " + "counts in the current year's gross household income." + ) + unit = USD + definition_period = YEAR + reference = ( + # ARM 42.4.301(2)(c): "any state refundable tax credits received, + # including elderly homeowner/renter credit refunds" + "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", + # 2024 Schedule 2EC, line 8: "including your elderly homeowner/renter + # credit received in 2024" + "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=47", + ) + # An input rather than this credit read at period.last_year. This year's + # credit cannot count without a circular reference. Reading last year's + # credit would rerun the whole prior-year tax computation, every member's + # federal refundable credits included, for each record, and it reads 0 + # whenever the inputs cover only one year. diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py index 2efb5ad2407..229fe2db8e6 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py @@ -7,9 +7,16 @@ class mt_child_dependent_care_expense_deduction(Variable): label = "Montana child dependent care expense deduction" unit = USD definition_period = YEAR + documentation = ( + "The deduction in each filer's column: an unmarried filer's return, or " + "each spouse's column when spouses file separately on the same form. " + "A joint return uses mt_child_dependent_care_expense_deduction_joint." + ) reference = ( "https://mca.legmt.gov/bills/2019/mca/title_0150/chapter_0300/part_0210/section_0310/0150-0300-0210-0310.html", "https://web.archive.org/web/20230914140156/https://mtrevenue.gov/wp-content/uploads/dlm_uploads/2022/12/2441-M_2022.pdf#page=1", + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 14 + "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=35", ) defined_for = StateCode.MT @@ -19,26 +26,26 @@ def formula(person, period, parameters): ).gov.states.mt.tax.income.deductions.child_dependent_care_expense if not p.in_effect: return 0 - tax_unit = person.tax_unit - members = tax_unit.members - qualifying_individual = members( - "mt_child_dependent_care_expense_deduction_qualifying_individual", - period, - ) - # Form 2441-M line 1 - qualifying_individuals = tax_unit.sum(qualifying_individual) - qualifying_care_expenses = tax_unit.sum( - members("care_expenses", period) * qualifying_individual + # Form 2441-M line 2 + eligible_expenses = person.tax_unit( + "mt_child_dependent_care_expense_deduction_eligible_expenses", period ) - childcare_expenses = tax_unit("tax_unit_childcare_expenses", period) - total_expenses = childcare_expenses + qualifying_care_expenses - # Form 2441-M line 2: the lesser of actual dependent care expenses - # or the cap based on the number of qualifying individuals. - capped_expenses = min_(total_expenses, p.cap.calc(qualifying_individuals)) - # Line 3 - agi = person("mt_agi_indiv", period) - # Line 6 - reduction = p.phase_out.calc(agi) - # The deduction has to be allocated equally between spouses + # Line 3: add the Montana adjusted gross income in columns A and B, + # that is, the couple's combined income, not each spouse's own. head_or_spouse = person("is_tax_unit_head_or_spouse", period) - return head_or_spouse * (max_(0, capped_expenses - reduction) * 0.5) + combined_agi = person.tax_unit.sum( + head_or_spouse * person("mt_agi_indiv", period) + ) + # Lines 4-7 + deduction = max_(eligible_expenses - p.phase_out.calc(combined_agi), 0) + # Spouses filing separately on the same form each enter one-half of + # line 7 in their column; an unmarried filer enters all of it. + married = person.tax_unit("tax_unit_married", period) + head = person("is_tax_unit_head", period) + share = where(married, head_or_spouse * 0.5, head * 1.0) + # Married couples must file a joint return or file separately on the + # same form (MCA 15-30-2131(1)(c)(vi)(A)); a spouse filing on a + # separate form cannot take the deduction. + filing_status = person.tax_unit("filing_status", period) + separate_form = filing_status == filing_status.possible_values.SEPARATE + return deduction * share * ~separate_form diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_eligible_expenses.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_eligible_expenses.py new file mode 100644 index 00000000000..642214c3e55 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_eligible_expenses.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * + + +class mt_child_dependent_care_expense_deduction_eligible_expenses(Variable): + value_type = float + entity = TaxUnit + label = "Montana child and dependent care expenses eligible for the deduction" + unit = USD + definition_period = YEAR + reference = ( + "https://mca.legmt.gov/bills/2019/mca/title_0150/chapter_0300/part_0210/section_0310/0150-0300-0210-0310.html", + "https://web.archive.org/web/20230914140156/https://mtrevenue.gov/wp-content/uploads/dlm_uploads/2022/12/2441-M_2022.pdf#page=1", + ) + defined_for = StateCode.MT + + def formula(tax_unit, period, parameters): + p = parameters( + period + ).gov.states.mt.tax.income.deductions.child_dependent_care_expense + person = tax_unit.members + qualifying_individual = person( + "mt_child_dependent_care_expense_deduction_qualifying_individual", + period, + ) + # Form 2441-M line 1 + qualifying_individuals = tax_unit.sum(qualifying_individual) + qualifying_care_expenses = tax_unit.sum( + person("care_expenses", period) * qualifying_individual + ) + childcare_expenses = tax_unit("tax_unit_childcare_expenses", period) + total_expenses = childcare_expenses + qualifying_care_expenses + # Form 2441-M line 2: the lesser of actual dependent care expenses + # or the cap based on the number of qualifying individuals. + return min_(total_expenses, p.cap.calc(qualifying_individuals)) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py new file mode 100644 index 00000000000..967ce181cb0 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * + + +class mt_child_dependent_care_expense_deduction_joint(Variable): + value_type = float + entity = TaxUnit + label = "Montana child dependent care expense deduction on a joint return" + unit = USD + definition_period = YEAR + reference = ( + "https://mca.legmt.gov/bills/2019/mca/title_0150/chapter_0300/part_0210/section_0310/0150-0300-0210-0310.html", + "https://web.archive.org/web/20230914140156/https://mtrevenue.gov/wp-content/uploads/dlm_uploads/2022/12/2441-M_2022.pdf#page=1", + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 14 + "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=35", + ) + defined_for = StateCode.MT + + def formula(tax_unit, period, parameters): + p = parameters( + period + ).gov.states.mt.tax.income.deductions.child_dependent_care_expense + if not p.in_effect: + return 0 + # Form 2441-M line 2 + eligible_expenses = tax_unit( + "mt_child_dependent_care_expense_deduction_eligible_expenses", period + ) + # Line 3: the return's Montana adjusted gross income; lines 4-7. + agi = tax_unit("mt_agi_joint", period) + deduction = max_(eligible_expenses - p.phase_out.calc(agi), 0) + # A married person filing on a separate form cannot take the deduction + # (MCA 15-30-2131(1)(c)(vi)(A)). + filing_status = tax_unit("filing_status", period) + separate_form = filing_status == filing_status.possible_values.SEPARATE + return deduction * ~separate_form diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.py index 94951d1ad9c..967ce5bdfb5 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.py @@ -7,28 +7,24 @@ class mt_federal_income_tax_deduction_for_federal_itemization_indiv(Variable): definition_period = YEAR label = "Montana federal income tax deduction when married couples are filing separately" reference = ( - "https://law.justia.com/codes/montana/2021/title-15/chapter-30/part-21/section-15-30-2131/" + "https://law.justia.com/codes/montana/2021/title-15/chapter-30/part-21/section-15-30-2131/", # MT Code § 15-30-2131 (2021) (1)(b) + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 4 + "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=33", ) unit = USD defined_for = "mt_married_filing_separately_on_same_return_eligible" def formula(person, period, parameters): - unit_deds = person.tax_unit( - "mt_federal_income_tax_deduction_for_federal_itemization", period - ) - person_agi = person("mt_agi_indiv", period) - total_agi = person.tax_unit.sum(person_agi) - - prorate = np.zeros_like(total_agi) - mask = total_agi > 0 - prorate[mask] = person_agi[mask] / total_agi[mask] + p = parameters( + period + ).gov.states.mt.tax.income.deductions.itemized.federal_income_tax + # The withholding estimate is computed on each person's own income, so + # each spouse deducts their own amount, up to their own cap. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + withheld_income_tax = head_or_spouse * person("mt_withheld_income_tax", period) filing_status = person.tax_unit( "state_filing_status_if_married_filing_separately_on_same_return", period, ) - p = parameters( - period - ).gov.states.mt.tax.income.deductions.itemized.federal_income_tax - cap = p.cap[filing_status] - return min_(unit_deds * prorate, cap) + return min_(withheld_income_tax, p.cap[filing_status]) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py index 1c1a1e3c8e4..8bda67333ca 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py @@ -38,6 +38,7 @@ def formula(person, period, parameters): period, [ "mt_misc_deductions", + "mt_child_dependent_care_expense_deduction", "mt_medical_expense_deduction_indiv", "mt_salt_deduction", "mt_federal_income_tax_deduction_for_federal_itemization_indiv", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py index c56b1d7272e..ba977871a6f 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py @@ -33,6 +33,7 @@ def formula(person, period, parameters): period, [ "mt_misc_deductions", + "mt_child_dependent_care_expense_deduction_joint", "mt_medical_expense_deduction_joint", "mt_salt_deduction", "mt_federal_income_tax_deduction_for_federal_itemization", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_indiv.py index 792174d9142..f84c008f403 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_indiv.py @@ -7,26 +7,31 @@ class mt_federal_income_tax_deduction_indiv(Variable): definition_period = YEAR label = "Montana federal income tax deduction when married couples are filing separately" reference = ( - "https://law.justia.com/codes/montana/2021/title-15/chapter-30/part-21/section-15-30-2131/" + "https://law.justia.com/codes/montana/2021/title-15/chapter-30/part-21/section-15-30-2131/", # MT Code § 15-30-2131 (2021) (1)(b) + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 4 + "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=33", ) unit = USD defined_for = "mt_married_filing_separately_on_same_return_eligible" def formula(person, period, parameters): - unit_deds = person.tax_unit("mt_federal_income_tax_deduction_unit", period) - person_agi = person("mt_agi_indiv", period) - total_agi = person.tax_unit.sum(person_agi) - - prorate = np.zeros_like(total_agi) - mask = total_agi > 0 - prorate[mask] = person_agi[mask] / total_agi[mask] + p = parameters( + period + ).gov.states.mt.tax.income.deductions.itemized.federal_income_tax + if not p.applies: + return 0 + # Federal income tax is reported by the spouse who earned the income + # it was paid on, and each spouse is held to their own cap; the + # deduction cannot be arbitrarily allocated. Without each spouse's + # federal withholding, attribute the couple's federal income tax by + # each spouse's share of federal adjusted gross income, then cap it. + federal_income_tax = max_( + person.tax_unit("income_tax_before_refundable_credits", period), 0 + ) + share = person("mt_federal_income_tax_deduction_share", period) filing_status = person.tax_unit( "state_filing_status_if_married_filing_separately_on_same_return", period, ) - p = parameters( - period - ).gov.states.mt.tax.income.deductions.itemized.federal_income_tax - cap = p.cap[filing_status] - return min_(unit_deds * prorate, cap) + return min_(federal_income_tax * share, p.cap[filing_status]) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_share.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_share.py new file mode 100644 index 00000000000..a2d93c5cc6f --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_share.py @@ -0,0 +1,33 @@ +from policyengine_us.model_api import * + + +class mt_federal_income_tax_deduction_share(Variable): + value_type = float + entity = Person + definition_period = YEAR + label = "Share of the couple's federal income tax attributed to each spouse for the Montana deduction" + reference = ( + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 4a: + # "If you are married filing separately with your spouse, the federal + # income tax withheld should be reported by the spouse who earned the + # income." + "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=33", + ) + unit = "/1" + defined_for = "mt_married_filing_separately_on_same_return_eligible" + + def formula(person, period, parameters): + # Each filer's share of the federal income the tax was paid on, + # counting only positive federal adjusted gross income. A dependent's + # income is on the dependent's own return. With no positive filer + # income, retain main's zero allocation; the instructions do not + # establish which spouse made the payments in that case. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + income = head_or_spouse * max_( + person("adjusted_gross_income_person", period), 0 + ) + total_income = person.tax_unit.sum(income) + share = np.zeros_like(total_income) + mask = total_income > 0 + share[mask] = income[mask] / total_income[mask] + return share diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py index 2ce15dcaaac..aa99346184a 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py @@ -40,6 +40,7 @@ def formula(person, period, parameters): period, [ "mt_misc_deductions", + "mt_child_dependent_care_expense_deduction", "mt_medical_expense_deduction_indiv", "mt_salt_deduction", "mt_federal_income_tax_deduction_indiv", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py index c13d9dbd54e..5f503e42a9b 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py @@ -33,6 +33,7 @@ def formula(person, period, parameters): period, [ "mt_misc_deductions", + "mt_child_dependent_care_expense_deduction_joint", "mt_medical_expense_deduction_joint", "mt_salt_deduction", "mt_federal_income_tax_deduction_unit",