From 654de3e2b6cf3235aefaa5e5981de5027e9202cc Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 13:29:45 -0500 Subject: [PATCH 1/3] 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 014fe66dc9d859bd98c219da745205df346cf3e1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 13:15:27 -0500 Subject: [PATCH 2/3] 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 38d50fbde3105ed55414bb46c94fa754eb7d4af0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 15:15:35 -0500 Subject: [PATCH 3/3] 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"