Context
PolicyEngine/policyengine-us#8076 added Person-level variables federal_benefit_cost / state_benefit_cost that split benefit expenditures between levels of government (currently covers Medicaid FMAP and CHIP eFMAP; will extend to SNAP OBBBA FY2028 state match, etc.).
Today economic_impact_analysis() in src/policyengine/tax_benefit_models/us/analysis.py returns per-program stats and aggregate impacts, but doesn't partition benefit spending by federal vs. state. Every consumer — policyengine-api's compare.py::budgetary_impact (PolicyEngine/policyengine-api#3481), analysis notebooks (PolicyEngine/analysis-notebooks#131), ad-hoc scripts — needs the same arithmetic, so the logic belongs here, not duplicated in each caller.
Proposal
Extend PolicyReformAnalysis to expose:
federal_budgetary_impact: federal tax revenue change (income_tax + payroll_tax) minus federal benefit spending change (federal_benefit_cost)
state_budgetary_impact: state tax revenue change (state_income_tax) minus state benefit spending change (state_benefit_cost)
total_budgetary_impact: existing sum, unchanged
Implementation: add two ChangeAggregate(variable="federal_benefit_cost", type=ChangeAggregateType.SUM) / state_benefit_cost calls alongside the existing per-program aggregates, and expose the federal/state splits as top-level fields on the analysis object.
Also tag ProgramStatistics entries with a level_of_government field ("federal", "state", or "shared") so consumers can filter per-program rows. For shared programs like Medicaid/CHIP, also populate federal_change / state_change using the new variables.
Why here vs. API
- Thin arithmetic on microsim sums — no Flask-specific state
- Analysis notebooks and ad-hoc Python scripts (not just the app) need this split
- policyengine-api#3481 becomes a pass-through wiring change once this lands
Example
from policyengine.tax_benefit_models.us import economic_impact_analysis
analysis = economic_impact_analysis(baseline_sim, reform_sim)
print(f"Federal cost: ${analysis.federal_budgetary_impact / 1e9:+.1f}B")
print(f"State cost: ${analysis.state_budgetary_impact / 1e9:+.1f}B")
print(f"Total: ${analysis.total_budgetary_impact / 1e9:+.1f}B")
Related
Context
PolicyEngine/policyengine-us#8076 added Person-level variables
federal_benefit_cost/state_benefit_costthat split benefit expenditures between levels of government (currently covers Medicaid FMAP and CHIP eFMAP; will extend to SNAP OBBBA FY2028 state match, etc.).Today
economic_impact_analysis()insrc/policyengine/tax_benefit_models/us/analysis.pyreturns per-program stats and aggregate impacts, but doesn't partition benefit spending by federal vs. state. Every consumer — policyengine-api'scompare.py::budgetary_impact(PolicyEngine/policyengine-api#3481), analysis notebooks (PolicyEngine/analysis-notebooks#131), ad-hoc scripts — needs the same arithmetic, so the logic belongs here, not duplicated in each caller.Proposal
Extend
PolicyReformAnalysisto expose:federal_budgetary_impact: federal tax revenue change (income_tax + payroll_tax) minus federal benefit spending change (federal_benefit_cost)state_budgetary_impact: state tax revenue change (state_income_tax) minus state benefit spending change (state_benefit_cost)total_budgetary_impact: existing sum, unchangedImplementation: add two
ChangeAggregate(variable="federal_benefit_cost", type=ChangeAggregateType.SUM)/state_benefit_costcalls alongside the existing per-program aggregates, and expose the federal/state splits as top-level fields on the analysis object.Also tag
ProgramStatisticsentries with alevel_of_governmentfield ("federal","state", or"shared") so consumers can filter per-program rows. For shared programs like Medicaid/CHIP, also populatefederal_change/state_changeusing the new variables.Why here vs. API
Example
Related