skills/analysis/policyengine-analysis-skill/SKILL.md
Common analysis patterns for PolicyEngine research repositories (CRFB, newsletters, dashboards, impact studies). For population-level estimates (cost, poverty, distributional impacts), use the policyengine-microsimulation skill instead.
npx skillsauth add policyengine/policyengine-claude policyengine-analysisInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Patterns for creating policy impact analyses, dashboards, and research using PolicyEngine.
For population-level estimates (budgetary cost, poverty impact, distributional analysis), use the policyengine-microsimulation skill instead. This skill covers analysis repo patterns, visualization, and household-level calculations.
See MICROSIMULATION_REFORM_GUIDE.md for UK-specific microsimulation patterns.
Analysis repositories produce the research you see on PolicyEngine:
Blog posts:
Dashboards:
Research reports:
Key sections in typical analysis:
The proposal:
Household impacts:
Statewide/national impacts:
See policyengine-writing-skill for writing conventions.
crfb-tob-impacts - Policy impact analysesnewsletters - Data-driven newsletters2024-election-dashboard - Policy comparison dashboardsmarginal-child - Specialized policy analysesgivecalc - Charitable giving calculatorStandard analysis repository structure:
analysis-repo/
├── analysis.ipynb # Main Jupyter notebook
├── requirements.txt # Python dependencies
├── README.md # Documentation
├── data/ # Data files (if needed)
└── outputs/ # Generated charts, tables
import pandas as pd
import numpy as np
from policyengine_us import Simulation
# Define reform
reform = {
"gov.irs.credits.ctc.amount.base[0].amount": {
"2026-01-01.2100-12-31": 5000
}
}
# Analyze across income distribution
incomes = np.linspace(0, 200000, 101)
results = []
for income in incomes:
# Baseline
situation = create_situation(income=income)
sim_baseline = Simulation(situation=situation)
tax_baseline = sim_baseline.calculate("income_tax", 2026)[0]
# Reform
sim_reform = Simulation(situation=situation, reform=reform)
tax_reform = sim_reform.calculate("income_tax", 2026)[0]
results.append({
"income": income,
"tax_baseline": tax_baseline,
"tax_reform": tax_reform,
"tax_change": tax_reform - tax_baseline
})
df = pd.DataFrame(results)
# Define representative households
households = {
"Single, No Children": {
"income": 40000,
"num_children": 0,
"married": False
},
"Single Parent, 2 Children": {
"income": 50000,
"num_children": 2,
"married": False
},
"Married, 2 Children": {
"income": 100000,
"num_children": 2,
"married": True
}
}
# Calculate impacts for each
case_studies = {}
for name, params in households.items():
situation = create_family(**params)
sim_baseline = Simulation(situation=situation)
sim_reform = Simulation(situation=situation, reform=reform)
case_studies[name] = {
"baseline_tax": sim_baseline.calculate("income_tax", 2026)[0],
"reform_tax": sim_reform.calculate("income_tax", 2026)[0],
"ctc_baseline": sim_baseline.calculate("ctc", 2026)[0],
"ctc_reform": sim_reform.calculate("ctc", 2026)[0]
}
case_df = pd.DataFrame(case_studies).T
states = ["CA", "NY", "TX", "FL", "PA", "OH", "IL", "MI"]
state_results = []
for state in states:
situation = create_situation(income=75000, state=state)
sim_baseline = Simulation(situation=situation)
sim_reform = Simulation(situation=situation, reform=reform)
state_results.append({
"state": state,
"baseline_net_income": sim_baseline.calculate("household_net_income", 2026)[0],
"reform_net_income": sim_reform.calculate("household_net_income", 2026)[0],
"change": (sim_reform.calculate("household_net_income", 2026)[0] -
sim_baseline.calculate("household_net_income", 2026)[0])
})
state_df = pd.DataFrame(state_results)
import plotly.graph_objects as go
# Calculate across income range
situation_with_axes = {
# ... setup ...
"axes": [[{
"name": "employment_income",
"count": 1001,
"min": 0,
"max": 200000,
"period": 2026
}]]
}
sim_baseline = Simulation(situation=situation_with_axes)
sim_reform = Simulation(situation=situation_with_axes, reform=reform)
incomes = sim_baseline.calculate("employment_income", 2026)
baseline_net = sim_baseline.calculate("household_net_income", 2026)
reform_net = sim_reform.calculate("household_net_income", 2026)
gains = reform_net - baseline_net
# Identify winners and losers
winners = gains > 0
losers = gains < 0
neutral = gains == 0
print(f"Winners: {winners.sum() / len(gains) * 100:.1f}%")
print(f"Losers: {losers.sum() / len(gains) * 100:.1f}%")
print(f"Neutral: {neutral.sum() / len(gains) * 100:.1f}%")
import plotly.graph_objects as go
# PolicyEngine brand colors — see policyengine-design-skill for canonical values.
# Python charts can't use CSS vars, so reference the design token hex values:
TEAL = "#319795" # --pe-color-primary-500
BLUE = "#026AA2" # --pe-color-blue-700
DARK_GRAY = "#5A5A5A" # --pe-color-text-secondary
def create_pe_layout(title, xaxis_title, yaxis_title):
"""Create standard PolicyEngine chart layout."""
return go.Layout(
title=title,
xaxis_title=xaxis_title,
yaxis_title=yaxis_title,
font=dict(family="Inter", size=14),
plot_bgcolor="white",
hovermode="x unified",
xaxis=dict(
showgrid=True,
gridcolor="lightgray",
zeroline=True
),
yaxis=dict(
showgrid=True,
gridcolor="lightgray",
zeroline=True
)
)
# Use in charts
fig = go.Figure(layout=create_pe_layout(
"Tax Impact by Income",
"Income",
"Tax Change"
))
fig.add_trace(go.Scatter(x=incomes, y=tax_change, line=dict(color=TEAL)))
1. Line Chart (Impact by Income)
fig = go.Figure()
fig.add_trace(go.Scatter(
x=df.income,
y=df.tax_change,
mode='lines',
name='Tax Change',
line=dict(color=TEAL, width=3)
))
fig.update_layout(
title="Tax Impact by Income Level",
xaxis_title="Income",
yaxis_title="Tax Change ($)",
xaxis_tickformat="$,.0f",
yaxis_tickformat="$,.0f"
)
2. Bar Chart (State Comparison)
fig = go.Figure()
fig.add_trace(go.Bar(
x=state_df.state,
y=state_df.change,
marker_color=TEAL
))
fig.update_layout(
title="Net Income Change by State",
xaxis_title="State",
yaxis_title="Change ($)",
yaxis_tickformat="$,.0f"
)
3. Waterfall Chart (Budget Impact)
fig = go.Figure(go.Waterfall(
x=["Baseline", "Tax Credit", "Phase-out", "Reform"],
y=[baseline_revenue, credit_cost, phaseout_revenue, 0],
measure=["absolute", "relative", "relative", "total"],
connector={"line": {"color": "gray"}}
))
# Cell 1: Title and Description
"""
# Policy Analysis: [Policy Name]
**Date:** [Date]
**Author:** [Your Name]
## Summary
Brief description of the analysis and key findings.
"""
# Cell 2: Imports
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from policyengine_us import Simulation
# Cell 3: Configuration
YEAR = 2026
STATES = ["CA", "NY", "TX", "FL"]
# Cell 4+: Analysis sections with markdown headers
# Save DataFrame
df.to_csv("outputs/impact_analysis.csv", index=False)
# Save Plotly chart
fig.write_html("outputs/chart.html")
fig.write_image("outputs/chart.png", width=1200, height=600)
# Save summary statistics
summary = {
"total_winners": winners.sum(),
"total_losers": losers.sum(),
"avg_gain": gains[winners].mean(),
"avg_loss": gains[losers].mean()
}
pd.DataFrame([summary]).to_csv("outputs/summary.csv", index=False)
This skill includes example templates in the examples/ directory:
impact_analysis_template.ipynb - Standard impact analysisstate_comparison.py - State-by-state analysiscase_studies.py - Household case studiesreform_definitions.py - Common reform patternsProblem: Mixing years across calculations
Solution: Define year constant at top:
CURRENT_YEAR = 2026
# Use everywhere
simulation.calculate("income_tax", CURRENT_YEAR)
Problem: Creating new simulation for each income level
Solution: Use axes for efficiency:
# SLOW
for income in incomes:
situation = create_situation(income=income)
sim = Simulation(situation=situation)
results.append(sim.calculate("income_tax", 2026)[0])
# FAST
situation_with_axes = create_situation_with_axes(incomes)
sim = Simulation(situation=situation_with_axes)
results = sim.calculate("income_tax", 2026) # Array of all results
Problem: Only showing reform results
Solution: Always show both:
results = {
"baseline": sim_baseline.calculate("income_tax", 2026),
"reform": sim_reform.calculate("income_tax", 2026),
"change": reform - baseline
}
For larger-scale analyses, use the PolicyEngine API:
import requests
def calculate_via_api(situation, reform=None):
"""Calculate using PolicyEngine API."""
url = "https://api.policyengine.org/us/calculate"
payload = {
"household": situation,
"policy_id": reform_id if reform else baseline_policy_id
}
response = requests.post(url, json=payload)
return response.json()
import pytest
def test_reform_increases_ctc():
"""Test that reform increases CTC as expected."""
situation = create_family(income=50000, num_children=2)
sim_baseline = Simulation(situation=situation)
sim_reform = Simulation(situation=situation, reform=reform)
ctc_baseline = sim_baseline.calculate("ctc", 2026)[0]
ctc_reform = sim_reform.calculate("ctc", 2026)[0]
assert ctc_reform > ctc_baseline, "Reform should increase CTC"
assert ctc_reform == 5000 * 2, "CTC should be $5000 per child"
# [Analysis Name]
## Overview
Brief description of the analysis.
## Key Findings
- Finding 1
- Finding 2
- Finding 3
## Methodology
Explanation of approach and data sources.
## How to Run
\```bash
uv pip install -r requirements.txt
python app.py # or jupyter notebook analysis.ipynb
\```
## Outputs
- `outputs/chart1.png` - Description
- `outputs/results.csv` - Description
## Contact
PolicyEngine Team - [email protected]
development
ALWAYS LOAD THIS SKILL for PolicyEngine PR reviews, including when the user invokes $review-program or Codex /review on a PolicyEngine PR. Performs read-only code validation, source-reference checks, regulatory review, optional PDF audit, summary reporting, and optional GitHub comment posting.
development
Use when the user invokes $fix-pr or asks Codex to apply fixes to a PolicyEngine PR based on $review-program findings, GitHub review comments, CI failures, or local review reports.
development
Use when the user invokes $encode-policy-v2 or asks Codex to implement a new PolicyEngine-US state benefit program from official rules. Covers research, source collection, requirement extraction, scoped implementation, tests, validation, and draft PR preparation.
development
Deploying PolicyEngine frontend apps to Vercel - naming, scope, team settings