From 654de3e2b6cf3235aefaa5e5981de5027e9202cc Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 13:29:45 -0500 Subject: [PATCH 01/13] Count veterans' and railroad retirement benefits in household and SPM benefits veterans_benefits and railroad_benefits reached neither household_benefits nor spm_unit_benefits, so neither net income nor SPM resources counted them. Census P60-200 Appendix A lists veterans' payments as a source of money income and counts railroad retirement among government transfers. Both now sit in household_benefits and spm_unit_benefits. Veterans' benefits stay out of federal gross income under 38 U.S.C. 5301(a)(1). Railroad benefits' federal tax treatment follows in a separate change. Co-Authored-By: Claude Opus 5.5 --- changelog.d/vet-rrb-income-totals.fixed.md | 1 + .../gov/household/household_benefits.yaml | 13 ++++ .../irs_gross_income/veterans_benefits.yaml | 19 ++++++ .../income/household/household_benefits.yaml | 30 ++++++++++ .../income/spm_unit/spm_unit_benefits.yaml | 30 ++++++++++ .../test_household_spm_resource_parity.py | 60 +++++++++++++++++++ .../person/general/veterans_benefits.py | 14 ++++- .../person/retirement/railroad_benefits.py | 7 ++- .../income/spm_unit/spm_unit_benefits.py | 4 ++ 9 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 changelog.d/vet-rrb-income-totals.fixed.md create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/veterans_benefits.yaml diff --git a/changelog.d/vet-rrb-income-totals.fixed.md b/changelog.d/vet-rrb-income-totals.fixed.md new file mode 100644 index 00000000000..67cdaa45736 --- /dev/null +++ b/changelog.d/vet-rrb-income-totals.fixed.md @@ -0,0 +1 @@ +Count veterans' benefits and Railroad Retirement Board benefits in household and SPM benefits, so both net incomes and SPM resources include them. diff --git a/policyengine_us/parameters/gov/household/household_benefits.yaml b/policyengine_us/parameters/gov/household/household_benefits.yaml index 2c48d38ca0e..5955d4a6a2c 100644 --- a/policyengine_us/parameters/gov/household/household_benefits.yaml +++ b/policyengine_us/parameters/gov/household/household_benefits.yaml @@ -19,6 +19,10 @@ values: - educational_assistance - financial_assistance - survivor_benefits + # Census money income counts veterans' payments, and railroad retirement + # as a government transfer (P60-200 Appendix A). + - veterans_benefits + - railroad_benefits - household_head_start_benefits # One-time energy relief payments. # Paid at the same time as the Alaska Permanent Fund Dividend, @@ -52,6 +56,10 @@ values: - educational_assistance - financial_assistance - survivor_benefits + # Census money income counts veterans' payments, and railroad retirement + # as a government transfer (P60-200 Appendix A). + - veterans_benefits + - railroad_benefits - household_head_start_benefits # One-time energy relief payments. # Paid at the same time as the Alaska Permanent Fund Dividend, @@ -72,3 +80,8 @@ metadata: unit: list period: year label: Household benefits + reference: + - title: Census Bureau, P60-200 Appendix A, Definitions and Explanations (veterans' payments, government transfers) + href: https://www2.census.gov/programs-surveys/cps/methodology/Appendix%20A%20P60-200%20income%20measurement%20definition.pdf#page=3 + - title: Census Bureau, P60-200 Appendix A, Definitions and Explanations (nonmeans-tested transfers, including U.S. railroad retirement) + href: https://www2.census.gov/programs-surveys/cps/methodology/Appendix%20A%20P60-200%20income%20measurement%20definition.pdf#page=4 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/veterans_benefits.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/veterans_benefits.yaml new file mode 100644 index 00000000000..8df77dd2719 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/veterans_benefits.yaml @@ -0,0 +1,19 @@ +- name: Veterans' benefits stay out of federal gross income. + period: 2026 + input: + age: 40 + veterans_benefits: 30_000 + output: + # 38 U.S.C. 5301(a)(1): VA benefits are exempt from taxation. + irs_gross_income: 0 + adjusted_gross_income: 0 + +- name: Veterans' benefits add nothing to a worker's federal gross income. + period: 2026 + input: + age: 40 + employment_income: 45_000 + veterans_benefits: 20_000 + output: + irs_gross_income: 45_000 + adjusted_gross_income: 45_000 diff --git a/policyengine_us/tests/policy/baseline/household/income/household/household_benefits.yaml b/policyengine_us/tests/policy/baseline/household/income/household/household_benefits.yaml index dfbde8229df..c54e53e0b10 100644 --- a/policyengine_us/tests/policy/baseline/household/income/household/household_benefits.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/household/household_benefits.yaml @@ -173,3 +173,33 @@ output: household_benefits: 2_000 spm_unit_benefits: 2_000 + +- name: Veterans' benefits and railroad retirement reach household benefits. + period: 2026 + input: + people: + veteran: + age: 40 + employment_income: 500_000 + veterans_benefits: 12_000 + railroad_annuitant: + age: 40 + employment_income: 500_000 + railroad_benefits: 30_000 + tax_units: + veteran_unit: + members: [veteran] + railroad_unit: + members: [railroad_annuitant] + spm_units: + spm_unit: + members: [veteran, railroad_annuitant] + households: + household: + members: [veteran, railroad_annuitant] + state_code: TX + output: + # Census P60-200 Appendix A: veterans' payments are money income, and + # railroad retirement is a government transfer. 12,000 + 30,000 = 42,000. + household_benefits: 42_000 + spm_unit_benefits: 42_000 diff --git a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_benefits.yaml b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_benefits.yaml index 2c8a14090b0..2474acb5ac1 100644 --- a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_benefits.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_benefits.yaml @@ -302,3 +302,33 @@ output: spm_unit_benefits: [1_000, 1_000] household_benefits: 2_000 + +- name: Each SPM unit counts its own veterans' and railroad retirement benefits. + period: 2026 + input: + people: + veteran: + age: 40 + employment_income: 500_000 + veterans_benefits: 9_000 + railroad_annuitant: + age: 40 + employment_income: 500_000 + railroad_benefits: 24_000 + tax_units: + veteran_unit: + members: [veteran] + railroad_unit: + members: [railroad_annuitant] + spm_units: + veteran_unit: + members: [veteran] + railroad_unit: + members: [railroad_annuitant] + households: + household: + members: [veteran, railroad_annuitant] + state_code: FL + output: + spm_unit_benefits: [9_000, 24_000] + household_benefits: 9_000 + 24_000 diff --git a/policyengine_us/tests/test_household_spm_resource_parity.py b/policyengine_us/tests/test_household_spm_resource_parity.py index 901c44a04c7..0583940837a 100644 --- a/policyengine_us/tests/test_household_spm_resource_parity.py +++ b/policyengine_us/tests/test_household_spm_resource_parity.py @@ -224,3 +224,63 @@ def value(name): - value("spm_unit_energy_subsidy") ) assert np.isclose(household_side, spm_side, atol=TOLERANCE) + + +# Federal cash benefits, paid in every state to the person who receives them. +# Census P60-200 Appendix A counts veterans' payments as money income and +# railroad retirement as a government transfer, so both totals count them. +FEDERAL_CASH_BENEFITS = ("veterans_benefits", "railroad_benefits") + + +@settings(max_examples=30, deadline=None) +@given( + name=st.sampled_from(FEDERAL_CASH_BENEFITS), + amount=st.integers(min_value=1, max_value=60_000), + state=st.sampled_from(STATES), +) +@example(name="veterans_benefits", amount=12_000, state="TX") +@example(name="railroad_benefits", amount=30_000, state="VA") +def test_a_dollar_of_federal_cash_benefits_adds_a_dollar_to_both_totals( + name, amount, state +): + year = 2026 + # Earnings this high rule out every means-tested response. + zero = Simulation( + situation=one_person(state, 500_000, year, **{name: ("person", 0)}) + ) + paid = Simulation( + situation=one_person(state, 500_000, year, **{name: ("person", amount)}) + ) + for names in ( + ("spm_unit_benefits",), + ("household_benefits",), + SPM_RESOURCES, + HOUSEHOLD_RESOURCES, + ): + change = total(paid, names, year) - total(zero, names, year) + assert change == pytest.approx(amount, abs=TOLERANCE), names + + +@settings(max_examples=20, deadline=None) +@given( + amount=st.integers(min_value=1, max_value=60_000), + earnings=st.integers(min_value=0, max_value=300_000), + state=st.sampled_from(STATES), +) +@example(amount=30_000, earnings=0, state="TX") +@example(amount=20_000, earnings=45_000, state="VT") +def test_veterans_benefits_never_change_federal_income_tax(amount, earnings, state): + """38 U.S.C. 5301(a)(1) exempts VA benefits from taxation.""" + year = 2026 + zero = Simulation( + situation=one_person(state, earnings, year, veterans_benefits=("person", 0)) + ) + paid = Simulation( + situation=one_person( + state, earnings, year, veterans_benefits=("person", amount) + ) + ) + for name in ("irs_gross_income", "adjusted_gross_income", "income_tax"): + assert total(paid, [name], year) == pytest.approx( + total(zero, [name], year), abs=TOLERANCE + ), name diff --git a/policyengine_us/variables/household/income/person/general/veterans_benefits.py b/policyengine_us/variables/household/income/person/general/veterans_benefits.py index a198c30f3be..00a01ea1af1 100644 --- a/policyengine_us/variables/household/income/person/general/veterans_benefits.py +++ b/policyengine_us/variables/household/income/person/general/veterans_benefits.py @@ -6,6 +6,18 @@ class veterans_benefits(Variable): entity = Person label = "Veterans benefits" unit = USD - documentation = "Veterans benefits from past military service." + documentation = ( + "Benefits the Department of Veterans Affairs pays from past military " + "service, such as disability compensation, pensions, dependency and " + "indemnity compensation to survivors, and education allowances. " + "38 U.S.C. 5301(a)(1) exempts them from taxation, so federal gross " + "income leaves them out. Census money income counts them as veterans' " + "payments, and household and SPM benefits include them. Military " + "retirement pay goes in military_retirement_pay instead." + ) definition_period = YEAR uprating = "gov.ssa.uprating" + reference = ( + "https://www.law.cornell.edu/uscode/text/38/5301#a_1", + "https://www2.census.gov/programs-surveys/cps/methodology/Appendix%20A%20P60-200%20income%20measurement%20definition.pdf#page=3", + ) diff --git a/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py b/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py index 65278a26348..f14cf72a30a 100644 --- a/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py +++ b/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py @@ -12,6 +12,11 @@ class railroad_benefits(Variable): "retirement, disability, spouse and survivor annuities, before Medicare " "premiums or other amounts are withheld. Railroad Unemployment Insurance " "Act benefits are reported elsewhere: unemployment benefits in " - "unemployment_compensation and sickness benefits in disability_benefits." + "unemployment_compensation and sickness benefits in disability_benefits. " + "Census counts railroad retirement as a government transfer, and " + "household and SPM benefits include these benefits. Enter them only " + "here, not also as social_security, pension_income, disability_benefits " + "or survivor_benefits." ) default_value = 0 + reference = "https://www2.census.gov/programs-surveys/cps/methodology/Appendix%20A%20P60-200%20income%20measurement%20definition.pdf#page=4" diff --git a/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py b/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py index 0295c517ff8..c9ab82f107e 100644 --- a/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py +++ b/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py @@ -80,6 +80,10 @@ def formula(spm_unit, period, parameters): "educational_assistance", "financial_assistance", "survivor_benefits", + # Census money income counts veterans' payments, and railroad + # retirement as a government transfer (P60-200 Appendix A). + "veterans_benefits", + "railroad_benefits", "spm_unit_energy_subsidy", "tanf", # Washington (WA) cash-assistance programs. wa_sfa and wa_rca From b942e782dc6db7d3bc0fc7af9b227995cdd950a1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 14:02:09 -0500 Subject: [PATCH 02/13] Tax railroad retirement benefits under IRC 86 and 72(r) and subtract them from state income IRC 86(d)(1)(B) treats tier 1 railroad retirement benefits as social security benefits, and 72(r)(1) treats the rest of the annuity as a benefit from a section 401(a) plan. 45 U.S.C. 231m(a) bars states from taxing either, so every state that starts from federal AGI or taxable income subtracts the amount federal gross income includes. Section 22's reduction now counts the Railroad Retirement Act amounts the head and spouse exclude from gross income, as 22(c)(3)(A)(i)(II) says. Co-Authored-By: Claude Opus 5.5 --- changelog.d/rrb-federal-tax.fixed.md | 1 + .../gov/hhs/medicaid/income/modification.yaml | 3 + .../state_sales_tax_table/income_sources.yaml | 8 +- .../gov/irs/gross_income/sources.yaml | 11 + .../tax/income/subtractions/subtractions.yaml | 5 + .../ca/tax/income/agi/subtractions.yaml | 4 + .../tax/income/subtractions/subtractions.yaml | 4 + .../tax/income/subtractions/subtractions.yaml | 3 + .../dc/tax/income/subtractions/sources.yaml | 3 + .../tax/income/subtractions/subtractions.yaml | 4 + .../tax/income/subtractions/subtractions.yaml | 6 + .../tax/income/subtractions/subtractions.yaml | 4 + .../income/taxable_income/subtractions.yaml | 3 + .../tax/income/subtractions/subtractions.yaml | 4 + .../il/tax/income/base/subtractions.yaml | 3 + .../in/tax/income/deductions/deductions.yaml | 21 +- .../states/ky/tax/income/subtractions.yaml | 3 + .../la/tax/income/exempt_income/sources.yaml | 3 + .../tax/income/agi/subtractions/sources.yaml | 4 + .../states/mi/tax/income/subtractions.yaml | 4 + .../mn/tax/income/subtractions/sources.yaml | 4 + .../income/subtractions/agi_subtractions.yaml | 4 + .../tax/income/subtractions/subtractions.yaml | 6 + .../nc/tax/income/deductions/deductions.yaml | 4 + .../taxable_income/subtractions/sources.yaml | 5 + .../income/agi/subtractions/subtractions.yaml | 4 + .../other_deductions_and_exemptions.yaml | 3 + .../tax/income/agi/subtractions/sources.yaml | 3 + .../oh/tax/income/deductions/deductions.yaml | 3 + .../income/agi/subtractions/subtractions.yaml | 3 + .../tax/income/subtractions/subtractions.yaml | 3 + .../tax/income/nontaxable_income_sources.yaml | 3 + .../income/agi/subtractions/subtractions.yaml | 4 + .../tax/income/subtractions/subtractions.yaml | 4 + .../tax/income/subtractions/subtractions.yaml | 11 +- .../vt/tax/income/agi/subtractions.yaml | 3 + .../wi/tax/income/subtractions/sources.yaml | 4 + .../tax/income/subtractions/subtractions.yaml | 3 + .../medicaid_magi_railroad_retirement.yaml | 47 + .../section_22_railroad_retirement.yaml | 75 ++ ...d_social_security_equivalent_benefits.yaml | 193 +++++ .../taxable_railroad_benefits.yaml | 99 +++ .../states/railroad_retirement_benefits.yaml | 812 ++++++++++++++++++ .../subtractions/va_railroad_retirement.yaml | 61 ++ .../test_railroad_retirement_taxation.py | 327 +++++++ .../elderly_and_disabled/section_22_income.py | 25 +- .../gov/irs/income/dependent_gross_income.py | 10 + .../capped_qualified_tuition_expenses_ald.py | 5 +- .../dependent_traditional_ira_deduction.py | 4 +- .../ira/ira_219g_magi.py | 6 +- .../ira/ira_219g_taxable_social_security.py | 13 +- ...oad_social_security_equivalent_benefits.py | 29 + .../dependent_taxable_social_security.py | 43 +- ...oad_social_security_equivalent_benefits.py | 17 + ...d_income_for_social_security_taxability.py | 4 +- ...oad_social_security_equivalent_benefits.py | 12 + ...rity_equivalent_benefits_for_taxability.py | 28 + .../tax_unit_section_86_benefits.py | 26 + ...oad_social_security_equivalent_benefits.py | 32 + .../tax_unit_taxable_section_86_benefits.py | 104 +++ .../tax_unit_taxable_social_security.py | 99 +-- ...oad_social_security_equivalent_benefits.py | 35 + .../taxable_social_security.py | 34 +- .../taxable_social_security_tier_1.py | 16 +- .../social_security/taxable_ss_magi.py | 6 +- .../taxable_railroad_benefits.py | 36 + ...x_unit_taxable_social_security_pre_1993.py | 15 +- .../ks/tax/income/ks_agi_subtractions.py | 6 + .../income/gross_income/ma_gross_income.py | 11 +- .../me_agi_subtractions.py | 7 +- .../income/taxable_income/ut_subtractions.py | 12 +- .../age_deduction/va_age_deduction_agi.py | 8 +- .../person/retirement/railroad_benefits.py | 22 +- ...oad_social_security_equivalent_benefits.py | 28 + .../tax_exempt_railroad_pension_benefits.py | 22 + .../taxable_railroad_pension_benefits.py | 26 + 76 files changed, 2397 insertions(+), 133 deletions(-) create mode 100644 changelog.d/rrb-federal-tax.fixed.md create mode 100644 policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_magi_railroad_retirement.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/credits/elderly_and_disabled/section_22_railroad_retirement.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/railroad_social_security_equivalent_benefits.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_railroad_benefits.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/railroad_retirement_benefits.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_railroad_retirement.yaml create mode 100644 policyengine_us/tests/test_railroad_retirement_taxation.py create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_railroad_social_security_equivalent_benefits.py create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_exempt_railroad_social_security_equivalent_benefits.py create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits.py create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits_for_taxability.py create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_section_86_benefits.py create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_railroad_social_security_equivalent_benefits.py create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_section_86_benefits.py create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_railroad_social_security_equivalent_benefits.py create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_railroad_benefits.py create mode 100644 policyengine_us/variables/household/income/person/retirement/railroad_social_security_equivalent_benefits.py create mode 100644 policyengine_us/variables/household/income/person/retirement/tax_exempt_railroad_pension_benefits.py create mode 100644 policyengine_us/variables/household/income/person/retirement/taxable_railroad_pension_benefits.py diff --git a/changelog.d/rrb-federal-tax.fixed.md b/changelog.d/rrb-federal-tax.fixed.md new file mode 100644 index 00000000000..3e76e793f43 --- /dev/null +++ b/changelog.d/rrb-federal-tax.fixed.md @@ -0,0 +1 @@ +Tax Railroad Retirement Act benefits federally, with tier 1 social security equivalent benefits under IRC 86 alongside Social Security and the rest of the annuity as a pension under IRC 72(r), and subtract them from state income in every state with an income tax, as 45 U.S.C. 231m(a) requires. diff --git a/policyengine_us/parameters/gov/hhs/medicaid/income/modification.yaml b/policyengine_us/parameters/gov/hhs/medicaid/income/modification.yaml index 24c48d75830..ad9b1461f83 100644 --- a/policyengine_us/parameters/gov/hhs/medicaid/income/modification.yaml +++ b/policyengine_us/parameters/gov/hhs/medicaid/income/modification.yaml @@ -5,6 +5,9 @@ values: - section_911_excluded_income - tax_exempt_interest_income - tax_exempt_social_security + # Section 36B(d)(2)(B)(iii) adds back social security benefits as section + # 86(d) defines them, which include tier 1 railroad retirement benefits. + - tax_exempt_railroad_social_security_equivalent_benefits metadata: unit: list label: Medicaid/CHIP/ACA-related MAGI additions over AGI diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_table/income_sources.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_table/income_sources.yaml index c3f01b309b2..b837590cb45 100644 --- a/policyengine_us/parameters/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_table/income_sources.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax_table/income_sources.yaml @@ -44,11 +44,13 @@ values: - veterans_benefits # Nontaxable combat pay - not available. - workers_compensation - # Nontaxable part of social security and railroad retirement benefits; - # railroad retirement benefits are not included. + # Nontaxable part of social security and railroad retirement benefits. - tax_exempt_social_security - # Nontaxable IRA, pension, or annuity distributions (excluding rollovers). + - tax_exempt_railroad_social_security_equivalent_benefits + # Nontaxable IRA, pension, or annuity distributions (excluding rollovers), + # including Railroad Retirement Act annuities' tax-free part. - tax_exempt_pension_income + - tax_exempt_railroad_pension_benefits - tax_exempt_retirement_distributions # Public assistance payments. - tanf diff --git a/policyengine_us/parameters/gov/irs/gross_income/sources.yaml b/policyengine_us/parameters/gov/irs/gross_income/sources.yaml index a2474e69e74..c269293693d 100644 --- a/policyengine_us/parameters/gov/irs/gross_income/sources.yaml +++ b/policyengine_us/parameters/gov/irs/gross_income/sources.yaml @@ -21,6 +21,10 @@ values: - dividend_income - taxable_pension_income # Pension income includes annuities and endowment contracts + # Railroad Retirement Act benefits other than the social security + # equivalent benefit, which IRC 72(r)(1) treats as benefits from a + # qualified employer plan (Form 1040 line 5b). + - taxable_railroad_pension_benefits # Disability benefits from a plan paid for by the employer (§ 105(a)), # on Form 1040 line 1h until minimum retirement age, then line 5. # Benefits from a plan the recipient paid for are excluded (§ 104(a)(3)). @@ -35,6 +39,9 @@ values: - estate_income - taxable_unemployment_compensation - taxable_social_security + # Tier 1 railroad retirement benefits, which section 86 taxes together + # with Social Security (IRC 86(d)(1)(B); Form 1040 line 6b). + - taxable_railroad_social_security_equivalent_benefits - illicit_income - taxable_retirement_distributions - taxable_roth_conversions @@ -56,6 +63,10 @@ metadata: reference: - title: 26 U.S. Code § 61 - Gross income defined href: https://www.law.cornell.edu/uscode/text/26/61 + - title: 26 U.S. Code § 72(r) - Certain railroad retirement benefits treated as received under employer plans + href: https://www.law.cornell.edu/uscode/text/26/72#r + - title: 26 U.S. Code § 86(d) - Social security benefit + href: https://www.law.cornell.edu/uscode/text/26/86#d - title: 26 U.S. Code § 129 - Dependent care assistance programs href: https://www.law.cornell.edu/uscode/text/26/129 - title: 26 U.S. Code § 111 - Recovery of tax benefit items diff --git a/policyengine_us/parameters/gov/states/az/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/az/tax/income/subtractions/subtractions.yaml index 6e0dbfd05a1..da2adf2d9da 100644 --- a/policyengine_us/parameters/gov/states/az/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/az/tax/income/subtractions/subtractions.yaml @@ -12,6 +12,7 @@ values: - az_adoption_expense_subtraction # Other subtractions - Item D - az_families_tax_rebate_subtraction # Line 36, Item U - az_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2025-01-01: - us_govt_interest # Line 28 - az_public_pension_exclusion # Line 29a @@ -29,6 +30,7 @@ values: - auto_loan_interest_deduction # MCTCP worksheet Line 4 (Sch 1-A Line 30) - additional_senior_deduction # MCTCP worksheet Line 5 (Sch 1-A Line 37) - az_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) # HB 4168 (2026): auto loan interest sunsets after TY2025 (43-1022(36)); # adds child & dependent care and IRC 530A distribution subtractions. 2026-01-01: @@ -48,6 +50,7 @@ values: - az_dependent_care_expense_subtraction # 43-1022(34) - az_530a_distribution_subtraction # 43-1022(33) - az_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list period: year @@ -71,3 +74,5 @@ metadata: href: https://www.azleg.gov/ars/43/01022.htm - title: Arizona HB 4168 (2026), Sec. 15, amending A.R.S. 43-1022(31)-(36) href: https://www.azleg.gov/legtext/57leg/2R/bills/HB4168H.pdf#page=28 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/ca/tax/income/agi/subtractions.yaml b/policyengine_us/parameters/gov/states/ca/tax/income/agi/subtractions.yaml index 13260aaa4b0..d21470f0925 100644 --- a/policyengine_us/parameters/gov/states/ca/tax/income/agi/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/ca/tax/income/agi/subtractions.yaml @@ -6,6 +6,7 @@ values: - us_govt_interest - salt_refund_income - ca_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2025-01-01: - taxable_social_security - taxable_unemployment_compensation @@ -13,6 +14,7 @@ values: - salt_refund_income - ca_military_retirement_exclusion - ca_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: reference: @@ -24,6 +26,8 @@ metadata: href: https://www.ftb.ca.gov/forms/2024/2024-540-ca-instructions.html - title: 2025 California Form 540 Schedule CA instructions href: https://www.ftb.ca.gov/forms/2025/2025-540-ca-instructions.html + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a period: year unit: list label: California AGI subtractions diff --git a/policyengine_us/parameters/gov/states/co/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/co/tax/income/subtractions/subtractions.yaml index 3d10afd68fa..2461b917464 100644 --- a/policyengine_us/parameters/gov/states/co/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/co/tax/income/subtractions/subtractions.yaml @@ -8,6 +8,7 @@ values: - co_collegeinvest_subtraction - co_charitable_contribution_subtraction - co_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) # Colorado Agricultural Land Capital Gain Subtraction # Qualified Reservation Income # PERA/DPSRS Subtraction, for PERA contributions made in 1984–1986 or DPSRS contributions made in 1986 @@ -29,6 +30,7 @@ values: - co_charitable_contribution_subtraction - co_able_contribution_subtraction - co_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) # Colorado Agricultural Land Capital Gain Subtraction # Qualified Reservation Income # PERA/DPSRS Subtraction, for PERA contributions made in 1984–1986 or DPSRS contributions made in 1986 @@ -58,3 +60,5 @@ metadata: href: https://tax.colorado.gov/sites/tax/files/documents/DR0104_book_2024.pdf#page=18 - title: 2025 Colorado Individual Tax Filing Guide 104 Book href: https://tax.colorado.gov/sites/tax/files/documents/Book104_2025.pdf#page=18 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/ct/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/ct/tax/income/subtractions/subtractions.yaml index 2cb8935c118..cacc25579b5 100644 --- a/policyengine_us/parameters/gov/states/ct/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/ct/tax/income/subtractions/subtractions.yaml @@ -8,6 +8,7 @@ values: - ct_pension_annuity_subtraction #ln 48b - salt_refund_income #ln 38 - ct_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -26,3 +27,5 @@ metadata: href: https://portal.ct.gov/-/media/drs/forms/2024/income/2024-ct-1040-instructions_1224.pdf#page=8 - title: 2025 Form CT-1040 Connecticut Resident Income Tax Return Instructions, Lines 39,41,44,48b href: https://portal.ct.gov/-/media/drs/forms/2025/income/2025-ct-1040-instructions_1225.pdf#page=8 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/dc/tax/income/subtractions/sources.yaml b/policyengine_us/parameters/gov/states/dc/tax/income/subtractions/sources.yaml index 58b685cfc8a..9f182f4070c 100644 --- a/policyengine_us/parameters/gov/states/dc/tax/income/subtractions/sources.yaml +++ b/policyengine_us/parameters/gov/states/dc/tax/income/subtractions/sources.yaml @@ -13,6 +13,8 @@ metadata: href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2023_D40_Book_Final_012324.pdf#page=34 - title: 2024 DC Form D-40 Booklet href: https://otr.cfo.dc.gov/sites/default/files/dc/sites/otr/publication/attachments/2024_D40_Booklet_011525.pdf#page=34 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a values: 2021-01-01: - taxable_social_security @@ -23,3 +25,4 @@ values: - salt_refund_income - dc_survivor_benefits_subtraction - dc_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) diff --git a/policyengine_us/parameters/gov/states/de/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/de/tax/income/subtractions/subtractions.yaml index c3d94c1174c..d307981bf5d 100644 --- a/policyengine_us/parameters/gov/states/de/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/de/tax/income/subtractions/subtractions.yaml @@ -10,6 +10,7 @@ values: - taxable_unemployment_compensation # (10) - salt_refund_income # 30 Del. C. § 1106(b)(9) / PIT-RES Line 7 - de_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) # The elderly or disabled income exclusion is computed separately # - de_elderly_or_disabled_income_exclusion # (2) 2022-01-01: @@ -19,6 +20,7 @@ values: - de_529_plan_subtraction # HB 145 - salt_refund_income # 30 Del. C. § 1106(b)(9) / PIT-RES Line 7 - de_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -31,3 +33,5 @@ metadata: href: https://legis.delaware.gov/BillDetail/79061 - title: 2021 Delaware Resident Individual Income Tax Return (PIT-RES) Instructions, Line 7 href: https://revenuefiles.delaware.gov/2021/PIT-RES_TY21_2021-01_Instructions.pdf + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/ga/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/ga/tax/income/subtractions/subtractions.yaml index a26405360c9..7d18f479b84 100644 --- a/policyengine_us/parameters/gov/states/ga/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/ga/tax/income/subtractions/subtractions.yaml @@ -11,6 +11,7 @@ values: - taxable_social_security - ga_investment_in_529_plan_deduction - ga_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2022-01-01: - us_govt_interest - ga_retirement_exclusion @@ -18,6 +19,7 @@ values: - taxable_social_security - ga_investment_in_529_plan_deduction - ga_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2026-01-01: - us_govt_interest - ga_retirement_exclusion @@ -27,6 +29,7 @@ values: - ga_qualified_overtime_exclusion - ga_cash_tip_exclusion - ga_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2029-01-01: - us_govt_interest - ga_retirement_exclusion @@ -34,6 +37,7 @@ values: - taxable_social_security - ga_investment_in_529_plan_deduction - ga_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -52,3 +56,5 @@ metadata: href: https://www.legis.ga.gov/api/legislation/document/20252026/249080#page=6 - title: Georgia HB463 (2025-2026), Section 2-4 adding O.C.G.A. § 48-7-27(a)(17) - cash tips exclusion href: https://www.legis.ga.gov/api/legislation/document/20252026/249080#page=7 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml index 7201735126e..c01c5f4543a 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml @@ -8,6 +8,7 @@ values: - us_govt_interest # Line 18 - student_loan_interest_ald # Line 18 - hi_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2025-01-01: - taxable_pension_income # Line 13 - military_retirement_income # Line 13 @@ -16,6 +17,7 @@ values: - us_govt_interest # Line 18 - hi_student_loan_interest_subtraction # Line 18 - hi_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: @@ -31,3 +33,5 @@ metadata: href: https://law.justia.com/codes/hawaii/2022/title-14/chapter-235/section-235-7/ - title: 2025 Hawaii N-11 Instructions Subtractions href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=13 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/taxable_income/subtractions.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/taxable_income/subtractions.yaml index 521a4ba10a8..ee8d201bc3f 100644 --- a/policyengine_us/parameters/gov/states/ia/tax/income/taxable_income/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/ia/tax/income/taxable_income/subtractions.yaml @@ -21,6 +21,7 @@ values: - ia_disability_income_exclusion # Schedule 1, line 11, column B, and line 19 - ia_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -35,3 +36,5 @@ metadata: href: https://revenue.iowa.gov/media/4096/download?inline=#page=1 - title: 2024 IA1040 Iowa Income Tax Return Instruction href: https://revenue.iowa.gov/media/4152/download?inline#page=61 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/id/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/id/tax/income/subtractions/subtractions.yaml index 1051f9780fb..3e22bb1f87e 100644 --- a/policyengine_us/parameters/gov/states/id/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/id/tax/income/subtractions/subtractions.yaml @@ -13,6 +13,7 @@ values: - id_529_plan_subtraction # Idaho Code 63-3022(n) - id_health_insurance_premiums_subtraction # Idaho Code 63-3022P - id_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2025-01-01: - salt_refund_income # (2) - us_govt_interest # (3)(a) @@ -27,6 +28,7 @@ values: - id_529_plan_subtraction # Idaho Code 63-3022(n) - id_health_insurance_premiums_subtraction # Idaho Code 63-3022P - id_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: @@ -44,3 +46,5 @@ metadata: href: https://legislature.idaho.gov/statutesrules/idstat/Title63/T63CH30/SECT63-3022P/ - title: Section 35.01.01.254 - (5) / (3) (a) href: https://regulations.justia.com/states/idaho/35/35-01-01/section-35-01-01-254/ + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/il/tax/income/base/subtractions.yaml b/policyengine_us/parameters/gov/states/il/tax/income/base/subtractions.yaml index f51d2d263dc..2fce313b314 100644 --- a/policyengine_us/parameters/gov/states/il/tax/income/base/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/il/tax/income/base/subtractions.yaml @@ -10,6 +10,7 @@ values: - il_529_plan_subtraction - salt_refund_income - il_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list period: year @@ -23,3 +24,5 @@ metadata: href: https://tax.illinois.gov/content/dam/soi/en/web/tax/forms/incometax/documents/currentyear/individual/il-1040-instr.pdf#page=7 - title: Illinois Department of Revenue Publication 120, Retirement Income href: https://tax.illinois.gov/content/dam/soi/en/web/tax/research/publications/pubs/documents/pub-120.pdf#page=2 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/in/tax/income/deductions/deductions.yaml b/policyengine_us/parameters/gov/states/in/tax/income/deductions/deductions.yaml index ec0b4336ca0..88f3962c5d4 100644 --- a/policyengine_us/parameters/gov/states/in/tax/income/deductions/deductions.yaml +++ b/policyengine_us/parameters/gov/states/in/tax/income/deductions/deductions.yaml @@ -5,7 +5,8 @@ values: - in_homeowners_property_tax_deduction - salt_refund_income - us_govt_interest - - tax_unit_taxable_social_security # includes railroad retirement benefits + - tax_unit_taxable_social_security + - taxable_railroad_benefits # 45 U.S.C. 231m(a) - in_military_service_deduction - in_nonpublic_school_deduction - in_nol @@ -25,7 +26,9 @@ values: # IC 6-8-5-1 - us_govt_interest # US Code 26-A-1-B-II-§86 - - tax_unit_taxable_social_security # includes railroad retirement benefits + - tax_unit_taxable_social_security + # 45 U.S.C. 231m(a) + - taxable_railroad_benefits # IC 6-3-2-4-(a)(1) - in_military_service_deduction # IC 6-3-2-22-(d)(1) @@ -55,7 +58,9 @@ values: # IC 6-8-5-1 - us_govt_interest # US Code 26-A-1-B-II-§86 - - tax_unit_taxable_social_security # includes railroad retirement benefits + - tax_unit_taxable_social_security + # 45 U.S.C. 231m(a) + - taxable_railroad_benefits # IC 6-3-2-4-(a)(1) - in_military_service_deduction # IC 6-3-2-22-(d)(1) @@ -87,7 +92,9 @@ values: # IC 6-8-5-1 - us_govt_interest # US Code 26-A-1-B-II-§86 - - tax_unit_taxable_social_security # includes railroad retirement benefits + - tax_unit_taxable_social_security + # 45 U.S.C. 231m(a) + - taxable_railroad_benefits # IC 6-3-2-4-(a)(1) - in_military_service_deduction # IC 6-3-2-22-(d)(1) @@ -125,7 +132,9 @@ values: # IC 6-8-5-1 - us_govt_interest # US Code 26-A-1-B-II-§86 - - tax_unit_taxable_social_security # includes railroad retirement benefits + - tax_unit_taxable_social_security + # 45 U.S.C. 231m(a) + - taxable_railroad_benefits # IC 6-3-2-4-(a)(1) - in_military_service_deduction # IC 6-3-2-22-(d)(1) @@ -160,3 +169,5 @@ metadata: href: https://www.law.cornell.edu/uscode/text - title: 2025 IT-40 Income Tax Form Instruction Booklet, pg. 19 href: https://forms.in.gov/Download.aspx?id=16915#page=19 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/ky/tax/income/subtractions.yaml b/policyengine_us/parameters/gov/states/ky/tax/income/subtractions.yaml index 58be361ebf2..073f59ae7e8 100644 --- a/policyengine_us/parameters/gov/states/ky/tax/income/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/ky/tax/income/subtractions.yaml @@ -9,6 +9,7 @@ values: # - Kentucky depreciation - military_service_income - ky_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list reference: @@ -28,3 +29,5 @@ metadata: href: https://revenue.ky.gov/Forms/740%20Packet%20Instructions%205-9-23.pdf#page=23 - title: 2021 Kentucky Individual Income Tax Forms href: https://taxsim.nber.org/historical_state_tax_forms/KY/2021/Form%20740%20Packet%20Instructions-2021.pdf#page=27 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/la/tax/income/exempt_income/sources.yaml b/policyengine_us/parameters/gov/states/la/tax/income/exempt_income/sources.yaml index 1e6ce5bfda9..df5b1315aef 100644 --- a/policyengine_us/parameters/gov/states/la/tax/income/exempt_income/sources.yaml +++ b/policyengine_us/parameters/gov/states/la/tax/income/exempt_income/sources.yaml @@ -13,6 +13,7 @@ values: - taxable_public_pension_income - la_retirement_exemption_person # 06E - taxable_social_security # 07E + - taxable_railroad_benefits # 45 U.S.C. 231m(a) # Native American Income 08E # START Savings Program Contributions 09E - la_military_pay_exclusion # 10E @@ -51,3 +52,5 @@ metadata: href: https://www.legis.la.gov/legis/Law.aspx?d=102133 - title: R.S. 47:293(9)(e) href: https://www.legis.la.gov/legis/Law.aspx?d=101760 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/md/tax/income/agi/subtractions/sources.yaml b/policyengine_us/parameters/gov/states/md/tax/income/agi/subtractions/sources.yaml index 13e8f6ca82f..4d273a3ae60 100644 --- a/policyengine_us/parameters/gov/states/md/tax/income/agi/subtractions/sources.yaml +++ b/policyengine_us/parameters/gov/states/md/tax/income/agi/subtractions/sources.yaml @@ -10,6 +10,7 @@ values: - md_529_deduction - salt_refund_income - md_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2022-01-01: - us_govt_interest - md_dependent_care_subtraction @@ -21,6 +22,7 @@ values: - md_529_deduction - salt_refund_income - md_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: reference: @@ -33,6 +35,8 @@ metadata: # Bill introducing the hundred year subtraction - title: House Bill 186 href: https://mgaleg.maryland.gov/mgawebsite/Legislation/Details/hb0186?ys=2022RS + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a period: year unit: list label: Maryland adjusted gross income subtractions diff --git a/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml b/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml index b9d9ff8abd2..9f23193500d 100644 --- a/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml @@ -12,6 +12,7 @@ values: - mi_529_deduction - salt_refund_income # Line 16 - mi_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2026-01-01: - us_govt_interest # Line 10 - mi_military_retirement_subtraction # Line 11 @@ -27,6 +28,7 @@ values: - mi_qualified_overtime_deduction # Qualified overtime compensation (PA 24 of 2025), TY2026-2028 - salt_refund_income # Line 16 - mi_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list label: Michigan taxable income subtraction sources @@ -48,3 +50,5 @@ metadata: href: https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Forms/IIT/TY2024/MI-1040-Instructions.pdf#page=13 - title: Michigan Retirement and Pension Benefits href: https://web.archive.org/web/20250202214915/https://www.michigan.gov/taxes/iit/retirement-and-pension-benefits + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/mn/tax/income/subtractions/sources.yaml b/policyengine_us/parameters/gov/states/mn/tax/income/subtractions/sources.yaml index 712d9eb223b..9fd99614706 100644 --- a/policyengine_us/parameters/gov/states/mn/tax/income/subtractions/sources.yaml +++ b/policyengine_us/parameters/gov/states/mn/tax/income/subtractions/sources.yaml @@ -15,6 +15,7 @@ values: - mn_529_contribution_subtraction - salt_refund_income - mn_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2023-01-01: - mn_charity_subtraction - mn_social_security_subtraction @@ -27,6 +28,7 @@ values: - mn_529_contribution_subtraction - salt_refund_income - mn_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: @@ -46,3 +48,5 @@ metadata: href: https://www.revenue.state.mn.us/sites/default/files/2025-11/m1m-24.pdf - title: 2025 Minnesota Schedule M1M, Income Additions and Subtractions href: https://www.revenue.state.mn.us/sites/default/files/2026-07/m1m-25.pdf#page=1 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/mo/tax/income/subtractions/agi_subtractions.yaml b/policyengine_us/parameters/gov/states/mo/tax/income/subtractions/agi_subtractions.yaml index c4c63f69efa..5edde528d1d 100644 --- a/policyengine_us/parameters/gov/states/mo/tax/income/subtractions/agi_subtractions.yaml +++ b/policyengine_us/parameters/gov/states/mo/tax/income/subtractions/agi_subtractions.yaml @@ -7,6 +7,7 @@ values: - salt_refund_income # 143.121 RSMo / Form MO-A Line 9 - military_retirement_pay # 143.121.3(12) RSMo / Form MO-A Line 10 - mo_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2025-01-01: - mo_qualified_health_insurance_premiums - mo_capital_gains_subtraction_person @@ -15,6 +16,7 @@ values: - salt_refund_income # 143.121 RSMo / Form MO-A Line 9 - military_retirement_pay # 143.121.3(12) RSMo / Form MO-A Line 10 - mo_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: @@ -28,3 +30,5 @@ metadata: href: https://www.revisor.mo.gov/main/OneSection.aspx?section=143.121&bid=57543 - title: 2025 MO-1040 Book - Individual Income Tax Long Form href: https://dor.mo.gov/forms/MO-1040%20Instructions_2025.pdf#page=12 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/subtractions/subtractions.yaml index 78b1cee6d27..7ea49d3fbe8 100644 --- a/policyengine_us/parameters/gov/states/mt/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/mt/tax/income/subtractions/subtractions.yaml @@ -4,6 +4,7 @@ values: - mt_disability_income_exclusion_person - mt_unemployment_compensation_subtraction - mt_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2021-01-01: - mt_interest_exemption_person - us_govt_interest_person @@ -14,6 +15,7 @@ values: - mt_unemployment_compensation_subtraction - salt_refund_income # Form 2 Subtractions Schedule - mt_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2024-01-01: # MCA 15-30-2120(3)(n) allows exempting up to 50% of military retirement pay # (capped at MT earned income, (3)(n)(i)) and up to 50% of military survivor @@ -24,6 +26,7 @@ values: - mt_disability_income_exclusion_person - salt_refund_income # MCA 15-30-2120 / Form 2 Subtractions Schedule - mt_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2025-01-01: # Section 15-30-2120 starts from federal taxable income, so federal # OBBBA Schedule 1-A deductions (enhanced senior deduction, qualified @@ -38,6 +41,7 @@ values: - mt_federal_obbba_subtraction - salt_refund_income # MCA 15-30-2120 / Form 2 Subtractions Schedule - mt_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list period: year @@ -53,3 +57,5 @@ metadata: href: https://leg.mt.gov/bills/mca/title_0150/chapter_0300/part_0210/section_0100/0150-0300-0210-0100.html - title: 2025 Montana Form 2 Instructions href: https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2025_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=21 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/deductions.yaml b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/deductions.yaml index d40b99c6571..364260b81ef 100644 --- a/policyengine_us/parameters/gov/states/nc/tax/income/deductions/deductions.yaml +++ b/policyengine_us/parameters/gov/states/nc/tax/income/deductions/deductions.yaml @@ -5,6 +5,7 @@ values: - nc_standard_or_itemized_deductions - nc_child_deduction - taxable_social_security + - taxable_railroad_benefits # 45 U.S.C. 231m(a) - salt_refund_income - nc_other_subtractions 2022-01-01: @@ -12,6 +13,7 @@ values: - nc_standard_or_itemized_deductions - nc_child_deduction - taxable_social_security + - taxable_railroad_benefits # 45 U.S.C. 231m(a) - nc_military_retirement_deduction - salt_refund_income - nc_other_subtractions @@ -30,3 +32,5 @@ metadata: href: https://www.ncdor.gov/2022-d-400-schedule-s-web-fill-version/open#page=17 - title: 2021 D-400 Schedule S href: https://www.ncdor.gov/2021-d-400-schedule-s-north-carolina-supplemental-schedule-web/open + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/nd/tax/income/taxable_income/subtractions/sources.yaml b/policyengine_us/parameters/gov/states/nd/tax/income/taxable_income/subtractions/sources.yaml index 09aaf7f05b6..d77d348bb6f 100644 --- a/policyengine_us/parameters/gov/states/nd/tax/income/taxable_income/subtractions/sources.yaml +++ b/policyengine_us/parameters/gov/states/nd/tax/income/taxable_income/subtractions/sources.yaml @@ -9,6 +9,7 @@ values: - nd_529_deduction - nd_stillborn_subtraction - nd_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2022-01-01: - us_govt_interest @@ -19,6 +20,7 @@ values: - nd_529_deduction - nd_stillborn_subtraction - nd_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2023-01-01: - us_govt_interest @@ -30,6 +32,7 @@ values: - nd_529_deduction - nd_stillborn_subtraction - nd_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: @@ -55,3 +58,5 @@ metadata: href: https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2025-iit/2025-individual-income-tax-booklet.pdf#page=14 - title: North Dakota Century Code Title 57 - Taxation Chapter 57-38 - Income Tax, 57-38-30.3 (2) href: https://law.justia.com/codes/north-dakota/2022/title-57/chapter-57-38/ + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/ne/tax/income/agi/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/ne/tax/income/agi/subtractions/subtractions.yaml index 1d364997dd8..834688eab96 100644 --- a/policyengine_us/parameters/gov/states/ne/tax/income/agi/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/ne/tax/income/agi/subtractions/subtractions.yaml @@ -7,6 +7,7 @@ values: - ne_529_deduction - salt_refund_income - ne_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2024-01-01: - ne_social_security_subtraction - taxable_public_pension_income @@ -15,6 +16,7 @@ values: - ne_529_deduction - salt_refund_income - ne_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -25,3 +27,5 @@ metadata: href: https://www.nebraskalegislature.gov/laws/statutes.php?statute=77-2716 - title: 2025 Nebraska Individual Income Tax Booklet, Schedule I href: https://revenue.nebraska.gov/sites/default/files/doc/tax-forms/2025/f_Individual_Income_Tax_Booklet.pdf#page=26 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/nm/tax/income/other_deductions_and_exemptions.yaml b/policyengine_us/parameters/gov/states/nm/tax/income/other_deductions_and_exemptions.yaml index a1db98438f2..779d6d0564a 100644 --- a/policyengine_us/parameters/gov/states/nm/tax/income/other_deductions_and_exemptions.yaml +++ b/policyengine_us/parameters/gov/states/nm/tax/income/other_deductions_and_exemptions.yaml @@ -5,6 +5,7 @@ values: - nm_529_plan_deduction - military_service_income - salt_refund_income # Form PIT-ADJ + - taxable_railroad_benefits # 45 U.S.C. 231m(a) - nm_other_subtractions metadata: unit: list @@ -22,3 +23,5 @@ metadata: href: https://www.srca.nm.gov/parts/title03/03.003.0001.html - title: New Mexico Income Tax Act, Chapter 7 - Taxation, 7-2-32. Deduction; payments into education trust fund. href: https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc141270056/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWDgJgHYADIICsANgCUAGmTZShCAEVEhXAE9oAck1SIhMLgTLVG7bv2GQAZTykAQhoBKAUQAyzgGoBBAHIBhZylSMAAjaFJ2CQkgA + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/ny/tax/income/agi/subtractions/sources.yaml b/policyengine_us/parameters/gov/states/ny/tax/income/agi/subtractions/sources.yaml index 30ced102407..56a74dab4f8 100644 --- a/policyengine_us/parameters/gov/states/ny/tax/income/agi/subtractions/sources.yaml +++ b/policyengine_us/parameters/gov/states/ny/tax/income/agi/subtractions/sources.yaml @@ -14,6 +14,7 @@ values: - taxable_public_pension_income - military_retirement_income - ny_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list period: year @@ -33,3 +34,5 @@ metadata: href: https://newyork.public.law/laws/n.y._tax_law_section_612 - title: N.Y. Comp. Codes R. & Regs. tit. 20 § 112.3 href: https://www.law.cornell.edu/regulations/new-york/20-NYCRR-112.3 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/oh/tax/income/deductions/deductions.yaml b/policyengine_us/parameters/gov/states/oh/tax/income/deductions/deductions.yaml index 278d049e468..67425028f03 100644 --- a/policyengine_us/parameters/gov/states/oh/tax/income/deductions/deductions.yaml +++ b/policyengine_us/parameters/gov/states/oh/tax/income/deductions/deductions.yaml @@ -3,6 +3,7 @@ values: 2021-01-01: - oh_section_179_expense_add_back - taxable_social_security + - taxable_railroad_benefits # 45 U.S.C. 231m(a) - oh_uniformed_services_retirement_income_deduction - oh_529_plan_deduction_person - pell_grant @@ -32,3 +33,5 @@ metadata: href: https://dam.assets.ohio.gov/image/upload/v1735920104/tax.ohio.gov/forms/ohio_individual/individual/2024/it1040-booklet.pdf#page=19 - title: Ohio Income Tax Form 2025 Instructions (Page 20) href: https://dam.assets.ohio.gov/image/upload/v1767095693/tax.ohio.gov/forms/ohio_individual/individual/2025/it1040-booklet.pdf#page=20 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/ok/tax/income/agi/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/ok/tax/income/agi/subtractions/subtractions.yaml index 1b2dada1d70..99a6281d9f1 100644 --- a/policyengine_us/parameters/gov/states/ok/tax/income/agi/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/ok/tax/income/agi/subtractions/subtractions.yaml @@ -8,6 +8,7 @@ values: - ok_529_plan_deduction - salt_refund_income - ok_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -24,3 +25,5 @@ metadata: href: https://oklahoma.gov/content/dam/ok/en/tax/documents/forms/individuals/past-year/2024/511-Pkt-2024.pdf#page=16 - title: 2025 Form 511 instructions href: https://oklahoma.gov/content/dam/ok/en/tax/documents/forms/individuals/current/511-Pkt.pdf#page=16 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/or/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/or/tax/income/subtractions/subtractions.yaml index bd8f2fc935a..e244fde3700 100644 --- a/policyengine_us/parameters/gov/states/or/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/or/tax/income/subtractions/subtractions.yaml @@ -20,6 +20,8 @@ metadata: href: https://www.oregon.gov/dor/forms/FormsPubs/form-or-40-inst_101-040-1_2023.pdf#page=13 - title: 2025 Oregon Income Tax Form OR-40 Instructions href: https://www.oregon.gov/dor/forms/FormsPubs/form-or-40-inst_101-040-1_2025.pdf#page=14 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a values: 2020-01-01: - or_federal_tax_liability_subtraction @@ -28,3 +30,4 @@ values: - salt_refund_income - or_federal_pension_subtraction - or_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) diff --git a/policyengine_us/parameters/gov/states/pa/tax/income/nontaxable_income_sources.yaml b/policyengine_us/parameters/gov/states/pa/tax/income/nontaxable_income_sources.yaml index 4f20466d5ad..0d3981c4bda 100644 --- a/policyengine_us/parameters/gov/states/pa/tax/income/nontaxable_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/pa/tax/income/nontaxable_income_sources.yaml @@ -6,6 +6,7 @@ values: - pa_nontaxable_roth_conversions - taxable_unemployment_compensation - taxable_social_security + - taxable_railroad_benefits # 45 U.S.C. 231m(a) - us_govt_interest - salt_refund_income - military_retirement_income @@ -23,3 +24,5 @@ metadata: href: https://www.pacodeandbulletin.gov/Display/pacode?file=/secure/pacode/data/061/chapter101/s101.6.html - title: "2024 Pennsylvania PIT Return Instructions (PA-40 IN), nontaxable sick pay and disability benefits and Code 3 disability distributions #page 8,13" href: https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2024/2024_pa-40in.pdf#page=8 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/subtractions.yaml index afb8cd1d13c..49df6b94c83 100644 --- a/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/subtractions.yaml @@ -7,6 +7,7 @@ values: - ri_retirement_income_subtraction - us_govt_interest - ri_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2023-01-01: - ri_social_security_modification @@ -15,6 +16,7 @@ values: - us_govt_interest - ri_military_retirement_subtraction - ri_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -33,3 +35,5 @@ metadata: href: https://tax.ri.gov/sites/g/files/xkgbur541/files/2025-12/2025%201040R%20Instructions%20122025.pdf#page=9 - title: 2025 RI Schedule M - page 1 RI Modifications to Federal AGI href: https://tax.ri.gov/sites/g/files/xkgbur541/files/2026-01/2025%20RI%20Schedule%20M_w.pdf#page=1 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/subtractions.yaml index f8e3ee742e5..d48db927302 100644 --- a/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/subtractions.yaml @@ -13,6 +13,7 @@ values: - sc_young_child_deduction - sc_dependent_exemption - sc_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) # - Out of state income/gain # - Volunteer deductions # - Active Trade or Business Income deduction @@ -36,6 +37,7 @@ values: - sc_dependent_exemption - sc_sciad - sc_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -60,3 +62,5 @@ metadata: href: https://dor.sc.gov/forms-site/Forms/IITPacket_2023.pdf#page=43 - title: SC 1040 tax form instructions (2024) href: https://dor.sc.gov/forms-site/Forms/SC1040Instr_2024.pdf#page=8 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/va/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/va/tax/income/subtractions/subtractions.yaml index 32dd8cb982d..19c30a6ff86 100644 --- a/policyengine_us/parameters/gov/states/va/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/va/tax/income/subtractions/subtractions.yaml @@ -6,7 +6,10 @@ values: - us_govt_interest_person - va_military_basic_pay_subtraction_person # Special instructions for members of the military - va_disability_income_subtraction_person # Certification Number 5 - - railroad_benefits # Certification Number 22 + # Tier 1 railroad benefits in federal AGI (Form 760 Line 5) and the tier 2, + # vested dual and other Railroad Retirement Act benefits in it (Schedule + # ADJ code 22); 45 U.S.C. 231m(a). + - taxable_railroad_benefits - va_national_guard_subtraction_person # Certification Number 28 - taxable_unemployment_compensation # Certification Number 37; Va. Code § 58.1-322.02(17) - va_federal_state_employees_subtraction_person # Certification Number 39 @@ -23,8 +26,8 @@ metadata: reference: - title: 2024 Form 760 Instructions, What's New href: https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2024-760-instructions.pdf#page=7 - - title: 2024 Form 760 Instructions, Subtractions from Income # latest list of subtractions as of 2025-02-14 - href: https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2024-760-instructions.pdf#page=24 + - title: "2024 Form 760 Instructions, Line 5 and Subtractions from Income #page 17,24-25" # latest list of subtractions as of 2025-02-14 + href: https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2024-760-instructions.pdf#page=17 - title: 2023 Virginia Schedule ADJ (Form 760-ADJ) href: https://www.tax.virginia.gov/sites/default/files/taxforms/individual-income-tax/2023/schedule-adj-2023.pdf#page=1 - title: 2022 Virginia Schedule ADJ (Form 760-ADJ) @@ -35,3 +38,5 @@ metadata: href: https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2022-760-instructions.pdf#page=25 - title: Code of Virginia § 58.1-322.02 href: https://law.lis.virginia.gov/vacodefull/title58.1/chapter3/article2/ + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/vt/tax/income/agi/subtractions.yaml b/policyengine_us/parameters/gov/states/vt/tax/income/agi/subtractions.yaml index 27a8320cf16..35f87cc2c59 100644 --- a/policyengine_us/parameters/gov/states/vt/tax/income/agi/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/vt/tax/income/agi/subtractions.yaml @@ -8,6 +8,7 @@ values: - vt_retirement_income_exemption - salt_refund_income - vt_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list label: Vermont subtractions @@ -22,3 +23,5 @@ metadata: href: https://tax.vermont.gov/sites/tax/files/documents/IN-112-2021.pdf#page=1 - title: Titl. 32 V.S.A. § 5811(21)(B)(i), (C)(iv), (B)(vi), (B)(ii), (B)(iv) href: https://legislature.vermont.gov/statutes/section/32/151/05811 + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/wi/tax/income/subtractions/sources.yaml b/policyengine_us/parameters/gov/states/wi/tax/income/subtractions/sources.yaml index 9cf8f3fb2e5..eca24b0111b 100644 --- a/policyengine_us/parameters/gov/states/wi/tax/income/subtractions/sources.yaml +++ b/policyengine_us/parameters/gov/states/wi/tax/income/subtractions/sources.yaml @@ -12,6 +12,7 @@ values: - salt_refund_income - wi_disability_income_exclusion # Schedule SB, Schedule 2440W - wi_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) 2022-01-01: - wi_unemployment_compensation_subtraction - tax_unit_taxable_social_security @@ -23,6 +24,7 @@ values: - salt_refund_income - wi_disability_income_exclusion # Schedule SB, Schedule 2440W - wi_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -42,3 +44,5 @@ metadata: href: https://www.revenue.wi.gov/TaxForms2024/2024-Schedule2440Wf.pdf#page=1 - title: 2025 Wisconsin Form 1 Schedule SB instructions href: https://www.revenue.wi.gov/TaxForms2025/2025-ScheduleSB-inst.pdf + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/subtractions.yaml index f73f809186b..4fb8056e9bf 100644 --- a/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/subtractions.yaml @@ -11,6 +11,7 @@ values: - wv_529_plan_deduction - wv_senior_citizen_disability_deduction # Line 47 - wv_other_subtractions + - taxable_railroad_benefits # 45 U.S.C. 231m(a) metadata: unit: list @@ -29,3 +30,5 @@ metadata: href: https://tax.wv.gov/Documents/TaxForms/2021/it140.pdf#page=3 - title: Code of West Virginia §11-21-12 href: https://code.wvlegislature.gov/11-21-12/ + - title: 45 U.S. Code § 231m(a) - Railroad Retirement Act annuities are exempt from state tax + href: https://www.law.cornell.edu/uscode/text/45/231m#a diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_magi_railroad_retirement.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_magi_railroad_retirement.yaml new file mode 100644 index 00000000000..c03a072645a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/income/medicaid_magi_railroad_retirement.yaml @@ -0,0 +1,47 @@ +# IRC 36B(d)(2)(B)(iii): MAGI adds back social security benefits as section +# 86(d) defines them that section 86 leaves out of gross income, so it adds +# the nontaxable tier 1 railroad benefits just as it adds nontaxable title II +# benefits. Medicaid MAGI follows 36B (42 U.S.C. 1396a(e)(14)(G)). + +- name: Nontaxable tier 1 railroad benefits are in MAGI. + period: 2024 + input: + people: + head: + age: 67 + employment_income: 20_000 + railroad_social_security_equivalent_benefits: 20_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + # Combined income = 30,000, so 2,500 is taxable and 17,500 is not. + adjusted_gross_income: 22_500 + tax_exempt_railroad_social_security_equivalent_benefits: 17_500 + medicaid_magi: 40_000 + aca_magi: 40_000 + medicaid_magi_person: 40_000 + +- name: MAGI is the same whether the benefits are title II or tier 1. + period: 2024 + input: + people: + head: + age: 67 + employment_income: 20_000 + social_security_retirement: 20_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + adjusted_gross_income: 22_500 + medicaid_magi: 40_000 + aca_magi: 40_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/elderly_and_disabled/section_22_railroad_retirement.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/elderly_and_disabled/section_22_railroad_retirement.yaml new file mode 100644 index 00000000000..71471c25063 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/elderly_and_disabled/section_22_railroad_retirement.yaml @@ -0,0 +1,75 @@ +# Section 22(c)(3)(A)(i)(II): the section 22 amount is reduced by amounts the +# head or spouse receives as a pension or annuity payable under the Railroad +# Retirement Act of 1974 and excluded from gross income. That covers tier 1 +# benefits section 86 leaves out and the tax-free part of the rest of the +# annuity (72(r)(2), 72(b)). Initial amounts: $5,000 single, $7,500 joint with +# two qualified individuals (22(c)(2)); AGI thresholds $7,500 and $10,000 +# (22(d)). + +- name: Nontaxable tier 1 benefits and the tax-free tier 2 part both reduce the amount. + period: 2024 + input: + people: + head: + age: 67 + railroad_social_security_equivalent_benefits: 3_000 + taxable_railroad_pension_benefits: 2_000 + tax_exempt_railroad_pension_benefits: 500 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + # Combined income = 2,000 + 1,500 = 3,500, so all 3,000 of tier 1 benefits + # are excluded. AGI = 2,000, under 7,500. + # 5,000 - 3,000 - 500 = 1,500. + section_22_income: 1_500 + +- name: On a joint return title II and railroad exclusions of both spouses reduce the amount. + period: 2024 + input: + people: + head: + age: 67 + railroad_social_security_equivalent_benefits: 2_000 + taxable_railroad_pension_benefits: 4_000 + tax_exempt_railroad_pension_benefits: 300 + spouse: + age: 67 + social_security_retirement: 1_000 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + # Combined income = 4,000 + 1,500 = 5,500, so no benefits are taxable. + # AGI = 4,000, under 10,000. + # 7,500 - 1,000 (title II) - 2,000 (tier 1) - 300 (tier 2) = 4,200. + section_22_income: 4_200 + +- name: The AGI phase-out still applies after the railroad reduction. + period: 2024 + input: + people: + head: + age: 67 + railroad_social_security_equivalent_benefits: 1_000 + taxable_railroad_pension_benefits: 9_500 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + # Combined income = 9,500 + 500 = 10,000: no tier 1 benefits are taxable. + # AGI = 9,500; 22(d): 0.5 * (9,500 - 7,500) = 1,000. + # 5,000 - 1,000 - 1,000 = 3,000. + section_22_income: 3_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/railroad_social_security_equivalent_benefits.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/railroad_social_security_equivalent_benefits.yaml new file mode 100644 index 00000000000..a957b3cbdba --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/railroad_social_security_equivalent_benefits.yaml @@ -0,0 +1,193 @@ +# Tier 1 railroad retirement benefits under IRC 86. Section 86(d)(1)(B) makes +# the social security equivalent benefit (Form RRB-1099 box 5) a social +# security benefit, so section 86 figures one taxable amount on it and title II +# benefits together (Pub. 915 Worksheet 1). Single filers: base amount $25,000, +# adjusted base amount $34,000 (86(c)(1)); joint: $32,000 and $44,000. + +- name: Tier 1 railroad benefits between the base and adjusted base amounts. + period: 2024 + input: + people: + head: + age: 67 + employment_income: 20_000 + railroad_social_security_equivalent_benefits: 20_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + # Combined income = 20,000 + 0.5 * 20,000 = 30,000 (86(b)(1)). + # 86(a)(1): min(0.5 * 20,000, 0.5 * (30,000 - 25,000)) = 2,500. + tax_unit_section_86_benefits: 20_000 + tax_unit_taxable_section_86_benefits: 2_500 + tax_unit_taxable_social_security: 0 + tax_unit_taxable_railroad_social_security_equivalent_benefits: 2_500 + taxable_railroad_social_security_equivalent_benefits: 2_500 + tax_exempt_railroad_social_security_equivalent_benefits: 17_500 + adjusted_gross_income: 22_500 + +- name: The same amount of title II benefits is taxed the same way. + period: 2024 + input: + people: + head: + age: 67 + employment_income: 20_000 + social_security_retirement: 20_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + tax_unit_section_86_benefits: 20_000 + tax_unit_taxable_section_86_benefits: 2_500 + tax_unit_taxable_social_security: 2_500 + taxable_social_security: 2_500 + tax_unit_taxable_railroad_social_security_equivalent_benefits: 0 + adjusted_gross_income: 22_500 + +- name: Title II and tier 1 benefits share one section 86 amount in proportion. + period: 2024 + input: + people: + head: + age: 67 + employment_income: 20_000 + social_security_retirement: 12_000 + railroad_social_security_equivalent_benefits: 8_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + # Combined income = 20,000 + 0.5 * 20,000 = 30,000, so 2,500 is taxable, + # split 12,000 / 20,000 to title II and 8,000 / 20,000 to tier 1. + tax_unit_taxable_section_86_benefits: 2_500 + tax_unit_taxable_social_security: 1_500 + tax_unit_taxable_railroad_social_security_equivalent_benefits: 1_000 + tax_exempt_social_security: 10_500 + tax_exempt_railroad_social_security_equivalent_benefits: 7_000 + adjusted_gross_income: 22_500 + +- name: Tier 1 railroad benefits above the adjusted base amount reach the 85 percent cap. + period: 2024 + input: + people: + head: + age: 67 + employment_income: 50_000 + railroad_social_security_equivalent_benefits: 24_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + # Combined income = 50,000 + 12,000 = 62,000. + # 86(a)(2)(A): 0.85 * (62,000 - 34,000) + min(12,000, 4,500) = 28,300. + # 86(a)(2)(B): 0.85 * 24,000 = 20,400, the lesser. + tax_unit_taxable_section_86_benefits: 20_400 + taxable_railroad_social_security_equivalent_benefits: 20_400 + adjusted_gross_income: 70_400 + +- name: On a joint return each spouse is allocated the taxable part of their own kind of benefit. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 67 + employment_income: 40_000 + social_security_retirement: 20_000 + spouse: + age: 67 + railroad_social_security_equivalent_benefits: 10_000 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: TX + output: + # Combined income = 40,000 + 0.5 * 30,000 = 55,000. + # 86(a)(1) amount: min(15,000, 0.5 * (55,000 - 32,000)) = 11,500. + # 86(a)(2)(A): 0.85 * (55,000 - 44,000) + min(11,500, 6,000) = 15,350. + # 86(a)(2)(B): 0.85 * 30,000 = 25,500, so 15,350 is taxable: two thirds + # title II (10,233.33) and one third tier 1 (5,116.67). + tax_unit_taxable_section_86_benefits: 15_350 + taxable_social_security: [10_233.33, 0] + taxable_railroad_social_security_equivalent_benefits: [0, 5_116.67] + adjusted_gross_income: 55_350 + +- name: A dependent's tier 1 benefits are taxed on the dependent's own income. + period: 2024 + input: + people: + head: + age: 45 + employment_income: 50_000 + child: + age: 17 + is_tax_unit_dependent: true + employment_income: 30_000 + railroad_social_security_equivalent_benefits: 10_000 + tax_units: + tax_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: TX + output: + # The child's combined income = 30,000 + 5,000 = 35,000, over the 34,000 + # adjusted base amount for an unmarried individual. + # 86(a)(2)(A): 0.85 * 1,000 + min(5,000, 4,500) = 5,350. + # 86(a)(2)(B): 0.85 * 10,000 = 8,500, so 5,350. + dependent_taxable_ss_magi: [0, 30_000] + dependent_taxable_railroad_social_security_equivalent_benefits: [0, 5_350] + dependent_taxable_social_security: [0, 0] + dependent_gross_income: [0, 35_350] + # The head's return holds none of the child's benefits. + tax_unit_railroad_social_security_equivalent_benefits_for_taxability: 0 + tax_unit_taxable_railroad_social_security_equivalent_benefits: 0 + irs_gross_income: [50_000, 0] + +- name: A dependent's title II and tier 1 benefits share one section 86 amount. + period: 2024 + input: + people: + head: + age: 45 + employment_income: 50_000 + child: + age: 17 + is_tax_unit_dependent: true + employment_income: 30_000 + social_security_survivors: 6_000 + railroad_social_security_equivalent_benefits: 4_000 + tax_units: + tax_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: TX + output: + # The same 5,350 on 10,000 of benefits: 60 percent title II, 40 percent + # tier 1. + dependent_taxable_social_security: [0, 3_210] + dependent_taxable_railroad_social_security_equivalent_benefits: [0, 2_140] + dependent_gross_income: [0, 35_350] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_railroad_benefits.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_railroad_benefits.yaml new file mode 100644 index 00000000000..ec3060aeb8b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_railroad_benefits.yaml @@ -0,0 +1,99 @@ +# Railroad Retirement Act benefits in federal gross income. IRC 72(r)(1) +# treats every benefit other than the tier 1 social security equivalent +# benefit as a benefit from a section 401(a) plan, and 72(r)(2) treats tier 2 +# employee taxes as contributions recovered tax-free (Form RRB-1099-R). +# taxable_railroad_benefits is what federal AGI includes from the Act, which +# 45 U.S.C. 231m(a) bars states from taxing. + +- name: The taxable part of the rest of the annuity is gross income, like a private pension. + period: 2024 + input: + people: + head: + age: 67 + railroad_social_security_equivalent_benefits: 18_000 + taxable_railroad_pension_benefits: 12_000 + tax_exempt_railroad_pension_benefits: 1_500 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + # Combined income = 12,000 + 0.5 * 18,000 = 21,000, under the 25,000 base + # amount, so no tier 1 benefits are taxable. + railroad_benefits: 31_500 + tax_unit_taxable_railroad_social_security_equivalent_benefits: 0 + irs_gross_income: 12_000 + adjusted_gross_income: 12_000 + taxable_railroad_benefits: 12_000 + +- name: Taxable tier 1 benefits and the taxable rest of the annuity both reach AGI. + period: 2024 + input: + people: + head: + age: 67 + employment_income: 30_000 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 10_000 + tax_exempt_railroad_pension_benefits: 1_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + # Modified AGI = 30,000 + 10,000 = 40,000; combined income = 50,000. + # 86(a)(2)(A): 0.85 * (50,000 - 34,000) + min(10,000, 4,500) = 18,100. + # 86(a)(2)(B): 0.85 * 20,000 = 17,000, the lesser. + railroad_benefits: 31_000 + taxable_railroad_social_security_equivalent_benefits: 17_000 + adjusted_gross_income: 57_000 + taxable_railroad_benefits: 27_000 + +- name: A dependent's railroad benefits are not in the filer's AGI, so states have nothing to remove. + period: 2024 + input: + people: + head: + age: 45 + employment_income: 50_000 + child: + age: 17 + is_tax_unit_dependent: true + taxable_railroad_pension_benefits: 5_000 + tax_units: + tax_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: TX + output: + irs_gross_income: [50_000, 0] + dependent_gross_income: [0, 5_000] + taxable_railroad_benefits: [0, 0] + +- name: A railroad_benefits total entered directly reaches no tax variable. + period: 2024 + input: + people: + head: + age: 67 + railroad_benefits: 30_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: TX + output: + tax_unit_section_86_benefits: 0 + irs_gross_income: 0 + taxable_railroad_benefits: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/railroad_retirement_benefits.yaml b/policyengine_us/tests/policy/baseline/gov/states/railroad_retirement_benefits.yaml new file mode 100644 index 00000000000..d61adec3aec --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/railroad_retirement_benefits.yaml @@ -0,0 +1,812 @@ +# 45 U.S.C. 231m(a): no State may tax an annuity or supplemental annuity under +# the Railroad Retirement Act. Federal AGI includes the taxable part of tier 1 +# benefits (IRC 86(d)(1)(B)) and of the rest of the annuity (IRC 72(r)(1)), so +# every state that starts from federal AGI or taxable income removes what it +# includes (taxable_railroad_benefits). States that build their own income +# base from other variables never take it in. +# +# Each household: single, 45, railroad benefits only: $20,000 of tier 1 social +# security equivalent benefits and $25,000 of the taxable rest of the annuity. +# Combined income = 25,000 + 10,000 = 35,000, so IRC 86(a)(2) makes +# 0.85 * 1,000 + 4,500 = 5,350 of the tier 1 benefits taxable, and federal AGI +# is 25,000 + 5,350 = 30,350. None of it is state income. + +- name: Railroad retirement benefits are not AL income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: AL + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not AZ income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: AZ + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not AR income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: AR + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not CA income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: CA + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not CO income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: CO + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not CT income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: CT + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not DE income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: DE + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not DC income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: DC + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not GA income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: GA + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not HI income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: HI + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not ID income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: ID + output: + adjusted_gross_income: 30_350 + # The $10 permanent building fund tax falls on every required filer + # (Idaho Code 63-3082), so the test checks taxable income. + id_taxable_income: 0 + +- name: Railroad retirement benefits are not IL income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: IL + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not IN income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: IN + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not IA income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: IA + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not KS income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: KS + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not KY income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: KY + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not LA income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: LA + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not ME income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: ME + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not MD income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MD + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not MA income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MA + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not MI income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MI + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not MN income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MN + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not MS income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MS + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not MO income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MO + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not MT income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MT + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not NE income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NE + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not NJ income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NJ + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not NM income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NM + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not NY income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NY + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not NC income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NC + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not ND income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: ND + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not OH income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: OH + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not OK income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: OK + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not OR income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: OR + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not PA income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: PA + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not RI income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: RI + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not SC income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: SC + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not UT income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: UT + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not VT income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: VT + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not VA income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: VA + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not WV income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: WV + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 + +- name: Railroad retirement benefits are not WI income. + period: 2025 + input: + people: + head: + age: 45 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 25_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: WI + output: + adjusted_gross_income: 30_350 + state_income_tax_before_refundable_credits: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_railroad_retirement.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_railroad_retirement.yaml new file mode 100644 index 00000000000..09f8f4fa74b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_railroad_retirement.yaml @@ -0,0 +1,61 @@ +# Virginia subtracts the tier 1 railroad benefits in federal AGI on Form 760 +# line 5 and the rest of the Railroad Retirement Act benefits in it on Schedule +# ADJ (code 22); 45 U.S.C. 231m(a). Adjusted federal AGI for the age deduction +# removes benefits taxed solely under IRC 86, which include tier 1 but not +# tier 2 (Va. Code 58.1-322.03(5)). A single filer born after January 1, 1939 +# gets $12,000, less each dollar of adjusted federal AGI over $50,000. + +- name: Virginia removes railroad benefits from AGI and the age deduction test. + period: 2024 + input: + people: + head: + age: 67 + employment_income: 20_000 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 10_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: VA + output: + # Federal: combined income = 30,000 + 10,000 = 40,000. + # 86(a)(2)(A): 0.85 * 6,000 + min(7,500, 4,500) = 9,600, under the + # 17,000 cap. AGI = 20,000 + 10,000 + 9,600 = 39,600. + adjusted_gross_income: 39_600 + taxable_railroad_benefits: 19_600 + # 39,600 - 9,600 = 30,000, under 50,000: the full 12,000. + va_age_deduction_agi: 30_000 + va_age_deduction: 12_000 + va_subtractions: 31_600 + va_agi: 8_000 + +- name: Tier 2 benefits stay in adjusted federal AGI for the age deduction. + period: 2024 + input: + people: + head: + age: 67 + employment_income: 45_000 + railroad_social_security_equivalent_benefits: 10_000 + taxable_railroad_pension_benefits: 15_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: VA + output: + # Federal: combined income = 60,000 + 5,000 = 65,000, so the 85 percent + # cap binds: 8,500. AGI = 45,000 + 15,000 + 8,500 = 68,500. + adjusted_gross_income: 68_500 + taxable_railroad_benefits: 23_500 + # 68,500 - 8,500 = 60,000; 12,000 - (60,000 - 50,000) = 2,000. + va_age_deduction_agi: 60_000 + va_age_deduction: 2_000 + va_subtractions: 25_500 + va_agi: 43_000 diff --git a/policyengine_us/tests/test_railroad_retirement_taxation.py b/policyengine_us/tests/test_railroad_retirement_taxation.py new file mode 100644 index 00000000000..5ab2c0b0d38 --- /dev/null +++ b/policyengine_us/tests/test_railroad_retirement_taxation.py @@ -0,0 +1,327 @@ +"""Property tests for the taxation of Railroad Retirement Act benefits. + +Invariants, for every input the strategies draw: + +I1. IRC 86(d)(1): moving any part of a filer's benefits from title II to the + tier 1 social security equivalent benefit leaves federal AGI, federal + income tax, the section 86 taxable amount and Medicaid MAGI unchanged. +I2. IRC 72(r)(1): moving any amount from the taxable part of a private + pension to the taxable part of the rest of the railroad annuity leaves + federal AGI and federal income tax unchanged. +I3. 45 U.S.C. 231m(a): a household whose only income is railroad benefits + owes no state income tax in any state. Idaho's $10 permanent building + fund tax falls on every required filer, so there the test checks that + taxable income is zero. +I4. For a wage earner under 65, adding railroad benefits leaves the state's + AGI unchanged, and in states whose reported AGI includes them it leaves + state taxable income unchanged. STATE_BASE_EXCEPTIONS lists the states + with a provision measured with federal AGI or federal income tax, which + railroad benefits raise. +I5. Totals: railroad_benefits is the sum of its three parts; the title II + and tier 1 shares sum to the section 86 taxable amount; and + taxable_railroad_benefits is what the head's and spouse's federal AGI + includes from the Act, and zero for dependents. +D1. Differential: section 86 runs once on the head and spouse's return and + once on a dependent's own return. For the same income and benefits the + two give the same taxable amount. + +YAML tests pin worked examples; these properties quantify over amounts, +filing status and every state, which YAML cannot. Each example builds one +Simulation that holds every household it compares. +""" + +import numpy as np +from hypothesis import given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +YEAR = 2025 +TOLERANCE = 0.01 + +INCOME_TAX_STATES = ( + "AL AZ AR CA CO CT DE DC GA HI ID IL IN IA KS KY LA ME MD MA MI MN MS MO " + "MT NE NJ NM NY NC ND OH OK OR PA RI SC UT VT VA WV WI" +).split() + +# States whose AGI as taxsim_state_agi reports it includes railroad benefits: +# the states_using_federal_agi list, which reports federal AGI, and Utah, +# which reports TC-40 line 6 total income (federal AGI plus additions) and +# subtracts the benefits on the way to taxable income. I4 compares their +# taxable income. +FEDERAL_AGI_STATES = {"CO", "MI", "MN", "MT", "NC", "ND", "NM", "SC", "UT"} + +# Provisions measured with federal AGI or federal income tax. Railroad +# benefits raise both, so these states' bases move by the state's own rule. +STATE_BASE_EXCEPTIONS = { + # or_agi subtracts the federal income tax subtraction (ORS 316.680(1)(b)). + "OR": "federal income tax subtraction inside Oregon AGI", + # nm_low_and_middle_income_exemption phases out with adjusted_gross_income + # (NMSA 7-2-5.8; 7-2-2 defines adjusted gross income as federal AGI). + "NM": "low- and middle-income exemption phases out on federal AGI", +} +I4_STATES = [s for s in INCOME_TAX_STATES if s not in STATE_BASE_EXCEPTIONS] + +ROLE_FLAGS = ("is_tax_unit_head", "is_tax_unit_spouse", "is_tax_unit_dependent") + + +def situation(households, year=YEAR): + """Build a situation from (state, members) pairs. + + Each member is a dict of person inputs plus an optional "role": the first + member defaults to head and the rest to dependent. The head and spouse + share a marital unit; each dependent has one of its own. + """ + people, units, households_out, marital_units = {}, {}, {}, {} + for i, (state, members) in enumerate(households): + names, couple = [], [] + for j, inputs in enumerate(members): + inputs = dict(inputs) + role = inputs.pop("role", "head" if j == 0 else "dependent") + name = f"h{i}p{j}" + names.append(name) + people[name] = {key: {year: value} for key, value in inputs.items()} + for flag in ROLE_FLAGS: + people[name][flag] = {year: flag == f"is_tax_unit_{role}"} + if role == "dependent": + marital_units[f"{name}m"] = {"members": [name]} + else: + couple.append(name) + marital_units[f"h{i}m"] = {"members": couple} + units[f"h{i}"] = names + households_out[f"h{i}"] = {"members": names, "state_name": {year: state}} + + def group(): + return {key: {"members": list(value)} for key, value in units.items()} + + return { + "people": people, + "tax_units": group(), + "spm_units": group(), + "families": group(), + "marital_units": marital_units, + "households": households_out, + } + + +def calc(simulation, variable, year=YEAR): + return np.asarray(simulation.calculate(variable, year), dtype=float) + + +def section_86_parts(simulation): + return calc(simulation, "tax_unit_taxable_social_security") + calc( + simulation, "tax_unit_taxable_railroad_social_security_equivalent_benefits" + ) + + +amounts = st.integers(min_value=0, max_value=60_000) +wages = st.integers(min_value=0, max_value=150_000) +shares = st.integers(min_value=0, max_value=100).map(lambda x: x / 100) + + +@settings(max_examples=25, deadline=None) +@given( + wages=wages, + head_benefits=amounts, + spouse_benefits=amounts, + head_share=shares, + spouse_share=shares, + joint=st.booleans(), + age=st.integers(min_value=62, max_value=85), +) +def test_i1_tier_1_benefits_are_taxed_as_title_ii_benefits( + wages, head_benefits, spouse_benefits, head_share, spouse_share, joint, age +): + def person(benefits, railroad_share, role): + return { + "role": role, + "age": age, + "social_security_retirement": benefits * (1 - railroad_share), + "railroad_social_security_equivalent_benefits": benefits * railroad_share, + } + + def household(head_share, spouse_share): + members = [person(head_benefits, head_share, "head")] + members[0]["employment_income"] = wages + if joint: + members.append(person(spouse_benefits, spouse_share, "spouse")) + return ("TX", members) + + simulation = Simulation( + situation=situation([household(0, 0), household(head_share, spouse_share)]) + ) + for variable in ( + "adjusted_gross_income", + "income_tax", + "tax_unit_taxable_section_86_benefits", + "medicaid_magi", + ): + title_ii, railroad = calc(simulation, variable)[:2] + assert abs(title_ii - railroad) < TOLERANCE, (variable, title_ii, railroad) + np.testing.assert_allclose( + section_86_parts(simulation), + calc(simulation, "tax_unit_taxable_section_86_benefits"), + atol=TOLERANCE, + ) + + +@settings(max_examples=25, deadline=None) +@given( + wages=wages, + pension=st.integers(min_value=0, max_value=80_000), + title_ii=amounts, + age=st.integers(min_value=60, max_value=85), +) +def test_i2_the_rest_of_the_annuity_is_taxed_as_a_pension( + wages, pension, title_ii, age +): + base = { + "age": age, + "employment_income": wages, + "social_security_retirement": title_ii, + } + simulation = Simulation( + situation=situation( + [ + ("TX", [base | {"taxable_pension_income": pension}]), + ("TX", [base | {"taxable_railroad_pension_benefits": pension}]), + ] + ) + ) + for variable in ("adjusted_gross_income", "income_tax"): + private, railroad = calc(simulation, variable) + assert abs(private - railroad) < TOLERANCE, (variable, private, railroad) + + +@settings(max_examples=8, deadline=None) +@given( + tier_1=st.integers(min_value=0, max_value=60_000), + tier_2=st.integers(min_value=1, max_value=80_000), + tax_free=st.integers(min_value=0, max_value=5_000), + age=st.integers(min_value=18, max_value=90), + joint=st.booleans(), +) +def test_i3_no_state_taxes_railroad_benefits(tier_1, tier_2, tax_free, age, joint): + head = { + "age": age, + "railroad_social_security_equivalent_benefits": tier_1, + "taxable_railroad_pension_benefits": tier_2, + "tax_exempt_railroad_pension_benefits": tax_free, + } + members = [head] + ([head | {"role": "spouse"}] if joint else []) + simulation = Simulation( + situation=situation([(state, members) for state in INCOME_TAX_STATES]) + ) + # Tier 2 benefits are always taxable federally, so the test is not vacuous. + assert calc(simulation, "adjusted_gross_income").min() >= tier_2 + tax = calc(simulation, "state_income_tax_before_refundable_credits") + idaho_taxable_income = calc(simulation, "id_taxable_income") + for i, state in enumerate(INCOME_TAX_STATES): + if state == "ID": + assert idaho_taxable_income[i] == 0, (state, idaho_taxable_income[i]) + else: + assert abs(tax[i]) < TOLERANCE, (state, tax[i]) + + +@settings(max_examples=8, deadline=None) +@given( + wages=st.integers(min_value=0, max_value=120_000), + tier_1=st.integers(min_value=0, max_value=30_000), + tier_2=st.integers(min_value=0, max_value=40_000), + age=st.integers(min_value=25, max_value=64), +) +def test_i4_railroad_benefits_leave_state_bases_unchanged(wages, tier_1, tier_2, age): + worker = {"age": age, "employment_income": wages} + retiree = worker | { + "railroad_social_security_equivalent_benefits": tier_1, + "taxable_railroad_pension_benefits": tier_2, + } + households = [(state, [worker]) for state in I4_STATES] + [ + (state, [retiree]) for state in I4_STATES + ] + simulation = Simulation(situation=situation(households)) + n = len(I4_STATES) + agi = calc(simulation, "taxsim_state_agi") + taxable_income = calc(simulation, "taxsim_state_taxable_income") + for i, state in enumerate(I4_STATES): + base = taxable_income if state in FEDERAL_AGI_STATES else agi + assert abs(base[i] - base[i + n]) < TOLERANCE, (state, base[i], base[i + n]) + + +@settings(max_examples=25, deadline=None) +@given( + tier_1=st.lists(amounts, min_size=3, max_size=3), + tier_2=st.lists(amounts, min_size=3, max_size=3), + tax_free=st.lists( + st.integers(min_value=0, max_value=5_000), min_size=3, max_size=3 + ), + title_ii=st.lists(amounts, min_size=3, max_size=3), + earnings=st.lists(wages, min_size=3, max_size=3), +) +def test_i5_railroad_totals_add_up(tier_1, tier_2, tax_free, title_ii, earnings): + roles, ages = ("head", "spouse", "dependent"), (70, 68, 17) + members = [ + { + "role": roles[i], + "age": ages[i], + "employment_income": earnings[i], + "social_security_retirement": title_ii[i], + "railroad_social_security_equivalent_benefits": tier_1[i], + "taxable_railroad_pension_benefits": tier_2[i], + "tax_exempt_railroad_pension_benefits": tax_free[i], + } + for i in range(3) + ] + simulation = Simulation(situation=situation([("TX", members)])) + np.testing.assert_allclose( + calc(simulation, "railroad_benefits"), + np.add(np.add(tier_1, tier_2), tax_free), + ) + np.testing.assert_allclose( + section_86_parts(simulation), + calc(simulation, "tax_unit_taxable_section_86_benefits"), + atol=TOLERANCE, + ) + taxable_tier_1 = calc( + simulation, "taxable_railroad_social_security_equivalent_benefits" + ) + # The head's and spouse's shares add up to the tax unit's tier 1 amount. + assert taxable_tier_1[2] == 0 + np.testing.assert_allclose( + taxable_tier_1.sum(), + calc( + simulation, "tax_unit_taxable_railroad_social_security_equivalent_benefits" + )[0], + atol=TOLERANCE, + ) + np.testing.assert_allclose( + calc(simulation, "taxable_railroad_benefits"), + taxable_tier_1 + np.array(tier_2, dtype=float) * [1, 1, 0], + atol=TOLERANCE, + ) + + +@settings(max_examples=25, deadline=None) +@given(wages=wages, title_ii=amounts, tier_1=amounts) +def test_d1_dependent_and_filer_section_86_agree(wages, title_ii, tier_1): + income = { + "employment_income": wages, + "social_security_survivors": title_ii, + "railroad_social_security_equivalent_benefits": tier_1, + } + simulation = Simulation( + situation=situation( + [ + ("TX", [{"age": 40} | income]), + ("TX", [{"age": 45}, {"age": 17} | income]), + ] + ) + ) + filer = calc(simulation, "tax_unit_taxable_section_86_benefits")[0] + dependent = ( + calc(simulation, "dependent_taxable_social_security") + + calc( + simulation, "dependent_taxable_railroad_social_security_equivalent_benefits" + ) + )[2] + assert abs(filer - dependent) < TOLERANCE, (filer, dependent) diff --git a/policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py b/policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py index 7f37ba286b1..dd4684c3913 100644 --- a/policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py +++ b/policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py @@ -11,6 +11,8 @@ class section_22_income(Variable): reference = ( "https://www.law.cornell.edu/uscode/text/26/22#c", "https://www.law.cornell.edu/uscode/text/26/22#e_1", + # 2024 Instructions for Schedule R, lines 13a and 13b. + "https://www.irs.gov/pub/irs-prior/i1040sr--2024.pdf#page=5", ) def formula(tax_unit, period, parameters): @@ -62,8 +64,29 @@ def formula(tax_unit, period, parameters): total_social_security = tax_unit("tax_unit_social_security", period) taxable_social_security = tax_unit("tax_unit_taxable_social_security", period) non_taxable_social_security = total_social_security - taxable_social_security + # Section 22(c)(3)(A)(i)(II) reduces the amount by what the head or + # spouse receives as a pension or annuity payable under the Railroad + # Retirement Act of 1974 and excluded from gross income, whatever + # provision excludes it: the tier 1 benefits section 86 leaves out, + # and the tax-free part of the rest of the annuity, which 72(r)(2) + # and 72(b) exclude as a recovery of the employee's tier 2 taxes. + # Schedule R's line 13b keeps returns of cost out; the model follows + # the statute's words. + non_taxable_tier_1 = tax_unit( + "tax_unit_railroad_social_security_equivalent_benefits_for_taxability", + period, + ) - tax_unit( + "tax_unit_taxable_railroad_social_security_equivalent_benefits", period + ) + non_taxable_tier_2 = tax_unit_non_dep_add( + tax_unit, period, ["tax_exempt_railroad_pension_benefits"] + ) + non_taxable_railroad = non_taxable_tier_1 + non_taxable_tier_2 capped_reduced_amount = ( - capped_amount - non_taxable_pensions - non_taxable_social_security + capped_amount + - non_taxable_pensions + - non_taxable_social_security + - non_taxable_railroad ) agi = tax_unit("adjusted_gross_income", period) diff --git a/policyengine_us/variables/gov/irs/income/dependent_gross_income.py b/policyengine_us/variables/gov/irs/income/dependent_gross_income.py index f9e7c0859be..78d87e0c04b 100644 --- a/policyengine_us/variables/gov/irs/income/dependent_gross_income.py +++ b/policyengine_us/variables/gov/irs/income/dependent_gross_income.py @@ -14,6 +14,10 @@ # computed in dependent_taxable_social_security, on the dependent's own # income, and modified AGI is figured without it (IRC 86(b)(2)(A)). "taxable_social_security": [], + # Tier 1 railroad retirement benefits are social security benefits under + # IRC 86(d)(1)(B), handled the same way in + # dependent_taxable_railroad_social_security_equivalent_benefits. + "taxable_railroad_social_security_equivalent_benefits": [], # Gross income includes gains but not losses (computed below). Modified # AGI nets them, subject to the IRC 1211(b) limit. "capital_gains": [], @@ -74,6 +78,12 @@ def formula(person, period, parameters): if "taxable_social_security" in sources: gross_income += person("dependent_taxable_social_security", period) + if "taxable_railroad_social_security_equivalent_benefits" in sources: + gross_income += person( + "dependent_taxable_railroad_social_security_equivalent_benefits", + period, + ) + # Only dependents' own income is tested. is_dependent = ~person("is_tax_unit_head_or_spouse", period) return is_dependent * gross_income diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/capped_qualified_tuition_expenses_ald.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/capped_qualified_tuition_expenses_ald.py index ac0389d9366..88fc240475e 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/capped_qualified_tuition_expenses_ald.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/capped_qualified_tuition_expenses_ald.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.tax_unit_taxable_section_86_benefits import ( + SECTION_86_GROSS_INCOME_SOURCES, +) class capped_qualified_tuition_expenses_ald(Variable): @@ -49,7 +52,7 @@ def formula(tax_unit, period, parameters): else src ) for src in gross_income_sources - if src != "taxable_social_security" + if src not in SECTION_86_GROSS_INCOME_SOURCES ] magi = 0 for source in safe_sources: diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/dependent_traditional_ira_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/dependent_traditional_ira_deduction.py index 8b896ddd0e6..e97df76a897 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/dependent_traditional_ira_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/dependent_traditional_ira_deduction.py @@ -3,7 +3,7 @@ dependent_income_and_deductions, ) from policyengine_us.variables.gov.irs.income.taxable_income.adjusted_gross_income.irs_gross_income.social_security.dependent_taxable_social_security import ( - dependent_taxable_social_security_amount, + dependent_taxable_section_86_amount, ) from policyengine_us.variables.household.expense.retirement.ira_compensation import ( dependent_ira_compensation, @@ -63,7 +63,7 @@ def formula(person, period, parameters): d for d in deductions if d != "dependent_traditional_ira_deduction" ] agi_before_ira = income - add(person, period, other_deductions) - taxable_social_security = dependent_taxable_social_security_amount( + taxable_social_security = dependent_taxable_section_86_amount( person, period, parameters, diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/ira_219g_magi.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/ira_219g_magi.py index 6aa08f7f45e..b34fcb269c6 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/ira_219g_magi.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/ira_219g_magi.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.tax_unit_taxable_section_86_benefits import ( + SECTION_86_GROSS_INCOME_SOURCES, +) class ira_219g_magi(Variable): @@ -17,7 +20,8 @@ def formula(tax_unit, period, parameters): sources = [ s for s in irs.gross_income.sources - if s not in ["taxable_social_security", "taxable_unemployment_compensation"] + if s not in SECTION_86_GROSS_INCOME_SOURCES + and s != "taxable_unemployment_compensation" ] if "taxable_unemployment_compensation" in irs.gross_income.sources: # 219(g)(3)(A)(ii): without regard to 85(c). diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/ira_219g_taxable_social_security.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/ira_219g_taxable_social_security.py index 9bf574bb41c..2f1da03d315 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/ira_219g_taxable_social_security.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/ira/ira_219g_taxable_social_security.py @@ -1,12 +1,16 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.income.taxable_income.adjusted_gross_income.irs_gross_income.social_security.tax_unit_taxable_section_86_benefits import ( + SECTION_86_GROSS_INCOME_SOURCES, +) class ira_219g_taxable_social_security(Variable): """Section 86 taxable benefits figured without the IRA deduction. Pub. 590-A Appendix B, Worksheet 1 lines 1-17. Same inputs and thresholds as - the engine's taxable_ss_magi / tax_unit_taxable_social_security, except that - the IRA deduction is not subtracted (it is what is being computed). + the engine's taxable_ss_magi / tax_unit_taxable_section_86_benefits, except + that the IRA deduction is not subtracted (it is what is being computed). + Like section 86, it covers title II and tier 1 railroad benefits together. """ value_type = float @@ -26,7 +30,8 @@ def formula(tax_unit, period, parameters): sources = [ s for s in irs.gross_income.sources - if s not in ["taxable_social_security", "taxable_unemployment_compensation"] + if s not in SECTION_86_GROSS_INCOME_SOURCES + and s != "taxable_unemployment_compensation" ] if "taxable_unemployment_compensation" in irs.gross_income.sources: sources.append("total_unemployment_compensation") @@ -54,7 +59,7 @@ def formula(tax_unit, period, parameters): ) ss_magi = max_(0, gross - total_deductions) # tax_unit_taxable_social_security, with that MAGI. - gross_ss = tax_unit("tax_unit_social_security_for_taxability", period) + gross_ss = tax_unit("tax_unit_section_86_benefits", period) combined = ss_magi + p.combined_income_ss_fraction * gross_ss filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_railroad_social_security_equivalent_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_railroad_social_security_equivalent_benefits.py new file mode 100644 index 00000000000..cced20dfe17 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_railroad_social_security_equivalent_benefits.py @@ -0,0 +1,29 @@ +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_social_security import ( + dependent_taxable_section_86_share, +) + + +class dependent_taxable_railroad_social_security_equivalent_benefits(Variable): + value_type = float + entity = Person + label = ( + "Dependent's taxable Railroad Retirement social security equivalent benefits" + ) + unit = USD + documentation = ( + "The tier 1 railroad retirement share of the social security benefits " + "a dependent includes in gross income under IRC 86, figured on the " + "dependent's own return as in dependent_taxable_social_security." + ) + definition_period = YEAR + defined_for = "is_tax_unit_dependent" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/86#d_1_B", + "https://www.law.cornell.edu/uscode/text/26/152#b_2", + ) + + def formula(person, period, parameters): + return dependent_taxable_section_86_share( + person, period, parameters, title_ii=False + ) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_social_security.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_social_security.py index 0301ca340a3..4998f5678c8 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_social_security.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_social_security.py @@ -1,11 +1,21 @@ from policyengine_us.model_api import * -def dependent_taxable_social_security_amount(person, period, parameters, modified_agi): - """A dependent's taxable Social Security under IRC 86 for a given modified - AGI, on the dependent's own marital circumstances.""" +def dependent_section_86_benefits(person, period): + """A dependent's social security benefits as IRC 86(d)(1) defines them: + title II benefits and tier 1 railroad retirement benefits.""" + title_ii = max_(0, person("social_security", period)) + tier_1 = max_(0, person("railroad_social_security_equivalent_benefits", period)) + return title_ii, tier_1 + + +def dependent_taxable_section_86_amount(person, period, parameters, modified_agi): + """A dependent's taxable social security benefits under IRC 86 for a given + modified AGI, on the dependent's own marital circumstances. Section 86 + figures one amount on title II and tier 1 railroad benefits together.""" p = parameters(period).gov.irs.social_security.taxability - gross_ss = max_(0, person("social_security", period)) + title_ii, tier_1 = dependent_section_86_benefits(person, period) + gross_ss = title_ii + tier_1 # IRC 86(b)(1): modified AGI plus one-half of the benefits. combined_income = modified_agi + p.combined_income_ss_fraction * gross_ss lives_with_spouse = person("dependent_lives_with_spouse", period) @@ -61,7 +71,11 @@ class dependent_taxable_social_security(Variable): dependent does not file a joint return (IRC 152(b)(2)), so the joint amounts never apply. A dependent who lives with their spouse has zero base and adjusted base amounts under IRC 86(c)(1)(C) and (c)(2)(C), and - every other dependent has the unmarried amounts. + every other dependent has the unmarried amounts. Section 86 figures one + amount on title II and tier 1 railroad retirement benefits together; this + is the title II share, and + dependent_taxable_railroad_social_security_equivalent_benefits is the + railroad share. """ definition_period = YEAR defined_for = "is_tax_unit_dependent" @@ -76,6 +90,21 @@ class dependent_taxable_social_security(Variable): ) def formula(person, period, parameters): - return dependent_taxable_social_security_amount( - person, period, parameters, person("dependent_taxable_ss_magi", period) + return dependent_taxable_section_86_share( + person, period, parameters, title_ii=True ) + + +def dependent_taxable_section_86_share(person, period, parameters, title_ii): + """The title II or tier 1 railroad share of a dependent's taxable section + 86 benefits, in proportion to the two kinds of benefits.""" + taxable = dependent_taxable_section_86_amount( + person, period, parameters, person("dependent_taxable_ss_magi", period) + ) + title_ii_benefits, tier_1_benefits = dependent_section_86_benefits(person, period) + benefits = title_ii_benefits if title_ii else tier_1_benefits + total = title_ii_benefits + tier_1_benefits + share = np.zeros_like(total) + mask = total > 0 + share[mask] = benefits[mask] / total[mask] + return taxable * share diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_exempt_railroad_social_security_equivalent_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_exempt_railroad_social_security_equivalent_benefits.py new file mode 100644 index 00000000000..a4120a82590 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_exempt_railroad_social_security_equivalent_benefits.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class tax_exempt_railroad_social_security_equivalent_benefits(Variable): + value_type = float + entity = TaxUnit + label = "Tax-exempt Railroad Retirement social security equivalent benefits" + unit = USD + documentation = ( + "Tier 1 railroad retirement benefits that section 86 leaves out of " + "AGI, the counterpart of tax_exempt_social_security." + ) + definition_period = YEAR + reference = "https://www.law.cornell.edu/uscode/text/26/86#d_1_B" + + adds = ["tax_unit_railroad_social_security_equivalent_benefits"] + subtracts = ["tax_unit_taxable_railroad_social_security_equivalent_benefits"] diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_combined_income_for_social_security_taxability.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_combined_income_for_social_security_taxability.py index c2e13d72751..181a96af356 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_combined_income_for_social_security_taxability.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_combined_income_for_social_security_taxability.py @@ -14,7 +14,9 @@ class tax_unit_combined_income_for_social_security_taxability(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.irs.social_security.taxability - gross_ss = tax_unit("tax_unit_social_security_for_taxability", period) + # IRC 86(b)(1)(A)(ii) and (d)(1): one-half of the social security + # benefits, title II and tier 1 railroad benefits alike. + gross_ss = tax_unit("tax_unit_section_86_benefits", period) # The legislation directs the usage of an income definition that is # a particularly modified AGI, plus half of gross Social Security diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits.py new file mode 100644 index 00000000000..32828df521f --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits.py @@ -0,0 +1,12 @@ +from policyengine_us.model_api import * + + +class tax_unit_railroad_social_security_equivalent_benefits(Variable): + value_type = float + entity = TaxUnit + label = "Tax unit Railroad Retirement social security equivalent benefits" + unit = USD + definition_period = YEAR + reference = "https://www.law.cornell.edu/uscode/text/26/86#d_1_B" + + adds = ["railroad_social_security_equivalent_benefits"] diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits_for_taxability.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits_for_taxability.py new file mode 100644 index 00000000000..77b1a6182d9 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits_for_taxability.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class tax_unit_railroad_social_security_equivalent_benefits_for_taxability(Variable): + value_type = float + entity = TaxUnit + label = ( + "Tax unit Railroad Retirement social security equivalent benefits " + "for taxability" + ) + unit = USD + documentation = ( + "Social security equivalent benefits of the head and spouse. As in " + "tax_unit_social_security_for_taxability, a tax unit dependent's " + "benefits are the dependent's own income." + ) + definition_period = YEAR + reference = "https://www.law.cornell.edu/uscode/text/26/86#d_1_B" + + def formula(tax_unit, period, parameters): + person = tax_unit.members + total = tax_unit( + "tax_unit_railroad_social_security_equivalent_benefits", period + ) + is_dependent = person("is_tax_unit_dependent", period) + benefits = person("railroad_social_security_equivalent_benefits", period) + dependent_benefits = tax_unit.sum(is_dependent * benefits) + return max_(0, total - dependent_benefits) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_section_86_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_section_86_benefits.py new file mode 100644 index 00000000000..03ab927a72e --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_section_86_benefits.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class tax_unit_section_86_benefits(Variable): + value_type = float + entity = TaxUnit + label = "Tax unit social security benefits under section 86" + unit = USD + documentation = ( + "The head's and spouse's social security benefits as IRC 86(d)(1) " + "defines them: title II benefits and tier 1 railroad retirement " + "benefits (the social security equivalent benefit). Section 86 figures " + "one taxable amount on their total." + ) + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/86#d_1", + # IRS Pub. 915 (2024), Worksheet A line A: box 5 of all Forms + # SSA-1099 and RRB-1099. + "https://www.irs.gov/pub/irs-prior/p915--2024.pdf#page=4", + ) + + adds = [ + "tax_unit_social_security_for_taxability", + "tax_unit_railroad_social_security_equivalent_benefits_for_taxability", + ] diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_railroad_social_security_equivalent_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_railroad_social_security_equivalent_benefits.py new file mode 100644 index 00000000000..113d0c8b302 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_railroad_social_security_equivalent_benefits.py @@ -0,0 +1,32 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.income.taxable_income.adjusted_gross_income.irs_gross_income.social_security.tax_unit_taxable_social_security import ( + section_86_share, +) + + +class tax_unit_taxable_railroad_social_security_equivalent_benefits(Variable): + value_type = float + entity = TaxUnit + definition_period = YEAR + label = "Taxable Railroad Retirement social security equivalent benefits" + documentation = ( + "Tier 1 railroad retirement benefits included in AGI: the share of " + "tax_unit_taxable_section_86_benefits that the head's and spouse's " + "social security equivalent benefits make up. It is figured from the " + "benefits, not as the total less taxable title II benefits, so a " + "change to tax_unit_taxable_social_security leaves it alone." + ) + unit = USD + reference = ( + "https://www.law.cornell.edu/uscode/text/26/86#d_1_B", + "https://www.law.cornell.edu/uscode/text/26/86#d_4", + ) + + def formula(tax_unit, period, parameters): + taxable = tax_unit("tax_unit_taxable_section_86_benefits", period) + share = section_86_share( + tax_unit, + period, + "tax_unit_railroad_social_security_equivalent_benefits_for_taxability", + ) + return taxable * share diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_section_86_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_section_86_benefits.py new file mode 100644 index 00000000000..de266f3a07b --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_section_86_benefits.py @@ -0,0 +1,104 @@ +from policyengine_us.model_api import * + +# Gross income sources that hold the benefits section 86 includes. Income +# measures that section 86 itself depends on (its modified AGI, and others +# built the same way) leave all of them out. +SECTION_86_GROSS_INCOME_SOURCES = ( + "taxable_social_security", + "taxable_railroad_social_security_equivalent_benefits", +) + + +class tax_unit_taxable_section_86_benefits(Variable): + value_type = float + entity = TaxUnit + definition_period = YEAR + label = "Taxable social security benefits under section 86" + documentation = ( + "The head's and spouse's social security benefits that IRC 86 includes " + "in gross income: title II benefits and tier 1 railroad retirement " + "benefits together, as on Form 1040 line 6b. " + "tax_unit_taxable_social_security and " + "tax_unit_taxable_railroad_social_security_equivalent_benefits split " + "it between the two in proportion to the benefits." + ) + unit = USD + reference = ( + "https://www.law.cornell.edu/uscode/text/26/86", + # IRS Pub. 915 (2024), Worksheet 1. + "https://www.irs.gov/pub/irs-prior/p915--2024.pdf#page=16", + ) + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.irs.social_security.taxability + gross_ss = tax_unit("tax_unit_section_86_benefits", period) + + # The legislation directs the usage of an income definition that is + # a particularly modified AGI, plus half of gross Social Security + # payments. Per IRC Section 86(b)(1), this fraction is always 0.5 + # and is handled by the combined_income_ss_fraction parameter. + + combined_income = tax_unit( + "tax_unit_combined_income_for_social_security_taxability", period + ) + filing_status = tax_unit("filing_status", period) + status = filing_status.possible_values + separate = filing_status == status.SEPARATE + cohabitating = tax_unit("cohabitating_spouses", period) + # Cohabitating married couples filing separately receive a base + # and adjusted base amount of 0 + base_amount = where( + separate & cohabitating, + p.threshold.base.separate_cohabitating, + p.threshold.base.main[filing_status], + ) + adjusted_base_amount = where( + separate & cohabitating, + p.threshold.adjusted_base.separate_cohabitating, + p.threshold.adjusted_base.main[filing_status], + ) + + under_first_threshold = combined_income < base_amount + under_second_threshold = combined_income < adjusted_base_amount + + combined_income_excess = tax_unit("tax_unit_ss_combined_income_excess", period) + excess_over_adjusted_base = max_(0, combined_income - adjusted_base_amount) + + # Tier 1: Between base and adjusted base thresholds + # Per IRC §86(a)(1), "the amount determined under paragraph (1)" + # Taxable amount is lesser of: + # - base.benefit_cap * SS benefits [§86(a)(1)(A)] + # - base.excess * excess over base [§86(a)(1)(B)] + amount_under_paragraph_1 = min_( + p.rate.base.benefit_cap * gross_ss, + p.rate.base.excess * combined_income_excess, + ) + + # Tier 2: Above adjusted base threshold + # Per IRC §86(a)(2)(A)(ii), the lesser of: + # - "the amount determined under paragraph (1)" (calculated above) + # - "one-half of the difference between the adjusted base amount and the base amount" + bracket_amount = min_( + amount_under_paragraph_1, + p.rate.additional.bracket * (adjusted_base_amount - base_amount), + ) + + # Per IRC §86(a)(2), the lesser of: + # (A) 85% of excess over adjusted base + bracket amount, or + # (B) 85% of social security benefits + amount_if_over_second_threshold = min_( + p.rate.additional.excess * excess_over_adjusted_base + bracket_amount, + p.rate.additional.benefit_cap * gross_ss, + ) + + return select( + [ + under_first_threshold, + under_second_threshold, + ], + [ + 0, + amount_under_paragraph_1, + ], + default=amount_if_over_second_threshold, + ) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_social_security.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_social_security.py index f948e3cab9d..b59d66b426d 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_social_security.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_taxable_social_security.py @@ -1,85 +1,38 @@ from policyengine_us.model_api import * +def section_86_share(tax_unit, period, benefits_variable): + """Share of the head's and spouse's section 86 benefits that one kind of + benefit makes up. Section 86 figures a single taxable amount on title II + and tier 1 railroad benefits together; each kind carries its share.""" + benefits = tax_unit(benefits_variable, period) + total = tax_unit("tax_unit_section_86_benefits", period) + share = np.zeros_like(total) + mask = total > 0 + share[mask] = benefits[mask] / total[mask] + return share + + class tax_unit_taxable_social_security(Variable): value_type = float entity = TaxUnit definition_period = YEAR label = "Taxable Social Security benefits" - documentation = "Social security (OASDI) benefits included in AGI, including tier 1 railroad retirement benefits." + documentation = ( + "Title II Social Security (OASDI) benefits included in AGI: the title " + "II share of tax_unit_taxable_section_86_benefits. Tier 1 railroad " + "retirement benefits carry the rest, in " + "tax_unit_taxable_railroad_social_security_equivalent_benefits." + ) unit = USD - reference = "https://www.law.cornell.edu/uscode/text/26/86" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/86", + "https://www.law.cornell.edu/uscode/text/26/86#d_1", + ) def formula(tax_unit, period, parameters): - p = parameters(period).gov.irs.social_security.taxability - gross_ss = tax_unit("tax_unit_social_security_for_taxability", period) - - # The legislation directs the usage of an income definition that is - # a particularly modified AGI, plus half of gross Social Security - # payments. Per IRC Section 86(b)(1), this fraction is always 0.5 - # and is handled by the combined_income_ss_fraction parameter. - - combined_income = tax_unit( - "tax_unit_combined_income_for_social_security_taxability", period - ) - filing_status = tax_unit("filing_status", period) - status = filing_status.possible_values - separate = filing_status == status.SEPARATE - cohabitating = tax_unit("cohabitating_spouses", period) - # Cohabitating married couples filing separately receive a base - # and adjusted base amount of 0 - base_amount = where( - separate & cohabitating, - p.threshold.base.separate_cohabitating, - p.threshold.base.main[filing_status], - ) - adjusted_base_amount = where( - separate & cohabitating, - p.threshold.adjusted_base.separate_cohabitating, - p.threshold.adjusted_base.main[filing_status], - ) - - under_first_threshold = combined_income < base_amount - under_second_threshold = combined_income < adjusted_base_amount - - combined_income_excess = tax_unit("tax_unit_ss_combined_income_excess", period) - excess_over_adjusted_base = max_(0, combined_income - adjusted_base_amount) - - # Tier 1: Between base and adjusted base thresholds - # Per IRC §86(a)(1), "the amount determined under paragraph (1)" - # Taxable amount is lesser of: - # - base.benefit_cap * SS benefits [§86(a)(1)(A)] - # - base.excess * excess over base [§86(a)(1)(B)] - amount_under_paragraph_1 = min_( - p.rate.base.benefit_cap * gross_ss, - p.rate.base.excess * combined_income_excess, - ) - - # Tier 2: Above adjusted base threshold - # Per IRC §86(a)(2)(A)(ii), the lesser of: - # - "the amount determined under paragraph (1)" (calculated above) - # - "one-half of the difference between the adjusted base amount and the base amount" - bracket_amount = min_( - amount_under_paragraph_1, - p.rate.additional.bracket * (adjusted_base_amount - base_amount), - ) - - # Per IRC §86(a)(2), the lesser of: - # (A) 85% of excess over adjusted base + bracket amount, or - # (B) 85% of social security benefits - amount_if_over_second_threshold = min_( - p.rate.additional.excess * excess_over_adjusted_base + bracket_amount, - p.rate.additional.benefit_cap * gross_ss, - ) - - return select( - [ - under_first_threshold, - under_second_threshold, - ], - [ - 0, - amount_under_paragraph_1, - ], - default=amount_if_over_second_threshold, + taxable = tax_unit("tax_unit_taxable_section_86_benefits", period) + share = section_86_share( + tax_unit, period, "tax_unit_social_security_for_taxability" ) + return taxable * share diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_railroad_social_security_equivalent_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_railroad_social_security_equivalent_benefits.py new file mode 100644 index 00000000000..e07e96d845f --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_railroad_social_security_equivalent_benefits.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.income.taxable_income.adjusted_gross_income.irs_gross_income.social_security.taxable_social_security import ( + allocate_to_head_and_spouse, +) + + +class taxable_railroad_social_security_equivalent_benefits(Variable): + value_type = float + entity = Person + label = "Taxable Railroad Retirement social security equivalent benefits" + unit = USD + documentation = ( + "The head's or spouse's tier 1 railroad retirement benefits included " + "in gross income under IRC 86, allocated in proportion to their social " + "security equivalent benefits as taxable_social_security allocates " + "title II benefits. 45 U.S.C. 231m(a) bars states from taxing them." + ) + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/86#d_1_B", + "https://www.law.cornell.edu/uscode/text/45/231m#a", + ) + + def formula(person, period, parameters): + unit_taxable = person.tax_unit( + "tax_unit_taxable_railroad_social_security_equivalent_benefits", period + ) + benefits = person("railroad_social_security_equivalent_benefits", period) + unit_benefits = person.tax_unit( + "tax_unit_railroad_social_security_equivalent_benefits_for_taxability", + period, + ) + return allocate_to_head_and_spouse( + person, period, unit_taxable, benefits, unit_benefits + ) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_social_security.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_social_security.py index 79d4718a1aa..b8d59e09351 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_social_security.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_social_security.py @@ -1,6 +1,25 @@ from policyengine_us.model_api import * +def allocate_to_head_and_spouse(person, period, unit_amount, benefits, unit_benefits): + """Split a tax unit amount between the head and spouse in proportion to + their benefits. The spouse takes their share and the head the rest, so + the two parts always sum to the tax unit amount.""" + # Use mask instead of where to avoid divide-by-zero warnings. Default to zero. + person_frac = np.zeros_like(unit_benefits) + mask = unit_benefits > 0 + person_frac[mask] = benefits[mask] / unit_benefits[mask] + is_spouse = person("is_tax_unit_spouse", period) + spouse_frac = min_(1, where(is_spouse, person_frac, 0)) + unit_spouse_frac = person.tax_unit.sum(spouse_frac) + is_head = person("is_tax_unit_head", period) + head_frac = where(is_head, 1 - unit_spouse_frac, 0) + unit_head_frac = person.tax_unit.sum(head_frac) + return unit_amount * select( + [is_head, is_spouse], [unit_head_frac, unit_spouse_frac], default=0 + ) + + class taxable_social_security(Variable): value_type = float entity = Person @@ -13,17 +32,6 @@ def formula(person, period, parameters): # allocate unit_tss to head and spouse in proportion to social_security ind_socsec = person("social_security", period) unit_socsec = person.tax_unit("tax_unit_social_security_for_taxability", period) - # Compute each person's share of tax unit's Social Security benefits. - # Use mask instead of where to avoid divide-by-zero warnings. Default to zero. - person_frac = np.zeros_like(unit_socsec) - mask = unit_socsec > 0 - person_frac[mask] = ind_socsec[mask] / unit_socsec[mask] - is_spouse = person("is_tax_unit_spouse", period) - spouse_frac = min_(1, where(is_spouse, person_frac, 0)) - unit_spouse_frac = person.tax_unit.sum(spouse_frac) - is_head = person("is_tax_unit_head", period) - head_frac = where(is_head, 1 - unit_spouse_frac, 0) - unit_head_frac = person.tax_unit.sum(head_frac) - return unit_tss * select( - [is_head, is_spouse], [unit_head_frac, unit_spouse_frac], default=0 + return allocate_to_head_and_spouse( + person, period, unit_tss, ind_socsec, unit_socsec ) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_social_security_tier_1.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_social_security_tier_1.py index 763a428aee4..8d303b7a7cc 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_social_security_tier_1.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_social_security_tier_1.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.tax_unit_taxable_social_security import ( + section_86_share, +) class taxable_social_security_tier_1(Variable): @@ -9,14 +12,17 @@ class taxable_social_security_tier_1(Variable): documentation = ( "First-tier component of current-law taxable Social Security. " "Above the adjusted base amount this is the bracket amount in section " - "86(a)(2)(A)(ii), rather than the pre-1993 inclusion used for OASDI revenue." + "86(a)(2)(A)(ii), rather than the pre-1993 inclusion used for OASDI revenue. " + "Section 86 figures it on title II and tier 1 railroad benefits " + "together; this is the title II share, as in " + "tax_unit_taxable_social_security." ) unit = USD reference = "https://www.law.cornell.edu/uscode/text/26/86#a_1" def formula(tax_unit, period, parameters): p = parameters(period).gov.irs.social_security.taxability - gross_ss = tax_unit("tax_unit_social_security_for_taxability", period) + gross_ss = tax_unit("tax_unit_section_86_benefits", period) combined_income = tax_unit( "tax_unit_combined_income_for_social_security_taxability", period ) @@ -53,8 +59,12 @@ def formula(tax_unit, period, parameters): p.rate.additional.bracket * (adjusted_base_amount - base_amount), ) - return select( + tier_1 = select( [under_first_threshold, under_second_threshold], [0, amount_under_paragraph_1], default=bracket_amount, ) + share = section_86_share( + tax_unit, period, "tax_unit_social_security_for_taxability" + ) + return tier_1 * share diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_ss_magi.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_ss_magi.py index 4f31eff45c2..011873dbe62 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_ss_magi.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/taxable_ss_magi.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.tax_unit_taxable_section_86_benefits import ( + SECTION_86_GROSS_INCOME_SOURCES, +) class taxable_ss_magi(Variable): @@ -23,7 +26,8 @@ def formula(tax_unit, period, parameters): else income_source ) for income_source in gross_income_sources - if income_source != "taxable_social_security" + # IRC 86(b)(2)(A): determined without regard to section 86. + if income_source not in SECTION_86_GROSS_INCOME_SOURCES ] income_sources_without_ss.append("tax_exempt_interest_income") gross_income = 0 diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_railroad_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_railroad_benefits.py new file mode 100644 index 00000000000..4756692bb43 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/taxable_railroad_benefits.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + +RAILROAD_GROSS_INCOME_SOURCES = ( + "taxable_railroad_social_security_equivalent_benefits", + "taxable_railroad_pension_benefits", +) + + +class taxable_railroad_benefits(Variable): + value_type = float + entity = Person + label = "Railroad Retirement benefits in federal gross income" + unit = USD + documentation = ( + "The Railroad Retirement Act benefits that federal gross income " + "includes for this person: the taxable tier 1 benefits treated as " + "social security and the taxable rest of the annuity, counted as " + "irs_gross_income counts them. 45 U.S.C. 231m(a) bars states from " + "taxing these annuities, so states that start from federal adjusted " + "gross income or taxable income subtract this amount." + ) + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/45/231m#a", + "https://www.law.cornell.edu/uscode/text/26/86#d_1_B", + "https://www.law.cornell.edu/uscode/text/26/72#r", + ) + + def formula(person, period, parameters): + sources = parameters(period).gov.irs.gross_income.sources + not_dependent = ~person("is_tax_unit_dependent", period) + total = 0 + for source in RAILROAD_GROSS_INCOME_SOURCES: + if source in sources: + total += max_(0, person(source, period)) + return not_dependent * total diff --git a/policyengine_us/variables/gov/ssa/revenue/tax_unit_taxable_social_security_pre_1993.py b/policyengine_us/variables/gov/ssa/revenue/tax_unit_taxable_social_security_pre_1993.py index 292c4c69f4f..ef1153e1023 100644 --- a/policyengine_us/variables/gov/ssa/revenue/tax_unit_taxable_social_security_pre_1993.py +++ b/policyengine_us/variables/gov/ssa/revenue/tax_unit_taxable_social_security_pre_1993.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.tax_unit_taxable_social_security import ( + section_86_share, +) class tax_unit_taxable_social_security_pre_1993(Variable): @@ -10,7 +13,10 @@ class tax_unit_taxable_social_security_pre_1993(Variable): "Counterfactual Social Security inclusion under section 86 before the " "1993 amendments, used to allocate taxation-of-benefits revenue to OASDI. " "The original law limits inclusion to half of benefits and half of " - "provisional income above the unindexed base amount." + "provisional income above the unindexed base amount. Section 86 has " + "covered tier 1 railroad retirement benefits since 1984, so the " + "inclusion is figured on both and this is the title II share: the " + "OASDI trust funds receive the tax on title II benefits." ) unit = USD reference = ( @@ -20,7 +26,7 @@ class tax_unit_taxable_social_security_pre_1993(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.ssa.revenue.pre_1993 - benefits = tax_unit("tax_unit_social_security_for_taxability", period) + benefits = tax_unit("tax_unit_section_86_benefits", period) # Section 86(b) also used one-half of benefits in provisional income. # Keep the historical counterfactual independent of current-law rates. combined_income = tax_unit("taxable_ss_magi", period) + p.rate * benefits @@ -33,4 +39,7 @@ def formula(tax_unit, period, parameters): p.base_amount.main[filing_status], ) excess = max_(combined_income - base_amount, 0) - return p.rate * min_(benefits, excess) + share = section_86_share( + tax_unit, period, "tax_unit_social_security_for_taxability" + ) + return p.rate * min_(benefits, excess) * share diff --git a/policyengine_us/variables/gov/states/ks/tax/income/ks_agi_subtractions.py b/policyengine_us/variables/gov/states/ks/tax/income/ks_agi_subtractions.py index 773a9b6b8d3..67527c42859 100644 --- a/policyengine_us/variables/gov/states/ks/tax/income/ks_agi_subtractions.py +++ b/policyengine_us/variables/gov/states/ks/tax/income/ks_agi_subtractions.py @@ -10,6 +10,7 @@ class ks_agi_subtractions(Variable): reference = ( "https://ksrevisor.gov/statutes/chapters/ch79/079_032_0117.html", "https://www.ksrevenue.gov/pdf/ip25.pdf#page=12", + "https://www.law.cornell.edu/uscode/text/45/231m#a", ) defined_for = StateCode.KS @@ -39,11 +40,16 @@ def formula(tax_unit, period, parameters): "military_retirement_income", ], ) + # 45 U.S.C. 231m(a) bars state tax on Railroad Retirement Act + # annuities, so the part federal AGI includes comes back out, + # whatever the federal AGI. + railroad = add(tax_unit, period, ["taxable_railroad_benefits"]) return ( oasdi_subtraction + us_govt_interest + plan_529 + salt_refund_income + public_and_military_pension + + railroad + tax_unit("ks_other_subtractions", period) ) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_gross_income.py b/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_gross_income.py index 67f1f1b4b20..3eec2e26cd3 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_gross_income.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_gross_income.py @@ -12,6 +12,7 @@ class ma_gross_income(Variable): # c.62C s.6(a): each individual with Massachusetts gross income over # $8,000 files a return, so a dependent reports their own income. "https://malegislature.gov/Laws/GeneralLaws/PartI/TitleIX/Chapter62C/Section6", + "https://www.law.cornell.edu/uscode/text/45/231m#a", ) defined_for = StateCode.MA @@ -52,7 +53,15 @@ def formula(tax_unit, period, parameters): "military_retirement_income", ], ) - deductions = social_security_in_agi + salt_refund_income + public_pension + # 45 U.S.C. 231m(a) bars state tax on Railroad Retirement Act + # annuities, so the part federal gross income includes comes out. + railroad_in_agi = add(tax_unit, period, ["taxable_railroad_benefits"]) + deductions = ( + social_security_in_agi + + railroad_in_agi + + salt_refund_income + + public_pension + ) return max_( 0, federal_gross_income diff --git a/policyengine_us/variables/gov/states/me/tax/income/adjusted_gross_income/me_agi_subtractions.py b/policyengine_us/variables/gov/states/me/tax/income/adjusted_gross_income/me_agi_subtractions.py index 8c1f18180ca..7c3a6a71241 100644 --- a/policyengine_us/variables/gov/states/me/tax/income/adjusted_gross_income/me_agi_subtractions.py +++ b/policyengine_us/variables/gov/states/me/tax/income/adjusted_gross_income/me_agi_subtractions.py @@ -9,10 +9,15 @@ class me_agi_subtractions(Variable): documentation = "Subtractions from ME AGI over federal AGI." definition_period = YEAR defined_for = StateCode.ME - reference = "https://www.maine.gov/revenue/sites/maine.gov.revenue/files/inline-files/22_1040me_sched_1s_ff.pdf" + reference = ( + "https://www.maine.gov/revenue/sites/maine.gov.revenue/files/inline-files/22_1040me_sched_1s_ff.pdf", + "https://www.law.cornell.edu/uscode/text/45/231m#a", + ) adds = [ "tax_unit_taxable_social_security", + # 45 U.S.C. 231m(a): railroad retirement benefits in federal AGI. + "taxable_railroad_benefits", "us_govt_interest", "me_pension_income_deduction", "me_529_plan_subtraction", diff --git a/policyengine_us/variables/gov/states/ut/tax/income/taxable_income/ut_subtractions.py b/policyengine_us/variables/gov/states/ut/tax/income/taxable_income/ut_subtractions.py index de72d5683fa..450a9c6c27e 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/taxable_income/ut_subtractions.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/taxable_income/ut_subtractions.py @@ -9,6 +9,14 @@ class ut_subtractions(Variable): documentation = "Form TC-40, line 8" definition_period = YEAR defined_for = StateCode.UT - reference = "https://le.utah.gov/xcode/Title59/Chapter10/59-10-S114.html?v=C59-10-S114_2022032320220323" + reference = ( + "https://le.utah.gov/xcode/Title59/Chapter10/59-10-S114.html?v=C59-10-S114_2022032320220323", + "https://www.law.cornell.edu/uscode/text/45/231m#a", + ) - adds = ["us_govt_interest", "ut_other_subtractions"] + adds = [ + "us_govt_interest", + # 45 U.S.C. 231m(a): railroad retirement benefits in federal AGI. + "taxable_railroad_benefits", + "ut_other_subtractions", + ] diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/age_deduction/va_age_deduction_agi.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/age_deduction/va_age_deduction_agi.py index f3047afbccb..ccae9317068 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/subtractions/age_deduction/va_age_deduction_agi.py +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/age_deduction/va_age_deduction_agi.py @@ -10,8 +10,14 @@ class va_age_deduction_agi(Variable): reference = [ "https://law.lis.virginia.gov/vacodefull/title58.1/chapter3/article2/", # § 58.1-322.03.(5)(b) "https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2022-760-instructions.pdf#page=17", + "https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2024-760-instructions.pdf#page=17", ] defined_for = StateCode.VA adds = ["adjusted_gross_income"] - subtracts = ["tax_unit_taxable_social_security"] + # Adjusted federal AGI is reduced by taxable Social Security and Tier 1 + # Railroad Retirement benefits (2024 Form 760 instructions, Line 4). + subtracts = [ + "tax_unit_taxable_social_security", + "tax_unit_taxable_railroad_social_security_equivalent_benefits", + ] diff --git a/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py b/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py index f14cf72a30a..7d4bbcd34ee 100644 --- a/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py +++ b/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py @@ -10,13 +10,21 @@ class railroad_benefits(Variable): documentation = ( "Gross annual benefits paid by the Railroad Retirement Board, such as " "retirement, disability, spouse and survivor annuities, before Medicare " - "premiums or other amounts are withheld. Railroad Unemployment Insurance " - "Act benefits are reported elsewhere: unemployment benefits in " - "unemployment_compensation and sickness benefits in disability_benefits. " - "Census counts railroad retirement as a government transfer, and " - "household and SPM benefits include these benefits. Enter them only " - "here, not also as social_security, pension_income, disability_benefits " + "premiums or other amounts are withheld: the social security " + "equivalent benefit (Form RRB-1099) plus the rest of the annuity " + "(Form RRB-1099-R). Railroad Unemployment Insurance Act benefits are " + "reported elsewhere: unemployment benefits in " + "unemployment_compensation and sickness benefits in " + "disability_benefits. Census counts railroad retirement as a " + "government transfer, and household and SPM benefits include these " + "benefits. Enter the parts, not this total, so that taxes see them; " + "a total entered here reaches benefit programs only. Do not also " + "enter them as social_security, pension_income, disability_benefits " "or survivor_benefits." ) - default_value = 0 + adds = [ + "railroad_social_security_equivalent_benefits", + "taxable_railroad_pension_benefits", + "tax_exempt_railroad_pension_benefits", + ] reference = "https://www2.census.gov/programs-surveys/cps/methodology/Appendix%20A%20P60-200%20income%20measurement%20definition.pdf#page=4" diff --git a/policyengine_us/variables/household/income/person/retirement/railroad_social_security_equivalent_benefits.py b/policyengine_us/variables/household/income/person/retirement/railroad_social_security_equivalent_benefits.py new file mode 100644 index 00000000000..b7901708b22 --- /dev/null +++ b/policyengine_us/variables/household/income/person/retirement/railroad_social_security_equivalent_benefits.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class railroad_social_security_equivalent_benefits(Variable): + value_type = float + entity = Person + label = "Railroad Retirement social security equivalent benefits" + unit = USD + definition_period = YEAR + documentation = ( + "The part of a tier 1 Railroad Retirement Act annuity equal to the " + "Social Security benefit the same work record would earn: the social " + "security equivalent benefit (SSEB) on Form RRB-1099, box 5, net of " + "repayments. IRC 86(d)(1)(B) counts it as a social security benefit, " + "so section 86 taxes it together with title II benefits. The rest of " + "the annuity, reported on Form RRB-1099-R, goes in " + "taxable_railroad_pension_benefits and " + "tax_exempt_railroad_pension_benefits." + ) + uprating = "gov.ssa.uprating" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/86#d_1_B", + "https://www.law.cornell.edu/uscode/text/26/86#d_4", + # IRS Pub. 915 (2024): the SSEB portion of tier 1 is treated as a + # social security benefit; Worksheet A line A adds box 5 of + # Forms SSA-1099 and RRB-1099. PDF pages 2, 4. + "https://www.irs.gov/pub/irs-prior/p915--2024.pdf#page=2", + ) diff --git a/policyengine_us/variables/household/income/person/retirement/tax_exempt_railroad_pension_benefits.py b/policyengine_us/variables/household/income/person/retirement/tax_exempt_railroad_pension_benefits.py new file mode 100644 index 00000000000..f68bb8bb997 --- /dev/null +++ b/policyengine_us/variables/household/income/person/retirement/tax_exempt_railroad_pension_benefits.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class tax_exempt_railroad_pension_benefits(Variable): + value_type = float + entity = Person + label = "Tax-exempt Railroad Retirement pension benefits" + unit = USD + definition_period = YEAR + documentation = ( + "The part of Form RRB-1099-R benefits that recovers the employee's " + "own contributions tax free. IRC 72(r)(2) treats tier 2 employee " + "taxes as employee contributions to the plan, and the annuitant " + "recovers them over the annuity's expected life, usually under the " + "Simplified Method." + ) + reference = ( + "https://www.law.cornell.edu/uscode/text/26/72#r_2", + # IRS Pub. 575 (2024): tax-free recovery of employee contributions + # from tier 2 and the non-social security equivalent part of tier 1. + "https://www.irs.gov/pub/irs-prior/p575--2024.pdf#page=7", + ) diff --git a/policyengine_us/variables/household/income/person/retirement/taxable_railroad_pension_benefits.py b/policyengine_us/variables/household/income/person/retirement/taxable_railroad_pension_benefits.py new file mode 100644 index 00000000000..f5a180f7709 --- /dev/null +++ b/policyengine_us/variables/household/income/person/retirement/taxable_railroad_pension_benefits.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class taxable_railroad_pension_benefits(Variable): + value_type = float + entity = Person + label = "Taxable Railroad Retirement pension benefits" + unit = USD + definition_period = YEAR + documentation = ( + "Railroad Retirement Act benefits other than the social security " + "equivalent benefit, less the employee contributions recovered tax " + "free: the non-social security equivalent part of tier 1, tier 2, " + "vested dual benefits and supplemental annuities reported on Form " + "RRB-1099-R. IRC 72(r)(1) treats these benefits as paid from a " + "section 401(a) employer plan, so the taxable part is gross income " + "like any other pension. 45 U.S.C. 231m(a) bars states from taxing " + "them." + ) + reference = ( + "https://www.law.cornell.edu/uscode/text/26/72#r", + "https://www.law.cornell.edu/uscode/text/45/231m#a", + # IRS Pub. 575 (2024): the second category of railroad retirement + # benefits and the Form RRB-1099-R boxes. PDF pages 7-8. + "https://www.irs.gov/pub/irs-prior/p575--2024.pdf#page=7", + ) From be4bf2e054e1242bf5ee9d52fd102c158b2349d1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 15:08:54 -0500 Subject: [PATCH 03/13] Let Colorado and South Carolina bases fall when federal taxable income floors, and log the section 22 return-of-cost conflict Colorado and, before the SCIAD, South Carolina start from federal taxable income, which floors at zero. When the standard deduction absorbs some of a filer's railroad benefits, the full 231m subtraction exceeds what federal taxable income gained, so I4 lets those two bases fall there. I4 always runs the wage-free case that exposed it. Section 22(c)(3)(A)(i)(II) reduces the section 22 amount by railroad annuity amounts excluded from gross income, including the tax-free recovery of tier 2 contributions; Schedule R line 13b leaves returns of cost out. candidates.yaml records the conflict and the statute reading the model takes. Co-Authored-By: Claude Opus 5.5 --- .../test_railroad_retirement_taxation.py | 33 +++++++++++++++---- source_conflicts/candidates.yaml | 31 +++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/policyengine_us/tests/test_railroad_retirement_taxation.py b/policyengine_us/tests/test_railroad_retirement_taxation.py index 5ab2c0b0d38..ed1864c38c3 100644 --- a/policyengine_us/tests/test_railroad_retirement_taxation.py +++ b/policyengine_us/tests/test_railroad_retirement_taxation.py @@ -14,9 +14,12 @@ taxable income is zero. I4. For a wage earner under 65, adding railroad benefits leaves the state's AGI unchanged, and in states whose reported AGI includes them it leaves - state taxable income unchanged. STATE_BASE_EXCEPTIONS lists the states - with a provision measured with federal AGI or federal income tax, which - railroad benefits raise. + state taxable income unchanged. Colorado and South Carolina start from + federal taxable income, which floors at zero; while the standard + deduction absorbs some of the benefits, their subtraction exceeds what + federal taxable income gained, so there the base can only fall. + STATE_BASE_EXCEPTIONS lists the states with a provision measured with + federal AGI or federal income tax, which railroad benefits raise. I5. Totals: railroad_benefits is the sum of its three parts; the title II and tier 1 shares sum to the section 86 taxable amount; and taxable_railroad_benefits is what the head's and spouse's federal AGI @@ -31,7 +34,7 @@ """ import numpy as np -from hypothesis import given, settings +from hypothesis import example, given, settings from hypothesis import strategies as st from policyengine_us import Simulation @@ -51,6 +54,11 @@ # taxable income. FEDERAL_AGI_STATES = {"CO", "MI", "MN", "MT", "NC", "ND", "NM", "SC", "UT"} +# co_taxable_income and, before the SCIAD, sc_taxable_income start from +# taxable_income, which floors at zero, and subtract the benefits federal AGI +# includes. North Dakota starts from AGI less deductions without the floor. +FEDERAL_TAXABLE_INCOME_STATES = {"CO", "SC"} + # Provisions measured with federal AGI or federal income tax. Railroad # benefits raise both, so these states' bases move by the state's own rule. STATE_BASE_EXCEPTIONS = { @@ -193,7 +201,7 @@ def test_i2_the_rest_of_the_annuity_is_taxed_as_a_pension( assert abs(private - railroad) < TOLERANCE, (variable, private, railroad) -@settings(max_examples=8, deadline=None) +@settings(max_examples=6, deadline=None) @given( tier_1=st.integers(min_value=0, max_value=60_000), tier_2=st.integers(min_value=1, max_value=80_000), @@ -223,13 +231,18 @@ def test_i3_no_state_taxes_railroad_benefits(tier_1, tier_2, tax_free, age, join assert abs(tax[i]) < TOLERANCE, (state, tax[i]) -@settings(max_examples=8, deadline=None) +@settings(max_examples=6, deadline=None) @given( wages=st.integers(min_value=0, max_value=120_000), tier_1=st.integers(min_value=0, max_value=30_000), tier_2=st.integers(min_value=0, max_value=40_000), age=st.integers(min_value=25, max_value=64), ) +# South Carolina adds back $750 of the 2025 standard deduction for every +# non-itemizer, so a dollar of benefits the standard deduction absorbs still +# lowers its base from 750 to 749. +@example(wages=0, tier_1=0, tier_2=1, age=25) +@example(wages=40_000, tier_1=12_000, tier_2=15_000, age=50) def test_i4_railroad_benefits_leave_state_bases_unchanged(wages, tier_1, tier_2, age): worker = {"age": age, "employment_income": wages} retiree = worker | { @@ -243,9 +256,15 @@ def test_i4_railroad_benefits_leave_state_bases_unchanged(wages, tier_1, tier_2, n = len(I4_STATES) agi = calc(simulation, "taxsim_state_agi") taxable_income = calc(simulation, "taxsim_state_taxable_income") + federal_taxable_income = calc(simulation, "taxable_income") for i, state in enumerate(I4_STATES): base = taxable_income if state in FEDERAL_AGI_STATES else agi - assert abs(base[i] - base[i + n]) < TOLERANCE, (state, base[i], base[i + n]) + before, after = base[i], base[i + n] + floored = federal_taxable_income[i] <= 0 + if state in FEDERAL_TAXABLE_INCOME_STATES and floored: + assert after <= before + TOLERANCE, (state, before, after) + else: + assert abs(before - after) < TOLERANCE, (state, before, after) @settings(max_examples=25, deadline=None) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 30643a51291..bf507c5be8a 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -12249,6 +12249,37 @@ candidates: zero and kept apart from the passive basket, so these royalties count although Worksheet 1 line 8 reads only Schedule E line 23b. Where rental real estate goes is a separate reading (us:eitc-rental-real-estate-basket). Register the entry and add the marker once #10297 is on main. +- id: us:elderly-credit-railroad-tier-2-return-of-cost + program: income-tax + years: + from: 2023 + type: tax + question: Does the tax-free recovery of tier 2 employee contributions from a Railroad Retirement Act annuity reduce + the section 22 amount for the credit for the elderly or disabled? + sources: + - kind: statute + citation: 26 U.S.C. 22(c)(3)(A)(i)(II); 72(b) and (r) + url: https://www.law.cornell.edu/uscode/text/26/22#c_3_A + - kind: instructions + citation: 2026 Instructions for Schedule R (Form 1040), lines 13a and 13b + url: https://www.irs.gov/pub/irs-pdf/i1040sr.pdf#page=4 + readings: + statute: Section 22(c)(3)(A)(i)(II) reduces the section 22 amount by amounts the individual, or either spouse on a + joint return, receives as a pension or annuity "which is excluded from gross income and payable under" the + Railroad Retirement Act of 1974. The tax-free part of the annuity other than the social security equivalent + benefit, which recovers tier 2 employee taxes under 72(b) and 72(r)(2), meets both conditions. + other: Line 13a takes nontaxable tier 1 benefits treated as social security. Line 13b says "Don't include amounts + that are treated as a return of your cost of a pension or annuity," so the tax-free recovery reduces nothing. + example: A single filer aged 67 with $3,000 of social security equivalent benefits, all nontaxable, $2,000 of taxable + tier 2 benefits and $500 recovered tax free has a section 22 amount of $1,500 under the statute ($5,000 - $3,000 - + $500) and $2,000 under the instructions ($5,000 - $3,000); the model gives $1,500. + pe_us: + follows: statute + code: + - policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py + state: ruled + note: The 2023, 2024 and 2025 instructions carry the same line 13b sentence. The model takes the tax-free recovery + from tax_exempt_railroad_pension_benefits for the head and spouse. - id: us:filing-chart-a-blindness-and-equality program: income-tax years: From 1fb47b2dfe961ae0c0557d24ba1b97cc3ceeffaa Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 15:10:46 -0500 Subject: [PATCH 04/13] Link #10696 from the section 22 return-of-cost conflict entry Co-Authored-By: Claude Opus 5.5 --- source_conflicts/candidates.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index bf507c5be8a..a779db6f533 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -12277,6 +12277,8 @@ candidates: follows: statute code: - policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10696 state: ruled note: The 2023, 2024 and 2025 instructions carry the same line 13b sentence. The model takes the tax-free recovery from tax_exempt_railroad_pension_benefits for the head and spouse. From a9f7ccfa04ef9e788b3870f27a6b39f825e62f2e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 16:23:46 -0500 Subject: [PATCH 05/13] Cite 72(d) for the tax-free recovery of tier 2 contributions Section 72(r)(1) treats Railroad Retirement Act benefits other than tier 1 as benefits from a qualified plan, and 72(d)(1)(A) turns off 72(b) for annuities under a qualified employer retirement plan. The Simplified Method in 72(d) therefore recovers tier 2 contributions for annuities that started after November 18, 1996, and 72(b) covers only some older ones (IRS Pub. 575 (2024), pages 7 and 8). The section 22 comment, the YAML test header and the conflict entry now cite both. Co-Authored-By: Claude Opus 5.5 --- .../elderly_and_disabled/section_22_railroad_retirement.yaml | 3 ++- .../irs/credits/elderly_and_disabled/section_22_income.py | 5 +++-- source_conflicts/candidates.yaml | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/elderly_and_disabled/section_22_railroad_retirement.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/elderly_and_disabled/section_22_railroad_retirement.yaml index 71471c25063..c4aca1207ee 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/elderly_and_disabled/section_22_railroad_retirement.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/elderly_and_disabled/section_22_railroad_retirement.yaml @@ -2,7 +2,8 @@ # head or spouse receives as a pension or annuity payable under the Railroad # Retirement Act of 1974 and excluded from gross income. That covers tier 1 # benefits section 86 leaves out and the tax-free part of the rest of the -# annuity (72(r)(2), 72(b)). Initial amounts: $5,000 single, $7,500 joint with +# annuity (72(r)(2); 72(d), or 72(b) for some annuities that started before +# November 19, 1996). Initial amounts: $5,000 single, $7,500 joint with # two qualified individuals (22(c)(2)); AGI thresholds $7,500 and $10,000 # (22(d)). diff --git a/policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py b/policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py index dd4684c3913..7ca6fd74d24 100644 --- a/policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py +++ b/policyengine_us/variables/gov/irs/credits/elderly_and_disabled/section_22_income.py @@ -68,8 +68,9 @@ def formula(tax_unit, period, parameters): # spouse receives as a pension or annuity payable under the Railroad # Retirement Act of 1974 and excluded from gross income, whatever # provision excludes it: the tier 1 benefits section 86 leaves out, - # and the tax-free part of the rest of the annuity, which 72(r)(2) - # and 72(b) exclude as a recovery of the employee's tier 2 taxes. + # and the tax-free part of the rest of the annuity, which recovers + # the employee's tier 2 taxes (72(r)(2); 72(d)'s Simplified Method, + # or 72(b) for some annuities that started before November 19, 1996). # Schedule R's line 13b keeps returns of cost out; the model follows # the statute's words. non_taxable_tier_1 = tax_unit( diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index a779db6f533..a8a669cd96c 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -12258,7 +12258,7 @@ candidates: the section 22 amount for the credit for the elderly or disabled? sources: - kind: statute - citation: 26 U.S.C. 22(c)(3)(A)(i)(II); 72(b) and (r) + citation: 26 U.S.C. 22(c)(3)(A)(i)(II); 72(b), (d) and (r) url: https://www.law.cornell.edu/uscode/text/26/22#c_3_A - kind: instructions citation: 2026 Instructions for Schedule R (Form 1040), lines 13a and 13b @@ -12267,7 +12267,8 @@ candidates: statute: Section 22(c)(3)(A)(i)(II) reduces the section 22 amount by amounts the individual, or either spouse on a joint return, receives as a pension or annuity "which is excluded from gross income and payable under" the Railroad Retirement Act of 1974. The tax-free part of the annuity other than the social security equivalent - benefit, which recovers tier 2 employee taxes under 72(b) and 72(r)(2), meets both conditions. + benefit, which recovers tier 2 employee taxes under 72(r)(2) and 72(d) (72(b) for some annuities that started + before November 19, 1996), meets both conditions. other: Line 13a takes nontaxable tier 1 benefits treated as social security. Line 13b says "Don't include amounts that are treated as a return of your cost of a pension or annuity," so the tax-free recovery reduces nothing. example: A single filer aged 67 with $3,000 of social security equivalent benefits, all nontaxable, $2,000 of taxable From 49192ef00cafd8116d6113e13872c637976e0e27 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 17:13:01 -0500 Subject: [PATCH 06/13] Map the two railroad gross income sources to railroad_benefits in the parity test #10663 made the parity test require every source in gov.irs.gross_income.sources to name the SPM resource that counts its cash. railroad_benefits, which #10693 adds to spm_unit_benefits, holds the whole annuity; taxable_railroad_pension_benefits and taxable_railroad_social_security_equivalent_benefits tax its parts. Co-Authored-By: Claude Opus 5.5 --- policyengine_us/tests/test_household_spm_resource_parity.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/policyengine_us/tests/test_household_spm_resource_parity.py b/policyengine_us/tests/test_household_spm_resource_parity.py index 600266edb68..9e053734e46 100644 --- a/policyengine_us/tests/test_household_spm_resource_parity.py +++ b/policyengine_us/tests/test_household_spm_resource_parity.py @@ -101,10 +101,14 @@ "rental_income": "rental_income", "dividend_income": "dividend_income", "taxable_pension_income": "pension_income", + # railroad_benefits holds the whole Railroad Retirement Act annuity; these + # two sources tax its non-SSEB part (IRC 72(r)) and its SSEB (IRC 86). + "taxable_railroad_pension_benefits": "railroad_benefits", "taxable_disability_benefits": "disability_benefits", "estate_income": "estate_income", "taxable_unemployment_compensation": "total_unemployment_compensation", "taxable_social_security": "social_security", + "taxable_railroad_social_security_equivalent_benefits": "railroad_benefits", "illicit_income": "illicit_income", "taxable_retirement_distributions": "retirement_distributions", "miscellaneous_income": "miscellaneous_income", From 014fe66dc9d859bd98c219da745205df346cf3e1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 13:15:27 -0500 Subject: [PATCH 07/13] Answer the round 1 review of the income totals change - Add veterans_benefits and railroad_benefits to the six reform copies of the benefit lists (BOOST, End Child Poverty Act, EDAA End Child Poverty Act, Texas rebate), which otherwise dropped both benefits and showed a false loss under each reform, and to SHARED_CASH_PROGRAMS so the guard catches a copy that drops them. - Narrow the veterans' benefits property test to gross income and AGI, checking income tax only when neither household itemizes: the IRS counts veterans' benefits as income for the optional sales tax tables. New state_sales_tax.yaml cases show the 2025 Texas row moving. - Keep Combat-Related Special Compensation in veterans_benefits, as military_retirement_pay directs, and point VA education benefits and dependency and indemnity compensation, and railroad survivor annuities, to their own variables. - Cite the Census appendix once, with its page range. Co-Authored-By: Claude Opus 5.5 --- changelog.d/vet-rrb-income-totals.fixed.md | 2 +- .../gov/household/household_benefits.yaml | 4 +-- .../boost/boost_middle_class_tax_credit.py | 8 ++++++ .../edaa_end_child_poverty_act.py | 4 +++ .../congress/tlaib/end_child_poverty_act.py | 8 ++++++ .../reforms/states/tx/rebate/tx_rebate.py | 4 +++ .../salt_and_real_estate/state_sales_tax.yaml | 27 +++++++++++++++++++ .../tests/test_aggregate_list_integrity.py | 4 +++ .../test_household_spm_resource_parity.py | 16 ++++++++--- .../person/general/survivor_benefits.py | 6 ++++- .../person/general/veterans_benefits.py | 20 +++++++++----- .../person/misc/educational_assistance.py | 5 +++- 12 files changed, 92 insertions(+), 16 deletions(-) diff --git a/changelog.d/vet-rrb-income-totals.fixed.md b/changelog.d/vet-rrb-income-totals.fixed.md index 67cdaa45736..5eeb08ad4f2 100644 --- a/changelog.d/vet-rrb-income-totals.fixed.md +++ b/changelog.d/vet-rrb-income-totals.fixed.md @@ -1 +1 @@ -Count veterans' benefits and Railroad Retirement Board benefits in household and SPM benefits, so both net incomes and SPM resources include them. +Count veterans' benefits and Railroad Retirement Board benefits in household and SPM benefits, and in the copies of those lists that the BOOST, End Child Poverty Act and Texas rebate reforms define, so both net incomes and SPM resources include them. diff --git a/policyengine_us/parameters/gov/household/household_benefits.yaml b/policyengine_us/parameters/gov/household/household_benefits.yaml index 5955d4a6a2c..18e36e3901d 100644 --- a/policyengine_us/parameters/gov/household/household_benefits.yaml +++ b/policyengine_us/parameters/gov/household/household_benefits.yaml @@ -81,7 +81,5 @@ metadata: period: year label: Household benefits reference: - - title: Census Bureau, P60-200 Appendix A, Definitions and Explanations (veterans' payments, government transfers) + - title: "Census Bureau, P60-200 Appendix A, Definitions and Explanations #page 3-4" href: https://www2.census.gov/programs-surveys/cps/methodology/Appendix%20A%20P60-200%20income%20measurement%20definition.pdf#page=3 - - title: Census Bureau, P60-200 Appendix A, Definitions and Explanations (nonmeans-tested transfers, including U.S. railroad retirement) - href: https://www2.census.gov/programs-surveys/cps/methodology/Appendix%20A%20P60-200%20income%20measurement%20definition.pdf#page=4 diff --git a/policyengine_us/reforms/congress/tlaib/boost/boost_middle_class_tax_credit.py b/policyengine_us/reforms/congress/tlaib/boost/boost_middle_class_tax_credit.py index d4a2face50a..ca6f87bcf74 100644 --- a/policyengine_us/reforms/congress/tlaib/boost/boost_middle_class_tax_credit.py +++ b/policyengine_us/reforms/congress/tlaib/boost/boost_middle_class_tax_credit.py @@ -63,6 +63,10 @@ def formula(household, period, parameters): "educational_assistance", "financial_assistance", "survivor_benefits", + # Census money income counts veterans' payments, and railroad + # retirement as a government transfer (P60-200 Appendix A). + "veterans_benefits", + "railroad_benefits", # Broadband subsidies. "acp", "ebb", @@ -122,6 +126,10 @@ def formula(spm_unit, period, parameters): "educational_assistance", "financial_assistance", "survivor_benefits", + # Census money income counts veterans' payments, and railroad + # retirement as a government transfer (P60-200 Appendix A). + "veterans_benefits", + "railroad_benefits", # Broadband subsidies. "acp", "ebb", diff --git a/policyengine_us/reforms/congress/tlaib/economic_dignity_for_all_agenda/edaa_end_child_poverty_act.py b/policyengine_us/reforms/congress/tlaib/economic_dignity_for_all_agenda/edaa_end_child_poverty_act.py index 4b6552e52e3..1b676805038 100644 --- a/policyengine_us/reforms/congress/tlaib/economic_dignity_for_all_agenda/edaa_end_child_poverty_act.py +++ b/policyengine_us/reforms/congress/tlaib/economic_dignity_for_all_agenda/edaa_end_child_poverty_act.py @@ -131,6 +131,10 @@ def formula(spm_unit, period, parameters): "educational_assistance", "financial_assistance", "survivor_benefits", + # Census money income counts veterans' payments, and railroad + # retirement as a government transfer (P60-200 Appendix A). + "veterans_benefits", + "railroad_benefits", # Broadband subsidies. "acp", "ebb", diff --git a/policyengine_us/reforms/congress/tlaib/end_child_poverty_act.py b/policyengine_us/reforms/congress/tlaib/end_child_poverty_act.py index 7dea7b0defc..d9a0dedda49 100644 --- a/policyengine_us/reforms/congress/tlaib/end_child_poverty_act.py +++ b/policyengine_us/reforms/congress/tlaib/end_child_poverty_act.py @@ -99,6 +99,10 @@ def formula(household, period, parameters): "educational_assistance", "financial_assistance", "survivor_benefits", + # Census money income counts veterans' payments, and railroad + # retirement as a government transfer (P60-200 Appendix A). + "veterans_benefits", + "railroad_benefits", # Broadband subsidies. "acp", "ebb", @@ -150,6 +154,10 @@ def formula(spm_unit, period, parameters): "educational_assistance", "financial_assistance", "survivor_benefits", + # Census money income counts veterans' payments, and railroad + # retirement as a government transfer (P60-200 Appendix A). + "veterans_benefits", + "railroad_benefits", # Broadband subsidies. "acp", "ebb", diff --git a/policyengine_us/reforms/states/tx/rebate/tx_rebate.py b/policyengine_us/reforms/states/tx/rebate/tx_rebate.py index 3ae570a9999..ba7675165aa 100644 --- a/policyengine_us/reforms/states/tx/rebate/tx_rebate.py +++ b/policyengine_us/reforms/states/tx/rebate/tx_rebate.py @@ -99,6 +99,10 @@ def formula(spm_unit, period, parameters): "educational_assistance", "financial_assistance", "survivor_benefits", + # Census money income counts veterans' payments, and railroad + # retirement as a government transfer (P60-200 Appendix A). + "veterans_benefits", + "railroad_benefits", "spm_unit_energy_subsidy", "tanf", # Washington (WA) cash-assistance programs. wa_sfa and wa_rca diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml index 68d0729ca1e..25ac620ce63 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml @@ -1534,3 +1534,30 @@ salt_deduction: 21_728 itemized_taxable_income_deductions_reduction: 2_661 itemized_taxable_income_deductions: 19_067 + +# Form 1040 line 11b plus "any nontaxable items", veterans' benefits among +# them, sets the row (2025 Instructions for Schedule A, page 5). In the 2025 +# table, a Texas family of one owes $931 in the $100,000-$120,000 row and $992 +# in the $120,000-$140,000 row (page 16). Veterans' benefits stay out of AGI +# under 38 U.S.C. 5301(a)(1) but raise this deduction for a filer who +# itemizes and deducts sales tax. +- name: A Texas filer with $100,000 of AGI uses the $100,000-$120,000 row, 2025 + period: 2025 + input: + state_code: TX + employment_income: 100_000 + output: + adjusted_gross_income: 100_000 + state_sales_tax_income_bracket: 10 + state_sales_tax: 931 + +- name: $30,000 of veterans' benefits moves the same filer to the $120,000-$140,000 row, 2025 + period: 2025 + input: + state_code: TX + employment_income: 100_000 + veterans_benefits: 30_000 + output: + adjusted_gross_income: 100_000 + state_sales_tax_income_bracket: 11 + state_sales_tax: 992 diff --git a/policyengine_us/tests/test_aggregate_list_integrity.py b/policyengine_us/tests/test_aggregate_list_integrity.py index e76e164a2f0..4e97258ac72 100644 --- a/policyengine_us/tests/test_aggregate_list_integrity.py +++ b/policyengine_us/tests/test_aggregate_list_integrity.py @@ -289,11 +289,15 @@ def test_reform_aggregates_keep_baseline_contrib_entries( "ma_eaedc", "nj_property_tax_relief", "ny_drive_clean_rebate", + "railroad_benefits", + "veterans_benefits", }, "household_benefits": { "ak_energy_relief", "household_local_benefits", "ny_drive_clean_rebate", + "railroad_benefits", + "veterans_benefits", }, } diff --git a/policyengine_us/tests/test_household_spm_resource_parity.py b/policyengine_us/tests/test_household_spm_resource_parity.py index 600266edb68..2ab3a21d9d7 100644 --- a/policyengine_us/tests/test_household_spm_resource_parity.py +++ b/policyengine_us/tests/test_household_spm_resource_parity.py @@ -448,8 +448,14 @@ def test_a_dollar_of_federal_cash_benefits_adds_a_dollar_to_both_totals( ) @example(amount=30_000, earnings=0, state="TX") @example(amount=20_000, earnings=45_000, state="VT") -def test_veterans_benefits_never_change_federal_income_tax(amount, earnings, state): - """38 U.S.C. 5301(a)(1) exempts VA benefits from taxation.""" +def test_veterans_benefits_stay_out_of_federal_gross_income(amount, earnings, state): + """38 U.S.C. 5301(a)(1) exempts VA benefits from taxation. + + The IRS still counts them as income for the optional sales tax tables + (state_sales_tax.yaml), so a filer who itemizes and deducts sales tax can + owe less federal income tax with them. The test checks income tax only + when neither household itemizes. + """ year = 2026 zero = Simulation( situation=one_person(state, earnings, year, veterans_benefits=("person", 0)) @@ -459,7 +465,11 @@ def test_veterans_benefits_never_change_federal_income_tax(amount, earnings, sta state, earnings, year, veterans_benefits=("person", amount) ) ) - for name in ("irs_gross_income", "adjusted_gross_income", "income_tax"): + for name in ("irs_gross_income", "adjusted_gross_income"): assert total(paid, [name], year) == pytest.approx( total(zero, [name], year), abs=TOLERANCE ), name + if not any(total(s, ["tax_unit_itemizes"], year) for s in (zero, paid)): + assert total(paid, ["income_tax"], year) == pytest.approx( + total(zero, ["income_tax"], year), abs=TOLERANCE + ) diff --git a/policyengine_us/variables/household/income/person/general/survivor_benefits.py b/policyengine_us/variables/household/income/person/general/survivor_benefits.py index 62c6b897960..e3b3ac9ee1c 100644 --- a/policyengine_us/variables/household/income/person/general/survivor_benefits.py +++ b/policyengine_us/variables/household/income/person/general/survivor_benefits.py @@ -5,6 +5,10 @@ class survivor_benefits(Variable): value_type = float entity = Person label = "survivor benefits" - documentation = "Survivor benefits other than Social Security survivor benefits." + documentation = ( + "Survivor benefits other than Social Security survivor benefits. VA " + "dependency and indemnity compensation goes in veterans_benefits, and " + "Railroad Retirement Board survivor annuities in railroad_benefits." + ) unit = USD definition_period = YEAR diff --git a/policyengine_us/variables/household/income/person/general/veterans_benefits.py b/policyengine_us/variables/household/income/person/general/veterans_benefits.py index 00a01ea1af1..644dfd0fcfd 100644 --- a/policyengine_us/variables/household/income/person/general/veterans_benefits.py +++ b/policyengine_us/variables/household/income/person/general/veterans_benefits.py @@ -7,17 +7,23 @@ class veterans_benefits(Variable): label = "Veterans benefits" unit = USD documentation = ( - "Benefits the Department of Veterans Affairs pays from past military " - "service, such as disability compensation, pensions, dependency and " - "indemnity compensation to survivors, and education allowances. " - "38 U.S.C. 5301(a)(1) exempts them from taxation, so federal gross " - "income leaves them out. Census money income counts them as veterans' " - "payments, and household and SPM benefits include them. Military " - "retirement pay goes in military_retirement_pay instead." + "Tax-exempt payments for past military service: benefits the " + "Department of Veterans Affairs pays, such as disability compensation, " + "pensions, dependency and indemnity compensation to survivors, and " + "education allowances, and Combat-Related Special Compensation (CRSC), " + "which the military departments pay under 10 U.S.C. 1413a. " + "38 U.S.C. 5301(a)(1) exempts VA benefits from taxation, and IRS " + "Publication 525 lets retirees exclude CRSC from income, so federal " + "gross income leaves both out. Census money income counts VA payments " + "as veterans' payments, and household and SPM benefits include this " + "variable. Taxable military retirement pay goes in " + "military_retirement_pay instead." ) definition_period = YEAR uprating = "gov.ssa.uprating" reference = ( "https://www.law.cornell.edu/uscode/text/38/5301#a_1", + "https://www.law.cornell.edu/uscode/text/10/1413a", + "https://www.irs.gov/pub/irs-prior/p525--2024.pdf#page=19", "https://www2.census.gov/programs-surveys/cps/methodology/Appendix%20A%20P60-200%20income%20measurement%20definition.pdf#page=3", ) diff --git a/policyengine_us/variables/household/income/person/misc/educational_assistance.py b/policyengine_us/variables/household/income/person/misc/educational_assistance.py index ec72d09ce29..f822902fccf 100644 --- a/policyengine_us/variables/household/income/person/misc/educational_assistance.py +++ b/policyengine_us/variables/household/income/person/misc/educational_assistance.py @@ -5,6 +5,9 @@ class educational_assistance(Variable): value_type = float entity = Person label = "educational assistance" - documentation = "Scholarships, grants, and other educational assistance." + documentation = ( + "Scholarships, grants, and other educational assistance. VA education " + "benefits go in veterans_benefits." + ) unit = USD definition_period = YEAR From f7bc856ed0bdf1a5647915a89bb4f6ab0acc71c3 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 13:31:56 -0500 Subject: [PATCH 08/13] Test the railroad items in the sales tax table's income The 2025 Instructions for Schedule A add the nontaxable part of social security and railroad retirement benefits, and of pension and annuity distributions, to Form 1040 line 11b to pick the sales tax table row. This PR adds the untaxed SSEB and the tax-free part of the annuity to that income list, and no test covered the change. Two Texas cases cross the $120,000 line on those amounts alone; both fail on the old list. Co-Authored-By: Claude Opus 5.5 --- .../salt_and_real_estate/state_sales_tax.yaml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml index 25ac620ce63..230f0203552 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml @@ -1561,3 +1561,35 @@ adjusted_gross_income: 100_000 state_sales_tax_income_bracket: 11 state_sales_tax: 992 + +# The same page 5 list adds the "nontaxable part of social security and +# railroad retirement benefits" and the "nontaxable part of IRA, pension, or +# annuity distributions" to line 11b. A Railroad Retirement Act annuity's +# tax-free part and the untaxed part of the social security equivalent benefit +# (SSEB) therefore raise the row, as veterans' benefits do. +- name: $30,000 of tax-free railroad annuity moves the same filer to the $120,000-$140,000 row, 2025 + period: 2025 + input: + state_code: TX + employment_income: 100_000 + tax_exempt_railroad_pension_benefits: 30_000 + output: + adjusted_gross_income: 100_000 + state_sales_tax_income_bracket: 11 + state_sales_tax: 992 + +- name: The untaxed part of a $22,000 SSEB moves the same filer to the $120,000-$140,000 row, 2025 + period: 2025 + input: + state_code: TX + employment_income: 100_000 + railroad_social_security_equivalent_benefits: 22_000 + output: + # Provisional income comes to $100,000 + $22,000 / 2 = $111,000. Section + # 86(a)(2) taxes the lesser of 85% of the benefit ($18,700) and 85% of + # $111,000 - $34,000 plus $4,500 ($69,950), so AGI reaches $118,700, in + # the $100,000-$120,000 row. The untaxed $3,300 lifts the total to $122,000. + adjusted_gross_income: 118_700 + tax_exempt_railroad_social_security_equivalent_benefits: 3_300 + state_sales_tax_income_bracket: 11 + state_sales_tax: 992 From 033d78ed1bc6967ef898758afc402fc358f9aa8b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 13:55:56 -0500 Subject: [PATCH 09/13] Allow float32 rounding in the railroad property tests At $131,072 of wages, float32 steps are $0.015625 wide. Hypothesis found a case there in which moving 1 percent of a $1 benefit from title II to the SSEB moved AGI by one step: the railroad household adds its title II and SSEB shares to AGI separately, and each sum rounds on its own. The tests' fixed one-cent tolerance fell below one step. The comparisons now allow eight float32 steps at the sum of the household's income inputs, and never less than a cent. I3's zero-tax check keeps the cent. test_i1 pins the failing case with @example; with a one-cent tolerance it fails on that example, with AGI of $131,072.84375 against $131,072.859375. Co-Authored-By: Claude Opus 5.5 --- .../test_railroad_retirement_taxation.py | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/policyengine_us/tests/test_railroad_retirement_taxation.py b/policyengine_us/tests/test_railroad_retirement_taxation.py index ed1864c38c3..7aed7f059b1 100644 --- a/policyengine_us/tests/test_railroad_retirement_taxation.py +++ b/policyengine_us/tests/test_railroad_retirement_taxation.py @@ -31,6 +31,12 @@ YAML tests pin worked examples; these properties quantify over amounts, filing status and every state, which YAML cannot. Each example builds one Simulation that holds every household it compares. + +The model keeps money in float32, whose step grows with the amount: 2^-7 +from $65,536 to $131,072 and 2^-6 from there to $262,144. Two calculations +that add the same dollars in a different order can land a few steps apart, +so the comparisons allow eight steps at the largest amount involved, and +never less than a cent. """ import numpy as np @@ -40,7 +46,14 @@ from policyengine_us import Simulation YEAR = 2025 -TOLERANCE = 0.01 +CENT = 0.01 + + +def tolerance(*amounts): + """Eight float32 steps at the largest of the amounts, or a cent.""" + largest = max(abs(float(amount)) for amount in amounts) + return max(CENT, 8 * float(np.spacing(np.float32(largest)))) + INCOME_TAX_STATES = ( "AL AZ AR CA CO CT DE DC GA HI ID IL IN IA KS KY LA ME MD MA MI MN MS MO " @@ -137,6 +150,18 @@ def section_86_parts(simulation): joint=st.booleans(), age=st.integers(min_value=62, max_value=85), ) +# At $131,072 of wages, float32 rounds the two households' AGI one $0.015625 +# step apart: the railroad household adds its title II and SSEB shares to AGI +# separately. +@example( + wages=131_072, + head_benefits=1, + spouse_benefits=0, + head_share=0.01, + spouse_share=0.0, + joint=False, + age=62, +) def test_i1_tier_1_benefits_are_taxed_as_title_ii_benefits( wages, head_benefits, spouse_benefits, head_share, spouse_share, joint, age ): @@ -158,6 +183,7 @@ def household(head_share, spouse_share): simulation = Simulation( situation=situation([household(0, 0), household(head_share, spouse_share)]) ) + tol = tolerance(wages + head_benefits + spouse_benefits) for variable in ( "adjusted_gross_income", "income_tax", @@ -165,11 +191,11 @@ def household(head_share, spouse_share): "medicaid_magi", ): title_ii, railroad = calc(simulation, variable)[:2] - assert abs(title_ii - railroad) < TOLERANCE, (variable, title_ii, railroad) + assert abs(title_ii - railroad) < tol, (variable, title_ii, railroad) np.testing.assert_allclose( section_86_parts(simulation), calc(simulation, "tax_unit_taxable_section_86_benefits"), - atol=TOLERANCE, + atol=tol, ) @@ -196,9 +222,10 @@ def test_i2_the_rest_of_the_annuity_is_taxed_as_a_pension( ] ) ) + tol = tolerance(wages + pension + title_ii) for variable in ("adjusted_gross_income", "income_tax"): private, railroad = calc(simulation, variable) - assert abs(private - railroad) < TOLERANCE, (variable, private, railroad) + assert abs(private - railroad) < tol, (variable, private, railroad) @settings(max_examples=6, deadline=None) @@ -228,7 +255,7 @@ def test_i3_no_state_taxes_railroad_benefits(tier_1, tier_2, tax_free, age, join if state == "ID": assert idaho_taxable_income[i] == 0, (state, idaho_taxable_income[i]) else: - assert abs(tax[i]) < TOLERANCE, (state, tax[i]) + assert abs(tax[i]) < CENT, (state, tax[i]) @settings(max_examples=6, deadline=None) @@ -257,14 +284,15 @@ def test_i4_railroad_benefits_leave_state_bases_unchanged(wages, tier_1, tier_2, agi = calc(simulation, "taxsim_state_agi") taxable_income = calc(simulation, "taxsim_state_taxable_income") federal_taxable_income = calc(simulation, "taxable_income") + tol = tolerance(wages + tier_1 + tier_2) for i, state in enumerate(I4_STATES): base = taxable_income if state in FEDERAL_AGI_STATES else agi before, after = base[i], base[i + n] floored = federal_taxable_income[i] <= 0 if state in FEDERAL_TAXABLE_INCOME_STATES and floored: - assert after <= before + TOLERANCE, (state, before, after) + assert after <= before + tol, (state, before, after) else: - assert abs(before - after) < TOLERANCE, (state, before, after) + assert abs(before - after) < tol, (state, before, after) @settings(max_examples=25, deadline=None) @@ -292,6 +320,7 @@ def test_i5_railroad_totals_add_up(tier_1, tier_2, tax_free, title_ii, earnings) for i in range(3) ] simulation = Simulation(situation=situation([("TX", members)])) + tol = tolerance(sum(earnings) + sum(title_ii) + sum(tier_1) + sum(tier_2)) np.testing.assert_allclose( calc(simulation, "railroad_benefits"), np.add(np.add(tier_1, tier_2), tax_free), @@ -299,7 +328,7 @@ def test_i5_railroad_totals_add_up(tier_1, tier_2, tax_free, title_ii, earnings) np.testing.assert_allclose( section_86_parts(simulation), calc(simulation, "tax_unit_taxable_section_86_benefits"), - atol=TOLERANCE, + atol=tol, ) taxable_tier_1 = calc( simulation, "taxable_railroad_social_security_equivalent_benefits" @@ -311,12 +340,12 @@ def test_i5_railroad_totals_add_up(tier_1, tier_2, tax_free, title_ii, earnings) calc( simulation, "tax_unit_taxable_railroad_social_security_equivalent_benefits" )[0], - atol=TOLERANCE, + atol=tol, ) np.testing.assert_allclose( calc(simulation, "taxable_railroad_benefits"), taxable_tier_1 + np.array(tier_2, dtype=float) * [1, 1, 0], - atol=TOLERANCE, + atol=tol, ) @@ -343,4 +372,7 @@ def test_d1_dependent_and_filer_section_86_agree(wages, title_ii, tier_1): simulation, "dependent_taxable_railroad_social_security_equivalent_benefits" ) )[2] - assert abs(filer - dependent) < TOLERANCE, (filer, dependent) + assert abs(filer - dependent) < tolerance(wages + title_ii + tier_1), ( + filer, + dependent, + ) From d62bc4cb21f652877cef62fe2e2abc2509f813d5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 15:14:15 -0500 Subject: [PATCH 10/13] Keep railroad benefits out of state minimum taxes, withholding and relief income 45 U.S.C. 231m(a) bars any state tax on Railroad Retirement Act annuities. - Minnesota's alternative minimum taxable income now subtracts taxable_railroad_benefits, since the 2024 Schedule M1MT instructions carry Schedule M1M line 17 into line 18. - Vermont's minimum tax under 32 V.S.A. 5822(a)(6) applies 3 percent to federal AGI less taxable_railroad_benefits; the $150,000 test still reads federal AGI. - The 42 state withholding estimates start from the new state_withholding_agi, each person's federal AGI less taxable_railroad_benefits, so neither the estimates nor the federal deduction for state and local taxes count the benefits. - Pennsylvania's property tax and rent rebate income replaces the taxable SSEB in federal AGI with half of the SSEB (PA-1000 line 5) and adds the tax-free recovery of the rest of the annuity (line 6, gross amount). - Utah's homeowner and renter relief adds only the railroad benefits that federal AGI leaves out. Tests: new YAML cases for each fix, zero withholding in the 42 railroad-only state cases, and two Hypothesis examples plus a withholding check in the state invariant. Each new case fails at 033d78ed1b except North Dakota's, whose zero bracket already left withholding at zero. Co-Authored-By: Claude Opus 5.5 --- changelog.d/rrb-federal-tax.fixed.md | 2 +- .../nontaxable_income/sources.yaml | 1 - .../mn/tax/income/mn_amt_taxable_income.yaml | 53 +++++++++++++++++++ ...pa_property_tax_or_rent_rebate_income.yaml | 53 ++++++++++++++++++- .../states/railroad_retirement_benefits.yaml | 46 +++++++++++++++- .../ut_homeowner_renter_relief.yaml | 25 +++++++++ .../gov/states/vt/tax/income/vt_amt.yaml | 44 +++++++++++++++ .../test_railroad_retirement_taxation.py | 20 ++++--- .../al/tax/income/al_withheld_income_tax.py | 2 +- .../ar/tax/income/ar_withheld_income_tax.py | 2 +- .../az/tax/income/az_withheld_income_tax.py | 2 +- .../ca/tax/income/ca_withheld_income_tax.py | 2 +- .../co/tax/income/co_withheld_income_tax.py | 2 +- .../ct/tax/income/ct_withheld_income_tax.py | 2 +- .../dc/tax/income/dc_withheld_income_tax.py | 2 +- .../de/tax/income/de_withheld_income_tax.py | 2 +- .../ga/tax/income/ga_withheld_income_tax.py | 2 +- .../hi/tax/income/hi_withheld_income_tax.py | 2 +- .../ia/tax/income/ia_withheld_income_tax.py | 2 +- .../id/tax/income/id_withheld_income_tax.py | 2 +- .../il/tax/income/il_withheld_income_tax.py | 2 +- .../in/tax/income/in_withheld_income_tax.py | 2 +- .../ks/tax/income/ks_withheld_income_tax.py | 2 +- .../ky/tax/income/ky_withheld_income_tax.py | 2 +- .../la/tax/income/la_withheld_income_tax.py | 2 +- .../ma/tax/income/ma_withheld_income_tax.py | 2 +- .../md/tax/income/md_withheld_income_tax.py | 2 +- .../me/tax/income/me_withheld_income_tax.py | 2 +- .../mi/tax/income/mi_withheld_income_tax.py | 2 +- .../tax/income/amt/mn_amt_taxable_income.py | 9 ++++ .../mn/tax/income/mn_withheld_income_tax.py | 2 +- .../mo/tax/income/mo_withheld_income_tax.py | 2 +- .../ms/tax/income/ms_withheld_income_tax.py | 2 +- .../tax/income/base/mt_withheld_income_tax.py | 2 +- .../nc/tax/income/nc_withheld_income_tax.py | 2 +- .../nd/tax/income/nd_withheld_income_tax.py | 2 +- .../ne/tax/income/ne_withheld_income_tax.py | 2 +- .../nj/tax/income/nj_withheld_income_tax.py | 2 +- .../nm/tax/income/nm_withheld_income_tax.py | 2 +- .../ny/tax/income/ny_withheld_income_tax.py | 2 +- .../oh/tax/income/oh_withheld_income_tax.py | 2 +- .../ok/tax/income/ok_withheld_income_tax.py | 2 +- .../or/tax/income/or_withheld_income_tax.py | 2 +- .../pa/tax/income/pa_withheld_income_tax.py | 2 +- .../pa_property_tax_or_rent_rebate_income.py | 14 ++++- .../ri/tax/income/ri_withheld_income_tax.py | 2 +- .../sc/tax/income/sc_withheld_income_tax.py | 2 +- .../tax/income/state_withholding_agi.py | 24 +++++++++ .../ut/tax/income/ut_withheld_income_tax.py | 2 +- ...meowner_renter_relief_nontaxable_income.py | 8 +++ .../va/tax/income/va_withheld_income_tax.py | 2 +- .../gov/states/vt/tax/income/vt_amt.py | 8 ++- .../vt/tax/income/vt_withheld_income_tax.py | 2 +- .../wi/tax/income/wi_withheld_income_tax.py | 2 +- .../wv/tax/income/wv_withheld_income_tax.py | 2 +- 55 files changed, 336 insertions(+), 55 deletions(-) create mode 100644 policyengine_us/variables/gov/states/tax/income/state_withholding_agi.py diff --git a/changelog.d/rrb-federal-tax.fixed.md b/changelog.d/rrb-federal-tax.fixed.md index 3e76e793f43..f41d60b4fd7 100644 --- a/changelog.d/rrb-federal-tax.fixed.md +++ b/changelog.d/rrb-federal-tax.fixed.md @@ -1 +1 @@ -Tax Railroad Retirement Act benefits federally, with tier 1 social security equivalent benefits under IRC 86 alongside Social Security and the rest of the annuity as a pension under IRC 72(r), and subtract them from state income in every state with an income tax, as 45 U.S.C. 231m(a) requires. +Tax Railroad Retirement Act benefits federally, with tier 1 social security equivalent benefits under IRC 86 alongside Social Security and the rest of the annuity as a pension under IRC 72(r). Keep them out of every state income tax, Minnesota's and Vermont's minimum taxes and the state withholding estimates, as 45 U.S.C. 231m(a) requires, and count tier 2 benefits in full in Pennsylvania's property tax and rent rebate income. diff --git a/policyengine_us/parameters/gov/states/ut/tax/property/homeowner_renter_relief/nontaxable_income/sources.yaml b/policyengine_us/parameters/gov/states/ut/tax/property/homeowner_renter_relief/nontaxable_income/sources.yaml index 409a999269a..76565be0ed5 100644 --- a/policyengine_us/parameters/gov/states/ut/tax/property/homeowner_renter_relief/nontaxable_income/sources.yaml +++ b/policyengine_us/parameters/gov/states/ut/tax/property/homeowner_renter_relief/nontaxable_income/sources.yaml @@ -3,7 +3,6 @@ values: 2024-01-01: - alimony_income - child_support_received - - railroad_benefits - strike_benefits - tax_exempt_interest_income - tax_exempt_pension_income diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml index aadc46a547a..e003293078f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml @@ -112,3 +112,56 @@ state_code: MN output: mn_amt_taxable_income: 200_000 + +- name: MN AMT taxable income leaves out railroad retirement benefits + period: 2024 + input: + people: + head: + age: 70 + taxable_railroad_pension_benefits: 64_255 + spouse: + age: 70 + taxable_railroad_pension_benefits: 64_255 + tax_units: + tax_unit: + members: [head, spouse] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MN + output: + # Federal AGI = 2 x 64,255 = 128,510, all of it the taxable rest of the + # railroad annuity (IRC 72(r)). 2024 Schedule M1MT line 18 subtracts + # Schedule M1M line 17, the Railroad Retirement Board benefits in federal + # AGI, and 45 U.S.C. 231m(a) bars any state tax on them: + # 128,510 - 128,510 = 0, so no AMT. + filing_status: JOINT + adjusted_gross_income: 128_510 + mn_amt_taxable_income: 0 + mn_amt: 0 + mn_income_tax_before_credits: 0 + +- name: MN AMT taxable income subtracts railroad retirement benefits from a wage earner's AGI + period: 2024 + input: + people: + head: + age: 50 + employment_income: 300_000 + taxable_railroad_pension_benefits: 100_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MN + output: + # Federal AGI = 300,000 + 100,000 = 400,000; M1MT line 18 subtracts the + # 100,000 of railroad benefits: 400,000 - 100,000 = 300,000. + adjusted_gross_income: 400_000 + mn_amt_taxable_income: 300_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml index ec86404383c..a5fac09e371 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml @@ -36,7 +36,7 @@ output: pa_property_tax_or_rent_rebate_income: 20_000 -- name: Case 3, SSI and railroad benefits are half-counted. +- name: Case 3, SSI and railroad tier 1 benefits are half-counted. period: 2025 input: people: @@ -44,7 +44,7 @@ age: 65 ssi: 2_000 pa_ssp: 0 - railroad_benefits: 10_000 + railroad_social_security_equivalent_benefits: 10_000 tax_units: tax_unit: members: [person1] @@ -55,6 +55,55 @@ output: pa_property_tax_or_rent_rebate_income: 6_000 +- name: Case 3a, railroad tier 2 benefits count in full and once. + period: 2025 + input: + people: + person1: + age: 65 + railroad_social_security_equivalent_benefits: 10_000 + taxable_railroad_pension_benefits: 10_000 + tax_exempt_railroad_pension_benefits: 2_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: PA + output: + # Combined income = 10,000 + 10,000 / 2 = 15,000, below the 25,000 base + # amount, so none of the tier 1 benefits is taxable (IRC 86) and federal + # AGI is the 10,000 taxable part of tier 2 (IRC 72(r)). + # PA-1000 line 5: half of tier 1 = 5,000. + # Line 6: gross tier 2 = 10,000 + 2,000 = 12,000. + adjusted_gross_income: 10_000 + pa_property_tax_or_rent_rebate_income: 17_000 + +- name: Case 3b, taxable railroad tier 1 benefits give way to half of the total. + period: 2025 + input: + people: + person1: + age: 65 + railroad_social_security_equivalent_benefits: 20_000 + taxable_railroad_pension_benefits: 30_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: PA + output: + # Combined income = 30,000 + 20,000 / 2 = 40,000. IRC 86(a)(2): the lesser + # of 85% x 20,000 = 17,000 and 85% x (40,000 - 34,000) + 4,500 = 9,600, + # so 9,600 of tier 1 is taxable and federal AGI is 39,600. + # PA-1000: 39,600 - 9,600 + 20,000 / 2 (line 5) = 40,000, which is line 5 + # (10,000) plus line 6 (30,000). + adjusted_gross_income: 39_600 + pa_property_tax_or_rent_rebate_income: 40_000 + - name: Case 4, tax-exempt interest and retirement income are included. period: 2025 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/railroad_retirement_benefits.yaml b/policyengine_us/tests/policy/baseline/gov/states/railroad_retirement_benefits.yaml index d61adec3aec..95401043afb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/railroad_retirement_benefits.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/railroad_retirement_benefits.yaml @@ -9,7 +9,9 @@ # security equivalent benefits and $25,000 of the taxable rest of the annuity. # Combined income = 25,000 + 10,000 = 35,000, so IRC 86(a)(2) makes # 0.85 * 1,000 + 4,500 = 5,350 of the tier 1 benefits taxable, and federal AGI -# is 25,000 + 5,350 = 30,350. None of it is state income. +# is 25,000 + 5,350 = 30,350. None of it is state income, and the state's +# withholding estimate, which feeds the federal deduction for state and +# local income taxes, counts none of it. - name: Railroad retirement benefits are not AL income. period: 2025 @@ -28,6 +30,7 @@ state_code: AL output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not AZ income. @@ -47,6 +50,7 @@ state_code: AZ output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not AR income. @@ -66,6 +70,7 @@ state_code: AR output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not CA income. @@ -85,6 +90,7 @@ state_code: CA output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not CO income. @@ -104,6 +110,7 @@ state_code: CO output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not CT income. @@ -123,6 +130,7 @@ state_code: CT output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not DE income. @@ -142,6 +150,7 @@ state_code: DE output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not DC income. @@ -161,6 +170,7 @@ state_code: DC output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not GA income. @@ -180,6 +190,7 @@ state_code: GA output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not HI income. @@ -199,6 +210,7 @@ state_code: HI output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not ID income. @@ -218,6 +230,7 @@ state_code: ID output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 # The $10 permanent building fund tax falls on every required filer # (Idaho Code 63-3082), so the test checks taxable income. id_taxable_income: 0 @@ -239,6 +252,7 @@ state_code: IL output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not IN income. @@ -258,6 +272,7 @@ state_code: IN output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not IA income. @@ -277,6 +292,7 @@ state_code: IA output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not KS income. @@ -296,6 +312,7 @@ state_code: KS output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not KY income. @@ -315,6 +332,7 @@ state_code: KY output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not LA income. @@ -334,6 +352,7 @@ state_code: LA output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not ME income. @@ -353,6 +372,7 @@ state_code: ME output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not MD income. @@ -372,6 +392,7 @@ state_code: MD output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not MA income. @@ -391,6 +412,7 @@ state_code: MA output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not MI income. @@ -410,6 +432,7 @@ state_code: MI output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not MN income. @@ -429,6 +452,7 @@ state_code: MN output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not MS income. @@ -448,6 +472,7 @@ state_code: MS output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not MO income. @@ -467,6 +492,7 @@ state_code: MO output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not MT income. @@ -486,6 +512,7 @@ state_code: MT output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not NE income. @@ -505,6 +532,7 @@ state_code: NE output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not NJ income. @@ -524,6 +552,7 @@ state_code: NJ output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not NM income. @@ -543,6 +572,7 @@ state_code: NM output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not NY income. @@ -562,6 +592,7 @@ state_code: NY output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not NC income. @@ -581,6 +612,7 @@ state_code: NC output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not ND income. @@ -600,6 +632,7 @@ state_code: ND output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not OH income. @@ -619,6 +652,7 @@ state_code: OH output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not OK income. @@ -638,6 +672,7 @@ state_code: OK output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not OR income. @@ -657,6 +692,7 @@ state_code: OR output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not PA income. @@ -676,6 +712,7 @@ state_code: PA output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not RI income. @@ -695,6 +732,7 @@ state_code: RI output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not SC income. @@ -714,6 +752,7 @@ state_code: SC output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not UT income. @@ -733,6 +772,7 @@ state_code: UT output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not VT income. @@ -752,6 +792,7 @@ state_code: VT output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not VA income. @@ -771,6 +812,7 @@ state_code: VA output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not WV income. @@ -790,6 +832,7 @@ state_code: WV output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 - name: Railroad retirement benefits are not WI income. @@ -809,4 +852,5 @@ state_code: WI output: adjusted_gross_income: 30_350 + state_withheld_income_tax: 0 state_income_tax_before_refundable_credits: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief.yaml index 4471bfc9683..99080a3140f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief.yaml @@ -232,3 +232,28 @@ output: self_employed_pension_contributions: 72_000 ut_homeowner_renter_relief_nontaxable_income: 72_000 + +- name: Railroad retirement benefits count once in household income. + period: 2025 + input: + people: + head: + age: 70 + railroad_social_security_equivalent_benefits: 6_000 + taxable_railroad_pension_benefits: 10_000 + tax_exempt_railroad_pension_benefits: 2_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: UT + output: + # Combined income = 10,000 + 6,000 / 2 = 13,000, below the 25,000 base + # amount, so none of the tier 1 benefits is taxable (IRC 86) and federal + # AGI is the 10,000 taxable rest of the annuity (IRC 72(r)). The + # nontaxable income adds the other 6,000 + 2,000 = 8,000, so household + # income holds each dollar of the 18,000 of benefits once. + adjusted_gross_income: 10_000 + ut_homeowner_renter_relief_household_income: 18_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_amt.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_amt.yaml index 59a67058207..fc5a99442f2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_amt.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/vt_amt.yaml @@ -15,3 +15,47 @@ state_code: VT output: vt_amt: 0 + +- name: Vermont minimum tax leaves out railroad retirement benefits + period: 2025 + input: + people: + head: + age: 70 + taxable_railroad_pension_benefits: 262_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: VT + output: + # Federal AGI = 262,000 exceeds 150,000, so 32 V.S.A. 5822(a)(6) applies, + # but 45 U.S.C. 231m(a) bars any state tax on the railroad annuity: + # 3% x (262,000 - 262,000) = 0. + adjusted_gross_income: 262_000 + vt_amt: 0 + vt_income_tax_before_non_refundable_credits: 0 + +- name: Vermont minimum tax applies to federal AGI less railroad retirement benefits + period: 2025 + input: + people: + head: + age: 40 + employment_income: 100_000 + taxable_railroad_pension_benefits: 100_000 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: VT + output: + # Federal AGI = 100,000 + 100,000 = 200,000 exceeds 150,000, so the + # minimum tax applies to the 100,000 that is not railroad benefits: + # 3% x (200,000 - 100,000) = 3,000. + adjusted_gross_income: 200_000 + vt_amt: 3_000 diff --git a/policyengine_us/tests/test_railroad_retirement_taxation.py b/policyengine_us/tests/test_railroad_retirement_taxation.py index 7aed7f059b1..dfaf0910a13 100644 --- a/policyengine_us/tests/test_railroad_retirement_taxation.py +++ b/policyengine_us/tests/test_railroad_retirement_taxation.py @@ -9,9 +9,10 @@ pension to the taxable part of the rest of the railroad annuity leaves federal AGI and federal income tax unchanged. I3. 45 U.S.C. 231m(a): a household whose only income is railroad benefits - owes no state income tax in any state. Idaho's $10 permanent building - fund tax falls on every required filer, so there the test checks that - taxable income is zero. + owes no state income tax in any state, alternative and minimum taxes + included, and no state's withholding estimate counts the benefits. + Idaho's $10 permanent building fund tax falls on every required filer, + so there the test checks that taxable income is zero. I4. For a wage earner under 65, adding railroad benefits leaves the state's AGI unchanged, and in states whose reported AGI includes them it leaves state taxable income unchanged. Colorado and South Carolina start from @@ -35,7 +36,7 @@ The model keeps money in float32, whose step grows with the amount: 2^-7 from $65,536 to $131,072 and 2^-6 from there to $262,144. Two calculations that add the same dollars in a different order can land a few steps apart, -so the comparisons allow eight steps at the largest amount involved, and +so the comparisons allow eight steps at the sum of the income inputs, and never less than a cent. """ @@ -183,7 +184,7 @@ def household(head_share, spouse_share): simulation = Simulation( situation=situation([household(0, 0), household(head_share, spouse_share)]) ) - tol = tolerance(wages + head_benefits + spouse_benefits) + tol = tolerance(wages + head_benefits + (spouse_benefits if joint else 0)) for variable in ( "adjusted_gross_income", "income_tax", @@ -231,11 +232,16 @@ def test_i2_the_rest_of_the_annuity_is_taxed_as_a_pension( @settings(max_examples=6, deadline=None) @given( tier_1=st.integers(min_value=0, max_value=60_000), - tier_2=st.integers(min_value=1, max_value=80_000), + tier_2=st.integers(min_value=1, max_value=300_000), tax_free=st.integers(min_value=0, max_value=5_000), age=st.integers(min_value=18, max_value=90), joint=st.booleans(), ) +# Minnesota's AMT starts from federal AGI (Schedule M1MT line 1) and Vermont's +# minimum tax takes 3% of federal AGI over $150,000 (32 V.S.A. 5822(a)(6)). +# Both bases must leave the benefits out. +@example(tier_1=0, tier_2=64_255, tax_free=0, age=70, joint=True) +@example(tier_1=0, tier_2=262_000, tax_free=0, age=70, joint=False) def test_i3_no_state_taxes_railroad_benefits(tier_1, tier_2, tax_free, age, joint): head = { "age": age, @@ -251,11 +257,13 @@ def test_i3_no_state_taxes_railroad_benefits(tier_1, tier_2, tax_free, age, join assert calc(simulation, "adjusted_gross_income").min() >= tier_2 tax = calc(simulation, "state_income_tax_before_refundable_credits") idaho_taxable_income = calc(simulation, "id_taxable_income") + withholding = calc(simulation, "state_withheld_income_tax") for i, state in enumerate(INCOME_TAX_STATES): if state == "ID": assert idaho_taxable_income[i] == 0, (state, idaho_taxable_income[i]) else: assert abs(tax[i]) < CENT, (state, tax[i]) + assert abs(withholding[i]) < CENT, (state, withholding[i]) @settings(max_examples=6, deadline=None) diff --git a/policyengine_us/variables/gov/states/al/tax/income/al_withheld_income_tax.py b/policyengine_us/variables/gov/states/al/tax/income/al_withheld_income_tax.py index 7828e3d2cd2..2c3e5ce5783 100644 --- a/policyengine_us/variables/gov/states/al/tax/income/al_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/al/tax/income/al_withheld_income_tax.py @@ -10,7 +10,7 @@ class al_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) # Assigning the maximum standard deduction amount p = parameters(period).gov.states.al.tax.income standard_deduction = p.deductions.standard.amount.max["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ar/tax/income/ar_withheld_income_tax.py b/policyengine_us/variables/gov/states/ar/tax/income/ar_withheld_income_tax.py index 2e6cef4ec5e..54dbad02233 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/ar_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/ar_withheld_income_tax.py @@ -10,7 +10,7 @@ class ar_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ar.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard["SINGLE"] diff --git a/policyengine_us/variables/gov/states/az/tax/income/az_withheld_income_tax.py b/policyengine_us/variables/gov/states/az/tax/income/az_withheld_income_tax.py index 055b5ddd47e..fe4ac808109 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/az_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/az/tax/income/az_withheld_income_tax.py @@ -10,7 +10,7 @@ class az_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.az.tax.income standard_deduction = p.deductions.standard.amount["SINGLE"] reduced_agi = max_(agi - standard_deduction, 0) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_withheld_income_tax.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_withheld_income_tax.py index 7d097cea141..c1ddda9d377 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/ca_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_withheld_income_tax.py @@ -10,7 +10,7 @@ class ca_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ca.tax.income standard_deduction = p.deductions.standard.amount["SINGLE"] reduced_agi = max_(agi - standard_deduction, 0) diff --git a/policyengine_us/variables/gov/states/co/tax/income/co_withheld_income_tax.py b/policyengine_us/variables/gov/states/co/tax/income/co_withheld_income_tax.py index 43e14fe0652..c29fe19325e 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/co_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/co/tax/income/co_withheld_income_tax.py @@ -10,7 +10,7 @@ class co_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) # Colorado adopts federal taxable income, so use federal standard deduction. p_irs = parameters(period).gov.irs.deductions.standard standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ct/tax/income/ct_withheld_income_tax.py b/policyengine_us/variables/gov/states/ct/tax/income/ct_withheld_income_tax.py index 8ef2e232e3f..bb2e78d427d 100644 --- a/policyengine_us/variables/gov/states/ct/tax/income/ct_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ct/tax/income/ct_withheld_income_tax.py @@ -10,7 +10,7 @@ class ct_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ct.tax.income # Since Connecticut does not have a standard deduction, we apply the maximum # personal exemption amount diff --git a/policyengine_us/variables/gov/states/dc/tax/income/dc_withheld_income_tax.py b/policyengine_us/variables/gov/states/dc/tax/income/dc_withheld_income_tax.py index 2a0279c875b..3f9226990e2 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/dc_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/dc_withheld_income_tax.py @@ -10,7 +10,7 @@ class dc_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p_irs = parameters(period).gov.irs.deductions.standard # We apply the maximum standard deduction amount standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/de/tax/income/de_withheld_income_tax.py b/policyengine_us/variables/gov/states/de/tax/income/de_withheld_income_tax.py index 74c6a53c855..682b27d83b0 100644 --- a/policyengine_us/variables/gov/states/de/tax/income/de_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/de/tax/income/de_withheld_income_tax.py @@ -10,7 +10,7 @@ class de_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.de.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ga/tax/income/ga_withheld_income_tax.py b/policyengine_us/variables/gov/states/ga/tax/income/ga_withheld_income_tax.py index e824290e144..fb0114f7c14 100644 --- a/policyengine_us/variables/gov/states/ga/tax/income/ga_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ga/tax/income/ga_withheld_income_tax.py @@ -10,7 +10,7 @@ class ga_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ga.tax.income # We apply the base standard deduction amount personal_exemptions = p.deductions.standard.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/hi/tax/income/hi_withheld_income_tax.py b/policyengine_us/variables/gov/states/hi/tax/income/hi_withheld_income_tax.py index 1f8b2a819ba..1a20c3a640f 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/hi_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/hi_withheld_income_tax.py @@ -10,7 +10,7 @@ class hi_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.hi.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ia/tax/income/ia_withheld_income_tax.py b/policyengine_us/variables/gov/states/ia/tax/income/ia_withheld_income_tax.py index 7709214b199..b292d2d58de 100644 --- a/policyengine_us/variables/gov/states/ia/tax/income/ia_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ia/tax/income/ia_withheld_income_tax.py @@ -10,7 +10,7 @@ class ia_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ia.tax.income # We apply the base standard deduction amount if p.deductions.standard.applies_federal: diff --git a/policyengine_us/variables/gov/states/id/tax/income/id_withheld_income_tax.py b/policyengine_us/variables/gov/states/id/tax/income/id_withheld_income_tax.py index 45760166678..c7a872aa7c0 100644 --- a/policyengine_us/variables/gov/states/id/tax/income/id_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/id/tax/income/id_withheld_income_tax.py @@ -10,7 +10,7 @@ class id_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p_irs = parameters(period).gov.irs.deductions.standard # We apply the base IRS standard deduction amount standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/il/tax/income/il_withheld_income_tax.py b/policyengine_us/variables/gov/states/il/tax/income/il_withheld_income_tax.py index 04838f8fbcd..bab78b41b43 100644 --- a/policyengine_us/variables/gov/states/il/tax/income/il_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/il/tax/income/il_withheld_income_tax.py @@ -10,7 +10,7 @@ class il_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.il.tax.income # Since Illinois does not have a standard deduction # we apply the personal exemption amount diff --git a/policyengine_us/variables/gov/states/in/tax/income/in_withheld_income_tax.py b/policyengine_us/variables/gov/states/in/tax/income/in_withheld_income_tax.py index e603b2abbb1..2ec48eb60b3 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/in_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/in/tax/income/in_withheld_income_tax.py @@ -10,7 +10,7 @@ class in_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states["in"].tax.income # Since Indiana does not have a standard deduction, we apply the maximum # personal exemption amount diff --git a/policyengine_us/variables/gov/states/ks/tax/income/ks_withheld_income_tax.py b/policyengine_us/variables/gov/states/ks/tax/income/ks_withheld_income_tax.py index 0ec33ebccad..541bec5eaf3 100644 --- a/policyengine_us/variables/gov/states/ks/tax/income/ks_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ks/tax/income/ks_withheld_income_tax.py @@ -10,7 +10,7 @@ class ks_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ks.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard.base_amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ky/tax/income/ky_withheld_income_tax.py b/policyengine_us/variables/gov/states/ky/tax/income/ky_withheld_income_tax.py index b82b31651f0..d17df6ad5aa 100644 --- a/policyengine_us/variables/gov/states/ky/tax/income/ky_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ky/tax/income/ky_withheld_income_tax.py @@ -10,7 +10,7 @@ class ky_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ky.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard diff --git a/policyengine_us/variables/gov/states/la/tax/income/la_withheld_income_tax.py b/policyengine_us/variables/gov/states/la/tax/income/la_withheld_income_tax.py index ea7f5634908..74a1c39f3b2 100644 --- a/policyengine_us/variables/gov/states/la/tax/income/la_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/la/tax/income/la_withheld_income_tax.py @@ -10,7 +10,7 @@ class la_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p_irs = parameters(period).gov.irs.deductions.standard # We apply the base IRS standard deduction amount standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ma/tax/income/ma_withheld_income_tax.py b/policyengine_us/variables/gov/states/ma/tax/income/ma_withheld_income_tax.py index ed52c9253fe..05ab497c58b 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/ma_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/ma_withheld_income_tax.py @@ -10,7 +10,7 @@ class ma_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ma.tax.income # Since Massachusetts does not have a standard deduction, we apply the # base personal exemption amount diff --git a/policyengine_us/variables/gov/states/md/tax/income/md_withheld_income_tax.py b/policyengine_us/variables/gov/states/md/tax/income/md_withheld_income_tax.py index d42abd83fc0..f4049f3fa97 100644 --- a/policyengine_us/variables/gov/states/md/tax/income/md_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/md/tax/income/md_withheld_income_tax.py @@ -10,7 +10,7 @@ class md_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.md.tax.income # We apply the maximum standard deduction amount standard_deduction = p.deductions.standard.max["SINGLE"] diff --git a/policyengine_us/variables/gov/states/me/tax/income/me_withheld_income_tax.py b/policyengine_us/variables/gov/states/me/tax/income/me_withheld_income_tax.py index 141b7bdd8a4..0820c721c03 100644 --- a/policyengine_us/variables/gov/states/me/tax/income/me_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/me/tax/income/me_withheld_income_tax.py @@ -10,7 +10,7 @@ class me_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p_irs = parameters(period).gov.irs.deductions.standard # We apply the base IRS standard deduction amount standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/mi/tax/income/mi_withheld_income_tax.py b/policyengine_us/variables/gov/states/mi/tax/income/mi_withheld_income_tax.py index 7d3b59eeb26..518b476fb39 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/mi_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/mi_withheld_income_tax.py @@ -10,7 +10,7 @@ class mi_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.mi.tax.income # The MI standard deduction only applys for elderly # Wo do not apply deductions here diff --git a/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py b/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py index 874312cee15..5ad12cf893a 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py @@ -12,12 +12,17 @@ class mn_amt_taxable_income(Variable): "https://www.revenue.state.mn.us/sites/default/files/2023-01/m1mt_22.pdf", # 2024 Schedule M1MT, line 12 "https://www.revenue.state.mn.us/sites/default/files/2025-07/m1mt-24.pdf#page=1", + # 2024 Schedule M1MT instructions, line 18: adds Schedule M1M line 17, + # Railroad Retirement Board benefits. + "https://www.revenue.state.mn.us/sites/default/files/2025-07/m1mt-24.pdf#page=2", # 2020 Schedule M1MT instructions, line 4: includes Schedule M1NC # line 6, the federal charitable deduction for non-itemizers. "https://www.revenue.state.mn.us/sites/default/files/2023-02/m1mt_20.pdf#page=2", # Laws 2023, ch. 1, sec. 22(c)(2) and (d)(2): the addition applies # to alternative minimum taxable income. "https://www.revisor.mn.gov/laws/2023/0/Session+Law/Chapter/1/", + # No state may tax Railroad Retirement Act annuities. + "https://www.law.cornell.edu/uscode/text/45/231m#a", ) defined_for = StateCode.MN @@ -49,6 +54,10 @@ def formula(tax_unit, period, parameters): "mn_charity_subtraction", "mn_social_security_subtraction", "mn_military_pension_subtraction", + # M1MT line 18 takes Schedule M1M line 17, the Railroad + # Retirement Board benefits in federal AGI; 45 U.S.C. 231m(a) + # bars any state tax on them. + "taxable_railroad_benefits", ] amt_subs = add(tax_unit, period, AMT_SUBTRACTIONS) return fagi + charitable_addition - some_itm_deds - amt_subs diff --git a/policyengine_us/variables/gov/states/mn/tax/income/mn_withheld_income_tax.py b/policyengine_us/variables/gov/states/mn/tax/income/mn_withheld_income_tax.py index 02de7f721c0..005ba430a30 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/mn_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/mn_withheld_income_tax.py @@ -10,7 +10,7 @@ class mn_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.mn.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard.base["SINGLE"] diff --git a/policyengine_us/variables/gov/states/mo/tax/income/mo_withheld_income_tax.py b/policyengine_us/variables/gov/states/mo/tax/income/mo_withheld_income_tax.py index e2d8617e238..78bd9f7ed4b 100644 --- a/policyengine_us/variables/gov/states/mo/tax/income/mo_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/mo/tax/income/mo_withheld_income_tax.py @@ -10,7 +10,7 @@ class mo_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p_irs = parameters(period).gov.irs.deductions.standard # We apply the base IRS standard deduction amount standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ms/tax/income/ms_withheld_income_tax.py b/policyengine_us/variables/gov/states/ms/tax/income/ms_withheld_income_tax.py index 5385c9c176d..a072bbfec22 100644 --- a/policyengine_us/variables/gov/states/ms/tax/income/ms_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ms/tax/income/ms_withheld_income_tax.py @@ -10,7 +10,7 @@ class ms_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ms.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_withheld_income_tax.py b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_withheld_income_tax.py index cdc6b807737..6a734540c74 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_withheld_income_tax.py @@ -10,7 +10,7 @@ class mt_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.mt.tax.income # We apply the maximum standard deduction amount standard_deduction = p.deductions.standard.cap["SINGLE"] diff --git a/policyengine_us/variables/gov/states/nc/tax/income/nc_withheld_income_tax.py b/policyengine_us/variables/gov/states/nc/tax/income/nc_withheld_income_tax.py index b6ee21caa6d..00a93bce26f 100644 --- a/policyengine_us/variables/gov/states/nc/tax/income/nc_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/nc/tax/income/nc_withheld_income_tax.py @@ -10,7 +10,7 @@ class nc_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.nc.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/nd/tax/income/nd_withheld_income_tax.py b/policyengine_us/variables/gov/states/nd/tax/income/nd_withheld_income_tax.py index c74dd0caff1..171617f7470 100644 --- a/policyengine_us/variables/gov/states/nd/tax/income/nd_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/nd/tax/income/nd_withheld_income_tax.py @@ -10,7 +10,7 @@ class nd_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p_irs = parameters(period).gov.irs.deductions.standard # We apply the base IRS standard deduction amount standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ne/tax/income/ne_withheld_income_tax.py b/policyengine_us/variables/gov/states/ne/tax/income/ne_withheld_income_tax.py index a3a5e2dfdb8..0d8b62b782f 100644 --- a/policyengine_us/variables/gov/states/ne/tax/income/ne_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ne/tax/income/ne_withheld_income_tax.py @@ -10,7 +10,7 @@ class ne_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ne.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard.base_amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/nj/tax/income/nj_withheld_income_tax.py b/policyengine_us/variables/gov/states/nj/tax/income/nj_withheld_income_tax.py index d363def3e0f..d0627b21b4a 100644 --- a/policyengine_us/variables/gov/states/nj/tax/income/nj_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/nj/tax/income/nj_withheld_income_tax.py @@ -10,7 +10,7 @@ class nj_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.nj.tax.income # Since New Jersey does not have a standard deduction # we apply the regular personal exemption amount diff --git a/policyengine_us/variables/gov/states/nm/tax/income/nm_withheld_income_tax.py b/policyengine_us/variables/gov/states/nm/tax/income/nm_withheld_income_tax.py index 2913ee8ef32..1463423a1ff 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/nm_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/nm_withheld_income_tax.py @@ -10,7 +10,7 @@ class nm_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p_irs = parameters(period).gov.irs.deductions.standard # We apply the base IRS standard deduction amount standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ny/tax/income/ny_withheld_income_tax.py b/policyengine_us/variables/gov/states/ny/tax/income/ny_withheld_income_tax.py index 45719a0dba5..3265cd264ff 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/ny_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/ny_withheld_income_tax.py @@ -10,7 +10,7 @@ class ny_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ny.tax.income # We apply the base standard deduction amount standard_deduction = p.deductions.standard.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/oh/tax/income/oh_withheld_income_tax.py b/policyengine_us/variables/gov/states/oh/tax/income/oh_withheld_income_tax.py index ea9f3888835..50242c4984a 100644 --- a/policyengine_us/variables/gov/states/oh/tax/income/oh_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/oh/tax/income/oh_withheld_income_tax.py @@ -10,7 +10,7 @@ class oh_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.oh.tax.income exempt = agi <= p.agi_threshold # Since Ohio does not have a standard deduction, we apply the diff --git a/policyengine_us/variables/gov/states/ok/tax/income/ok_withheld_income_tax.py b/policyengine_us/variables/gov/states/ok/tax/income/ok_withheld_income_tax.py index c897bb2f260..f5b05b90334 100644 --- a/policyengine_us/variables/gov/states/ok/tax/income/ok_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ok/tax/income/ok_withheld_income_tax.py @@ -10,7 +10,7 @@ class ok_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ok.tax.income standard_deduction = p.deductions.standard.amount["SINGLE"] reduced_agi = max_(agi - standard_deduction, 0) diff --git a/policyengine_us/variables/gov/states/or/tax/income/or_withheld_income_tax.py b/policyengine_us/variables/gov/states/or/tax/income/or_withheld_income_tax.py index 955ff3ae4ab..179244e123f 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/or_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/or/tax/income/or_withheld_income_tax.py @@ -10,7 +10,7 @@ class or_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states["or"].tax.income # We apply the maximum standard deduction standard_deduction = p.deductions.standard.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/pa/tax/income/pa_withheld_income_tax.py b/policyengine_us/variables/gov/states/pa/tax/income/pa_withheld_income_tax.py index fda42e111ac..93080f61b81 100644 --- a/policyengine_us/variables/gov/states/pa/tax/income/pa_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/pa/tax/income/pa_withheld_income_tax.py @@ -10,7 +10,7 @@ class pa_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) # Pennsylvania does not have standard deductions, personal exemptions, or itemized deductions. # Also, They do not use the federal standard deduction amounts. p = parameters(period).gov.states.pa.tax.income diff --git a/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py b/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py index 13dd8cab593..a3f06d3e172 100644 --- a/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py +++ b/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py @@ -22,6 +22,11 @@ def formula(tax_unit, period, parameters): adjusted_gross_income = tax_unit("adjusted_gross_income", period) above_the_line_deductions = tax_unit("above_the_line_deductions", period) taxable_social_security = tax_unit("tax_unit_taxable_social_security", period) + # Federal AGI includes taxable tier 1 benefits, which line 5 replaces + # with half of the total. + taxable_railroad_tier_1 = tax_unit( + "tax_unit_taxable_railroad_social_security_equivalent_benefits", period + ) half_counted_benefits = tax_unit.sum( head_or_spouse * add( @@ -34,7 +39,9 @@ def formula(tax_unit, period, parameters): # Medicaid facility residents; the PA-1000 does not say # whether the Department of Revenue treats it as SSP. "pa_ssp", - "railroad_benefits", + # Line 5: half of Railroad Retirement Tier 1 benefits as + # Form RRB-1099 shows them. + "railroad_social_security_equivalent_benefits", ], ) ) @@ -47,6 +54,10 @@ def formula(tax_unit, period, parameters): "tax_exempt_interest_income", "tax_exempt_pension_income", "tax_exempt_retirement_distributions", + # Line 6 takes the gross amount of Tier 2 Railroad + # Retirement benefits; their taxable part is in + # adjusted_gross_income. + "tax_exempt_railroad_pension_benefits", ], ) ) @@ -81,6 +92,7 @@ def formula(tax_unit, period, parameters): adjusted_gross_income + above_the_line_deductions - taxable_social_security + - taxable_railroad_tier_1 + p.benefit_income_rate * half_counted_benefits + tax_exempt_income + pa_1000_line_11_income diff --git a/policyengine_us/variables/gov/states/ri/tax/income/ri_withheld_income_tax.py b/policyengine_us/variables/gov/states/ri/tax/income/ri_withheld_income_tax.py index 1de8c9c8d11..7ecd9958bd1 100644 --- a/policyengine_us/variables/gov/states/ri/tax/income/ri_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ri/tax/income/ri_withheld_income_tax.py @@ -10,7 +10,7 @@ class ri_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.ri.tax.income # We apply the maximum standard deduction standard_deduction = p.deductions.standard.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/sc/tax/income/sc_withheld_income_tax.py b/policyengine_us/variables/gov/states/sc/tax/income/sc_withheld_income_tax.py index f1be877f69a..f9800f2c35b 100644 --- a/policyengine_us/variables/gov/states/sc/tax/income/sc_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/sc/tax/income/sc_withheld_income_tax.py @@ -10,7 +10,7 @@ class sc_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p_irs = parameters(period).gov.irs.deductions.standard # We apply the base IRS standard deduction amount standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/tax/income/state_withholding_agi.py b/policyengine_us/variables/gov/states/tax/income/state_withholding_agi.py new file mode 100644 index 00000000000..dd603ee2727 --- /dev/null +++ b/policyengine_us/variables/gov/states/tax/income/state_withholding_agi.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class state_withholding_agi(Variable): + value_type = float + entity = Person + label = "Federal AGI that state income tax withholding reaches" + unit = USD + documentation = ( + "Each person's federal adjusted gross income " + "(adjusted_gross_income_person) less the Railroad Retirement Act " + "benefits it includes (taxable_railroad_benefits). 45 U.S.C. 231m(a) " + "bars any state tax on those annuities. The state withholding " + "estimates (the *_withheld_income_tax variables) start from this " + "amount, and they feed the federal deduction for state and local " + "income taxes." + ) + definition_period = YEAR + reference = "https://www.law.cornell.edu/uscode/text/45/231m#a" + + def formula(person, period, parameters): + agi = person("adjusted_gross_income_person", period) + railroad = person("taxable_railroad_benefits", period) + return agi - railroad diff --git a/policyengine_us/variables/gov/states/ut/tax/income/ut_withheld_income_tax.py b/policyengine_us/variables/gov/states/ut/tax/income/ut_withheld_income_tax.py index a0be0b6cb4f..7e009522549 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/ut_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/ut_withheld_income_tax.py @@ -10,7 +10,7 @@ class ut_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p_irs = parameters(period).gov.irs.deductions.standard # We apply the maximum standard deduction amount standard_deduction = p_irs.amount["SINGLE"] diff --git a/policyengine_us/variables/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief_nontaxable_income.py b/policyengine_us/variables/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief_nontaxable_income.py index 3ea3d1d381b..8eaac61c950 100644 --- a/policyengine_us/variables/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief_nontaxable_income.py +++ b/policyengine_us/variables/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief_nontaxable_income.py @@ -21,6 +21,13 @@ def formula(person, period, parameters): nontaxable_social_security = max_( 0, social_security - ssdi - taxable_social_security ) + # Federal AGI includes the head's and spouse's taxable railroad + # retirement benefits, so the rest of the benefits is added here. + nontaxable_railroad_benefits = max_( + 0, + person("railroad_benefits", period) + - person("taxable_railroad_benefits", period), + ) total_unemployment_compensation = person( "total_unemployment_compensation", period ) @@ -34,5 +41,6 @@ def formula(person, period, parameters): return ( listed_sources + nontaxable_social_security + + nontaxable_railroad_benefits + nontaxable_unemployment_compensation ) diff --git a/policyengine_us/variables/gov/states/va/tax/income/va_withheld_income_tax.py b/policyengine_us/variables/gov/states/va/tax/income/va_withheld_income_tax.py index 2a60af817ab..442ea82e849 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/va_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/va/tax/income/va_withheld_income_tax.py @@ -10,7 +10,7 @@ class va_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.va.tax.income standard_deduction = p.deductions.standard["SINGLE"] reduced_agi = max_(agi - standard_deduction, 0) diff --git a/policyengine_us/variables/gov/states/vt/tax/income/vt_amt.py b/policyengine_us/variables/gov/states/vt/tax/income/vt_amt.py index 30ad06cf944..95563a5a1a4 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/vt_amt.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/vt_amt.py @@ -11,6 +11,7 @@ class vt_amt(Variable): "https://legislature.vermont.gov/statutes/section/32/151/05822", # Vermont §5822. Tax on income of individuals, estates, and trusts (a) "https://tax.vermont.gov/sites/tax/files/documents/RateSched-2021.pdf#page=1", # Vermont 2021 Income Tax Rate Schedules "https://tax.vermont.gov/sites/tax/files/documents/RateSched-2022.pdf#page=1", # Vermont 2022 Income Tax Return Booklet Forms and Instructions + "https://www.law.cornell.edu/uscode/text/45/231m#a", # No state tax on Railroad Retirement Act annuities ) defined_for = StateCode.VT @@ -23,6 +24,11 @@ def formula(tax_unit, period, parameters): # not in the legal code (Vermont §5822 (a)(6)). federal_agi = tax_unit("adjusted_gross_income", period) us_govt_interest = tax_unit("us_govt_interest", period) + # 45 U.S.C. 231m(a) bars any state tax on Railroad Retirement Act + # annuities, so the percentage applies to federal AGI less the + # benefits it includes. The threshold test still uses federal AGI, + # as section 5822(a)(6) writes it. + railroad = add(tax_unit, period, ["taxable_railroad_benefits"]) amt_rate = p.amt.calc(federal_agi) - base_amt = amt_rate * federal_agi + base_amt = amt_rate * max_(federal_agi - railroad, 0) return max_(base_amt - us_govt_interest, 0) diff --git a/policyengine_us/variables/gov/states/vt/tax/income/vt_withheld_income_tax.py b/policyengine_us/variables/gov/states/vt/tax/income/vt_withheld_income_tax.py index 222be10482b..90549b62d03 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/vt_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/vt_withheld_income_tax.py @@ -10,7 +10,7 @@ class vt_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.vt.tax.income # We apply the base standard deduction amount personal_exmptions = p.deductions.standard.base["SINGLE"] diff --git a/policyengine_us/variables/gov/states/wi/tax/income/wi_withheld_income_tax.py b/policyengine_us/variables/gov/states/wi/tax/income/wi_withheld_income_tax.py index a55ad1d531d..d9b3794cd6c 100644 --- a/policyengine_us/variables/gov/states/wi/tax/income/wi_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/wi/tax/income/wi_withheld_income_tax.py @@ -10,7 +10,7 @@ class wi_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.wi.tax.income # We apply the maximum standard deduction amount standard_deduction = p.deductions.standard.max["SINGLE"] diff --git a/policyengine_us/variables/gov/states/wv/tax/income/wv_withheld_income_tax.py b/policyengine_us/variables/gov/states/wv/tax/income/wv_withheld_income_tax.py index 2a26dbffea1..92373af2533 100644 --- a/policyengine_us/variables/gov/states/wv/tax/income/wv_withheld_income_tax.py +++ b/policyengine_us/variables/gov/states/wv/tax/income/wv_withheld_income_tax.py @@ -10,7 +10,7 @@ class wv_withheld_income_tax(Variable): definition_period = YEAR def formula(person, period, parameters): - agi = person("adjusted_gross_income_person", period) + agi = person("state_withholding_agi", period) p = parameters(period).gov.states.wv.tax.income # Since West Virginia does not have a standard deduction, we apply the maximum # personal exemption amount From 38d50fbde3105ed55414bb46c94fa754eb7d4af0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 15:15:35 -0500 Subject: [PATCH 11/13] Document the veteran flag and VA disability routing; correct notes - veterans_benefits: is_veteran reads this input, so a survivor or dependent whose VA payments go here should set is_veteran to false. - disability_benefits: VA disability compensation goes in veterans_benefits. - SSI unearned sources note: household net income now counts railroad benefits; SNAP, HUD and the other program lists still don't (#10551). - Parity test docstring: list the two new invariants, and say the veterans' income tax check covers filers under 65 without a disability, since the model doesn't yet reduce the section 22 credit by VA pensions (26 U.S.C. 22(c)(3)(A)(i)(III)). - Sales tax test comment: the table gives the amount a filer may deduct. Co-Authored-By: Claude Opus 5.5 --- .../gov/ssa/ssi/income/sources/unearned.yaml | 6 +++--- .../salt_and_real_estate/state_sales_tax.yaml | 4 ++-- .../tests/test_household_spm_resource_parity.py | 13 +++++++++++-- .../income/person/general/disability_benefits.py | 3 ++- .../income/person/general/veterans_benefits.py | 4 +++- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/policyengine_us/parameters/gov/ssa/ssi/income/sources/unearned.yaml b/policyengine_us/parameters/gov/ssa/ssi/income/sources/unearned.yaml index 9492706d3be..f71c3c18244 100644 --- a/policyengine_us/parameters/gov/ssa/ssi/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/ssa/ssi/income/sources/unearned.yaml @@ -4,9 +4,9 @@ values: # (a) Annuities, pensions, and other periodic payments. - pension_income - social_security - # NOTE: Household net income, SNAP, HUD annual income and other program - # income lists count social_security and ssi but not railroad_benefits, so - # an SSI recipient's railroad annuity drops out of them. + # NOTE: SNAP, HUD annual income and other program income lists count + # social_security and ssi but not railroad_benefits, so an SSI recipient's + # railroad annuity drops out of them. # https://github.com/PolicyEngine/policyengine-us/issues/10551 - railroad_benefits - disability_benefits diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml index 25ac620ce63..6bc45d0f98c 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/itemized/salt_and_real_estate/state_sales_tax.yaml @@ -1537,8 +1537,8 @@ # Form 1040 line 11b plus "any nontaxable items", veterans' benefits among # them, sets the row (2025 Instructions for Schedule A, page 5). In the 2025 -# table, a Texas family of one owes $931 in the $100,000-$120,000 row and $992 -# in the $120,000-$140,000 row (page 16). Veterans' benefits stay out of AGI +# table, a Texas family of one may deduct $931 in the $100,000-$120,000 row +# and $992 in the $120,000-$140,000 row (page 16). Veterans' benefits stay out of AGI # under 38 U.S.C. 5301(a)(1) but raise this deduction for a filer who # itemizes and deducts sales tax. - name: A Texas filer with $100,000 of AGI uses the $100,000-$120,000 row, 2025 diff --git a/policyengine_us/tests/test_household_spm_resource_parity.py b/policyengine_us/tests/test_household_spm_resource_parity.py index 2ab3a21d9d7..8dc1141712b 100644 --- a/policyengine_us/tests/test_household_spm_resource_parity.py +++ b/policyengine_us/tests/test_household_spm_resource_parity.py @@ -4,7 +4,7 @@ cash. Each total keeps its own list of what it adds, in two different files, and the lists drift: a cash program reaches one total and not the other. -Six invariants hold for every input. +Eight invariants hold for every input. 1. The list difference is exactly the documented one. Every program that one total counts and the other does not appears below with its reason. @@ -20,6 +20,12 @@ means-tested benefits to lose. 6. The head and spouse together hold a tax unit's whole Form 4797 amount, with its sign, and gross income holds its positive part. +7. For a household with no means-tested benefits to lose, a dollar of veterans' + or railroad benefits adds a dollar to both benefit totals and to market + income plus benefits. +8. Veterans' benefits never change federal gross income or AGI, and they leave + federal income tax unchanged for a filer under 65 without a disability who + doesn't itemize. Child care stays out of this file: household_state_benefits and the SPM treat subsidies differently on purpose, and #9405 and #10569 cover that difference. @@ -454,7 +460,10 @@ def test_veterans_benefits_stay_out_of_federal_gross_income(amount, earnings, st The IRS still counts them as income for the optional sales tax tables (state_sales_tax.yaml), so a filer who itemizes and deducts sales tax can owe less federal income tax with them. The test checks income tax only - when neither household itemizes. + when neither household itemizes. The person is 40 and has no disability, + so the credit for the elderly or disabled doesn't apply. 26 U.S.C. + 22(c)(3)(A)(i)(III) reduces that credit by VA pensions, and the model + doesn't yet. """ year = 2026 zero = Simulation( diff --git a/policyengine_us/variables/household/income/person/general/disability_benefits.py b/policyengine_us/variables/household/income/person/general/disability_benefits.py index c7d6b7624e8..b30e82abe62 100644 --- a/policyengine_us/variables/household/income/person/general/disability_benefits.py +++ b/policyengine_us/variables/household/income/person/general/disability_benefits.py @@ -9,7 +9,8 @@ class disability_benefits(Variable): documentation = ( "Disability benefits from employment (not Social Security), except for " "worker's compensation. Includes both the taxable and the tax-exempt part; " - "see taxable_disability_benefits and tax_exempt_disability_benefits." + "see taxable_disability_benefits and tax_exempt_disability_benefits. " + "VA disability compensation goes in veterans_benefits." ) definition_period = YEAR uprating = "calibration.gov.irs.soi.employment_income" diff --git a/policyengine_us/variables/household/income/person/general/veterans_benefits.py b/policyengine_us/variables/household/income/person/general/veterans_benefits.py index 644dfd0fcfd..631bc7b0ab4 100644 --- a/policyengine_us/variables/household/income/person/general/veterans_benefits.py +++ b/policyengine_us/variables/household/income/person/general/veterans_benefits.py @@ -17,7 +17,9 @@ class veterans_benefits(Variable): "gross income leaves both out. Census money income counts VA payments " "as veterans' payments, and household and SPM benefits include this " "variable. Taxable military retirement pay goes in " - "military_retirement_pay instead." + "military_retirement_pay instead. The model marks anyone with " + "veterans_benefits as a veteran (is_veteran), so set is_veteran to " + "false for a survivor or dependent whose VA payments go here." ) definition_period = YEAR uprating = "gov.ssa.uprating" From 14f9d531f182ae38be59bc81bc0d2de9ea03378e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 16:02:25 -0500 Subject: [PATCH 12/13] Count railroad benefits as each state's form does; WATCA, TAXSIM, TOB Answers the federal, states and tests reviews of 033d78ed1b. States: - West Virginia: box (c) of Schedule M line 47 leaves out income reported on lines 35 to 48, and filers subtract railroad benefits on line 40, so taxable_railroad_benefits joins the excluded sources. - Arizona: the 2025 Form 140PTC instructions (page 4) leave railroad retirement benefits out of the property tax credit's income. Subtract the head's and spouse's taxable_railroad_benefits from federal AGI and leave both railroad sources out of a dependent's income. - Oregon: ORS 316.157(2)(f) counts tier 1 benefits as Social Security benefits and (2)(b) leaves them out of household income. Subtract the taxable SSEB from household income and add the SSEB to line 5. - Minnesota: Minn. Stat. 290.0802 subd. 1(a) and (c). Subtract taxable_railroad_benefits from the elderly and disabled subtraction's AGI and reduce its base by the head's and spouse's railroad_benefits. Federal: - WATCA's alternative tax MAGI adds the nontaxable SSEB (S.4083 section 1A adds the section 86(d) benefits gross income leaves out). - taxsim_gssi counts the SSEB, so taxsim_v12 reruns section 86 on both. - Reject a negative or non-finite SSEB, as the model rejects negative title II benefits; the model doesn't net amounts as Publication 915 does. - The TOB variables document that they leave out HI's 1993-amendment share of the tax on the SSEB (section 121(e)(1)(B), (3)(C)). - Uprate the railroad pension inputs with SOI pension income. - Document that an entered railroad_benefits total replaces the parts, reaches no tax and applies only to its year. Tests: a YAML case for each fix and a WATCA and TAXSIM case; I1 checks the section 22 amount with an age-70 example; I3 draws 2025 and 2026; I5 checks each spouse's share of the taxable SSEB; a negative and NaN SSEB test; the sales tax test lists both untaxed railroad items. The added inputs move to an added changelog fragment. Co-Authored-By: Claude Opus 5.5 --- changelog.d/rrb-federal-tax.added.md | 1 + changelog.d/rrb-federal-tax.fixed.md | 2 +- .../excluded_income_sources.yaml | 2 + .../watca/working_americans_tax_cut_act.py | 8 +- .../income/az_property_tax_credit_agi.yaml | 30 +++++ .../mn_elderly_disabled_subtraction.yaml | 25 ++++ .../or_retirement_credit.yaml | 33 +++++ ...r_citizen_disability_deduction_person.yaml | 15 +++ .../tests/policy/contrib/congress/watca.yaml | 26 ++++ .../policy/contrib/taxsim/taxsim_v12.yaml | 25 ++++ .../test_railroad_retirement_taxation.py | 116 ++++++++++++++---- .../tests/test_state_sales_tax_table.py | 6 +- .../variables/contrib/taxsim/taxsim_gssi.py | 8 +- ...oad_social_security_equivalent_benefits.py | 25 +++- .../ssa/revenue/tob_revenue_medicare_hi.py | 9 +- .../gov/ssa/revenue/tob_revenue_oasdi.py | 7 +- .../gov/ssa/revenue/tob_revenue_total.py | 9 +- .../az_property_tax_credit_agi.py | 16 ++- ...az_property_tax_credit_dependent_income.py | 14 ++- .../mn_elderly_disabled_subtraction.py | 23 +++- .../retirement_credit/or_retirement_credit.py | 9 +- .../or_retirement_credit_household_income.py | 12 +- .../person/retirement/railroad_benefits.py | 10 +- ...oad_social_security_equivalent_benefits.py | 4 +- .../tax_exempt_railroad_pension_benefits.py | 1 + .../taxable_railroad_pension_benefits.py | 1 + 26 files changed, 379 insertions(+), 58 deletions(-) create mode 100644 changelog.d/rrb-federal-tax.added.md diff --git a/changelog.d/rrb-federal-tax.added.md b/changelog.d/rrb-federal-tax.added.md new file mode 100644 index 00000000000..ec745630257 --- /dev/null +++ b/changelog.d/rrb-federal-tax.added.md @@ -0,0 +1 @@ +Add inputs for the three parts of Railroad Retirement Act benefits: the tier 1 social security equivalent benefit, the taxable rest of the annuity and its tax-free part. diff --git a/changelog.d/rrb-federal-tax.fixed.md b/changelog.d/rrb-federal-tax.fixed.md index f41d60b4fd7..e0582bebdb2 100644 --- a/changelog.d/rrb-federal-tax.fixed.md +++ b/changelog.d/rrb-federal-tax.fixed.md @@ -1 +1 @@ -Tax Railroad Retirement Act benefits federally, with tier 1 social security equivalent benefits under IRC 86 alongside Social Security and the rest of the annuity as a pension under IRC 72(r). Keep them out of every state income tax, Minnesota's and Vermont's minimum taxes and the state withholding estimates, as 45 U.S.C. 231m(a) requires, and count tier 2 benefits in full in Pennsylvania's property tax and rent rebate income. +Tax Railroad Retirement Act benefits federally, with tier 1 social security equivalent benefits under IRC 86 alongside Social Security and the rest of the annuity as a pension under IRC 72(r). Keep them out of every state income tax, Minnesota's and Vermont's minimum taxes and the state withholding estimates, as 45 U.S.C. 231m(a) requires, and count them as each state's rules do in Arizona's property tax credit, Minnesota's elderly and disabled subtraction, Oregon's retirement income credit, West Virginia's senior citizen deduction, Pennsylvania's property tax and rent rebate and Utah's homeowner and renter relief. diff --git a/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/senior_citizen_disability_deduction/excluded_income_sources.yaml b/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/senior_citizen_disability_deduction/excluded_income_sources.yaml index 63c9c52dc00..39887cb8543 100644 --- a/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/senior_citizen_disability_deduction/excluded_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/wv/tax/income/subtractions/senior_citizen_disability_deduction/excluded_income_sources.yaml @@ -12,6 +12,8 @@ values: # removes income within the form's excluded range. Use the contributor's # own amount rather than projecting the tax-unit 529 deduction to spouses. - investment_in_529_plan_indv # Line 39 + # Line 40 subtracts the Railroad Retirement Act benefits in federal AGI. + - taxable_railroad_benefits # Line 40 metadata: unit: list diff --git a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py index 27becb3a73f..e0754486d8c 100644 --- a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py +++ b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py @@ -17,7 +17,12 @@ class watca_alternative_tax_magi(Variable): definition_period = YEAR unit = USD reference = WATCA_REFERENCES - documentation = "Section 1A(d): AGI + foreign income exclusions (sec 911, 931, 933) + non-taxable Social Security." + documentation = ( + "Section 1A(d): AGI + foreign income exclusions (sec 911, 931, " + "933) + the social security benefits, as section 86(d) defines " + "them, that section 86 leaves out of gross income: Social " + "Security and tier 1 railroad retirement benefits." + ) adds = [ "adjusted_gross_income", @@ -25,6 +30,7 @@ class watca_alternative_tax_magi(Variable): "specified_possession_income", "puerto_rico_income", "tax_exempt_social_security", + "tax_exempt_railroad_social_security_equivalent_benefits", ] class watca_surtax_magi(Variable): diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/az_property_tax_credit_agi.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/az_property_tax_credit_agi.yaml index 35f8565d5f3..cd99c105f5d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/az_property_tax_credit_agi.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/az_property_tax_credit_agi.yaml @@ -186,3 +186,33 @@ az_property_tax_credit_agi: 3_000 az_property_tax_credit_household_income: 3_000 az_property_tax_credit: 212 + +- name: Railroad retirement benefits are excluded, a dependent's too. + period: 2025 + input: + people: + head: + age: 70 + railroad_social_security_equivalent_benefits: 10_000 + taxable_railroad_pension_benefits: 3_000 + taxable_interest_income: 2_000 + child: + age: 15 + taxable_railroad_pension_benefits: 1_000 + taxable_interest_income: 500 + tax_units: + tax_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: AZ + output: + # Combined income is 3,000 + 2,000 + 0.5 x 10,000 = 10,000, under the + # $25,000 base, so federal AGI holds 3,000 of tier 2 benefits and 2,000 + # of interest. The 2025 Form 140PTC instructions (page 4) leave railroad + # retirement benefits out of household income. + adjusted_gross_income: 5_000 + az_property_tax_credit_agi: 2_000 + # The child's railroad annuity stays out too; the interest counts. + az_property_tax_credit_dependent_income: 500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_elderly_disabled_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_elderly_disabled_subtraction.yaml index 88fbcdcd603..cfa147adc01 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_elderly_disabled_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_elderly_disabled_subtraction.yaml @@ -120,3 +120,28 @@ state_code: MN output: mn_elderly_disabled_subtraction: 0 # = 12_000 - 0.5 * (42_000 - 18_000) + +- name: Railroad retirement benefits reduce the base and leave AGI. + period: 2025 + input: + people: + person1: + age: 70 + taxable_ira_distributions: 15_000 + railroad_social_security_equivalent_benefits: 1_000 + taxable_railroad_pension_benefits: 2_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Combined income is 17,000 + 0.5 x 1,000 = 17,500, under the $25,000 + # base, so none of the tier 1 benefits enters federal AGI of 17,000. + adjusted_gross_income: 17_000 + # Minn. Stat. 290.0802, subd. 1(a): AGI = 17,000 - 2,000 = 15,000. + # Subd. 1(c) and 2(b)(2): 9,600 - (1,000 + 2,000) - 0.5 x (15,000 - + # 14,500) = 6,350. + mn_elderly_disabled_subtraction: 6_350 diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/retirement_income/or_retirement_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/retirement_income/or_retirement_credit.yaml index 59d020855c0..b1806582003 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/retirement_income/or_retirement_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/retirement_income/or_retirement_credit.yaml @@ -219,3 +219,36 @@ state_code: OR output: or_retirement_credit: 675 + +- name: Tier 1 railroad benefits count as Social Security on lines 5 and 7. + period: 2025 + input: + people: + head: + age: 66 + taxable_private_pension_income: 25_000 + taxable_interest_income: 5_000 + spouse: + age: 66 + railroad_social_security_equivalent_benefits: 4_000 + taxable_railroad_pension_benefits: 3_000 + tax_units: + tax_unit: + members: [head, spouse] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: OR + output: + # Section 86: combined income is 33,000 + 0.5 x 4,000 = 35,000, which + # exceeds the $32,000 joint base by 3,000, so 1,500 of the tier 1 + # benefits enters federal AGI. + adjusted_gross_income: 34_500 + # ORS 316.157(2)(b) and (f): household income leaves out the 1,500. + or_retirement_credit_household_income: 33_000 + # Line 6: 15,000 - 4,000 = 11,000. Line 10: 11,000 - (33,000 - 30,000) + # = 8,000. Line 11: min(25,000, 8,000) x 9% = 720. + or_retirement_credit: 720 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/senior_citizen_disability/wv_senior_citizen_disability_deduction_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/senior_citizen_disability/wv_senior_citizen_disability_deduction_person.yaml index 0db631f2408..f03bb85a31f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/senior_citizen_disability/wv_senior_citizen_disability_deduction_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/senior_citizen_disability/wv_senior_citizen_disability_deduction_person.yaml @@ -53,6 +53,21 @@ wv_subtractions: 6_000 wv_agi: 0 +- name: Railroad retirement benefits on line 40 stay out of box c. + period: 2025 + input: + age: 70 + taxable_private_pension_income: 5_000 + taxable_railroad_pension_benefits: 20_000 + state_code: WV + output: + adjusted_gross_income_person: 25_000 + # Line 40 subtracts the 20,000 of railroad benefits in federal AGI. + # Box (c) = min(25,000 - 20,000, 8,000) = 5,000; box (d) = 0. + wv_senior_citizen_disability_deduction_person: 5_000 + wv_subtractions: 25_000 + wv_agi: 0 + - name: The 2024 line 49 excludes taxable state refunds from box c. period: 2024 input: diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index 2b40103ff7f..c4a942e0d9a 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -353,6 +353,32 @@ # MAGI = 50_000 + (20_000 - 5_000) = 65_000 watca_alternative_tax_magi: 65_000 +- name: Alternative tax MAGI includes non-taxable tier 1 railroad retirement benefits + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + people: + person1: + age: 67 + employment_income: 20_000 + railroad_social_security_equivalent_benefits: 20_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + # Section 86(d)(1) counts the benefit as Social Security. Combined income + # is 20,000 + 0.5 x 20,000 = 30,000, which exceeds the $25,000 base by + # 5,000, so 2,500 enters AGI and 17,500 stays out. + adjusted_gross_income: 22_500 + tax_exempt_railroad_social_security_equivalent_benefits: 17_500 + # MAGI = 22,500 + 17,500 = 40,000 + watca_alternative_tax_magi: 40_000 + - name: Alternative tax MAGI includes specified possession income period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object diff --git a/policyengine_us/tests/policy/contrib/taxsim/taxsim_v12.yaml b/policyengine_us/tests/policy/contrib/taxsim/taxsim_v12.yaml index 2a385454e38..fc51328c70c 100644 --- a/policyengine_us/tests/policy/contrib/taxsim/taxsim_v12.yaml +++ b/policyengine_us/tests/policy/contrib/taxsim/taxsim_v12.yaml @@ -34,3 +34,28 @@ cohabitating_spouses: true output: taxsim_v12: 4_250 + +- name: Case 5, TAXSIM counts a railroad retiree's social security equivalent benefit as Social Security. + period: 2025 + input: + people: + person1: + age: 67 + employment_income: 20_000 + railroad_social_security_equivalent_benefits: 20_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + taxsim_gssi: 20_000 + taxable_ss_magi: 20_000 + # Section 86(d)(1) counts the benefit as Social Security. Combined income + # is 20,000 + 0.5 x 20,000 = 30,000, which exceeds the $25,000 base by + # 5,000. Half of that excess, 2,500, is less than half the benefits, so + # 2,500 enters AGI. + taxsim_v12: 2_500 + adjusted_gross_income: 22_500 diff --git a/policyengine_us/tests/test_railroad_retirement_taxation.py b/policyengine_us/tests/test_railroad_retirement_taxation.py index dfaf0910a13..72cff1e2700 100644 --- a/policyengine_us/tests/test_railroad_retirement_taxation.py +++ b/policyengine_us/tests/test_railroad_retirement_taxation.py @@ -4,25 +4,36 @@ I1. IRC 86(d)(1): moving any part of a filer's benefits from title II to the tier 1 social security equivalent benefit leaves federal AGI, federal - income tax, the section 86 taxable amount and Medicaid MAGI unchanged. + income tax, the section 86 taxable amount, Medicaid MAGI and the section + 22 amount unchanged. Section 22(c)(3)(A)(i) reduces that amount by + nontaxable title II benefits under (I) and by nontaxable Railroad + Retirement Act benefits under (II). I2. IRC 72(r)(1): moving any amount from the taxable part of a private pension to the taxable part of the rest of the railroad annuity leaves federal AGI and federal income tax unchanged. I3. 45 U.S.C. 231m(a): a household whose only income is railroad benefits - owes no state income tax in any state, alternative and minimum taxes - included, and no state's withholding estimate counts the benefits. + owes no state income tax in any state in 2025 or 2026, alternative and + minimum taxes included, and no state's withholding estimate counts the + benefits. Idaho's $10 permanent building fund tax falls on every required filer, so there the test checks that taxable income is zero. -I4. For a wage earner under 65, adding railroad benefits leaves the state's - AGI unchanged, and in states whose reported AGI includes them it leaves - state taxable income unchanged. Colorado and South Carolina start from - federal taxable income, which floors at zero; while the standard - deduction absorbs some of the benefits, their subtraction exceeds what - federal taxable income gained, so there the base can only fall. +I4. For a wage earner under 65 in 2025, with wages up to $120,000 and + benefits up to $70,000, adding railroad benefits leaves the state's AGI + unchanged, and in states whose reported AGI includes them it leaves state + taxable income unchanged. Colorado and South Carolina start from federal + taxable income, which floors at zero; while the standard deduction + absorbs some of the benefits, their subtraction exceeds what federal + taxable income gained, so there the base can only fall. STATE_BASE_EXCEPTIONS lists the states with a provision measured with - federal AGI or federal income tax, which railroad benefits raise. -I5. Totals: railroad_benefits is the sum of its three parts; the title II - and tier 1 shares sum to the section 86 taxable amount; and + federal AGI or federal income tax that binds in that range. Outside it, + more such provisions bind: Minnesota reduces its standard deduction with + federal AGI above $238,950 in 2025 and phases out its dependent + exemptions with federal AGI, and from 2026 South Carolina phases out its + income adjusted deduction (SCIAD) with federal AGI. +I5. Totals: when railroad_benefits itself is left unset, it is the sum of + its three parts; the title II and tier 1 shares sum to the section 86 + taxable amount; each spouse carries the share of the unit's taxable tier + 1 amount that their own tier 1 benefits make up; and taxable_railroad_benefits is what the head's and spouse's federal AGI includes from the Act, and zero for dependents. D1. Differential: section 86 runs once on the head and spouse's return and @@ -41,6 +52,7 @@ """ import numpy as np +import pytest from hypothesis import example, given, settings from hypothesis import strategies as st @@ -163,6 +175,17 @@ def section_86_parts(simulation): joint=False, age=62, ) +# A 70-year-old with no other income keeps a section 22 amount: $5,000 less +# the $2,000 of nontaxable benefits, whichever kind they are. +@example( + wages=0, + head_benefits=2_000, + spouse_benefits=0, + head_share=1.0, + spouse_share=0.0, + joint=False, + age=70, +) def test_i1_tier_1_benefits_are_taxed_as_title_ii_benefits( wages, head_benefits, spouse_benefits, head_share, spouse_share, joint, age ): @@ -190,6 +213,7 @@ def household(head_share, spouse_share): "income_tax", "tax_unit_taxable_section_86_benefits", "medicaid_magi", + "section_22_income", ): title_ii, railroad = calc(simulation, variable)[:2] assert abs(title_ii - railroad) < tol, (variable, title_ii, railroad) @@ -236,13 +260,18 @@ def test_i2_the_rest_of_the_annuity_is_taxed_as_a_pension( tax_free=st.integers(min_value=0, max_value=5_000), age=st.integers(min_value=18, max_value=90), joint=st.booleans(), + year=st.sampled_from((2025, 2026)), ) # Minnesota's AMT starts from federal AGI (Schedule M1MT line 1) and Vermont's # minimum tax takes 3% of federal AGI over $150,000 (32 V.S.A. 5822(a)(6)). # Both bases must leave the benefits out. -@example(tier_1=0, tier_2=64_255, tax_free=0, age=70, joint=True) -@example(tier_1=0, tier_2=262_000, tax_free=0, age=70, joint=False) -def test_i3_no_state_taxes_railroad_benefits(tier_1, tier_2, tax_free, age, joint): +@example(tier_1=0, tier_2=64_255, tax_free=0, age=70, joint=True, year=2025) +@example(tier_1=0, tier_2=79_918, tax_free=0, age=18, joint=False, year=2025) +@example(tier_1=0, tier_2=262_000, tax_free=0, age=70, joint=False, year=2025) +@example(tier_1=0, tier_2=262_000, tax_free=0, age=70, joint=False, year=2026) +def test_i3_no_state_taxes_railroad_benefits( + tier_1, tier_2, tax_free, age, joint, year +): head = { "age": age, "railroad_social_security_equivalent_benefits": tier_1, @@ -251,13 +280,15 @@ def test_i3_no_state_taxes_railroad_benefits(tier_1, tier_2, tax_free, age, join } members = [head] + ([head | {"role": "spouse"}] if joint else []) simulation = Simulation( - situation=situation([(state, members) for state in INCOME_TAX_STATES]) + situation=situation( + [(state, members) for state in INCOME_TAX_STATES], year=year + ) ) # Tier 2 benefits are always taxable federally, so the test is not vacuous. - assert calc(simulation, "adjusted_gross_income").min() >= tier_2 - tax = calc(simulation, "state_income_tax_before_refundable_credits") - idaho_taxable_income = calc(simulation, "id_taxable_income") - withholding = calc(simulation, "state_withheld_income_tax") + assert calc(simulation, "adjusted_gross_income", year).min() >= tier_2 + tax = calc(simulation, "state_income_tax_before_refundable_credits", year) + idaho_taxable_income = calc(simulation, "id_taxable_income", year) + withholding = calc(simulation, "state_withheld_income_tax", year) for i, state in enumerate(INCOME_TAX_STATES): if state == "ID": assert idaho_taxable_income[i] == 0, (state, idaho_taxable_income[i]) @@ -343,13 +374,18 @@ def test_i5_railroad_totals_add_up(tier_1, tier_2, tax_free, title_ii, earnings) ) # The head's and spouse's shares add up to the tax unit's tier 1 amount. assert taxable_tier_1[2] == 0 - np.testing.assert_allclose( - taxable_tier_1.sum(), - calc( - simulation, "tax_unit_taxable_railroad_social_security_equivalent_benefits" - )[0], - atol=tol, - ) + unit_taxable_tier_1 = calc( + simulation, "tax_unit_taxable_railroad_social_security_equivalent_benefits" + )[0] + np.testing.assert_allclose(taxable_tier_1.sum(), unit_taxable_tier_1, atol=tol) + # Each spouse carries the share of it that their own tier 1 benefits make up. + couple_tier_1 = tier_1[0] + tier_1[1] + if couple_tier_1 > 0: + np.testing.assert_allclose( + taxable_tier_1[:2], + unit_taxable_tier_1 * np.array(tier_1[:2], dtype=float) / couple_tier_1, + atol=tol, + ) np.testing.assert_allclose( calc(simulation, "taxable_railroad_benefits"), taxable_tier_1 + np.array(tier_2, dtype=float) * [1, 1, 0], @@ -384,3 +420,29 @@ def test_d1_dependent_and_filer_section_86_agree(wages, title_ii, tier_1): filer, dependent, ) + + +@pytest.mark.parametrize("amount", [-5_000, float("nan")]) +def test_negative_or_missing_tier_1_benefits_raise(amount): + """Box 5 of Form RRB-1099 can be negative when repayments exceed the + year's benefits. The model doesn't net such amounts as IRS Publication 915 + does, so it rejects them, as it rejects negative title II benefits.""" + simulation = Simulation( + situation=situation( + [ + ( + "TX", + [ + { + "age": 70, + "employment_income": 20_000, + "social_security_retirement": 20_000, + "railroad_social_security_equivalent_benefits": amount, + } + ], + ) + ] + ) + ) + with pytest.raises(ValueError, match="finite and nonnegative"): + simulation.calculate("adjusted_gross_income", YEAR) diff --git a/policyengine_us/tests/test_state_sales_tax_table.py b/policyengine_us/tests/test_state_sales_tax_table.py index f23bdeacc93..7bdd0f4164a 100644 --- a/policyengine_us/tests/test_state_sales_tax_table.py +++ b/policyengine_us/tests/test_state_sales_tax_table.py @@ -94,15 +94,17 @@ # the nontaxable part of social security and railroad retirement benefits; the # nontaxable part of IRA, pension, or annuity distributions; and public # assistance payments. -# These are the PolicyEngine variables for those items. Combat pay and -# railroad retirement benefits are not included. +# These are the PolicyEngine variables for those items. Combat pay is not +# included. TABLE_INCOME_SOURCES = [ "adjusted_gross_income", "tax_exempt_interest_income", "veterans_benefits", "workers_compensation", "tax_exempt_social_security", + "tax_exempt_railroad_social_security_equivalent_benefits", "tax_exempt_pension_income", + "tax_exempt_railroad_pension_benefits", "tax_exempt_retirement_distributions", "tanf", "ssi", diff --git a/policyengine_us/variables/contrib/taxsim/taxsim_gssi.py b/policyengine_us/variables/contrib/taxsim/taxsim_gssi.py index b56f9a73a9d..9f8e956fe81 100644 --- a/policyengine_us/variables/contrib/taxsim/taxsim_gssi.py +++ b/policyengine_us/variables/contrib/taxsim/taxsim_gssi.py @@ -6,6 +6,12 @@ class taxsim_gssi(Variable): entity = TaxUnit label = "Gross Social Security Income" unit = USD + documentation = ( + "Social Security benefits and the social security equivalent part of " + "tier 1 railroad retirement benefits, which section 86(d)(1) counts " + "together, so taxsim_v12 runs section 86 on both." + ) + reference = "https://www.law.cornell.edu/uscode/text/26/86#d_1" definition_period = YEAR - adds = ["social_security"] + adds = ["social_security", "railroad_social_security_equivalent_benefits"] diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits.py index 32828df521f..db816c3528f 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/tax_unit_railroad_social_security_equivalent_benefits.py @@ -6,7 +6,28 @@ class tax_unit_railroad_social_security_equivalent_benefits(Variable): entity = TaxUnit label = "Tax unit Railroad Retirement social security equivalent benefits" unit = USD + documentation = ( + "The members' social security equivalent benefits. As with reported " + "Social Security benefits, each amount must be finite and " + "nonnegative. When repayments exceed a year's benefits, box 5 of " + "Form RRB-1099 shows a negative amount, which IRS Publication 915 " + "offsets against the box 5 amounts on the filer's other forms and, " + "on a joint return, the spouse's; the model doesn't net them." + ) definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/86#d_1_B" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/86#d_1_B", + # Pub. 915 (2024), "Repayments More Than Gross Benefits", PDF page 15. + "https://www.irs.gov/pub/irs-prior/p915--2024.pdf#page=15", + ) - adds = ["railroad_social_security_equivalent_benefits"] + def formula(tax_unit, period, parameters): + benefits = tax_unit.members( + "railroad_social_security_equivalent_benefits", period + ) + if np.any(~np.isfinite(benefits) | (benefits < 0)): + raise ValueError( + "railroad_social_security_equivalent_benefits must be finite " + "and nonnegative, as reported Social Security benefits must be" + ) + return tax_unit.sum(benefits) diff --git a/policyengine_us/variables/gov/ssa/revenue/tob_revenue_medicare_hi.py b/policyengine_us/variables/gov/ssa/revenue/tob_revenue_medicare_hi.py index 74372fdab5d..ab7d31c43f7 100644 --- a/policyengine_us/variables/gov/ssa/revenue/tob_revenue_medicare_hi.py +++ b/policyengine_us/variables/gov/ssa/revenue/tob_revenue_medicare_hi.py @@ -18,13 +18,20 @@ class tob_revenue_medicare_hi(Variable): "retaining negative unit differences for aggregation. The OASDI " "allocation caps old-law inclusion at actual inclusion, so HI gets " "nothing from a unit whose inclusion does not exceed the pre-1993 " - "amount." + "amount. Section 121(e)(3)(C) gives social security benefits the " + "section 86(d)(1) meaning, so section 121(e)(1)(B) also credits HI " + "with the 1993 amendments' increase on the social security " + "equivalent part of tier 1 railroad retirement benefits. The " + "counterfactuals remove only title II benefits' taxable share " + "(tax_unit_taxable_social_security), so this variable leaves that " + "railroad amount out." ) unit = USD reference = ( "https://www.law.cornell.edu/uscode/text/42/401", "https://www.law.cornell.edu/uscode/text/26/1411", "https://www.ssa.gov/OP_Home/comp2/F098-021.html", + "https://www.law.cornell.edu/uscode/text/26/86#d_1", ) def formula(tax_unit, period, parameters): diff --git a/policyengine_us/variables/gov/ssa/revenue/tob_revenue_oasdi.py b/policyengine_us/variables/gov/ssa/revenue/tob_revenue_oasdi.py index 4798a41038c..f76b8500713 100644 --- a/policyengine_us/variables/gov/ssa/revenue/tob_revenue_oasdi.py +++ b/policyengine_us/variables/gov/ssa/revenue/tob_revenue_oasdi.py @@ -17,13 +17,18 @@ class tob_revenue_oasdi(Variable): "impact. Negative unit differences are retained when aggregating " "tax liabilities. Actual inclusion caps the old-law inclusion. When " "a reform cuts inclusion below the pre-1993 amount, the 1993 " - "amendments add nothing, and OASDI takes the whole increase." + "amendments add nothing, and OASDI takes the whole increase. It " + "covers title II benefits only: under 45 U.S.C. 231n-1(b)(1)(B), the " + "section 121(e) amount from taxing the social security equivalent " + "part of tier 1 railroad retirement benefits goes to the Social " + "Security Equivalent Benefit Account, not to OASDI." ) unit = USD reference = ( "https://www.law.cornell.edu/uscode/text/42/401", "https://www.law.cornell.edu/uscode/text/26/1411", "https://www.ssa.gov/OP_Home/comp2/F098-021.html", + "https://www.law.cornell.edu/uscode/text/45/231n-1", ) def formula(tax_unit, period, parameters): diff --git a/policyengine_us/variables/gov/ssa/revenue/tob_revenue_total.py b/policyengine_us/variables/gov/ssa/revenue/tob_revenue_total.py index 784a510d5f8..c962ea4c199 100644 --- a/policyengine_us/variables/gov/ssa/revenue/tob_revenue_total.py +++ b/policyengine_us/variables/gov/ssa/revenue/tob_revenue_total.py @@ -12,12 +12,19 @@ class tob_revenue_total(Variable): "benefits, per 42 U.S.C. 401 note. Excludes chapter 2A net investment " "income tax under section 1411. The total and its " "OASDI and Medicare HI shares are signed: negative unit differences " - "are retained for aggregation." + "are retained for aggregation. The counterfactual removes only title " + "II benefits' taxable share (tax_unit_taxable_social_security), so " + "the total leaves out the tax on the social security equivalent part " + "of tier 1 railroad retirement benefits. Section 121(e) and 45 U.S.C. " + "231n-1(b)(1)(B) credit that tax to the Social Security Equivalent " + "Benefit Account, except the part from the 1993 amendments, which " + "goes to HI." ) unit = USD reference = ( "https://www.law.cornell.edu/uscode/text/42/401", "https://www.law.cornell.edu/uscode/text/26/1411", + "https://www.law.cornell.edu/uscode/text/45/231n-1", ) def formula(tax_unit, period, parameters): diff --git a/policyengine_us/variables/gov/states/az/tax/income/credits/property_tax_credit/az_property_tax_credit_agi.py b/policyengine_us/variables/gov/states/az/tax/income/credits/property_tax_credit/az_property_tax_credit_agi.py index 026ffb8d2fd..2671303244b 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/credits/property_tax_credit/az_property_tax_credit_agi.py +++ b/policyengine_us/variables/gov/states/az/tax/income/credits/property_tax_credit/az_property_tax_credit_agi.py @@ -11,7 +11,9 @@ class az_property_tax_credit_agi(Variable): "Adjusted gross income as defined for Arizona property tax credit purposes, " "for members who are not dependents. Per ARS 43-1072(H)(6) and ITR 12-1, " "this starts with federal AGI and subtracts taxable Social Security (ARS " - "43-1072(I)) and the unemployment compensation it holds, as (I) excludes " + "43-1072(I)), the taxable railroad retirement benefits, which the Form " + "140PTC instructions also leave out of income, and the unemployment " + "compensation it holds, as (I) excludes " "Arizona unemployment insurance payments; other states' payments are " "added back in az_property_tax_credit_unemployment. Federal AGI already " "leaves out most other subsection (I) items. Federal above-the-line " @@ -43,14 +45,17 @@ def formula(tax_unit, period, parameters): # Per ARS 43-1072(I) and ITR 12-1 "Items Excluded from Income", # we exclude Social Security benefits and Arizona unemployment - # compensation (below). Other excluded items, like railroad - # retirement, workers comp, veterans disability pensions, welfare and - # gifts, are typically not in Federal AGI anyway. + # compensation (below). The 2025 Form 140PTC instructions (page 4) + # also leave out railroad retirement benefits. Other excluded items, + # like workers comp, veterans disability pensions, welfare and gifts, + # are typically not in Federal AGI anyway. # # Federal AGI only includes the TAXABLE portion of Social Security, # but for property tax credit, ALL Social Security should be excluded. - # So we subtract the taxable portion that's in Federal AGI. + # So we subtract the taxable portion that's in Federal AGI. The same + # holds for the railroad retirement benefits federal AGI includes. taxable_social_security = tax_unit("tax_unit_taxable_social_security", period) + taxable_railroad_benefits = add(tax_unit, period, ["taxable_railroad_benefits"]) # NOTE: We do NOT subtract Arizona's regular subtractions here because: # - Pension exclusions (az_public_pension_exclusion, @@ -156,6 +161,7 @@ def formula(tax_unit, period, parameters): return ( federal_agi - taxable_social_security + - taxable_railroad_benefits - federal_capital_gains + federal_capital_loss + line_d diff --git a/policyengine_us/variables/gov/states/az/tax/income/credits/property_tax_credit/az_property_tax_credit_dependent_income.py b/policyengine_us/variables/gov/states/az/tax/income/credits/property_tax_credit/az_property_tax_credit_dependent_income.py index 245bd005964..d88bf8d160f 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/credits/property_tax_credit/az_property_tax_credit_dependent_income.py +++ b/policyengine_us/variables/gov/states/az/tax/income/credits/property_tax_credit/az_property_tax_credit_dependent_income.py @@ -44,13 +44,17 @@ def formula(tax_unit, period, parameters): sources = parameters(period).gov.irs.gross_income.sources income = 0 for source in sources: - # ARS 43-1072(I): Social Security benefits are not income. - # Capital gains and losses, including capital gain distributions, - # are added below as line D. ARS 43-1072(I): Arizona unemployment - # insurance payments are not income; other states' payments are - # in az_property_tax_credit_unemployment. + # ARS 43-1072(I): Social Security benefits are not income, and + # the 2025 Form 140PTC instructions (page 4) leave out railroad + # retirement benefits too. Capital gains and losses, including + # capital gain distributions, are added below as line D. ARS + # 43-1072(I): Arizona unemployment insurance payments are not + # income; other states' payments are in + # az_property_tax_credit_unemployment. if source in ( "taxable_social_security", + "taxable_railroad_social_security_equivalent_benefits", + "taxable_railroad_pension_benefits", "taxable_unemployment_compensation", "capital_gains", "non_sch_d_capital_gains", diff --git a/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_elderly_disabled_subtraction.py b/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_elderly_disabled_subtraction.py index 0f2226338c4..6c5ab403764 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_elderly_disabled_subtraction.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_elderly_disabled_subtraction.py @@ -10,6 +10,7 @@ class mn_elderly_disabled_subtraction(Variable): reference = ( "https://www.revenue.state.mn.us/sites/default/files/2023-01/m1r_21.pdf", "https://www.revenue.state.mn.us/sites/default/files/2023-01/m1r_22.pdf", + "https://www.revisor.mn.gov/statutes/cite/290.0802", ) defined_for = StateCode.MN @@ -45,13 +46,29 @@ def formula(tax_unit, period, parameters): unit_socsec = add(tax_unit, period, ["social_security"]) unit_taxed_socsec = add(tax_unit, period, ["taxable_social_security"]) untaxed_social_security = unit_socsec - unit_taxed_socsec - # ... determine total disability and social security benefits - benefits = disability_income + untaxed_social_security + # Minn. Stat. 290.0802, subd. 1(c), counts as nontaxable retirement + # benefits both the section 22(c)(3) reduction, which takes in the + # Railroad Retirement Act benefits that gross income leaves out, and + # the benefits in federal gross income that Minnesota doesn't tax, + # which take in the rest (45 U.S.C. 231m(a)). So all of the head's + # and spouse's railroad benefits reduce the base. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + railroad = tax_unit.sum(head_or_spouse * person("railroad_benefits", period)) + # ... determine total disability, social security and railroad benefits + benefits = disability_income + untaxed_social_security + railroad # ... subtract benefits from base amount amount = max_(0, base_amount - benefits) # ... determine net agi fed_agi = tax_unit("adjusted_gross_income", period) - agi = fed_agi + tax_unit("form_4972_lumpsum_distributions", period) + # Subd. 1(a) takes out of AGI the pension and annuity benefits in + # federal gross income that Minnesota doesn't tax: the taxable + # railroad benefits. + taxable_railroad = add(tax_unit, period, ["taxable_railroad_benefits"]) + agi = ( + fed_agi + + tax_unit("form_4972_lumpsum_distributions", period) + - taxable_railroad + ) agi_offset_base = p.agi_offset_base[filing_status] joint = filing_status == filing_status.possible_values.JOINT head_eligible = tax_unit.any(elderly_head | disabled_head) diff --git a/policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit.py b/policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit.py index deac860a046..5ffe3b92276 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit.py +++ b/policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit.py @@ -30,8 +30,13 @@ def formula(tax_unit, period, parameters): 0, total_retirement_income - federal_pension_subtraction ) - # Line 5, total social security - social_security = add(tax_unit, period, ["social_security"]) + # Line 5, total social security. ORS 316.157(2)(f) counts tier 1 + # railroad retirement benefits as Social Security benefits. + social_security = add( + tax_unit, + period, + ["social_security", "railroad_social_security_equivalent_benefits"], + ) # Line 6, base amount reduced by social security benefits received p = parameters(period).gov.states["or"].tax.income.credits.retirement_income diff --git a/policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit_household_income.py b/policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit_household_income.py index fc76438a361..ffaa40722ff 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit_household_income.py +++ b/policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit_household_income.py @@ -7,10 +7,20 @@ class or_retirement_credit_household_income(Variable): label = "Household income for the Oregon Retirement Income Tax Credit" unit = USD definition_period = YEAR + documentation = ( + "Federal AGI plus tax-exempt interest, less the Social Security " + "benefits AGI holds. ORS 316.157(2)(b) leaves Social Security " + "benefits out of household income, and (2)(f) defines them as " + "section 86 benefits: title II benefits and tier 1 railroad " + "retirement benefits." + ) reference = ( "https://www.oregonlegislature.gov/bills_laws/ors/ors316.html" # ORS 316.157 ) defined_for = StateCode.OR adds = ["adjusted_gross_income", "tax_exempt_interest_income"] - subtracts = ["taxable_social_security"] + subtracts = [ + "taxable_social_security", + "taxable_railroad_social_security_equivalent_benefits", + ] diff --git a/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py b/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py index 7d4bbcd34ee..becc13dfb01 100644 --- a/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py +++ b/policyengine_us/variables/household/income/person/retirement/railroad_benefits.py @@ -17,10 +17,12 @@ class railroad_benefits(Variable): "unemployment_compensation and sickness benefits in " "disability_benefits. Census counts railroad retirement as a " "government transfer, and household and SPM benefits include these " - "benefits. Enter the parts, not this total, so that taxes see them; " - "a total entered here reaches benefit programs only. Do not also " - "enter them as social_security, pension_income, disability_benefits " - "or survivor_benefits." + "benefits. Enter the parts, not this total, so that taxes see them. " + "A total entered here replaces the sum of the parts and reaches " + "income totals and benefit programs but no tax, and it applies only " + "to the year it is entered for, while the parts carry into later " + "years. Do not also enter them as social_security, pension_income, " + "disability_benefits or survivor_benefits." ) adds = [ "railroad_social_security_equivalent_benefits", diff --git a/policyengine_us/variables/household/income/person/retirement/railroad_social_security_equivalent_benefits.py b/policyengine_us/variables/household/income/person/retirement/railroad_social_security_equivalent_benefits.py index b7901708b22..a1c820d5211 100644 --- a/policyengine_us/variables/household/income/person/retirement/railroad_social_security_equivalent_benefits.py +++ b/policyengine_us/variables/household/income/person/retirement/railroad_social_security_equivalent_benefits.py @@ -11,7 +11,9 @@ class railroad_social_security_equivalent_benefits(Variable): "The part of a tier 1 Railroad Retirement Act annuity equal to the " "Social Security benefit the same work record would earn: the social " "security equivalent benefit (SSEB) on Form RRB-1099, box 5, net of " - "repayments. IRC 86(d)(1)(B) counts it as a social security benefit, " + "repayments. The model takes only nonnegative amounts, as it does for " + "reported Social Security benefits. IRC 86(d)(1)(B) counts it as a " + "social security benefit, " "so section 86 taxes it together with title II benefits. The rest of " "the annuity, reported on Form RRB-1099-R, goes in " "taxable_railroad_pension_benefits and " diff --git a/policyengine_us/variables/household/income/person/retirement/tax_exempt_railroad_pension_benefits.py b/policyengine_us/variables/household/income/person/retirement/tax_exempt_railroad_pension_benefits.py index f68bb8bb997..9780f5b10e2 100644 --- a/policyengine_us/variables/household/income/person/retirement/tax_exempt_railroad_pension_benefits.py +++ b/policyengine_us/variables/household/income/person/retirement/tax_exempt_railroad_pension_benefits.py @@ -7,6 +7,7 @@ class tax_exempt_railroad_pension_benefits(Variable): label = "Tax-exempt Railroad Retirement pension benefits" unit = USD definition_period = YEAR + uprating = "calibration.gov.irs.soi.tax_exempt_pension_income" documentation = ( "The part of Form RRB-1099-R benefits that recovers the employee's " "own contributions tax free. IRC 72(r)(2) treats tier 2 employee " diff --git a/policyengine_us/variables/household/income/person/retirement/taxable_railroad_pension_benefits.py b/policyengine_us/variables/household/income/person/retirement/taxable_railroad_pension_benefits.py index f5a180f7709..2db4b8c4a5b 100644 --- a/policyengine_us/variables/household/income/person/retirement/taxable_railroad_pension_benefits.py +++ b/policyengine_us/variables/household/income/person/retirement/taxable_railroad_pension_benefits.py @@ -7,6 +7,7 @@ class taxable_railroad_pension_benefits(Variable): label = "Taxable Railroad Retirement pension benefits" unit = USD definition_period = YEAR + uprating = "calibration.gov.irs.soi.taxable_pension_income" documentation = ( "Railroad Retirement Act benefits other than the social security " "equivalent benefit, less the employee contributions recovered tax " From 2c1e55859efe72f1200178f8e07af93cae38a605 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 11 Nov 2026 14:27:37 -0500 Subject: [PATCH 13/13] Follow Taxpayer Relief Act section 1303 for Pennsylvania's railroad benefits Section 1303, "Income" (2), counts 50% of railroad retirement benefits from 1999 and names no tier. Rebate income now takes the taxable railroad benefits out of federal AGI and adds half of all railroad benefits, in place of Form PA-1000's half of tier 1 and all of tier 2. Restore main's Case 3, add the same case entered as the tier 1 part, and move cases 3a and 3b to 11,000 and 25,000. Log the Pennsylvania and Oregon form conflicts in source_conflicts/candidates.yaml, and leave the Minnesota M1R conflict to the entry PR 10722 adds. Point the Utah reference at page 4. Tests: property examples draw deterministically; the SSEB check rejects an infinite amount as well as a negative one; I6 checks that each railroad dollar adds 50 cents to Pennsylvania rebate income, entered by part or as a total, with each entry in its own simulation; West Virginia's 2014 case enters taxable_railroad_benefits: 0, since it enters AGI directly. Co-Authored-By: Claude Opus 5.5 --- .../benefit_income_rate.yaml | 4 +- ...pa_property_tax_or_rent_rebate_income.yaml | 42 +++++++--- ...r_citizen_disability_deduction_person.yaml | 3 + .../test_railroad_retirement_taxation.py | 78 ++++++++++++++++--- .../ssa/revenue/tob_revenue_medicare_hi.py | 2 +- .../pa_property_tax_or_rent_rebate_income.py | 22 +++--- ...meowner_renter_relief_nontaxable_income.py | 2 +- source_conflicts/candidates.yaml | 66 ++++++++++++++++ 8 files changed, 185 insertions(+), 34 deletions(-) diff --git a/policyengine_us/parameters/gov/states/pa/tax/property/property_tax_or_rent_rebate/benefit_income_rate.yaml b/policyengine_us/parameters/gov/states/pa/tax/property/property_tax_or_rent_rebate/benefit_income_rate.yaml index 6ccaa1fc0cd..01ddc05dfbe 100644 --- a/policyengine_us/parameters/gov/states/pa/tax/property/property_tax_or_rent_rebate/benefit_income_rate.yaml +++ b/policyengine_us/parameters/gov/states/pa/tax/property/property_tax_or_rent_rebate/benefit_income_rate.yaml @@ -1,4 +1,4 @@ -description: Pennsylvania counts this share of Social Security, Supplemental Security Income, State Supplementary Payment, and Railroad Retirement Tier 1 benefits under the Property Tax/Rent Rebate program. +description: Pennsylvania counts this share of Social Security, Supplemental Security Income, State Supplementary Payment, and Railroad Retirement Act benefits under the Property Tax/Rent Rebate program. values: 2025-01-01: 0.5 metadata: @@ -6,5 +6,7 @@ metadata: period: year label: Pennsylvania Property Tax/Rent Rebate benefit income rate reference: + - title: 'Taxpayer Relief Act (Act 1 of 2006, Special Session 1), section 1303, "Income" (2) and (3)(i)' + href: https://www.legis.state.pa.us/WU01/LI/LI/US/PDF/2006/1/0001..PDF#page=38 - title: "2025 PA-1000 Pennsylvania Property Tax or Rent Rebate Program Instruction Booklet, State Supplementary Payment recipients and Lines 4 and 5 #page 3,7" href: https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/ptrr/documents/2025_pa-1000_inst.pdf#page=3 diff --git a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml index a5fac09e371..670b7731ab4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.yaml @@ -36,7 +36,26 @@ output: pa_property_tax_or_rent_rebate_income: 20_000 -- name: Case 3, SSI and railroad tier 1 benefits are half-counted. +- name: Case 3, SSI and railroad benefits are half-counted. + period: 2025 + input: + people: + person1: + age: 65 + ssi: 2_000 + pa_ssp: 0 + railroad_benefits: 10_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: PA + output: + pa_property_tax_or_rent_rebate_income: 6_000 + +- name: Case 3, entered as the tier 1 part, gives the same income. period: 2025 input: people: @@ -53,9 +72,13 @@ members: [person1] state_code: PA output: + # Combined income = 10,000 / 2 = 5,000, below the 25,000 base amount, so + # federal AGI holds none of the tier 1 benefits (IRC 86). Section 1303, + # "Income" (2), and the SSI rule: half of 2,000 + 10,000 = 6,000. + adjusted_gross_income: 0 pa_property_tax_or_rent_rebate_income: 6_000 -- name: Case 3a, railroad tier 2 benefits count in full and once. +- name: Case 3a, half of all railroad benefits count, tier 2 included, and once. period: 2025 input: people: @@ -75,12 +98,13 @@ # Combined income = 10,000 + 10,000 / 2 = 15,000, below the 25,000 base # amount, so none of the tier 1 benefits is taxable (IRC 86) and federal # AGI is the 10,000 taxable part of tier 2 (IRC 72(r)). - # PA-1000 line 5: half of tier 1 = 5,000. - # Line 6: gross tier 2 = 10,000 + 2,000 = 12,000. + # Taxpayer Relief Act section 1303, "Income" (2): 50% of railroad + # retirement benefits. The 10,000 in federal AGI comes out, and half of + # 10,000 + 10,000 + 2,000 goes in: 10,000 - 10,000 + 11,000 = 11,000. adjusted_gross_income: 10_000 - pa_property_tax_or_rent_rebate_income: 17_000 + pa_property_tax_or_rent_rebate_income: 11_000 -- name: Case 3b, taxable railroad tier 1 benefits give way to half of the total. +- name: Case 3b, taxable railroad benefits in federal AGI give way to half of the total. period: 2025 input: people: @@ -99,10 +123,10 @@ # Combined income = 30,000 + 20,000 / 2 = 40,000. IRC 86(a)(2): the lesser # of 85% x 20,000 = 17,000 and 85% x (40,000 - 34,000) + 4,500 = 9,600, # so 9,600 of tier 1 is taxable and federal AGI is 39,600. - # PA-1000: 39,600 - 9,600 + 20,000 / 2 (line 5) = 40,000, which is line 5 - # (10,000) plus line 6 (30,000). + # Section 1303, "Income" (2): all 39,600 in federal AGI comes out, and half + # of 20,000 + 30,000 goes in: 39,600 - 39,600 + 25,000 = 25,000. adjusted_gross_income: 39_600 - pa_property_tax_or_rent_rebate_income: 40_000 + pa_property_tax_or_rent_rebate_income: 25_000 - name: Case 4, tax-exempt interest and retirement income are included. period: 2025 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/senior_citizen_disability/wv_senior_citizen_disability_deduction_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/senior_citizen_disability/wv_senior_citizen_disability_deduction_person.yaml index f03bb85a31f..fe277f24e97 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/senior_citizen_disability/wv_senior_citizen_disability_deduction_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/subtractions/senior_citizen_disability/wv_senior_citizen_disability_deduction_person.yaml @@ -97,6 +97,9 @@ adjusted_gross_income_person: 6_000 salt_refund_income: 2_000 wv_senior_citizen_disability_deduction_total_modifications: 1_000 + # The case enters AGI directly. Federal taxable railroad benefits build on + # 401(k) limits that the model holds only from 2018. + taxable_railroad_benefits: 0 output: # Available income = 6,000 - 2,000 - 1,000 = 3,000. # Remaining allowance = 8,000 - 1,000 = 7,000; min = 3,000. diff --git a/policyengine_us/tests/test_railroad_retirement_taxation.py b/policyengine_us/tests/test_railroad_retirement_taxation.py index 72cff1e2700..5b7c54713e7 100644 --- a/policyengine_us/tests/test_railroad_retirement_taxation.py +++ b/policyengine_us/tests/test_railroad_retirement_taxation.py @@ -36,6 +36,10 @@ 1 amount that their own tier 1 benefits make up; and taxable_railroad_benefits is what the head's and spouse's federal AGI includes from the Act, and zero for dependents. +I6. Taxpayer Relief Act section 1303, "Income" (2): each dollar of railroad + benefits a filer or spouse receives, split among the three parts in any + way or entered as a total, raises Pennsylvania Property Tax/Rent Rebate + income by 50 cents, whatever share of it federal AGI holds. D1. Differential: section 86 runs once on the head and spouse's return and once on a dependent's own return. For the same income and benefits the two give the same taxable amount. @@ -153,7 +157,7 @@ def section_86_parts(simulation): shares = st.integers(min_value=0, max_value=100).map(lambda x: x / 100) -@settings(max_examples=25, deadline=None) +@settings(max_examples=25, deadline=None, derandomize=True) @given( wages=wages, head_benefits=amounts, @@ -224,7 +228,7 @@ def household(head_share, spouse_share): ) -@settings(max_examples=25, deadline=None) +@settings(max_examples=25, deadline=None, derandomize=True) @given( wages=wages, pension=st.integers(min_value=0, max_value=80_000), @@ -253,7 +257,7 @@ def test_i2_the_rest_of_the_annuity_is_taxed_as_a_pension( assert abs(private - railroad) < tol, (variable, private, railroad) -@settings(max_examples=6, deadline=None) +@settings(max_examples=6, deadline=None, derandomize=True) @given( tier_1=st.integers(min_value=0, max_value=60_000), tier_2=st.integers(min_value=1, max_value=300_000), @@ -297,7 +301,7 @@ def test_i3_no_state_taxes_railroad_benefits( assert abs(withholding[i]) < CENT, (state, withholding[i]) -@settings(max_examples=6, deadline=None) +@settings(max_examples=6, deadline=None, derandomize=True) @given( wages=st.integers(min_value=0, max_value=120_000), tier_1=st.integers(min_value=0, max_value=30_000), @@ -334,7 +338,7 @@ def test_i4_railroad_benefits_leave_state_bases_unchanged(wages, tier_1, tier_2, assert abs(before - after) < tol, (state, before, after) -@settings(max_examples=25, deadline=None) +@settings(max_examples=25, deadline=None, derandomize=True) @given( tier_1=st.lists(amounts, min_size=3, max_size=3), tier_2=st.lists(amounts, min_size=3, max_size=3), @@ -393,7 +397,61 @@ def test_i5_railroad_totals_add_up(tier_1, tier_2, tax_free, title_ii, earnings) ) -@settings(max_examples=25, deadline=None) +@settings(max_examples=10, deadline=None, derandomize=True) +@given( + tier_1=amounts, + tier_2=amounts, + tax_free=st.integers(min_value=0, max_value=5_000), + title_ii=amounts, + earnings=wages, + joint=st.booleans(), +) +# YAML Cases 3a and 3b of pa_property_tax_or_rent_rebate_income. +@example( + tier_1=10_000, tier_2=10_000, tax_free=2_000, title_ii=0, earnings=0, joint=False +) +@example(tier_1=20_000, tier_2=30_000, tax_free=0, title_ii=0, earnings=0, joint=False) +def test_i6_pennsylvania_rebate_income_counts_half_of_railroad_benefits( + tier_1, tier_2, tax_free, title_ii, earnings, joint +): + total = tier_1 + tier_2 + tax_free + # SSI and the state supplement would fall as railroad benefits rise, and + # the rebate half-counts them too, so both stay at zero. + head = {"age": 67, "employment_income": earnings, "ssi": 0, "pa_ssp": 0} + spouse = {"role": "spouse", "age": 67, "ssi": 0, "pa_ssp": 0} + if joint: + spouse["social_security_retirement"] = title_ii + else: + head["social_security_retirement"] = title_ii + + def household(railroad): + return ("PA", [head | railroad] + ([spouse] if joint else [])) + + def added_income(railroad): + # A variable that any person enters becomes an input for every person, + # with 0 for the rest, so a household that enters the three tiers and + # one that enters the total cannot share a simulation. Each runs beside + # a household without railroad benefits. + simulation = Simulation( + situation=situation([household({}), household(railroad)]) + ) + income = calc(simulation, "pa_property_tax_or_rent_rebate_income") + return income[1] - income[0] + + tiers = added_income( + { + "railroad_social_security_equivalent_benefits": tier_1, + "taxable_railroad_pension_benefits": tier_2, + "tax_exempt_railroad_pension_benefits": tax_free, + } + ) + entered_total = added_income({"railroad_benefits": total}) + tol = tolerance(earnings + title_ii + total) + assert abs(tiers - total / 2) < tol, tiers + assert abs(entered_total - total / 2) < tol, entered_total + + +@settings(max_examples=25, deadline=None, derandomize=True) @given(wages=wages, title_ii=amounts, tier_1=amounts) def test_d1_dependent_and_filer_section_86_agree(wages, title_ii, tier_1): income = { @@ -422,11 +480,13 @@ def test_d1_dependent_and_filer_section_86_agree(wages, title_ii, tier_1): ) -@pytest.mark.parametrize("amount", [-5_000, float("nan")]) -def test_negative_or_missing_tier_1_benefits_raise(amount): +@pytest.mark.parametrize("amount", [-5_000, float("inf")]) +def test_negative_or_infinite_tier_1_benefits_raise(amount): """Box 5 of Form RRB-1099 can be negative when repayments exceed the year's benefits. The model doesn't net such amounts as IRS Publication 915 - does, so it rejects them, as it rejects negative title II benefits.""" + does, so it rejects them, as it rejects negative title II benefits. It + also rejects an infinite amount. policyengine-core refuses a NaN input + before any formula runs, so that case needs no test here.""" simulation = Simulation( situation=situation( [ diff --git a/policyengine_us/variables/gov/ssa/revenue/tob_revenue_medicare_hi.py b/policyengine_us/variables/gov/ssa/revenue/tob_revenue_medicare_hi.py index ab7d31c43f7..7f931bbed39 100644 --- a/policyengine_us/variables/gov/ssa/revenue/tob_revenue_medicare_hi.py +++ b/policyengine_us/variables/gov/ssa/revenue/tob_revenue_medicare_hi.py @@ -22,7 +22,7 @@ class tob_revenue_medicare_hi(Variable): "section 86(d)(1) meaning, so section 121(e)(1)(B) also credits HI " "with the 1993 amendments' increase on the social security " "equivalent part of tier 1 railroad retirement benefits. The " - "counterfactuals remove only title II benefits' taxable share " + "counterfactuals change only title II benefits' taxable share " "(tax_unit_taxable_social_security), so this variable leaves that " "railroad amount out." ) diff --git a/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py b/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py index a3f06d3e172..ccb22ee27ac 100644 --- a/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py +++ b/policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py @@ -12,6 +12,8 @@ class pa_property_tax_or_rent_rebate_income(Variable): "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/" "formsandpublications/formsforindividuals/ptrr/documents/" "2025_pa-1000_inst.pdf#page=3", + # Taxpayer Relief Act section 1303, "Income" (2) and (3)(i) + "https://www.legis.state.pa.us/WU01/LI/LI/US/PDF/2006/1/0001..PDF#page=38", ) defined_for = StateCode.PA @@ -22,11 +24,9 @@ def formula(tax_unit, period, parameters): adjusted_gross_income = tax_unit("adjusted_gross_income", period) above_the_line_deductions = tax_unit("above_the_line_deductions", period) taxable_social_security = tax_unit("tax_unit_taxable_social_security", period) - # Federal AGI includes taxable tier 1 benefits, which line 5 replaces - # with half of the total. - taxable_railroad_tier_1 = tax_unit( - "tax_unit_taxable_railroad_social_security_equivalent_benefits", period - ) + # Section 1303 counts half of all railroad retirement benefits, so the + # part that federal AGI holds comes out before the half goes in. + taxable_railroad_benefits = add(tax_unit, period, ["taxable_railroad_benefits"]) half_counted_benefits = tax_unit.sum( head_or_spouse * add( @@ -39,9 +39,9 @@ def formula(tax_unit, period, parameters): # Medicaid facility residents; the PA-1000 does not say # whether the Department of Revenue treats it as SSP. "pa_ssp", - # Line 5: half of Railroad Retirement Tier 1 benefits as - # Form RRB-1099 shows them. - "railroad_social_security_equivalent_benefits", + # Section 1303 "Income" (2): 50% of railroad retirement + # benefits, every tier, from 1999. + "railroad_benefits", ], ) ) @@ -54,10 +54,6 @@ def formula(tax_unit, period, parameters): "tax_exempt_interest_income", "tax_exempt_pension_income", "tax_exempt_retirement_distributions", - # Line 6 takes the gross amount of Tier 2 Railroad - # Retirement benefits; their taxable part is in - # adjusted_gross_income. - "tax_exempt_railroad_pension_benefits", ], ) ) @@ -92,7 +88,7 @@ def formula(tax_unit, period, parameters): adjusted_gross_income + above_the_line_deductions - taxable_social_security - - taxable_railroad_tier_1 + - taxable_railroad_benefits + p.benefit_income_rate * half_counted_benefits + tax_exempt_income + pa_1000_line_11_income diff --git a/policyengine_us/variables/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief_nontaxable_income.py b/policyengine_us/variables/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief_nontaxable_income.py index 8eaac61c950..eb6f8eaf235 100644 --- a/policyengine_us/variables/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief_nontaxable_income.py +++ b/policyengine_us/variables/gov/states/ut/tax/property/homeowner_renter_relief/ut_homeowner_renter_relief_nontaxable_income.py @@ -8,7 +8,7 @@ class ut_homeowner_renter_relief_nontaxable_income(Variable): unit = USD definition_period = YEAR reference = ( - "https://le.utah.gov/xcode/Title59/Chapter2A/C59-2a_2026010120250507.pdf#page=5" + "https://le.utah.gov/xcode/Title59/Chapter2A/C59-2a_2026010120250507.pdf#page=4" ) defined_for = StateCode.UT diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index f4bf0f8500d..27f2335b877 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -8642,6 +8642,41 @@ candidates: state: unreviewed note: PE-US applies the floor to the prior year's guideline, rounded to cents, with the statute's strict "less than" (or_liheap_eligible.py). +- id: us-or:retirement-credit-household-income-railroad-tier-2 + program: income-tax + years: + from: 2025 + type: tax + question: Does household income for Oregon's retirement income credit count the tier 2 railroad annuity, or leave + out every Railroad Retirement benefit? + sources: + - kind: statute + citation: ORS 316.157(2)(b), (c) and (f) + url: https://www.oregonlegislature.gov/bills_laws/ors/ors316.html + - kind: regulation + citation: OAR 150-316-0225(3) + url: https://secure.sos.state.or.us/oard/view.action?ruleNumber=150-316-0225 + - kind: guidance + citation: 2025 Publication OR-17, retirement income credit + url: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2025.pdf#page=107 + readings: + statute: Household income leaves out Social Security benefits, which (2)(f) defines as section 86 benefits, + "Title II Social Security or tier 1 railroad retirement benefits", so the tier 2 annuity in federal AGI stays + in. OR-17 lists "RRB benefits (Tier 2 only)" as household income. + other: The rule says that "benefits received from Social Security or Railroad Retirement are not included in + computing the household income limitation", which, read literally, leaves tier 2 out as well. + example: Joint filers aged 66 with $25,000 of pension income, $5,000 of interest, $4,000 of tier 1 benefits and a + $3,000 taxable tier 2 annuity have household income of $33,000 and a credit of $720 under the statute. Leaving + tier 2 out gives $30,000 and $990. + pe_us: + follows: statute + code: + - policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit_household_income.py + - policyengine_us/variables/gov/states/or/tax/income/credits/retirement_credit/or_retirement_credit.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10696 + state: ruled + note: The statute comes first, and the department's own publication reads it the same way. - id: us-pa:liheap-interest-dividend-threshold program: liheap years: @@ -8746,6 +8781,37 @@ candidates: state: ruled note: PE-US follows the memoranda, the issuance directives for a payment the plan says "may" be issued (ruled 2026-10-28). The memoranda are served only over http. +- id: us-pa:ptrr-railroad-retirement-tier-2-share + program: property-tax-credit + years: + from: 2025 + type: calendar + question: Does Pennsylvania Property Tax/Rent Rebate income count half of all railroad retirement benefits, or half + of tier 1 and the whole gross tier 2 annuity? + sources: + - kind: statute + citation: Taxpayer Relief Act (Act 1 of 2006, Special Session 1), section 1303, "Income" (2) + url: https://www.legis.state.pa.us/WU01/LI/LI/US/PDF/2006/1/0001..PDF#page=38 + - kind: instructions + citation: 2025 PA-1000 instructions, lines 5 and 6 + url: https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/ptrr/documents/2025_pa-1000_inst.pdf#page=7 + readings: + statute: Income includes "50% of railroad retirement benefits for calendar years 1999 and thereafter". The act + names no tiers. + other: Line 5 takes one-half of Railroad Retirement Tier 1 Benefits, and line 6 takes the gross amount, not the + taxable amount, of Tier 2 Railroad Retirement Benefits. + example: A claimant aged 65 with $10,000 of tier 1 benefits, a $10,000 taxable tier 2 annuity and a $2,000 tax-free + recovery has income of $11,000 under the act and $17,000 under the instructions. + pe_us: + follows: statute + code: + - policyengine_us/variables/gov/states/pa/tax/property/property_tax_or_rent_rebate/pa_property_tax_or_rent_rebate_income.py + - policyengine_us/parameters/gov/states/pa/tax/property/property_tax_or_rent_rebate/benefit_income_rate.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10696 + state: ruled + note: The act's only railroad words sit in "Income" (2). PE-US takes the railroad benefits in federal AGI out of + income and adds half of all of them, so the tier 2 annuity counts once, at half. - id: us-pa:snap-child-support-deduction program: snap years: