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..5eeb08ad4f2 --- /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, 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 2c48d38ca0e..18e36e3901d 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,6 @@ metadata: unit: list period: year label: Household benefits + reference: + - 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 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/reforms/congress/tlaib/boost/boost_middle_class_tax_credit.py b/policyengine_us/reforms/congress/tlaib/boost/boost_middle_class_tax_credit.py index 2c201a5d984..ac0a0c02d23 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 73c3048bdc9..22a4e54b210 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 7c4cd0f5f98..97b87f1175b 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 5779d0789a1..9e1016a4f6f 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..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 @@ -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 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 + 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/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_aggregate_list_integrity.py b/policyengine_us/tests/test_aggregate_list_integrity.py index 2599fa64000..0fd94fd7981 100644 --- a/policyengine_us/tests/test_aggregate_list_integrity.py +++ b/policyengine_us/tests/test_aggregate_list_integrity.py @@ -290,12 +290,16 @@ def test_reform_aggregates_keep_baseline_contrib_entries( "ma_eaedc", "nj_property_tax_relief", "ny_drive_clean_rebate", + "railroad_benefits", "state_property_tax_refunds", + "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 b3d554e469a..fbfb001f282 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. -Seven invariants hold for every input. +Nine 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. @@ -24,6 +24,12 @@ same amount. A state income tax credit list or state_property_tax_refunds carries each payment into both totals, and the bill reductions documented below reach neither, since neither total subtracts property tax. +8. 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. +9. 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. @@ -601,3 +607,76 @@ def change(name): expected = 0 if name in PROPERTY_TAX_BILL_REDUCTIONS else amount assert household_change == pytest.approx(expected, abs=TOLERANCE), name assert spm_change == pytest.approx(expected, abs=TOLERANCE), name + + +# 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_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. 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( + 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"): + 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/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/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 a198c30f3be..631bc7b0ab4 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,26 @@ class veterans_benefits(Variable): entity = Person label = "Veterans benefits" unit = USD - documentation = "Veterans benefits from past military service." + documentation = ( + "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. 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" + 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 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 1c4aba748a1..38d2c7aff6a 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