Initial commit
This commit is contained in:
329
skills/microdf-skill/SKILL.md
Normal file
329
skills/microdf-skill/SKILL.md
Normal file
@@ -0,0 +1,329 @@
|
||||
---
|
||||
name: microdf
|
||||
description: Weighted pandas DataFrames for survey microdata analysis - inequality, poverty, and distributional calculations
|
||||
---
|
||||
|
||||
# MicroDF
|
||||
|
||||
MicroDF provides weighted pandas DataFrames and Series for analyzing survey microdata, with built-in support for inequality and poverty calculations.
|
||||
|
||||
## For Users 👥
|
||||
|
||||
### What is MicroDF?
|
||||
|
||||
When you see poverty rates, Gini coefficients, or distributional charts in PolicyEngine, those are calculated using MicroDF.
|
||||
|
||||
**MicroDF powers:**
|
||||
- Poverty rate calculations (SPM)
|
||||
- Inequality metrics (Gini coefficient)
|
||||
- Income distribution analysis
|
||||
- Weighted statistics from survey data
|
||||
|
||||
### Understanding the Metrics
|
||||
|
||||
**Gini coefficient:**
|
||||
- Calculated using MicroDF from weighted income data
|
||||
- Ranges from 0 (perfect equality) to 1 (perfect inequality)
|
||||
- US typically around 0.48
|
||||
|
||||
**Poverty rates:**
|
||||
- Calculated using MicroDF with weighted household data
|
||||
- Compares income to poverty thresholds
|
||||
- Accounts for household composition
|
||||
|
||||
**Percentiles:**
|
||||
- MicroDF calculates weighted percentiles
|
||||
- Shows income distribution (10th, 50th, 90th percentile)
|
||||
|
||||
## For Analysts 📊
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pip install microdf-python
|
||||
```
|
||||
|
||||
### Quick Start
|
||||
|
||||
```python
|
||||
import microdf as mdf
|
||||
import pandas as pd
|
||||
|
||||
# Create sample data
|
||||
df = pd.DataFrame({
|
||||
'income': [10000, 20000, 30000, 40000, 50000],
|
||||
'weights': [1, 2, 3, 2, 1]
|
||||
})
|
||||
|
||||
# Create MicroDataFrame
|
||||
mdf_df = mdf.MicroDataFrame(df, weights='weights')
|
||||
|
||||
# All operations are weight-aware
|
||||
print(f"Weighted mean: ${mdf_df.income.mean():,.0f}")
|
||||
print(f"Gini coefficient: {mdf_df.income.gini():.3f}")
|
||||
```
|
||||
|
||||
### Common Operations
|
||||
|
||||
**Weighted statistics:**
|
||||
```python
|
||||
mdf_df.income.mean() # Weighted mean
|
||||
mdf_df.income.median() # Weighted median
|
||||
mdf_df.income.sum() # Weighted sum
|
||||
mdf_df.income.std() # Weighted standard deviation
|
||||
```
|
||||
|
||||
**Inequality metrics:**
|
||||
```python
|
||||
mdf_df.income.gini() # Gini coefficient
|
||||
mdf_df.income.top_x_pct_share(10) # Top 10% share
|
||||
mdf_df.income.top_x_pct_share(1) # Top 1% share
|
||||
```
|
||||
|
||||
**Poverty analysis:**
|
||||
```python
|
||||
# Poverty rate (income < threshold)
|
||||
poverty_rate = mdf_df.poverty_rate(
|
||||
income_measure='income',
|
||||
threshold=poverty_line
|
||||
)
|
||||
|
||||
# Poverty gap (how far below threshold)
|
||||
poverty_gap = mdf_df.poverty_gap(
|
||||
income_measure='income',
|
||||
threshold=poverty_line
|
||||
)
|
||||
|
||||
# Deep poverty (income < 50% of threshold)
|
||||
deep_poverty_rate = mdf_df.deep_poverty_rate(
|
||||
income_measure='income',
|
||||
threshold=poverty_line,
|
||||
deep_poverty_line=0.5
|
||||
)
|
||||
```
|
||||
|
||||
**Quantiles:**
|
||||
```python
|
||||
# Deciles
|
||||
mdf_df.income.decile_values()
|
||||
|
||||
# Quintiles
|
||||
mdf_df.income.quintile_values()
|
||||
|
||||
# Custom quantiles
|
||||
mdf_df.income.quantile(0.25) # 25th percentile
|
||||
```
|
||||
|
||||
### MicroSeries
|
||||
|
||||
```python
|
||||
# Extract a Series with weights
|
||||
income_series = mdf_df.income # This is a MicroSeries
|
||||
|
||||
# MicroSeries operations
|
||||
income_series.mean()
|
||||
income_series.gini()
|
||||
income_series.percentile(50)
|
||||
```
|
||||
|
||||
### Working with PolicyEngine Results
|
||||
|
||||
```python
|
||||
import microdf as mdf
|
||||
from policyengine_us import Simulation
|
||||
|
||||
# Run simulation with axes (multiple households)
|
||||
situation_with_axes = {...} # See policyengine-us-skill
|
||||
sim = Simulation(situation=situation_with_axes)
|
||||
|
||||
# Get results as arrays
|
||||
incomes = sim.calculate("household_net_income", 2024)
|
||||
weights = sim.calculate("household_weight", 2024)
|
||||
|
||||
# Create MicroDataFrame
|
||||
df = pd.DataFrame({'income': incomes, 'weight': weights})
|
||||
mdf_df = mdf.MicroDataFrame(df, weights='weight')
|
||||
|
||||
# Calculate metrics
|
||||
gini = mdf_df.income.gini()
|
||||
poverty_rate = mdf_df.poverty_rate('income', threshold=15000)
|
||||
|
||||
print(f"Gini: {gini:.3f}")
|
||||
print(f"Poverty rate: {poverty_rate:.1%}")
|
||||
```
|
||||
|
||||
## For Contributors 💻
|
||||
|
||||
### Repository
|
||||
|
||||
**Location:** PolicyEngine/microdf
|
||||
|
||||
**Clone:**
|
||||
```bash
|
||||
git clone https://github.com/PolicyEngine/microdf
|
||||
cd microdf
|
||||
```
|
||||
|
||||
### Current Implementation
|
||||
|
||||
**To see current API:**
|
||||
```bash
|
||||
# Main classes
|
||||
cat microdf/microframe.py # MicroDataFrame
|
||||
cat microdf/microseries.py # MicroSeries
|
||||
|
||||
# Key modules
|
||||
cat microdf/generic.py # Generic weighted operations
|
||||
cat microdf/inequality.py # Gini, top shares
|
||||
cat microdf/poverty.py # Poverty metrics
|
||||
```
|
||||
|
||||
**To see all methods:**
|
||||
```bash
|
||||
# MicroDataFrame methods
|
||||
grep "def " microdf/microframe.py
|
||||
|
||||
# MicroSeries methods
|
||||
grep "def " microdf/microseries.py
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
**To see test patterns:**
|
||||
```bash
|
||||
ls tests/
|
||||
cat tests/test_microframe.py
|
||||
```
|
||||
|
||||
**Run tests:**
|
||||
```bash
|
||||
make test
|
||||
|
||||
# Or
|
||||
pytest tests/ -v
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
**Before contributing:**
|
||||
1. Check if method already exists
|
||||
2. Ensure it's weighted correctly
|
||||
3. Add tests
|
||||
4. Follow policyengine-standards-skill
|
||||
|
||||
**Common contributions:**
|
||||
- New inequality metrics
|
||||
- New poverty measures
|
||||
- Performance optimizations
|
||||
- Bug fixes
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Custom Aggregations
|
||||
|
||||
```python
|
||||
# Define custom weighted aggregation
|
||||
def weighted_operation(series, weights):
|
||||
return (series * weights).sum() / weights.sum()
|
||||
|
||||
# Apply to MicroSeries
|
||||
result = weighted_operation(mdf_df.income, mdf_df.weights)
|
||||
```
|
||||
|
||||
### Groupby Operations
|
||||
|
||||
```python
|
||||
# Group by with weights
|
||||
grouped = mdf_df.groupby('state')
|
||||
state_means = grouped.income.mean() # Weighted means by state
|
||||
```
|
||||
|
||||
### Inequality Decomposition
|
||||
|
||||
**To see decomposition methods:**
|
||||
```bash
|
||||
grep -A 20 "def.*decomp" microdf/
|
||||
```
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Example 1: PolicyEngine Blog Post Analysis
|
||||
|
||||
```python
|
||||
# Pattern from PolicyEngine blog posts
|
||||
import microdf as mdf
|
||||
|
||||
# Get simulation results
|
||||
baseline_income = baseline_sim.calculate("household_net_income", 2024)
|
||||
reform_income = reform_sim.calculate("household_net_income", 2024)
|
||||
weights = baseline_sim.calculate("household_weight", 2024)
|
||||
|
||||
# Create MicroDataFrame
|
||||
df = pd.DataFrame({
|
||||
'baseline_income': baseline_income,
|
||||
'reform_income': reform_income,
|
||||
'weight': weights
|
||||
})
|
||||
mdf_df = mdf.MicroDataFrame(df, weights='weight')
|
||||
|
||||
# Calculate impacts
|
||||
baseline_gini = mdf_df.baseline_income.gini()
|
||||
reform_gini = mdf_df.reform_income.gini()
|
||||
|
||||
print(f"Gini change: {reform_gini - baseline_gini:+.4f}")
|
||||
```
|
||||
|
||||
### Example 2: Poverty Analysis
|
||||
|
||||
```python
|
||||
# Calculate poverty under baseline and reform
|
||||
from policyengine_us import Simulation
|
||||
|
||||
baseline_sim = Simulation(situation=situation)
|
||||
reform_sim = Simulation(situation=situation, reform=reform)
|
||||
|
||||
# Get incomes
|
||||
baseline_income = baseline_sim.calculate("spm_unit_net_income", 2024)
|
||||
reform_income = reform_sim.calculate("spm_unit_net_income", 2024)
|
||||
spm_threshold = baseline_sim.calculate("spm_unit_poverty_threshold", 2024)
|
||||
weights = baseline_sim.calculate("spm_unit_weight", 2024)
|
||||
|
||||
# Calculate poverty rates
|
||||
df_baseline = mdf.MicroDataFrame(
|
||||
pd.DataFrame({'income': baseline_income, 'threshold': spm_threshold, 'weight': weights}),
|
||||
weights='weight'
|
||||
)
|
||||
|
||||
poverty_baseline = (df_baseline.income < df_baseline.threshold).mean() # Weighted
|
||||
|
||||
# Similar for reform
|
||||
print(f"Poverty reduction: {(poverty_baseline - poverty_reform):.1%}")
|
||||
```
|
||||
|
||||
## Package Status
|
||||
|
||||
**Maturity:** Stable, production-ready
|
||||
**API stability:** Stable (rarely breaking changes)
|
||||
**Performance:** Optimized for large datasets
|
||||
|
||||
**To see version:**
|
||||
```bash
|
||||
pip show microdf-python
|
||||
```
|
||||
|
||||
**To see changelog:**
|
||||
```bash
|
||||
cat CHANGELOG.md # In microdf repo
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **policyengine-us-skill** - Generating data for microdf analysis
|
||||
- **policyengine-analysis-skill** - Using microdf in policy analysis
|
||||
- **policyengine-us-data-skill** - Data sources for microdf
|
||||
|
||||
## Resources
|
||||
|
||||
**Repository:** https://github.com/PolicyEngine/microdf
|
||||
**PyPI:** https://pypi.org/project/microdf-python/
|
||||
**Issues:** https://github.com/PolicyEngine/microdf/issues
|
||||
569
skills/policyengine-analysis-skill/SKILL.md
Normal file
569
skills/policyengine-analysis-skill/SKILL.md
Normal file
@@ -0,0 +1,569 @@
|
||||
---
|
||||
name: policyengine-analysis
|
||||
description: Common analysis patterns for PolicyEngine research repositories (CRFB, newsletters, dashboards, impact studies)
|
||||
---
|
||||
|
||||
# PolicyEngine Analysis
|
||||
|
||||
Patterns for creating policy impact analyses, dashboards, and research using PolicyEngine.
|
||||
|
||||
## For Users 👥
|
||||
|
||||
### What are Analysis Repositories?
|
||||
|
||||
Analysis repositories produce the research you see on PolicyEngine:
|
||||
|
||||
**Blog posts:**
|
||||
- "How Montana's tax cuts affect poverty"
|
||||
- "Harris EITC proposal costs and impacts"
|
||||
- "UK Budget 2024 analysis"
|
||||
|
||||
**Dashboards:**
|
||||
- State tax comparisons
|
||||
- Policy proposal scorecards
|
||||
- Interactive calculators (GiveCalc, SALT calculator)
|
||||
|
||||
**Research reports:**
|
||||
- Distributional analyses for organizations
|
||||
- Policy briefs for legislators
|
||||
- Impact assessments
|
||||
|
||||
### How Analysis Works
|
||||
|
||||
1. **Define policy reform** using PolicyEngine parameters
|
||||
2. **Create household examples** showing specific impacts
|
||||
3. **Run population simulations** for aggregate effects
|
||||
4. **Calculate distributional impacts** (who wins, who loses)
|
||||
5. **Create visualizations** (charts, tables)
|
||||
6. **Write report** following policyengine-writing-skill style
|
||||
7. **Publish** to blog or share with stakeholders
|
||||
|
||||
### Reading PolicyEngine Analysis
|
||||
|
||||
**Key sections in typical analysis:**
|
||||
|
||||
**The proposal:**
|
||||
- What policy changes
|
||||
- Specific parameter values
|
||||
|
||||
**Household impacts:**
|
||||
- 3-5 example households
|
||||
- Dollar amounts for each
|
||||
- Charts showing impact across income range
|
||||
|
||||
**Statewide/national impacts:**
|
||||
- Total cost or revenue
|
||||
- Winners and losers by income decile
|
||||
- Poverty and inequality effects
|
||||
|
||||
**See policyengine-writing-skill for writing conventions.**
|
||||
|
||||
## For Analysts 📊
|
||||
|
||||
### When to Use This Skill
|
||||
|
||||
- Creating policy impact analyses
|
||||
- Building interactive dashboards with Streamlit/Plotly
|
||||
- Writing analysis notebooks
|
||||
- Calculating distributional impacts
|
||||
- Comparing policy proposals
|
||||
- Creating visualizations for research
|
||||
- Publishing policy research
|
||||
|
||||
### Example Analysis Repositories
|
||||
|
||||
- `crfb-tob-impacts` - Policy impact analyses
|
||||
- `newsletters` - Data-driven newsletters
|
||||
- `2024-election-dashboard` - Policy comparison dashboards
|
||||
- `marginal-child` - Specialized policy analyses
|
||||
- `givecalc` - Charitable giving calculator
|
||||
|
||||
## Repository Structure
|
||||
|
||||
Standard analysis repository structure:
|
||||
|
||||
```
|
||||
analysis-repo/
|
||||
├── analysis.ipynb # Main Jupyter notebook
|
||||
├── app.py # Streamlit app (if applicable)
|
||||
├── requirements.txt # Python dependencies
|
||||
├── README.md # Documentation
|
||||
├── data/ # Data files (if needed)
|
||||
├── outputs/ # Generated charts, tables
|
||||
└── .streamlit/ # Streamlit config
|
||||
└── config.toml
|
||||
```
|
||||
|
||||
## Common Analysis Patterns
|
||||
|
||||
### Pattern 1: Impact Analysis Across Income Distribution
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from policyengine_us import Simulation
|
||||
|
||||
# Define reform
|
||||
reform = {
|
||||
"gov.irs.credits.ctc.amount.base_amount": {
|
||||
"2024-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", 2024)[0]
|
||||
|
||||
# Reform
|
||||
sim_reform = Simulation(situation=situation, reform=reform)
|
||||
tax_reform = sim_reform.calculate("income_tax", 2024)[0]
|
||||
|
||||
results.append({
|
||||
"income": income,
|
||||
"tax_baseline": tax_baseline,
|
||||
"tax_reform": tax_reform,
|
||||
"tax_change": tax_reform - tax_baseline
|
||||
})
|
||||
|
||||
df = pd.DataFrame(results)
|
||||
```
|
||||
|
||||
### Pattern 2: Household-Level Case Studies
|
||||
|
||||
```python
|
||||
# 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", 2024)[0],
|
||||
"reform_tax": sim_reform.calculate("income_tax", 2024)[0],
|
||||
"ctc_baseline": sim_baseline.calculate("ctc", 2024)[0],
|
||||
"ctc_reform": sim_reform.calculate("ctc", 2024)[0]
|
||||
}
|
||||
|
||||
case_df = pd.DataFrame(case_studies).T
|
||||
```
|
||||
|
||||
### Pattern 3: State-by-State Comparison
|
||||
|
||||
```python
|
||||
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", 2024)[0],
|
||||
"reform_net_income": sim_reform.calculate("household_net_income", 2024)[0],
|
||||
"change": (sim_reform.calculate("household_net_income", 2024)[0] -
|
||||
sim_baseline.calculate("household_net_income", 2024)[0])
|
||||
})
|
||||
|
||||
state_df = pd.DataFrame(state_results)
|
||||
```
|
||||
|
||||
### Pattern 4: Marginal Analysis (Winners/Losers)
|
||||
|
||||
```python
|
||||
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": 2024
|
||||
}]]
|
||||
}
|
||||
|
||||
sim_baseline = Simulation(situation=situation_with_axes)
|
||||
sim_reform = Simulation(situation=situation_with_axes, reform=reform)
|
||||
|
||||
incomes = sim_baseline.calculate("employment_income", 2024)
|
||||
baseline_net = sim_baseline.calculate("household_net_income", 2024)
|
||||
reform_net = sim_reform.calculate("household_net_income", 2024)
|
||||
|
||||
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}%")
|
||||
```
|
||||
|
||||
## Visualization Patterns
|
||||
|
||||
### Standard Plotly Configuration
|
||||
|
||||
```python
|
||||
import plotly.graph_objects as go
|
||||
|
||||
# PolicyEngine brand colors
|
||||
TEAL = "#39C6C0"
|
||||
BLUE = "#2C6496"
|
||||
DARK_GRAY = "#616161"
|
||||
|
||||
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="Roboto Serif", 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)))
|
||||
```
|
||||
|
||||
### Common Chart Types
|
||||
|
||||
**1. Line Chart (Impact by Income)**
|
||||
```python
|
||||
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)**
|
||||
```python
|
||||
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)**
|
||||
```python
|
||||
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"}}
|
||||
))
|
||||
```
|
||||
|
||||
## Streamlit Dashboard Patterns
|
||||
|
||||
### Basic Streamlit Setup
|
||||
|
||||
```python
|
||||
import streamlit as st
|
||||
from policyengine_us import Simulation
|
||||
|
||||
st.set_page_config(page_title="Policy Analysis", layout="wide")
|
||||
|
||||
st.title("Policy Impact Calculator")
|
||||
|
||||
# User inputs
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
income = st.number_input("Income", value=60000, step=5000)
|
||||
with col2:
|
||||
state = st.selectbox("State", ["CA", "NY", "TX", "FL"])
|
||||
with col3:
|
||||
num_children = st.number_input("Children", value=0, min_value=0, max_value=10)
|
||||
|
||||
# Calculate
|
||||
if st.button("Calculate"):
|
||||
situation = create_family(
|
||||
parent_income=income,
|
||||
num_children=num_children,
|
||||
state=state
|
||||
)
|
||||
|
||||
sim_baseline = Simulation(situation=situation)
|
||||
sim_reform = Simulation(situation=situation, reform=reform)
|
||||
|
||||
# Display results
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
st.metric(
|
||||
"Baseline Tax",
|
||||
f"${sim_baseline.calculate('income_tax', 2024)[0]:,.0f}"
|
||||
)
|
||||
with col2:
|
||||
st.metric(
|
||||
"Reform Tax",
|
||||
f"${sim_reform.calculate('income_tax', 2024)[0]:,.0f}"
|
||||
)
|
||||
with col3:
|
||||
change = (sim_reform.calculate('income_tax', 2024)[0] -
|
||||
sim_baseline.calculate('income_tax', 2024)[0])
|
||||
st.metric("Change", f"${change:,.0f}", delta=f"${-change:,.0f}")
|
||||
```
|
||||
|
||||
### Interactive Chart with Streamlit
|
||||
|
||||
```python
|
||||
# Create chart based on user inputs
|
||||
incomes = np.linspace(0, income_max, 1001)
|
||||
results = []
|
||||
|
||||
for income in incomes:
|
||||
situation = create_situation(income=income, state=selected_state)
|
||||
sim = Simulation(situation=situation, reform=reform)
|
||||
results.append(sim.calculate("household_net_income", 2024)[0])
|
||||
|
||||
fig = go.Figure()
|
||||
fig.add_trace(go.Scatter(x=incomes, y=results, line=dict(color=TEAL)))
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
```
|
||||
|
||||
## Jupyter Notebook Best Practices
|
||||
|
||||
### Notebook Structure
|
||||
|
||||
```python
|
||||
# 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 = 2024
|
||||
STATES = ["CA", "NY", "TX", "FL"]
|
||||
|
||||
# Cell 4+: Analysis sections with markdown headers
|
||||
```
|
||||
|
||||
### Export Results
|
||||
|
||||
```python
|
||||
# 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)
|
||||
```
|
||||
|
||||
## Repository-Specific Examples
|
||||
|
||||
This skill includes example templates in the `examples/` directory:
|
||||
|
||||
- `impact_analysis_template.ipynb` - Standard impact analysis
|
||||
- `dashboard_template.py` - Streamlit dashboard
|
||||
- `state_comparison.py` - State-by-state analysis
|
||||
- `case_studies.py` - Household case studies
|
||||
- `reform_definitions.py` - Common reform patterns
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Pitfall 1: Not Using Consistent Year
|
||||
**Problem:** Mixing 2024 and 2025 calculations
|
||||
|
||||
**Solution:** Define year constant at top:
|
||||
```python
|
||||
CURRENT_YEAR = 2024
|
||||
# Use everywhere
|
||||
simulation.calculate("income_tax", CURRENT_YEAR)
|
||||
```
|
||||
|
||||
### Pitfall 2: Inefficient Simulations
|
||||
**Problem:** Creating new simulation for each income level
|
||||
|
||||
**Solution:** Use axes for efficiency:
|
||||
```python
|
||||
# SLOW
|
||||
for income in incomes:
|
||||
situation = create_situation(income=income)
|
||||
sim = Simulation(situation=situation)
|
||||
results.append(sim.calculate("income_tax", 2024)[0])
|
||||
|
||||
# FAST
|
||||
situation_with_axes = create_situation_with_axes(incomes)
|
||||
sim = Simulation(situation=situation_with_axes)
|
||||
results = sim.calculate("income_tax", 2024) # Array of all results
|
||||
```
|
||||
|
||||
### Pitfall 3: Forgetting to Compare Baseline and Reform
|
||||
**Problem:** Only showing reform results
|
||||
|
||||
**Solution:** Always show both:
|
||||
```python
|
||||
results = {
|
||||
"baseline": sim_baseline.calculate("income_tax", 2024),
|
||||
"reform": sim_reform.calculate("income_tax", 2024),
|
||||
"change": reform - baseline
|
||||
}
|
||||
```
|
||||
|
||||
## PolicyEngine API Usage
|
||||
|
||||
For larger-scale analyses, use the PolicyEngine API:
|
||||
|
||||
```python
|
||||
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()
|
||||
```
|
||||
|
||||
## Testing Analysis Code
|
||||
|
||||
```python
|
||||
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", 2024)[0]
|
||||
ctc_reform = sim_reform.calculate("ctc", 2024)[0]
|
||||
|
||||
assert ctc_reform > ctc_baseline, "Reform should increase CTC"
|
||||
assert ctc_reform == 5000 * 2, "CTC should be $5000 per child"
|
||||
```
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
### README Template
|
||||
|
||||
```markdown
|
||||
# [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
|
||||
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 - hello@policyengine.org
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **PolicyEngine API Docs:** https://policyengine.org/us/api
|
||||
- **Analysis Examples:** https://github.com/PolicyEngine/analysis-notebooks
|
||||
- **Streamlit Docs:** https://docs.streamlit.io
|
||||
- **Plotly Docs:** https://plotly.com/python/
|
||||
178
skills/policyengine-analysis-skill/examples/reform_template.py
Normal file
178
skills/policyengine-analysis-skill/examples/reform_template.py
Normal file
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
Template for PolicyEngine reform impact analysis.
|
||||
|
||||
This template provides a starting point for analyzing the impact
|
||||
of a policy reform across the income distribution.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
from policyengine_us import Simulation
|
||||
|
||||
# Configuration
|
||||
CURRENT_YEAR = 2024
|
||||
INCOME_MIN = 0
|
||||
INCOME_MAX = 200000
|
||||
INCOME_STEPS = 101
|
||||
|
||||
# Define your reform here
|
||||
REFORM = {
|
||||
"gov.irs.credits.ctc.amount.base_amount": {
|
||||
"2024-01-01.2100-12-31": 5000 # Example: Increase CTC to $5,000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def create_situation(income, num_children=0, state="CA"):
|
||||
"""Create a basic household situation."""
|
||||
people = {
|
||||
"parent": {
|
||||
"age": {CURRENT_YEAR: 35},
|
||||
"employment_income": {CURRENT_YEAR: income}
|
||||
}
|
||||
}
|
||||
|
||||
members = ["parent"]
|
||||
|
||||
# Add children
|
||||
for i in range(num_children):
|
||||
child_id = f"child_{i+1}"
|
||||
people[child_id] = {"age": {CURRENT_YEAR: 8}}
|
||||
members.append(child_id)
|
||||
|
||||
return {
|
||||
"people": people,
|
||||
"families": {"family": {"members": members}},
|
||||
"marital_units": {"marital_unit": {"members": ["parent"]}},
|
||||
"tax_units": {"tax_unit": {"members": members}},
|
||||
"spm_units": {"spm_unit": {"members": members}},
|
||||
"households": {
|
||||
"household": {
|
||||
"members": members,
|
||||
"state_name": {CURRENT_YEAR: state}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def analyze_reform(num_children=2, state="CA"):
|
||||
"""Analyze reform impact across income distribution."""
|
||||
incomes = np.linspace(INCOME_MIN, INCOME_MAX, INCOME_STEPS)
|
||||
results = []
|
||||
|
||||
for income in incomes:
|
||||
situation = create_situation(
|
||||
income=income,
|
||||
num_children=num_children,
|
||||
state=state
|
||||
)
|
||||
|
||||
# Baseline
|
||||
sim_baseline = Simulation(situation=situation)
|
||||
income_tax_baseline = sim_baseline.calculate("income_tax", CURRENT_YEAR)[0]
|
||||
ctc_baseline = sim_baseline.calculate("ctc", CURRENT_YEAR)[0]
|
||||
net_income_baseline = sim_baseline.calculate("household_net_income", CURRENT_YEAR)[0]
|
||||
|
||||
# Reform
|
||||
sim_reform = Simulation(situation=situation, reform=REFORM)
|
||||
income_tax_reform = sim_reform.calculate("income_tax", CURRENT_YEAR)[0]
|
||||
ctc_reform = sim_reform.calculate("ctc", CURRENT_YEAR)[0]
|
||||
net_income_reform = sim_reform.calculate("household_net_income", CURRENT_YEAR)[0]
|
||||
|
||||
results.append({
|
||||
"income": income,
|
||||
"income_tax_baseline": income_tax_baseline,
|
||||
"income_tax_reform": income_tax_reform,
|
||||
"ctc_baseline": ctc_baseline,
|
||||
"ctc_reform": ctc_reform,
|
||||
"net_income_baseline": net_income_baseline,
|
||||
"net_income_reform": net_income_reform,
|
||||
"tax_change": income_tax_reform - income_tax_baseline,
|
||||
"ctc_change": ctc_reform - ctc_baseline,
|
||||
"net_income_change": net_income_reform - net_income_baseline
|
||||
})
|
||||
|
||||
return pd.DataFrame(results)
|
||||
|
||||
|
||||
def create_chart(df, title="Reform Impact Analysis"):
|
||||
"""Create Plotly chart of reform impacts."""
|
||||
TEAL = "#39C6C0"
|
||||
|
||||
fig = go.Figure()
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=df.income,
|
||||
y=df.net_income_change,
|
||||
mode='lines',
|
||||
name='Net Income Change',
|
||||
line=dict(color=TEAL, width=3)
|
||||
))
|
||||
|
||||
fig.update_layout(
|
||||
title=title,
|
||||
xaxis_title="Income",
|
||||
yaxis_title="Net Income Change ($)",
|
||||
font=dict(family="Roboto Serif", size=14),
|
||||
plot_bgcolor="white",
|
||||
hovermode="x unified",
|
||||
xaxis=dict(
|
||||
tickformat="$,.0f",
|
||||
showgrid=True,
|
||||
gridcolor="lightgray"
|
||||
),
|
||||
yaxis=dict(
|
||||
tickformat="$,.0f",
|
||||
showgrid=True,
|
||||
gridcolor="lightgray",
|
||||
zeroline=True,
|
||||
zerolinecolor="black",
|
||||
zerolinewidth=1
|
||||
)
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
def print_summary(df):
|
||||
"""Print summary statistics."""
|
||||
print("\n=== Reform Impact Summary ===\n")
|
||||
|
||||
winners = df[df.net_income_change > 0]
|
||||
losers = df[df.net_income_change < 0]
|
||||
|
||||
print(f"Winners: {len(winners) / len(df) * 100:.1f}%")
|
||||
print(f"Losers: {len(losers) / len(df) * 100:.1f}%")
|
||||
|
||||
if len(winners) > 0:
|
||||
print(f"\nAverage gain (winners): ${winners.net_income_change.mean():,.2f}")
|
||||
print(f"Max gain: ${df.net_income_change.max():,.2f}")
|
||||
|
||||
if len(losers) > 0:
|
||||
print(f"\nAverage loss (losers): ${losers.net_income_change.mean():,.2f}")
|
||||
print(f"Max loss: ${df.net_income_change.min():,.2f}")
|
||||
|
||||
print(f"\nAverage CTC change: ${df.ctc_change.mean():,.2f}")
|
||||
print(f"Average tax change: ${df.tax_change.mean():,.2f}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run analysis
|
||||
print("Running reform analysis...")
|
||||
df = analyze_reform(num_children=2, state="CA")
|
||||
|
||||
# Print summary
|
||||
print_summary(df)
|
||||
|
||||
# Save results
|
||||
df.to_csv("reform_impact_results.csv", index=False)
|
||||
print("\nResults saved to reform_impact_results.csv")
|
||||
|
||||
# Create and save chart
|
||||
fig = create_chart(df)
|
||||
fig.write_html("reform_impact_chart.html")
|
||||
print("Chart saved to reform_impact_chart.html")
|
||||
|
||||
# Display chart (if running interactively)
|
||||
fig.show()
|
||||
880
skills/policyengine-design-skill/SKILL.md
Normal file
880
skills/policyengine-design-skill/SKILL.md
Normal file
@@ -0,0 +1,880 @@
|
||||
---
|
||||
name: policyengine-design
|
||||
description: PolicyEngine visual identity - colors, fonts, logos, and branding for web apps, calculators, charts, and research
|
||||
---
|
||||
|
||||
# PolicyEngine Design System
|
||||
|
||||
PolicyEngine's visual identity and branding guidelines for creating consistent user experiences across web apps, calculators, charts, and research outputs.
|
||||
|
||||
## For Users 👥
|
||||
|
||||
### PolicyEngine Visual Identity
|
||||
|
||||
**Brand colors:**
|
||||
- **Teal** (#39C6C0) - Primary accent color (buttons, highlights, interactive elements)
|
||||
- **Blue** (#2C6496) - Secondary color (links, charts, headers)
|
||||
|
||||
**Typography:**
|
||||
- **Charts:** Roboto Serif
|
||||
- **Web app:** System fonts (sans-serif)
|
||||
- **Streamlit apps:** Default sans-serif
|
||||
|
||||
**Logo:**
|
||||
- Used in charts (bottom right)
|
||||
- Blue version for light backgrounds
|
||||
- White version for dark backgrounds
|
||||
|
||||
### Recognizing PolicyEngine Content
|
||||
|
||||
**You can identify PolicyEngine content by:**
|
||||
- Teal accent color (#39C6C0) on buttons and interactive elements
|
||||
- Blue (#2C6496) in charts and links
|
||||
- Roboto Serif font in charts
|
||||
- PolicyEngine logo in chart footer
|
||||
- Clean, minimal white backgrounds
|
||||
- Data-focused, quantitative presentation
|
||||
|
||||
## For Analysts 📊
|
||||
|
||||
### Chart Branding
|
||||
|
||||
When creating charts for PolicyEngine analysis, follow these guidelines:
|
||||
|
||||
#### Color Palette
|
||||
|
||||
**Primary colors:**
|
||||
```python
|
||||
TEAL_ACCENT = "#39C6C0" # Primary color (teal)
|
||||
BLUE_PRIMARY = "#2C6496" # Secondary color (blue)
|
||||
DARK_GRAY = "#616161" # Text color
|
||||
```
|
||||
|
||||
**Extended palette:**
|
||||
```python
|
||||
# Blues
|
||||
BLUE = "#2C6496"
|
||||
BLUE_LIGHT = "#D8E6F3"
|
||||
BLUE_PRESSED = "#17354F"
|
||||
BLUE_98 = "#F7FAFD"
|
||||
DARK_BLUE_HOVER = "#1d3e5e"
|
||||
DARKEST_BLUE = "#0C1A27"
|
||||
|
||||
# Teals
|
||||
TEAL_ACCENT = "#39C6C0"
|
||||
TEAL_LIGHT = "#F7FDFC"
|
||||
TEAL_PRESSED = "#227773"
|
||||
|
||||
# Grays
|
||||
DARK_GRAY = "#616161"
|
||||
GRAY = "#808080"
|
||||
MEDIUM_LIGHT_GRAY = "#BDBDBD"
|
||||
MEDIUM_DARK_GRAY = "#D2D2D2"
|
||||
LIGHT_GRAY = "#F2F2F2"
|
||||
|
||||
# Accents
|
||||
WHITE = "#FFFFFF"
|
||||
BLACK = "#000000"
|
||||
DARK_RED = "#b50d0d" # For negative values
|
||||
```
|
||||
|
||||
**See current colors:**
|
||||
```bash
|
||||
cat policyengine-app/src/style/colors.js
|
||||
```
|
||||
|
||||
#### Plotly Chart Formatting
|
||||
|
||||
**Standard PolicyEngine chart:**
|
||||
|
||||
```python
|
||||
import plotly.graph_objects as go
|
||||
|
||||
def format_fig(fig):
|
||||
"""Format chart with PolicyEngine branding."""
|
||||
fig.update_layout(
|
||||
# Typography
|
||||
font=dict(
|
||||
family="Roboto Serif",
|
||||
color="black"
|
||||
),
|
||||
|
||||
# Background
|
||||
plot_bgcolor="white",
|
||||
template="plotly_white",
|
||||
|
||||
# Margins (leave room for logo)
|
||||
margin=dict(
|
||||
l=50,
|
||||
r=100,
|
||||
t=50,
|
||||
b=120,
|
||||
pad=4
|
||||
),
|
||||
|
||||
# Chart size
|
||||
height=600,
|
||||
width=800,
|
||||
)
|
||||
|
||||
# Add PolicyEngine logo (bottom right)
|
||||
fig.add_layout_image(
|
||||
dict(
|
||||
source="https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png",
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
x=1.0,
|
||||
y=-0.10,
|
||||
sizex=0.10,
|
||||
sizey=0.10,
|
||||
xanchor="right",
|
||||
yanchor="bottom"
|
||||
)
|
||||
)
|
||||
|
||||
# Clean modebar
|
||||
fig.update_layout(
|
||||
modebar=dict(
|
||||
bgcolor="rgba(0,0,0,0)",
|
||||
color="rgba(0,0,0,0)"
|
||||
)
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
# Usage
|
||||
fig = go.Figure()
|
||||
fig.add_trace(go.Scatter(x=x_data, y=y_data, line=dict(color=TEAL_ACCENT)))
|
||||
fig = format_fig(fig)
|
||||
```
|
||||
|
||||
**Current implementation:**
|
||||
```bash
|
||||
# See format_fig in action
|
||||
cat givecalc/ui/visualization.py
|
||||
cat policyengine-app/src/pages/policy/output/...
|
||||
```
|
||||
|
||||
#### Chart Colors
|
||||
|
||||
**For line charts:**
|
||||
- Primary line: Teal (#39C6C0) or Blue (#2C6496)
|
||||
- Background lines: Light gray (rgb(180, 180, 180))
|
||||
- Markers: Teal with 70% opacity
|
||||
|
||||
**For bar charts:**
|
||||
- Positive values: Teal (#39C6C0)
|
||||
- Negative values: Dark red (#b50d0d)
|
||||
- Neutral: Gray
|
||||
|
||||
**For multiple series:**
|
||||
Use variations of blue and teal, or discrete color scale:
|
||||
```python
|
||||
colors = ["#2C6496", "#39C6C0", "#17354F", "#227773"]
|
||||
```
|
||||
|
||||
#### Typography
|
||||
|
||||
**Charts:**
|
||||
```python
|
||||
font=dict(family="Roboto Serif", size=14, color="black")
|
||||
```
|
||||
|
||||
**Axis labels:**
|
||||
```python
|
||||
xaxis=dict(
|
||||
title=dict(text="Label", font=dict(size=14)),
|
||||
tickfont=dict(size=12)
|
||||
)
|
||||
```
|
||||
|
||||
**Load Roboto font:**
|
||||
```python
|
||||
# In Streamlit apps
|
||||
st.markdown("""
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
""", unsafe_allow_html=True)
|
||||
```
|
||||
|
||||
### Streamlit App Branding
|
||||
|
||||
**Streamlit configuration (.streamlit/config.toml):**
|
||||
|
||||
```toml
|
||||
[theme]
|
||||
base = "light"
|
||||
primaryColor = "#39C6C0" # Teal accent
|
||||
backgroundColor = "#FFFFFF" # White background
|
||||
secondaryBackgroundColor = "#F7FDFC" # Teal light
|
||||
textColor = "#616161" # Dark gray
|
||||
|
||||
[client]
|
||||
toolbarMode = "minimal"
|
||||
```
|
||||
|
||||
**Current implementation:**
|
||||
```bash
|
||||
cat givecalc/.streamlit/config.toml
|
||||
cat salt-amt-calculator/.streamlit/config.toml # Other calculators
|
||||
```
|
||||
|
||||
### Logo Usage
|
||||
|
||||
**Logo URLs:**
|
||||
|
||||
```python
|
||||
# Blue logo (for light backgrounds)
|
||||
LOGO_BLUE = "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png"
|
||||
|
||||
# White logo (for dark backgrounds)
|
||||
LOGO_WHITE = "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/white.png"
|
||||
|
||||
# SVG versions (scalable)
|
||||
LOGO_BLUE_SVG = "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.svg"
|
||||
LOGO_WHITE_SVG = "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/white.svg"
|
||||
```
|
||||
|
||||
**Logo placement in charts:**
|
||||
- Bottom right corner
|
||||
- 10% of chart width
|
||||
- Slightly below bottom edge (y=-0.10)
|
||||
|
||||
**Current logos:**
|
||||
```bash
|
||||
ls policyengine-app/src/images/logos/policyengine/
|
||||
```
|
||||
|
||||
### Complete Example: Branded Chart
|
||||
|
||||
```python
|
||||
import plotly.graph_objects as go
|
||||
|
||||
# PolicyEngine colors
|
||||
TEAL_ACCENT = "#39C6C0"
|
||||
BLUE_PRIMARY = "#2C6496"
|
||||
|
||||
# Create chart
|
||||
fig = go.Figure()
|
||||
|
||||
# Add data
|
||||
fig.add_trace(go.Scatter(
|
||||
x=incomes,
|
||||
y=taxes,
|
||||
mode='lines',
|
||||
name='Tax liability',
|
||||
line=dict(color=TEAL_ACCENT, width=3)
|
||||
))
|
||||
|
||||
# Apply PolicyEngine branding
|
||||
fig.update_layout(
|
||||
# Typography
|
||||
font=dict(family="Roboto Serif", size=14, color="black"),
|
||||
|
||||
# Title and labels
|
||||
title="Tax liability by income",
|
||||
xaxis_title="Income",
|
||||
yaxis_title="Tax ($)",
|
||||
|
||||
# Formatting
|
||||
xaxis_tickformat="$,.0f",
|
||||
yaxis_tickformat="$,.0f",
|
||||
|
||||
# Appearance
|
||||
plot_bgcolor="white",
|
||||
template="plotly_white",
|
||||
|
||||
# Size and margins
|
||||
height=600,
|
||||
width=800,
|
||||
margin=dict(l=50, r=100, t=50, b=120, pad=4)
|
||||
)
|
||||
|
||||
# Add logo
|
||||
fig.add_layout_image(
|
||||
dict(
|
||||
source="https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png",
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
x=1.0,
|
||||
y=-0.10,
|
||||
sizex=0.10,
|
||||
sizey=0.10,
|
||||
xanchor="right",
|
||||
yanchor="bottom"
|
||||
)
|
||||
)
|
||||
|
||||
# Show
|
||||
fig.show()
|
||||
```
|
||||
|
||||
## For Contributors 💻
|
||||
|
||||
### Brand Assets
|
||||
|
||||
**Repository:** PolicyEngine/policyengine-app-v2 (current), PolicyEngine/policyengine-app (legacy)
|
||||
|
||||
**Logo files:**
|
||||
```bash
|
||||
# Logos in app (both v1 and v2 use same logos)
|
||||
ls policyengine-app/src/images/logos/policyengine/
|
||||
# - blue.png - For light backgrounds
|
||||
# - white.png - For dark backgrounds
|
||||
# - blue.svg - Scalable blue logo
|
||||
# - white.svg - Scalable white logo
|
||||
# - banners/ - Banner variations
|
||||
# - profile/ - Profile/avatar versions
|
||||
```
|
||||
|
||||
**Access logos:**
|
||||
```bash
|
||||
# View logo files (v1 repo has the assets)
|
||||
cd policyengine-app/src/images/logos/policyengine/
|
||||
ls -la
|
||||
```
|
||||
|
||||
### Color Definitions
|
||||
|
||||
**⚠️ IMPORTANT: App V2 Transition**
|
||||
|
||||
PolicyEngine is transitioning to policyengine-app-v2 with updated design tokens. Use app-v2 colors for new projects.
|
||||
|
||||
**Current colors (policyengine-app-v2):**
|
||||
|
||||
```typescript
|
||||
// policyengine-app-v2/app/src/designTokens/colors.ts
|
||||
|
||||
// Primary (teal) - 50 to 900 scale
|
||||
primary[500]: "#319795" // Main teal
|
||||
primary[400]: "#38B2AC" // Lighter teal
|
||||
primary[600]: "#2C7A7B" // Darker teal
|
||||
|
||||
// Blue scale
|
||||
blue[700]: "#026AA2" // Primary blue
|
||||
blue[500]: "#0EA5E9" // Lighter blue
|
||||
|
||||
// Gray scale
|
||||
gray[700]: "#344054" // Dark text
|
||||
gray[100]: "#F2F4F7" // Light backgrounds
|
||||
|
||||
// Semantic
|
||||
success: "#22C55E"
|
||||
warning: "#FEC601"
|
||||
error: "#EF4444"
|
||||
|
||||
// Background
|
||||
background.primary: "#FFFFFF"
|
||||
background.secondary: "#F5F9FF"
|
||||
|
||||
// Text
|
||||
text.primary: "#000000"
|
||||
text.secondary: "#5A5A5A"
|
||||
```
|
||||
|
||||
**To see current design tokens:**
|
||||
```bash
|
||||
cat policyengine-app-v2/app/src/designTokens/colors.ts
|
||||
cat policyengine-app-v2/app/src/styles/colors.ts # Mantine integration
|
||||
```
|
||||
|
||||
**Legacy colors (policyengine-app - still used in some projects):**
|
||||
|
||||
```javascript
|
||||
// policyengine-app/src/style/colors.js
|
||||
TEAL_ACCENT = "#39C6C0" // Old teal (slightly different from v2)
|
||||
BLUE = "#2C6496" // Old blue
|
||||
DARK_GRAY = "#616161" // Old dark gray
|
||||
```
|
||||
|
||||
**To see legacy colors:**
|
||||
```bash
|
||||
cat policyengine-app/src/style/colors.js
|
||||
```
|
||||
|
||||
**Usage in React (app-v2):**
|
||||
```typescript
|
||||
import { colors } from 'designTokens';
|
||||
|
||||
<Button style={{ backgroundColor: colors.primary[500] }} />
|
||||
<Text style={{ color: colors.text.primary }} />
|
||||
```
|
||||
|
||||
**Usage in Python/Plotly (use legacy colors for now):**
|
||||
```python
|
||||
# For charts, continue using legacy colors until officially migrated
|
||||
TEAL_ACCENT = "#39C6C0" # From original app
|
||||
BLUE_PRIMARY = "#2C6496" # From original app
|
||||
|
||||
# Or use app-v2 colors
|
||||
TEAL_PRIMARY = "#319795" # From app-v2
|
||||
BLUE_PRIMARY_V2 = "#026AA2" # From app-v2
|
||||
```
|
||||
|
||||
### Typography
|
||||
|
||||
**Fonts:**
|
||||
|
||||
**For charts (Plotly):**
|
||||
```python
|
||||
font=dict(family="Roboto Serif")
|
||||
```
|
||||
|
||||
**For web apps:**
|
||||
```javascript
|
||||
// System font stack
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ...
|
||||
```
|
||||
|
||||
**Loading Google Fonts:**
|
||||
```html
|
||||
<!-- In Streamlit or HTML -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Roboto+Serif:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
### Chart Formatting Function
|
||||
|
||||
**Reference implementation:**
|
||||
|
||||
```bash
|
||||
# GiveCalc format_fig function
|
||||
cat givecalc/ui/visualization.py
|
||||
|
||||
# Shows:
|
||||
# - Roboto Serif font
|
||||
# - White background
|
||||
# - Logo placement
|
||||
# - Margin configuration
|
||||
```
|
||||
|
||||
**Pattern to follow:**
|
||||
```python
|
||||
def format_fig(fig: go.Figure) -> go.Figure:
|
||||
"""Format figure with PolicyEngine branding.
|
||||
|
||||
This function is used across PolicyEngine projects to ensure
|
||||
consistent chart appearance.
|
||||
"""
|
||||
# Font
|
||||
fig.update_layout(
|
||||
font=dict(family="Roboto Serif", color="black")
|
||||
)
|
||||
|
||||
# Background
|
||||
fig.update_layout(
|
||||
template="plotly_white",
|
||||
plot_bgcolor="white"
|
||||
)
|
||||
|
||||
# Size
|
||||
fig.update_layout(height=600, width=800)
|
||||
|
||||
# Margins (room for logo)
|
||||
fig.update_layout(
|
||||
margin=dict(l=50, r=100, t=50, b=120, pad=4)
|
||||
)
|
||||
|
||||
# Logo
|
||||
fig.add_layout_image(
|
||||
dict(
|
||||
source="https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png",
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
x=1.0,
|
||||
y=-0.10,
|
||||
sizex=0.10,
|
||||
sizey=0.10,
|
||||
xanchor="right",
|
||||
yanchor="bottom"
|
||||
)
|
||||
)
|
||||
|
||||
# Clean modebar
|
||||
fig.update_layout(
|
||||
modebar=dict(
|
||||
bgcolor="rgba(0,0,0,0)",
|
||||
color="rgba(0,0,0,0)"
|
||||
)
|
||||
)
|
||||
|
||||
return fig
|
||||
```
|
||||
|
||||
### Streamlit Theme Configuration
|
||||
|
||||
**Standard .streamlit/config.toml:**
|
||||
|
||||
```toml
|
||||
[theme]
|
||||
base = "light"
|
||||
primaryColor = "#39C6C0" # Teal accent
|
||||
backgroundColor = "#FFFFFF" # White
|
||||
secondaryBackgroundColor = "#F7FDFC" # Teal light
|
||||
textColor = "#616161" # Dark gray
|
||||
font = "sans serif"
|
||||
|
||||
[client]
|
||||
toolbarMode = "minimal"
|
||||
showErrorDetails = true
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Create .streamlit directory in your project
|
||||
mkdir .streamlit
|
||||
|
||||
# Copy configuration
|
||||
cat > .streamlit/config.toml << 'EOF'
|
||||
[theme]
|
||||
base = "light"
|
||||
primaryColor = "#39C6C0"
|
||||
backgroundColor = "#FFFFFF"
|
||||
secondaryBackgroundColor = "#F7FDFC"
|
||||
textColor = "#616161"
|
||||
font = "sans serif"
|
||||
EOF
|
||||
```
|
||||
|
||||
**Current examples:**
|
||||
```bash
|
||||
cat givecalc/.streamlit/config.toml
|
||||
```
|
||||
|
||||
## Design Patterns by Project Type
|
||||
|
||||
### Streamlit Calculators (GiveCalc, SALT Calculator, etc.)
|
||||
|
||||
**Required branding:**
|
||||
1. ✅ .streamlit/config.toml with PolicyEngine theme
|
||||
2. ✅ Charts use format_fig() function with logo
|
||||
3. ✅ Teal accent color for interactive elements
|
||||
4. ✅ Roboto Serif for charts
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
import streamlit as st
|
||||
import plotly.graph_objects as go
|
||||
|
||||
# Constants
|
||||
TEAL_ACCENT = "#39C6C0"
|
||||
|
||||
# Streamlit UI
|
||||
st.title("Calculator Name") # Uses theme colors automatically
|
||||
st.button("Calculate", type="primary") # Teal accent from theme
|
||||
|
||||
# Charts
|
||||
fig = go.Figure()
|
||||
fig.add_trace(go.Scatter(x=x, y=y, line=dict(color=TEAL_ACCENT)))
|
||||
fig = format_fig(fig) # Add branding
|
||||
st.plotly_chart(fig)
|
||||
```
|
||||
|
||||
### Jupyter Notebooks / Analysis Scripts
|
||||
|
||||
**Required branding:**
|
||||
1. ✅ Charts use format_fig() with logo
|
||||
2. ✅ PolicyEngine color palette
|
||||
3. ✅ Roboto Serif font
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
import plotly.graph_objects as go
|
||||
|
||||
TEAL_ACCENT = "#39C6C0"
|
||||
BLUE_PRIMARY = "#2C6496"
|
||||
|
||||
fig = go.Figure()
|
||||
fig.add_trace(go.Scatter(
|
||||
x=data.income,
|
||||
y=data.tax_change,
|
||||
line=dict(color=TEAL_ACCENT, width=3)
|
||||
))
|
||||
|
||||
fig.update_layout(
|
||||
font=dict(family="Roboto Serif", size=14),
|
||||
title="Tax impact by income",
|
||||
xaxis_title="Income",
|
||||
yaxis_title="Tax change ($)",
|
||||
plot_bgcolor="white"
|
||||
)
|
||||
|
||||
# Add logo
|
||||
fig.add_layout_image(...)
|
||||
```
|
||||
|
||||
### React App Components
|
||||
|
||||
**Color usage:**
|
||||
```javascript
|
||||
import colors from "style/colors";
|
||||
|
||||
// Interactive elements
|
||||
<Button style={{ backgroundColor: colors.TEAL_ACCENT }}>
|
||||
Click me
|
||||
</Button>
|
||||
|
||||
// Links
|
||||
<a style={{ color: colors.BLUE }}>Learn more</a>
|
||||
|
||||
// Text
|
||||
<p style={{ color: colors.DARK_GRAY }}>Description</p>
|
||||
```
|
||||
|
||||
**Current colors:**
|
||||
```bash
|
||||
cat policyengine-app/src/style/colors.js
|
||||
```
|
||||
|
||||
## Visual Guidelines
|
||||
|
||||
### Chart Design Principles
|
||||
|
||||
1. **Minimal decoration** - Let data speak
|
||||
2. **White backgrounds** - Clean, print-friendly
|
||||
3. **Clear axis labels** - Always include units
|
||||
4. **Formatted numbers** - Currency ($), percentages (%), etc.
|
||||
5. **Logo inclusion** - Bottom right, never intrusive
|
||||
6. **Consistent sizing** - 800x600 standard
|
||||
7. **Roboto Serif** - Professional, readable font
|
||||
|
||||
### Color Usage Rules
|
||||
|
||||
**Primary actions:**
|
||||
- Use TEAL_ACCENT (#39C6C0)
|
||||
- Buttons, highlights, current selection
|
||||
|
||||
**Chart lines:**
|
||||
- Primary data: TEAL_ACCENT or BLUE_PRIMARY
|
||||
- Secondary data: BLUE_LIGHT or GRAY
|
||||
- Negative values: DARK_RED (#b50d0d)
|
||||
|
||||
**Backgrounds:**
|
||||
- Main: WHITE (#FFFFFF)
|
||||
- Secondary: TEAL_LIGHT (#F7FDFC) or BLUE_98 (#F7FAFD)
|
||||
- Plot area: WHITE
|
||||
|
||||
**Text:**
|
||||
- Primary: BLACK (#000000)
|
||||
- Secondary: DARK_GRAY (#616161)
|
||||
- Muted: GRAY (#808080)
|
||||
|
||||
### Accessibility
|
||||
|
||||
**Color contrast requirements:**
|
||||
- Text on background: 4.5:1 minimum (WCAG AA)
|
||||
- DARK_GRAY on WHITE: ✅ Passes
|
||||
- TEAL_ACCENT on WHITE: ✅ Passes for large text
|
||||
- Use sufficient line weights for visibility
|
||||
|
||||
**Don't rely on color alone:**
|
||||
- Use patterns or labels for different data series
|
||||
- Ensure charts work in grayscale
|
||||
|
||||
## Common Branding Tasks
|
||||
|
||||
### Task 1: Create Branded Plotly Chart
|
||||
|
||||
1. **Define colors:**
|
||||
```python
|
||||
TEAL_ACCENT = "#39C6C0"
|
||||
BLUE_PRIMARY = "#2C6496"
|
||||
```
|
||||
|
||||
2. **Create chart:**
|
||||
```python
|
||||
fig = go.Figure()
|
||||
fig.add_trace(go.Scatter(x=x, y=y, line=dict(color=TEAL_ACCENT)))
|
||||
```
|
||||
|
||||
3. **Apply branding:**
|
||||
```python
|
||||
fig = format_fig(fig) # See implementation above
|
||||
```
|
||||
|
||||
### Task 2: Setup Streamlit Branding
|
||||
|
||||
1. **Create config directory:**
|
||||
```bash
|
||||
mkdir .streamlit
|
||||
```
|
||||
|
||||
2. **Copy theme config:**
|
||||
```bash
|
||||
cat givecalc/.streamlit/config.toml > .streamlit/config.toml
|
||||
```
|
||||
|
||||
3. **Verify in app:**
|
||||
```python
|
||||
import streamlit as st
|
||||
|
||||
st.button("Test", type="primary") # Should be teal
|
||||
```
|
||||
|
||||
### Task 3: Brand Consistency Check
|
||||
|
||||
**Checklist:**
|
||||
- [ ] Charts use Roboto Serif font
|
||||
- [ ] Primary color is TEAL_ACCENT (#39C6C0)
|
||||
- [ ] Secondary color is BLUE_PRIMARY (#2C6496)
|
||||
- [ ] White backgrounds
|
||||
- [ ] Logo in charts (bottom right)
|
||||
- [ ] Currency formatted with $ and commas
|
||||
- [ ] Percentages formatted with %
|
||||
- [ ] Streamlit config.toml uses PolicyEngine theme
|
||||
|
||||
## Reference Implementations
|
||||
|
||||
### Excellent Examples
|
||||
|
||||
**Streamlit calculators:**
|
||||
```bash
|
||||
# GiveCalc - Complete example
|
||||
cat givecalc/ui/visualization.py
|
||||
cat givecalc/.streamlit/config.toml
|
||||
|
||||
# Other calculators
|
||||
ls salt-amt-calculator/
|
||||
ls ctc-calculator/
|
||||
```
|
||||
|
||||
**Blog post charts:**
|
||||
```bash
|
||||
# Analysis with branded charts
|
||||
cat policyengine-app/src/posts/articles/harris-eitc.md
|
||||
cat policyengine-app/src/posts/articles/montana-tax-cuts-2026.md
|
||||
```
|
||||
|
||||
**React app components:**
|
||||
```bash
|
||||
# Charts in app
|
||||
cat policyengine-app/src/pages/policy/output/DistributionalImpact.jsx
|
||||
```
|
||||
|
||||
### Don't Use These
|
||||
|
||||
**❌ Wrong colors:**
|
||||
```python
|
||||
# Don't use random colors
|
||||
color = "#FF5733"
|
||||
color = "red"
|
||||
color = "green"
|
||||
```
|
||||
|
||||
**❌ Wrong fonts:**
|
||||
```python
|
||||
# Don't use other fonts for charts
|
||||
font = dict(family="Arial")
|
||||
font = dict(family="Times New Roman")
|
||||
```
|
||||
|
||||
**❌ Missing logo:**
|
||||
```python
|
||||
# Don't skip the logo in charts for publication
|
||||
# All published charts should include PolicyEngine logo
|
||||
```
|
||||
|
||||
## Assets and Resources
|
||||
|
||||
### Logo Files
|
||||
|
||||
**In policyengine-app repository:**
|
||||
```bash
|
||||
policyengine-app/src/images/logos/policyengine/
|
||||
├── blue.png # Primary logo (light backgrounds)
|
||||
├── white.png # Logo for dark backgrounds
|
||||
├── blue.svg # Scalable blue logo
|
||||
├── white.svg # Scalable white logo
|
||||
├── banners/ # Banner variations
|
||||
└── profile/ # Profile/avatar versions
|
||||
```
|
||||
|
||||
**Raw URLs for direct use:**
|
||||
```python
|
||||
# Use these URLs in code
|
||||
LOGO_URL = "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png"
|
||||
```
|
||||
|
||||
### Font Files
|
||||
|
||||
**Roboto (charts):**
|
||||
- Google Fonts: https://fonts.google.com/specimen/Roboto
|
||||
- Family: Roboto Serif
|
||||
- Weights: 300 (light), 400 (regular), 500 (medium), 700 (bold)
|
||||
|
||||
**Loading:**
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Serif:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
### Color Reference Files
|
||||
|
||||
**JavaScript (React app):**
|
||||
```bash
|
||||
cat policyengine-app/src/style/colors.js
|
||||
```
|
||||
|
||||
**Python (calculators, analysis):**
|
||||
```python
|
||||
# Define in constants.py or at top of file
|
||||
TEAL_ACCENT = "#39C6C0"
|
||||
BLUE_PRIMARY = "#2C6496"
|
||||
DARK_GRAY = "#616161"
|
||||
WHITE = "#FFFFFF"
|
||||
```
|
||||
|
||||
## Brand Evolution
|
||||
|
||||
**Current identity (2025):**
|
||||
- Teal primary (#39C6C0)
|
||||
- Blue secondary (#2C6496)
|
||||
- Roboto Serif for charts
|
||||
- Minimal, data-focused design
|
||||
|
||||
**If brand evolves:**
|
||||
- Colors defined in policyengine-app/src/style/colors.js are source of truth
|
||||
- Update this skill to point to current definitions
|
||||
- Never hardcode - always reference colors.js
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Color Codes
|
||||
|
||||
| Color | Hex | Usage |
|
||||
|-------|-----|-------|
|
||||
| Teal Accent | #39C6C0 | Primary interactive elements |
|
||||
| Blue Primary | #2C6496 | Secondary, links, charts |
|
||||
| Dark Gray | #616161 | Body text |
|
||||
| White | #FFFFFF | Backgrounds |
|
||||
| Teal Light | #F7FDFC | Secondary backgrounds |
|
||||
| Dark Red | #b50d0d | Negative values, errors |
|
||||
|
||||
### Font Families
|
||||
|
||||
| Context | Font |
|
||||
|---------|------|
|
||||
| Charts | Roboto Serif |
|
||||
| Web app | System sans-serif |
|
||||
| Streamlit | Default sans-serif |
|
||||
| Code blocks | Monospace |
|
||||
|
||||
### Logo URLs
|
||||
|
||||
| Background | Format | URL |
|
||||
|------------|--------|-----|
|
||||
| Light | PNG | https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png |
|
||||
| Light | SVG | https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.svg |
|
||||
| Dark | PNG | https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/white.png |
|
||||
| Dark | SVG | https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/white.svg |
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **policyengine-app-skill** - React component styling
|
||||
- **policyengine-analysis-skill** - Chart creation patterns
|
||||
- **policyengine-writing-skill** - Content style (complements visual style)
|
||||
|
||||
## Resources
|
||||
|
||||
**Brand assets:** PolicyEngine/policyengine-app/src/images/
|
||||
**Color definitions:** PolicyEngine/policyengine-app/src/style/colors.js
|
||||
**Examples:** givecalc, salt-amt-calculator, crfb-tob-impacts
|
||||
356
skills/policyengine-python-client-skill/SKILL.md
Normal file
356
skills/policyengine-python-client-skill/SKILL.md
Normal file
@@ -0,0 +1,356 @@
|
||||
---
|
||||
name: policyengine-python-client
|
||||
description: Using PolicyEngine programmatically via Python client or REST API
|
||||
---
|
||||
|
||||
# PolicyEngine Python Client
|
||||
|
||||
This skill covers programmatic access to PolicyEngine for analysts and researchers.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Install the Python client
|
||||
pip install policyengine
|
||||
|
||||
# Or for local development
|
||||
pip install policyengine-us # Just the US model (offline)
|
||||
```
|
||||
|
||||
## Quick Start: Python Client
|
||||
|
||||
```python
|
||||
from policyengine import Simulation
|
||||
|
||||
# Create a household
|
||||
household = {
|
||||
"people": {
|
||||
"you": {
|
||||
"age": {"2024": 30},
|
||||
"employment_income": {"2024": 50000}
|
||||
}
|
||||
},
|
||||
"households": {
|
||||
"your household": {
|
||||
"members": ["you"],
|
||||
"state_name": {"2024": "CA"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Run simulation
|
||||
sim = Simulation(situation=household, country_id="us")
|
||||
income_tax = sim.calculate("income_tax", "2024")
|
||||
```
|
||||
|
||||
## For Users: Why Use Python?
|
||||
|
||||
**Web app limitations:**
|
||||
- ✅ Great for exploring policies interactively
|
||||
- ❌ Can't analyze many households at once
|
||||
- ❌ Can't automate repetitive analyses
|
||||
- ❌ Limited customization of charts
|
||||
|
||||
**Python benefits:**
|
||||
- ✅ Analyze thousands of households in batch
|
||||
- ✅ Automate regular policy analysis
|
||||
- ✅ Create custom visualizations
|
||||
- ✅ Integrate with other data sources
|
||||
- ✅ Reproducible research
|
||||
|
||||
## For Analysts: Common Workflows
|
||||
|
||||
### Workflow 1: Calculate Your Own Taxes
|
||||
|
||||
```python
|
||||
from policyengine import Simulation
|
||||
|
||||
# Your household (more complex than web app)
|
||||
household = {
|
||||
"people": {
|
||||
"you": {
|
||||
"age": {"2024": 35},
|
||||
"employment_income": {"2024": 75000},
|
||||
"qualified_dividend_income": {"2024": 5000},
|
||||
"charitable_cash_donations": {"2024": 3000}
|
||||
},
|
||||
"spouse": {
|
||||
"age": {"2024": 33},
|
||||
"employment_income": {"2024": 60000}
|
||||
},
|
||||
"child1": {"age": {"2024": 8}},
|
||||
"child2": {"age": {"2024": 5}}
|
||||
},
|
||||
# ... entities setup (see policyengine-us-skill)
|
||||
}
|
||||
|
||||
sim = Simulation(situation=household, country_id="us")
|
||||
|
||||
# Calculate specific values
|
||||
federal_income_tax = sim.calculate("income_tax", "2024")
|
||||
state_income_tax = sim.calculate("state_income_tax", "2024")
|
||||
ctc = sim.calculate("ctc", "2024")
|
||||
eitc = sim.calculate("eitc", "2024")
|
||||
|
||||
print(f"Federal income tax: ${federal_income_tax:,.0f}")
|
||||
print(f"State income tax: ${state_income_tax:,.0f}")
|
||||
print(f"Child Tax Credit: ${ctc:,.0f}")
|
||||
print(f"EITC: ${eitc:,.0f}")
|
||||
```
|
||||
|
||||
### Workflow 2: Analyze a Policy Reform
|
||||
|
||||
```python
|
||||
from policyengine import Simulation
|
||||
|
||||
# Define reform (increase CTC to $5,000)
|
||||
reform = {
|
||||
"gov.irs.credits.ctc.amount.base_amount": {
|
||||
"2024-01-01.2100-12-31": 5000
|
||||
}
|
||||
}
|
||||
|
||||
# Compare baseline vs reform
|
||||
household = create_household() # Your household definition
|
||||
|
||||
sim_baseline = Simulation(situation=household, country_id="us")
|
||||
sim_reform = Simulation(situation=household, country_id="us", reform=reform)
|
||||
|
||||
ctc_baseline = sim_baseline.calculate("ctc", "2024")
|
||||
ctc_reform = sim_reform.calculate("ctc", "2024")
|
||||
|
||||
print(f"CTC baseline: ${ctc_baseline:,.0f}")
|
||||
print(f"CTC reform: ${ctc_reform:,.0f}")
|
||||
print(f"Increase: ${ctc_reform - ctc_baseline:,.0f}")
|
||||
```
|
||||
|
||||
### Workflow 3: Batch Analysis
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
from policyengine import Simulation
|
||||
|
||||
# Analyze multiple households
|
||||
households = [
|
||||
{"income": 30000, "children": 0},
|
||||
{"income": 50000, "children": 2},
|
||||
{"income": 100000, "children": 3},
|
||||
]
|
||||
|
||||
results = []
|
||||
for h in households:
|
||||
situation = create_household(income=h["income"], num_children=h["children"])
|
||||
sim = Simulation(situation=situation, country_id="us")
|
||||
|
||||
results.append({
|
||||
"income": h["income"],
|
||||
"children": h["children"],
|
||||
"income_tax": sim.calculate("income_tax", "2024"),
|
||||
"ctc": sim.calculate("ctc", "2024"),
|
||||
"eitc": sim.calculate("eitc", "2024")
|
||||
})
|
||||
|
||||
df = pd.DataFrame(results)
|
||||
print(df)
|
||||
```
|
||||
|
||||
## Using the REST API Directly
|
||||
|
||||
### Authentication
|
||||
|
||||
**Public access:**
|
||||
- 100 requests per minute (unauthenticated)
|
||||
- No API key needed for basic use
|
||||
|
||||
**Authenticated access:**
|
||||
- 1,000 requests per minute
|
||||
- Contact hello@policyengine.org for API key
|
||||
|
||||
### Key Endpoints
|
||||
|
||||
**Calculate household impact:**
|
||||
```python
|
||||
import requests
|
||||
|
||||
url = "https://api.policyengine.org/us/calculate"
|
||||
payload = {
|
||||
"household": household_dict,
|
||||
"policy_id": reform_id # or None for baseline
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload)
|
||||
result = response.json()
|
||||
```
|
||||
|
||||
**Get policy details:**
|
||||
```python
|
||||
# Get policy metadata
|
||||
response = requests.get("https://api.policyengine.org/us/policy/12345")
|
||||
policy = response.json()
|
||||
```
|
||||
|
||||
**Get parameter values:**
|
||||
```python
|
||||
# Get current parameter value
|
||||
response = requests.get(
|
||||
"https://api.policyengine.org/us/parameter/gov.irs.credits.ctc.amount.base_amount"
|
||||
)
|
||||
parameter = response.json()
|
||||
```
|
||||
|
||||
### For Full API Documentation
|
||||
|
||||
**OpenAPI spec:** https://api.policyengine.org/docs
|
||||
|
||||
**To explore:**
|
||||
```bash
|
||||
# View all endpoints
|
||||
curl https://api.policyengine.org/docs
|
||||
|
||||
# Test calculate endpoint
|
||||
curl -X POST https://api.policyengine.org/us/calculate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"household": {...}}'
|
||||
```
|
||||
|
||||
## Limitations and Considerations
|
||||
|
||||
### Rate Limits
|
||||
|
||||
**Unauthenticated:**
|
||||
- 100 requests/minute
|
||||
- Good for exploratory analysis
|
||||
|
||||
**Authenticated:**
|
||||
- 1,000 requests/minute
|
||||
- Required for production use
|
||||
|
||||
### Data Privacy
|
||||
|
||||
- PolicyEngine does not store household data
|
||||
- All calculations happen server-side and are not logged
|
||||
- Reform URLs are public (don't include personal info in reforms)
|
||||
|
||||
### Performance
|
||||
|
||||
**API calls:**
|
||||
- Simple household: ~200-500ms
|
||||
- Population impact: ~5-30 seconds (varies by reform)
|
||||
- Use caching for repeated calculations
|
||||
|
||||
**Local simulation (policyengine-us):**
|
||||
- Faster for batch analysis
|
||||
- No rate limits
|
||||
- No network dependency
|
||||
- Limited to one country per package
|
||||
|
||||
## Choosing Local vs API
|
||||
|
||||
### Use Local (policyengine-us package)
|
||||
|
||||
**When:**
|
||||
- Batch analysis of many households
|
||||
- Need offline capability
|
||||
- Analyzing parameter sweeps (axes)
|
||||
- Development/testing
|
||||
|
||||
**Install:**
|
||||
```bash
|
||||
pip install policyengine-us # US only
|
||||
pip install policyengine-uk # UK only
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
from policyengine_us import Simulation
|
||||
|
||||
# Works offline
|
||||
sim = Simulation(situation=household)
|
||||
```
|
||||
|
||||
### Use API (policyengine or requests)
|
||||
|
||||
**When:**
|
||||
- Multi-country analysis
|
||||
- Using latest model version
|
||||
- Don't want to manage dependencies
|
||||
- Integration with web services
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Requires internet
|
||||
response = requests.post("https://api.policyengine.org/us/calculate", ...)
|
||||
```
|
||||
|
||||
## For Contributors: Understanding the Client
|
||||
|
||||
**Repository:** PolicyEngine/policyengine.py
|
||||
|
||||
**To see implementation:**
|
||||
```bash
|
||||
# Clone the client
|
||||
git clone https://github.com/PolicyEngine/policyengine.py
|
||||
|
||||
# See the Simulation class
|
||||
cat policyengine/simulation.py
|
||||
|
||||
# See API integration
|
||||
cat policyengine/api.py
|
||||
```
|
||||
|
||||
**Architecture:**
|
||||
- `Simulation` class wraps API calls
|
||||
- `calculate()` method handles caching
|
||||
- Transparent fallback between API and local
|
||||
|
||||
## Advanced: Direct Country Package Usage
|
||||
|
||||
For maximum control and performance, use country packages directly:
|
||||
|
||||
```python
|
||||
from policyengine_us import Simulation
|
||||
|
||||
# Full control over situation structure
|
||||
situation = {
|
||||
# Complete situation dictionary
|
||||
# See policyengine-us-skill for patterns
|
||||
}
|
||||
|
||||
sim = Simulation(situation=situation)
|
||||
result = sim.calculate("variable_name", 2024)
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- No API dependency
|
||||
- Faster (no network)
|
||||
- Full access to all variables
|
||||
- Use axes for parameter sweeps
|
||||
|
||||
**See policyengine-us-skill for detailed patterns.**
|
||||
|
||||
## Examples and Tutorials
|
||||
|
||||
**PolicyEngine documentation:**
|
||||
- US: https://policyengine.org/us/docs
|
||||
- UK: https://policyengine.org/uk/docs
|
||||
|
||||
**Example notebooks:**
|
||||
- Repository: PolicyEngine/analysis-notebooks
|
||||
- See policyengine-analysis-skill for analysis patterns
|
||||
|
||||
**Community examples:**
|
||||
- Blog posts: policyengine.org/us/research
|
||||
- GitHub discussions: github.com/PolicyEngine discussions
|
||||
|
||||
## Getting Help
|
||||
|
||||
**For usage questions:**
|
||||
- GitHub Discussions: https://github.com/PolicyEngine/policyengine-us/discussions
|
||||
|
||||
**For bugs:**
|
||||
- File issues in appropriate repo (policyengine-us, policyengine.py, etc.)
|
||||
|
||||
**For collaboration:**
|
||||
- Email: hello@policyengine.org
|
||||
660
skills/policyengine-uk-skill/SKILL.md
Normal file
660
skills/policyengine-uk-skill/SKILL.md
Normal file
@@ -0,0 +1,660 @@
|
||||
---
|
||||
name: policyengine-uk
|
||||
description: PolicyEngine-UK tax and benefit microsimulation patterns, situation creation, and common workflows
|
||||
---
|
||||
|
||||
# PolicyEngine-UK
|
||||
|
||||
PolicyEngine-UK models the UK tax and benefit system, including devolved variations for Scotland and Wales.
|
||||
|
||||
## For Users 👥
|
||||
|
||||
### What is PolicyEngine-UK?
|
||||
|
||||
PolicyEngine-UK is the "calculator" for UK taxes and benefits. When you use policyengine.org/uk, PolicyEngine-UK runs behind the scenes.
|
||||
|
||||
**What it models:**
|
||||
|
||||
**Direct taxes:**
|
||||
- Income tax (UK-wide, Scottish, and Welsh variations)
|
||||
- National Insurance (Classes 1, 2, 4)
|
||||
- Capital gains tax
|
||||
- Dividend tax
|
||||
|
||||
**Property and transaction taxes:**
|
||||
- Council Tax
|
||||
- Stamp Duty Land Tax (England/NI)
|
||||
- Land and Buildings Transaction Tax (Scotland)
|
||||
- Land Transaction Tax (Wales)
|
||||
|
||||
**Universal Credit:**
|
||||
- Standard allowance
|
||||
- Child elements
|
||||
- Housing cost element
|
||||
- Childcare costs element
|
||||
- Carer element
|
||||
- Work capability elements
|
||||
|
||||
**Legacy benefits (being phased out):**
|
||||
- Working Tax Credit
|
||||
- Child Tax Credit
|
||||
- Income Support
|
||||
- Income-based JSA/ESA
|
||||
- Housing Benefit
|
||||
|
||||
**Other benefits:**
|
||||
- Child Benefit
|
||||
- Pension Credit
|
||||
- Personal Independence Payment (PIP)
|
||||
- Disability Living Allowance (DLA)
|
||||
- Attendance Allowance
|
||||
- State Pension
|
||||
|
||||
**See full list:** https://policyengine.org/uk/parameters
|
||||
|
||||
### Understanding Variables
|
||||
|
||||
When you see results in PolicyEngine, these are variables:
|
||||
|
||||
**Income variables:**
|
||||
- `employment_income` - Gross employment earnings/salary
|
||||
- `self_employment_income` - Self-employment profits
|
||||
- `pension_income` - Private pension income
|
||||
- `property_income` - Rental income
|
||||
- `savings_interest_income` - Interest from savings
|
||||
- `dividend_income` - Dividend income
|
||||
|
||||
**Tax variables:**
|
||||
- `income_tax` - Total income tax liability
|
||||
- `national_insurance` - Total NI contributions
|
||||
- `council_tax` - Council tax liability
|
||||
|
||||
**Benefit variables:**
|
||||
- `universal_credit` - Universal Credit amount
|
||||
- `child_benefit` - Child Benefit amount
|
||||
- `pension_credit` - Pension Credit amount
|
||||
- `working_tax_credit` - Working Tax Credit (legacy)
|
||||
- `child_tax_credit` - Child Tax Credit (legacy)
|
||||
|
||||
**Summary variables:**
|
||||
- `household_net_income` - Income after taxes and benefits
|
||||
- `disposable_income` - Income after taxes
|
||||
- `equivalised_household_net_income` - Adjusted for household size
|
||||
|
||||
## For Analysts 📊
|
||||
|
||||
### Installation and Setup
|
||||
|
||||
```bash
|
||||
# Install PolicyEngine-UK
|
||||
pip install policyengine-uk
|
||||
|
||||
# Or with uv (recommended)
|
||||
uv pip install policyengine-uk
|
||||
```
|
||||
|
||||
### Quick Start
|
||||
|
||||
```python
|
||||
from policyengine_uk import Simulation
|
||||
|
||||
# Create a household
|
||||
situation = {
|
||||
"people": {
|
||||
"person": {
|
||||
"age": {2025: 30},
|
||||
"employment_income": {2025: 30000}
|
||||
}
|
||||
},
|
||||
"benunits": {
|
||||
"benunit": {
|
||||
"members": ["person"]
|
||||
}
|
||||
},
|
||||
"households": {
|
||||
"household": {
|
||||
"members": ["person"],
|
||||
"region": {2025: "LONDON"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate taxes and benefits
|
||||
sim = Simulation(situation=situation)
|
||||
income_tax = sim.calculate("income_tax", 2025)[0]
|
||||
universal_credit = sim.calculate("universal_credit", 2025)[0]
|
||||
|
||||
print(f"Income tax: £{income_tax:,.0f}")
|
||||
print(f"Universal Credit: £{universal_credit:,.0f}")
|
||||
```
|
||||
|
||||
### Web App to Python
|
||||
|
||||
**Web app URL:**
|
||||
```
|
||||
policyengine.org/uk/household?household=12345
|
||||
```
|
||||
|
||||
**Equivalent Python (conceptually):**
|
||||
The household ID represents a situation dictionary. To replicate in Python, you'd create a similar situation.
|
||||
|
||||
### When to Use This Skill
|
||||
|
||||
- Creating household situations for tax/benefit calculations
|
||||
- Running microsimulations with PolicyEngine-UK
|
||||
- Analyzing policy reforms and their impacts
|
||||
- Building tools that use PolicyEngine-UK (calculators, analysis notebooks)
|
||||
- Debugging PolicyEngine-UK calculations
|
||||
|
||||
## For Contributors 💻
|
||||
|
||||
### Repository
|
||||
|
||||
**Location:** PolicyEngine/policyengine-uk
|
||||
|
||||
**To see current implementation:**
|
||||
```bash
|
||||
git clone https://github.com/PolicyEngine/policyengine-uk
|
||||
cd policyengine-uk
|
||||
|
||||
# Explore structure
|
||||
tree policyengine_uk/
|
||||
```
|
||||
|
||||
**Key directories:**
|
||||
```bash
|
||||
ls policyengine_uk/
|
||||
# - variables/ - Tax and benefit calculations
|
||||
# - parameters/ - Policy rules (YAML)
|
||||
# - reforms/ - Pre-defined reforms
|
||||
# - tests/ - Test cases
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Situation Dictionary Structure
|
||||
|
||||
PolicyEngine UK requires a nested dictionary defining household composition:
|
||||
|
||||
```python
|
||||
situation = {
|
||||
"people": {
|
||||
"person_id": {
|
||||
"age": {2025: 35},
|
||||
"employment_income": {2025: 30000},
|
||||
# ... other person attributes
|
||||
}
|
||||
},
|
||||
"benunits": {
|
||||
"benunit_id": {
|
||||
"members": ["person_id", ...]
|
||||
}
|
||||
},
|
||||
"households": {
|
||||
"household_id": {
|
||||
"members": ["person_id", ...],
|
||||
"region": {2025: "SOUTH_EAST"}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules:**
|
||||
- All entities must have consistent member lists
|
||||
- Use year keys for all values: `{2025: value}`
|
||||
- Region must be one of the ITL 1 regions (see below)
|
||||
- All monetary values in pounds (not pence)
|
||||
- UK tax year runs April 6 to April 5 (but use calendar year in code)
|
||||
|
||||
**Important Entity Difference:**
|
||||
- UK uses **benunits** (benefit units): a single adult OR couple + dependent children
|
||||
- This is the assessment unit for most means-tested benefits
|
||||
- Unlike US which uses families/marital_units/tax_units/spm_units
|
||||
|
||||
### 2. Creating Simulations
|
||||
|
||||
```python
|
||||
from policyengine_uk import Simulation
|
||||
|
||||
# Create simulation from situation
|
||||
simulation = Simulation(situation=situation)
|
||||
|
||||
# Calculate variables
|
||||
income_tax = simulation.calculate("income_tax", 2025)
|
||||
universal_credit = simulation.calculate("universal_credit", 2025)
|
||||
household_net_income = simulation.calculate("household_net_income", 2025)
|
||||
```
|
||||
|
||||
**Common Variables:**
|
||||
|
||||
**Income:**
|
||||
- `employment_income` - Gross employment earnings
|
||||
- `self_employment_income` - Self-employment profits
|
||||
- `pension_income` - Private pension income
|
||||
- `property_income` - Rental income
|
||||
- `savings_interest_income` - Interest income
|
||||
- `dividend_income` - Dividend income
|
||||
- `miscellaneous_income` - Other income sources
|
||||
|
||||
**Tax Outputs:**
|
||||
- `income_tax` - Total income tax liability
|
||||
- `national_insurance` - Total NI contributions
|
||||
- `council_tax` - Council tax liability
|
||||
- `VAT` - Value Added Tax paid
|
||||
|
||||
**Benefits:**
|
||||
- `universal_credit` - Universal Credit
|
||||
- `child_benefit` - Child Benefit
|
||||
- `pension_credit` - Pension Credit
|
||||
- `working_tax_credit` - Working Tax Credit (legacy)
|
||||
- `child_tax_credit` - Child Tax Credit (legacy)
|
||||
- `personal_independence_payment` - PIP
|
||||
- `attendance_allowance` - Attendance Allowance
|
||||
- `state_pension` - State Pension
|
||||
|
||||
**Summary:**
|
||||
- `household_net_income` - Income after taxes and benefits
|
||||
- `disposable_income` - Income after taxes
|
||||
- `equivalised_household_net_income` - Adjusted for household size
|
||||
|
||||
### 3. Using Axes for Parameter Sweeps
|
||||
|
||||
To vary a parameter across multiple values:
|
||||
|
||||
```python
|
||||
situation = {
|
||||
# ... normal situation setup ...
|
||||
"axes": [[{
|
||||
"name": "employment_income",
|
||||
"count": 1001,
|
||||
"min": 0,
|
||||
"max": 100000,
|
||||
"period": 2025
|
||||
}]]
|
||||
}
|
||||
|
||||
simulation = Simulation(situation=situation)
|
||||
# Now calculate() returns arrays of 1001 values
|
||||
incomes = simulation.calculate("employment_income", 2025) # Array of 1001 values
|
||||
taxes = simulation.calculate("income_tax", 2025) # Array of 1001 values
|
||||
```
|
||||
|
||||
**Important:** Remove axes before creating single-point simulations:
|
||||
```python
|
||||
situation_single = situation.copy()
|
||||
situation_single.pop("axes", None)
|
||||
simulation = Simulation(situation=situation_single)
|
||||
```
|
||||
|
||||
### 4. Policy Reforms
|
||||
|
||||
```python
|
||||
from policyengine_uk import Simulation
|
||||
|
||||
# Define a reform (modifies parameters)
|
||||
reform = {
|
||||
"gov.hmrc.income_tax.rates.uk.brackets[0].rate": {
|
||||
"2025-01-01.2100-12-31": 0.25 # Increase basic rate to 25%
|
||||
}
|
||||
}
|
||||
|
||||
# Create simulation with reform
|
||||
simulation = Simulation(situation=situation, reform=reform)
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Single Person Household Calculation
|
||||
|
||||
```python
|
||||
from policyengine_uk import Simulation
|
||||
|
||||
situation = {
|
||||
"people": {
|
||||
"person": {
|
||||
"age": {2025: 30},
|
||||
"employment_income": {2025: 30000}
|
||||
}
|
||||
},
|
||||
"benunits": {
|
||||
"benunit": {
|
||||
"members": ["person"]
|
||||
}
|
||||
},
|
||||
"households": {
|
||||
"household": {
|
||||
"members": ["person"],
|
||||
"region": {2025: "LONDON"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sim = Simulation(situation=situation)
|
||||
income_tax = sim.calculate("income_tax", 2025)[0]
|
||||
national_insurance = sim.calculate("national_insurance", 2025)[0]
|
||||
universal_credit = sim.calculate("universal_credit", 2025)[0]
|
||||
```
|
||||
|
||||
### Pattern 2: Couple with Children
|
||||
|
||||
```python
|
||||
situation = {
|
||||
"people": {
|
||||
"parent_1": {
|
||||
"age": {2025: 35},
|
||||
"employment_income": {2025: 35000}
|
||||
},
|
||||
"parent_2": {
|
||||
"age": {2025: 33},
|
||||
"employment_income": {2025: 25000}
|
||||
},
|
||||
"child_1": {
|
||||
"age": {2025: 8}
|
||||
},
|
||||
"child_2": {
|
||||
"age": {2025: 5}
|
||||
}
|
||||
},
|
||||
"benunits": {
|
||||
"benunit": {
|
||||
"members": ["parent_1", "parent_2", "child_1", "child_2"]
|
||||
}
|
||||
},
|
||||
"households": {
|
||||
"household": {
|
||||
"members": ["parent_1", "parent_2", "child_1", "child_2"],
|
||||
"region": {2025: "NORTH_WEST"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sim = Simulation(situation=situation)
|
||||
child_benefit = sim.calculate("child_benefit", 2025)[0]
|
||||
universal_credit = sim.calculate("universal_credit", 2025)[0]
|
||||
```
|
||||
|
||||
### Pattern 3: Marginal Tax Rate Analysis
|
||||
|
||||
```python
|
||||
# Create baseline with axes varying income
|
||||
situation_with_axes = {
|
||||
"people": {
|
||||
"person": {
|
||||
"age": {2025: 30}
|
||||
}
|
||||
},
|
||||
"benunits": {"benunit": {"members": ["person"]}},
|
||||
"households": {
|
||||
"household": {
|
||||
"members": ["person"],
|
||||
"region": {2025: "LONDON"}
|
||||
}
|
||||
},
|
||||
"axes": [[{
|
||||
"name": "employment_income",
|
||||
"count": 1001,
|
||||
"min": 0,
|
||||
"max": 100000,
|
||||
"period": 2025
|
||||
}]]
|
||||
}
|
||||
|
||||
sim = Simulation(situation=situation_with_axes)
|
||||
incomes = sim.calculate("employment_income", 2025)
|
||||
net_incomes = sim.calculate("household_net_income", 2025)
|
||||
|
||||
# Calculate marginal tax rate
|
||||
import numpy as np
|
||||
mtr = 1 - (np.gradient(net_incomes) / np.gradient(incomes))
|
||||
```
|
||||
|
||||
### Pattern 4: Regional Comparison
|
||||
|
||||
```python
|
||||
regions = ["LONDON", "SCOTLAND", "WALES", "NORTH_EAST"]
|
||||
results = {}
|
||||
|
||||
for region in regions:
|
||||
situation = create_situation(region=region, income=30000)
|
||||
sim = Simulation(situation=situation)
|
||||
results[region] = {
|
||||
"income_tax": sim.calculate("income_tax", 2025)[0],
|
||||
"national_insurance": sim.calculate("national_insurance", 2025)[0],
|
||||
"total_tax": sim.calculate("income_tax", 2025)[0] +
|
||||
sim.calculate("national_insurance", 2025)[0]
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: Policy Reform Impact
|
||||
|
||||
```python
|
||||
from policyengine_uk import Microsimulation, Reform
|
||||
|
||||
# Define reform: Increase basic rate to 25%
|
||||
class IncreaseBasicRate(Reform):
|
||||
def apply(self):
|
||||
def modify_parameters(parameters):
|
||||
parameters.gov.hmrc.income_tax.rates.uk.brackets[0].rate.update(
|
||||
period="year:2025:10", value=0.25
|
||||
)
|
||||
return parameters
|
||||
self.modify_parameters(modify_parameters)
|
||||
|
||||
# Run microsimulation
|
||||
baseline = Microsimulation()
|
||||
reformed = Microsimulation(reform=IncreaseBasicRate)
|
||||
|
||||
# Calculate revenue impact
|
||||
baseline_revenue = baseline.calc("income_tax", 2025).sum()
|
||||
reformed_revenue = reformed.calc("income_tax", 2025).sum()
|
||||
revenue_change = (reformed_revenue - baseline_revenue) / 1e9 # in billions
|
||||
|
||||
# Calculate household impact
|
||||
baseline_net_income = baseline.calc("household_net_income", 2025)
|
||||
reformed_net_income = reformed.calc("household_net_income", 2025)
|
||||
```
|
||||
|
||||
## Helper Scripts
|
||||
|
||||
This skill includes helper scripts in the `scripts/` directory:
|
||||
|
||||
```python
|
||||
from policyengine_uk_skills.situation_helpers import (
|
||||
create_single_person,
|
||||
create_couple,
|
||||
create_family_with_children,
|
||||
add_region
|
||||
)
|
||||
|
||||
# Quick situation creation
|
||||
situation = create_single_person(
|
||||
income=30000,
|
||||
region="LONDON",
|
||||
age=30
|
||||
)
|
||||
|
||||
# Create couple
|
||||
situation = create_couple(
|
||||
income_1=35000,
|
||||
income_2=25000,
|
||||
region="SCOTLAND"
|
||||
)
|
||||
```
|
||||
|
||||
## Common Pitfalls and Solutions
|
||||
|
||||
### Pitfall 1: Member Lists Out of Sync
|
||||
|
||||
**Problem:** Different entities have different members
|
||||
```python
|
||||
# WRONG
|
||||
"benunits": {"benunit": {"members": ["parent"]}},
|
||||
"households": {"household": {"members": ["parent", "child"]}}
|
||||
```
|
||||
|
||||
**Solution:** Keep all entity member lists consistent:
|
||||
```python
|
||||
# CORRECT
|
||||
all_members = ["parent", "child"]
|
||||
"benunits": {"benunit": {"members": all_members}},
|
||||
"households": {"household": {"members": all_members}}
|
||||
```
|
||||
|
||||
### Pitfall 2: Forgetting Year Keys
|
||||
|
||||
**Problem:** `"age": 35` instead of `"age": {2025: 35}`
|
||||
|
||||
**Solution:** Always use year dictionary:
|
||||
```python
|
||||
"age": {2025: 35},
|
||||
"employment_income": {2025: 30000}
|
||||
```
|
||||
|
||||
### Pitfall 3: Wrong Region Format
|
||||
|
||||
**Problem:** Using lowercase or incorrect region names
|
||||
|
||||
**Solution:** Use uppercase ITL 1 region codes:
|
||||
```python
|
||||
# CORRECT regions:
|
||||
"region": {2025: "LONDON"}
|
||||
"region": {2025: "SCOTLAND"}
|
||||
"region": {2025: "WALES"}
|
||||
"region": {2025: "NORTH_EAST"}
|
||||
"region": {2025: "SOUTH_EAST"}
|
||||
```
|
||||
|
||||
### Pitfall 4: Axes Persistence
|
||||
|
||||
**Problem:** Axes remain in situation when creating single-point simulation
|
||||
|
||||
**Solution:** Remove axes before single-point simulation:
|
||||
```python
|
||||
situation_single = situation.copy()
|
||||
situation_single.pop("axes", None)
|
||||
```
|
||||
|
||||
### Pitfall 5: Missing Benunits
|
||||
|
||||
**Problem:** Forgetting to include benunits (benefit units)
|
||||
|
||||
**Solution:** Always include benunits in UK simulations:
|
||||
```python
|
||||
# UK requires benunits
|
||||
situation = {
|
||||
"people": {...},
|
||||
"benunits": {"benunit": {"members": [...]}}, # Required!
|
||||
"households": {...}
|
||||
}
|
||||
```
|
||||
|
||||
## Regions in PolicyEngine UK
|
||||
|
||||
UK uses ITL 1 (International Territorial Level 1, formerly NUTS 1) regions:
|
||||
|
||||
**Regions:**
|
||||
- `NORTH_EAST` - North East England
|
||||
- `NORTH_WEST` - North West England
|
||||
- `YORKSHIRE` - Yorkshire and the Humber
|
||||
- `EAST_MIDLANDS` - East Midlands
|
||||
- `WEST_MIDLANDS` - West Midlands
|
||||
- `EAST_OF_ENGLAND` - East of England
|
||||
- `LONDON` - London
|
||||
- `SOUTH_EAST` - South East England
|
||||
- `SOUTH_WEST` - South West England
|
||||
- `WALES` - Wales
|
||||
- `SCOTLAND` - Scotland
|
||||
- `NORTHERN_IRELAND` - Northern Ireland
|
||||
|
||||
**Regional Tax Variations:**
|
||||
|
||||
**Scotland:**
|
||||
- Has devolved income tax with 6 bands (starter 19%, basic 20%, intermediate 21%, higher 42%, advanced 45%, top 47%)
|
||||
- Scottish residents automatically calculated with Scottish rates
|
||||
|
||||
**Wales:**
|
||||
- Has Welsh Rate of Income Tax (WRIT)
|
||||
- Currently maintains parity with England/NI rates
|
||||
|
||||
**England/Northern Ireland:**
|
||||
- Standard UK rates: basic 20%, higher 40%, additional 45%
|
||||
|
||||
## Key Parameters and Values (2025/26)
|
||||
|
||||
### Income Tax
|
||||
- **Personal Allowance:** £12,570
|
||||
- **Basic rate threshold:** £50,270
|
||||
- **Higher rate threshold:** £125,140
|
||||
- **Rates:** 20% (basic), 40% (higher), 45% (additional)
|
||||
- **Personal allowance tapering:** £1 reduction for every £2 over £100,000
|
||||
|
||||
### National Insurance (Class 1)
|
||||
- **Lower Earnings Limit:** £6,396/year
|
||||
- **Primary Threshold:** £12,570/year
|
||||
- **Upper Earnings Limit:** £50,270/year
|
||||
- **Rates:** 12% (between primary and upper), 2% (above upper)
|
||||
|
||||
### Universal Credit
|
||||
- **Standard allowance:** Varies by single/couple and age
|
||||
- **Taper rate:** 55% (rate at which UC reduced as income increases)
|
||||
- **Work allowance:** Amount you can earn before UC reduced
|
||||
|
||||
### Child Benefit
|
||||
- **First child:** Higher rate
|
||||
- **Subsequent children:** Lower rate
|
||||
- **High Income Charge:** Tapered withdrawal starting at £60,000
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
- Use `policyengine-uk>=1.0.0` for 2025 calculations
|
||||
- Check version: `import policyengine_uk; print(policyengine_uk.__version__)`
|
||||
- Different years may require different package versions
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
1. **Enable tracing:**
|
||||
```python
|
||||
simulation.trace = True
|
||||
result = simulation.calculate("variable_name", 2025)
|
||||
```
|
||||
|
||||
2. **Check intermediate calculations:**
|
||||
```python
|
||||
gross_income = simulation.calculate("gross_income", 2025)
|
||||
disposable_income = simulation.calculate("disposable_income", 2025)
|
||||
```
|
||||
|
||||
3. **Verify situation structure:**
|
||||
```python
|
||||
import json
|
||||
print(json.dumps(situation, indent=2))
|
||||
```
|
||||
|
||||
4. **Test with PolicyEngine web app:**
|
||||
- Go to policyengine.org/uk/household
|
||||
- Enter same inputs
|
||||
- Compare results
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Documentation:** https://policyengine.org/uk/docs
|
||||
- **API Reference:** https://github.com/PolicyEngine/policyengine-uk
|
||||
- **Variable Explorer:** https://policyengine.org/uk/variables
|
||||
- **Parameter Explorer:** https://policyengine.org/uk/parameters
|
||||
|
||||
## Examples Directory
|
||||
|
||||
See `examples/` for complete working examples:
|
||||
- `single_person.yaml` - Single person household
|
||||
- `couple.yaml` - Couple without children
|
||||
- `family_with_children.yaml` - Family with dependents
|
||||
- `universal_credit_sweep.yaml` - Analyzing UC with axes
|
||||
|
||||
## Key Differences from US System
|
||||
|
||||
1. **Benefit Units:** UK uses `benunits` (single/couple + children) instead of US multiple entity types
|
||||
2. **Universal Credit:** Consolidated means-tested benefit (vs separate SNAP, TANF, etc. in US)
|
||||
3. **National Insurance:** Separate from income tax with own thresholds (vs US Social Security tax)
|
||||
4. **Devolved Taxes:** Scotland and Wales have different income tax rates
|
||||
5. **Tax Year:** April 6 to April 5 (vs calendar year in US)
|
||||
6. **No State Variation:** Council Tax is local, but most taxes/benefits are national (vs 50 US states)
|
||||
29
skills/policyengine-uk-skill/examples/couple.yaml
Normal file
29
skills/policyengine-uk-skill/examples/couple.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
# Example: Couple without children in Scotland
|
||||
# Person 1: £35,000, Age: 35
|
||||
# Person 2: £25,000, Age: 33
|
||||
|
||||
people:
|
||||
person_1:
|
||||
age:
|
||||
2025: 35
|
||||
employment_income:
|
||||
2025: 35000
|
||||
person_2:
|
||||
age:
|
||||
2025: 33
|
||||
employment_income:
|
||||
2025: 25000
|
||||
|
||||
benunits:
|
||||
benunit:
|
||||
members:
|
||||
- person_1
|
||||
- person_2
|
||||
|
||||
households:
|
||||
household:
|
||||
members:
|
||||
- person_1
|
||||
- person_2
|
||||
region:
|
||||
2025: SCOTLAND
|
||||
@@ -0,0 +1,41 @@
|
||||
# Example: Family with children in Wales
|
||||
# Parent 1: £35,000, Age: 35
|
||||
# Parent 2: £25,000, Age: 33
|
||||
# Child 1: Age 8
|
||||
# Child 2: Age 5
|
||||
|
||||
people:
|
||||
parent_1:
|
||||
age:
|
||||
2025: 35
|
||||
employment_income:
|
||||
2025: 35000
|
||||
parent_2:
|
||||
age:
|
||||
2025: 33
|
||||
employment_income:
|
||||
2025: 25000
|
||||
child_1:
|
||||
age:
|
||||
2025: 8
|
||||
child_2:
|
||||
age:
|
||||
2025: 5
|
||||
|
||||
benunits:
|
||||
benunit:
|
||||
members:
|
||||
- parent_1
|
||||
- parent_2
|
||||
- child_1
|
||||
- child_2
|
||||
|
||||
households:
|
||||
household:
|
||||
members:
|
||||
- parent_1
|
||||
- parent_2
|
||||
- child_1
|
||||
- child_2
|
||||
region:
|
||||
2025: WALES
|
||||
21
skills/policyengine-uk-skill/examples/single_person.yaml
Normal file
21
skills/policyengine-uk-skill/examples/single_person.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# Example: Single person household in London
|
||||
# Income: £30,000, Age: 30
|
||||
|
||||
people:
|
||||
person:
|
||||
age:
|
||||
2025: 30
|
||||
employment_income:
|
||||
2025: 30000
|
||||
|
||||
benunits:
|
||||
benunit:
|
||||
members:
|
||||
- person
|
||||
|
||||
households:
|
||||
household:
|
||||
members:
|
||||
- person
|
||||
region:
|
||||
2025: LONDON
|
||||
@@ -0,0 +1,38 @@
|
||||
# Example: Analyzing Universal Credit with income variation
|
||||
# Single parent with 2 children in North West
|
||||
# Sweeps employment income from £0 to £50,000
|
||||
|
||||
people:
|
||||
parent:
|
||||
age:
|
||||
2025: 30
|
||||
child_1:
|
||||
age:
|
||||
2025: 8
|
||||
child_2:
|
||||
age:
|
||||
2025: 5
|
||||
|
||||
benunits:
|
||||
benunit:
|
||||
members:
|
||||
- parent
|
||||
- child_1
|
||||
- child_2
|
||||
|
||||
households:
|
||||
household:
|
||||
members:
|
||||
- parent
|
||||
- child_1
|
||||
- child_2
|
||||
region:
|
||||
2025: NORTH_WEST
|
||||
|
||||
# Axes: Vary employment income from £0 to £50,000
|
||||
axes:
|
||||
- - name: employment_income
|
||||
count: 1001
|
||||
min: 0
|
||||
max: 50000
|
||||
period: 2025
|
||||
339
skills/policyengine-uk-skill/scripts/situation_helpers.py
Normal file
339
skills/policyengine-uk-skill/scripts/situation_helpers.py
Normal file
@@ -0,0 +1,339 @@
|
||||
"""
|
||||
Helper functions for creating PolicyEngine-UK situations.
|
||||
|
||||
These utilities simplify the creation of situation dictionaries
|
||||
for common household configurations.
|
||||
"""
|
||||
|
||||
CURRENT_YEAR = 2025
|
||||
|
||||
# UK ITL 1 regions
|
||||
VALID_REGIONS = [
|
||||
"NORTH_EAST",
|
||||
"NORTH_WEST",
|
||||
"YORKSHIRE",
|
||||
"EAST_MIDLANDS",
|
||||
"WEST_MIDLANDS",
|
||||
"EAST_OF_ENGLAND",
|
||||
"LONDON",
|
||||
"SOUTH_EAST",
|
||||
"SOUTH_WEST",
|
||||
"WALES",
|
||||
"SCOTLAND",
|
||||
"NORTHERN_IRELAND"
|
||||
]
|
||||
|
||||
|
||||
def create_single_person(income, region="LONDON", age=30, **kwargs):
|
||||
"""
|
||||
Create a situation for a single person household.
|
||||
|
||||
Args:
|
||||
income (float): Employment income
|
||||
region (str): ITL 1 region (e.g., "LONDON", "SCOTLAND")
|
||||
age (int): Person's age
|
||||
**kwargs: Additional person attributes (e.g., self_employment_income)
|
||||
|
||||
Returns:
|
||||
dict: PolicyEngine situation dictionary
|
||||
"""
|
||||
if region not in VALID_REGIONS:
|
||||
raise ValueError(f"Invalid region. Must be one of: {', '.join(VALID_REGIONS)}")
|
||||
|
||||
person_attrs = {
|
||||
"age": {CURRENT_YEAR: age},
|
||||
"employment_income": {CURRENT_YEAR: income},
|
||||
}
|
||||
person_attrs.update({k: {CURRENT_YEAR: v} for k, v in kwargs.items()})
|
||||
|
||||
return {
|
||||
"people": {"person": person_attrs},
|
||||
"benunits": {"benunit": {"members": ["person"]}},
|
||||
"households": {
|
||||
"household": {
|
||||
"members": ["person"],
|
||||
"region": {CURRENT_YEAR: region}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def create_couple(
|
||||
income_1, income_2=0, region="LONDON", age_1=35, age_2=35, **kwargs
|
||||
):
|
||||
"""
|
||||
Create a situation for a couple without children.
|
||||
|
||||
Args:
|
||||
income_1 (float): First person's employment income
|
||||
income_2 (float): Second person's employment income
|
||||
region (str): ITL 1 region
|
||||
age_1 (int): First person's age
|
||||
age_2 (int): Second person's age
|
||||
**kwargs: Additional household attributes
|
||||
|
||||
Returns:
|
||||
dict: PolicyEngine situation dictionary
|
||||
"""
|
||||
if region not in VALID_REGIONS:
|
||||
raise ValueError(f"Invalid region. Must be one of: {', '.join(VALID_REGIONS)}")
|
||||
|
||||
members = ["person_1", "person_2"]
|
||||
|
||||
household_attrs = {
|
||||
"members": members,
|
||||
"region": {CURRENT_YEAR: region}
|
||||
}
|
||||
household_attrs.update({k: {CURRENT_YEAR: v} for k, v in kwargs.items()})
|
||||
|
||||
return {
|
||||
"people": {
|
||||
"person_1": {
|
||||
"age": {CURRENT_YEAR: age_1},
|
||||
"employment_income": {CURRENT_YEAR: income_1}
|
||||
},
|
||||
"person_2": {
|
||||
"age": {CURRENT_YEAR: age_2},
|
||||
"employment_income": {CURRENT_YEAR: income_2}
|
||||
}
|
||||
},
|
||||
"benunits": {"benunit": {"members": members}},
|
||||
"households": {"household": household_attrs}
|
||||
}
|
||||
|
||||
|
||||
def create_family_with_children(
|
||||
parent_income,
|
||||
num_children=1,
|
||||
child_ages=None,
|
||||
region="LONDON",
|
||||
parent_age=35,
|
||||
couple=False,
|
||||
partner_income=0,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Create a situation for a family with children.
|
||||
|
||||
Args:
|
||||
parent_income (float): Primary parent's employment income
|
||||
num_children (int): Number of children
|
||||
child_ages (list): List of child ages (defaults to [5, 8, 12, ...])
|
||||
region (str): ITL 1 region
|
||||
parent_age (int): Parent's age
|
||||
couple (bool): Whether this is a couple household
|
||||
partner_income (float): Partner's income if couple
|
||||
**kwargs: Additional household attributes
|
||||
|
||||
Returns:
|
||||
dict: PolicyEngine situation dictionary
|
||||
"""
|
||||
if region not in VALID_REGIONS:
|
||||
raise ValueError(f"Invalid region. Must be one of: {', '.join(VALID_REGIONS)}")
|
||||
|
||||
if child_ages is None:
|
||||
child_ages = [5 + i * 3 for i in range(num_children)]
|
||||
elif len(child_ages) != num_children:
|
||||
raise ValueError("Length of child_ages must match num_children")
|
||||
|
||||
people = {
|
||||
"parent": {
|
||||
"age": {CURRENT_YEAR: parent_age},
|
||||
"employment_income": {CURRENT_YEAR: parent_income}
|
||||
}
|
||||
}
|
||||
|
||||
members = ["parent"]
|
||||
|
||||
if couple:
|
||||
people["partner"] = {
|
||||
"age": {CURRENT_YEAR: parent_age},
|
||||
"employment_income": {CURRENT_YEAR: partner_income}
|
||||
}
|
||||
members.append("partner")
|
||||
|
||||
for i, age in enumerate(child_ages):
|
||||
child_id = f"child_{i+1}"
|
||||
people[child_id] = {"age": {CURRENT_YEAR: age}}
|
||||
members.append(child_id)
|
||||
|
||||
household_attrs = {
|
||||
"members": members,
|
||||
"region": {CURRENT_YEAR: region}
|
||||
}
|
||||
household_attrs.update({k: {CURRENT_YEAR: v} for k, v in kwargs.items()})
|
||||
|
||||
return {
|
||||
"people": people,
|
||||
"benunits": {"benunit": {"members": members}},
|
||||
"households": {"household": household_attrs}
|
||||
}
|
||||
|
||||
|
||||
def add_income_sources(
|
||||
situation,
|
||||
person_id=None,
|
||||
self_employment_income=0,
|
||||
pension_income=0,
|
||||
property_income=0,
|
||||
savings_interest_income=0,
|
||||
dividend_income=0,
|
||||
miscellaneous_income=0
|
||||
):
|
||||
"""
|
||||
Add additional income sources to a person in an existing situation.
|
||||
|
||||
Args:
|
||||
situation (dict): Existing PolicyEngine situation
|
||||
person_id (str): Person ID to add income to (defaults to first person)
|
||||
self_employment_income (float): Self-employment income
|
||||
pension_income (float): Private pension income
|
||||
property_income (float): Rental income
|
||||
savings_interest_income (float): Interest income
|
||||
dividend_income (float): Dividend income
|
||||
miscellaneous_income (float): Other income
|
||||
|
||||
Returns:
|
||||
dict: Updated situation with additional income
|
||||
"""
|
||||
# Get person ID
|
||||
if person_id is None:
|
||||
person_id = list(situation["people"].keys())[0]
|
||||
|
||||
# Add income sources
|
||||
if self_employment_income > 0:
|
||||
situation["people"][person_id]["self_employment_income"] = {
|
||||
CURRENT_YEAR: self_employment_income
|
||||
}
|
||||
|
||||
if pension_income > 0:
|
||||
situation["people"][person_id]["pension_income"] = {
|
||||
CURRENT_YEAR: pension_income
|
||||
}
|
||||
|
||||
if property_income > 0:
|
||||
situation["people"][person_id]["property_income"] = {
|
||||
CURRENT_YEAR: property_income
|
||||
}
|
||||
|
||||
if savings_interest_income > 0:
|
||||
situation["people"][person_id]["savings_interest_income"] = {
|
||||
CURRENT_YEAR: savings_interest_income
|
||||
}
|
||||
|
||||
if dividend_income > 0:
|
||||
situation["people"][person_id]["dividend_income"] = {
|
||||
CURRENT_YEAR: dividend_income
|
||||
}
|
||||
|
||||
if miscellaneous_income > 0:
|
||||
situation["people"][person_id]["miscellaneous_income"] = {
|
||||
CURRENT_YEAR: miscellaneous_income
|
||||
}
|
||||
|
||||
return situation
|
||||
|
||||
|
||||
def add_axes(situation, variable_name, min_val, max_val, count=1001):
|
||||
"""
|
||||
Add axes to a situation for parameter sweeps.
|
||||
|
||||
Args:
|
||||
situation (dict): Existing PolicyEngine situation
|
||||
variable_name (str): Variable to vary (e.g., "employment_income")
|
||||
min_val (float): Minimum value
|
||||
max_val (float): Maximum value
|
||||
count (int): Number of points (default: 1001)
|
||||
|
||||
Returns:
|
||||
dict: Updated situation with axes
|
||||
"""
|
||||
situation["axes"] = [[{
|
||||
"name": variable_name,
|
||||
"count": count,
|
||||
"min": min_val,
|
||||
"max": max_val,
|
||||
"period": CURRENT_YEAR
|
||||
}]]
|
||||
|
||||
return situation
|
||||
|
||||
|
||||
def set_region(situation, region):
|
||||
"""
|
||||
Set or change the region for a household.
|
||||
|
||||
Args:
|
||||
situation (dict): Existing PolicyEngine situation
|
||||
region (str): ITL 1 region (e.g., "LONDON", "SCOTLAND")
|
||||
|
||||
Returns:
|
||||
dict: Updated situation
|
||||
"""
|
||||
if region not in VALID_REGIONS:
|
||||
raise ValueError(f"Invalid region. Must be one of: {', '.join(VALID_REGIONS)}")
|
||||
|
||||
household_id = list(situation["households"].keys())[0]
|
||||
situation["households"][household_id]["region"] = {CURRENT_YEAR: region}
|
||||
|
||||
return situation
|
||||
|
||||
|
||||
def create_pensioner_household(
|
||||
pension_income,
|
||||
state_pension_income=0,
|
||||
region="LONDON",
|
||||
age=70,
|
||||
couple=False,
|
||||
partner_pension_income=0,
|
||||
partner_age=68,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Create a situation for a pensioner household.
|
||||
|
||||
Args:
|
||||
pension_income (float): Private pension income
|
||||
state_pension_income (float): State pension income
|
||||
region (str): ITL 1 region
|
||||
age (int): Pensioner's age
|
||||
couple (bool): Whether this is a couple household
|
||||
partner_pension_income (float): Partner's pension income if couple
|
||||
partner_age (int): Partner's age if couple
|
||||
**kwargs: Additional household attributes
|
||||
|
||||
Returns:
|
||||
dict: PolicyEngine situation dictionary
|
||||
"""
|
||||
if region not in VALID_REGIONS:
|
||||
raise ValueError(f"Invalid region. Must be one of: {', '.join(VALID_REGIONS)}")
|
||||
|
||||
people = {
|
||||
"pensioner": {
|
||||
"age": {CURRENT_YEAR: age},
|
||||
"pension_income": {CURRENT_YEAR: pension_income},
|
||||
"state_pension": {CURRENT_YEAR: state_pension_income}
|
||||
}
|
||||
}
|
||||
|
||||
members = ["pensioner"]
|
||||
|
||||
if couple:
|
||||
people["partner"] = {
|
||||
"age": {CURRENT_YEAR: partner_age},
|
||||
"pension_income": {CURRENT_YEAR: partner_pension_income},
|
||||
"state_pension": {CURRENT_YEAR: 0}
|
||||
}
|
||||
members.append("partner")
|
||||
|
||||
household_attrs = {
|
||||
"members": members,
|
||||
"region": {CURRENT_YEAR: region}
|
||||
}
|
||||
household_attrs.update({k: {CURRENT_YEAR: v} for k, v in kwargs.items()})
|
||||
|
||||
return {
|
||||
"people": people,
|
||||
"benunits": {"benunit": {"members": members}},
|
||||
"households": {"household": household_attrs}
|
||||
}
|
||||
524
skills/policyengine-us-skill/SKILL.md
Normal file
524
skills/policyengine-us-skill/SKILL.md
Normal file
@@ -0,0 +1,524 @@
|
||||
---
|
||||
name: policyengine-us
|
||||
description: PolicyEngine-US tax and benefit microsimulation patterns, situation creation, and common workflows
|
||||
---
|
||||
|
||||
# PolicyEngine-US
|
||||
|
||||
PolicyEngine-US models the US federal and state tax and benefit system.
|
||||
|
||||
## For Users 👥
|
||||
|
||||
### What is PolicyEngine-US?
|
||||
|
||||
PolicyEngine-US is the "calculator" for US taxes and benefits. When you use policyengine.org/us, PolicyEngine-US runs behind the scenes.
|
||||
|
||||
**What it models:**
|
||||
|
||||
**Federal taxes:**
|
||||
- Income tax (with standard/itemized deductions)
|
||||
- Payroll tax (Social Security, Medicare)
|
||||
- Capital gains tax
|
||||
|
||||
**Federal benefits:**
|
||||
- Earned Income Tax Credit (EITC)
|
||||
- Child Tax Credit (CTC)
|
||||
- SNAP (food stamps)
|
||||
- WIC, ACA premium tax credits
|
||||
- Social Security, SSI, TANF
|
||||
|
||||
**State programs (varies by state):**
|
||||
- State income tax (all 50 states + DC)
|
||||
- State EITC, CTC
|
||||
- State-specific benefits
|
||||
|
||||
**See full list:** https://policyengine.org/us/parameters
|
||||
|
||||
### Understanding Variables
|
||||
|
||||
When you see results in PolicyEngine, these are variables:
|
||||
|
||||
**Income variables:**
|
||||
- `employment_income` - W-2 wages
|
||||
- `self_employment_income` - 1099 income
|
||||
- `qualified_dividend_income` - Dividends
|
||||
- `capital_gains` - Capital gains
|
||||
|
||||
**Tax variables:**
|
||||
- `income_tax` - Federal income tax
|
||||
- `state_income_tax` - State income tax
|
||||
- `payroll_tax` - FICA taxes
|
||||
|
||||
**Benefit variables:**
|
||||
- `eitc` - Earned Income Tax Credit
|
||||
- `ctc` - Child Tax Credit
|
||||
- `snap` - SNAP benefits
|
||||
|
||||
**Summary variables:**
|
||||
- `household_net_income` - Income after taxes and benefits
|
||||
- `household_tax` - Total taxes
|
||||
- `household_benefits` - Total benefits
|
||||
|
||||
## For Analysts 📊
|
||||
|
||||
### Installation and Setup
|
||||
|
||||
```bash
|
||||
# Install PolicyEngine-US
|
||||
pip install policyengine-us
|
||||
|
||||
# Or with uv (recommended)
|
||||
uv pip install policyengine-us
|
||||
```
|
||||
|
||||
### Quick Start
|
||||
|
||||
```python
|
||||
from policyengine_us import Simulation
|
||||
|
||||
# Create a household
|
||||
situation = {
|
||||
"people": {
|
||||
"you": {
|
||||
"age": {2024: 30},
|
||||
"employment_income": {2024: 50000}
|
||||
}
|
||||
},
|
||||
"families": {"family": {"members": ["you"]}},
|
||||
"marital_units": {"marital_unit": {"members": ["you"]}},
|
||||
"tax_units": {"tax_unit": {"members": ["you"]}},
|
||||
"spm_units": {"spm_unit": {"members": ["you"]}},
|
||||
"households": {
|
||||
"household": {
|
||||
"members": ["you"],
|
||||
"state_name": {2024: "CA"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate taxes and benefits
|
||||
sim = Simulation(situation=situation)
|
||||
income_tax = sim.calculate("income_tax", 2024)[0]
|
||||
eitc = sim.calculate("eitc", 2024)[0]
|
||||
|
||||
print(f"Income tax: ${income_tax:,.0f}")
|
||||
print(f"EITC: ${eitc:,.0f}")
|
||||
```
|
||||
|
||||
### Web App to Python
|
||||
|
||||
**Web app URL:**
|
||||
```
|
||||
policyengine.org/us/household?household=12345
|
||||
```
|
||||
|
||||
**Equivalent Python (conceptually):**
|
||||
The household ID represents a situation dictionary. To replicate in Python, you'd create a similar situation.
|
||||
|
||||
### When to Use This Skill
|
||||
|
||||
- Creating household situations for tax/benefit calculations
|
||||
- Running microsimulations with PolicyEngine-US
|
||||
- Analyzing policy reforms and their impacts
|
||||
- Building tools that use PolicyEngine-US (calculators, analysis notebooks)
|
||||
- Debugging PolicyEngine-US calculations
|
||||
|
||||
## For Contributors 💻
|
||||
|
||||
### Repository
|
||||
|
||||
**Location:** PolicyEngine/policyengine-us
|
||||
|
||||
**To see current implementation:**
|
||||
```bash
|
||||
git clone https://github.com/PolicyEngine/policyengine-us
|
||||
cd policyengine-us
|
||||
|
||||
# Explore structure
|
||||
tree policyengine_us/
|
||||
```
|
||||
|
||||
**Key directories:**
|
||||
```bash
|
||||
ls policyengine_us/
|
||||
# - variables/ - Tax and benefit calculations
|
||||
# - parameters/ - Policy rules (YAML)
|
||||
# - reforms/ - Pre-defined reforms
|
||||
# - tests/ - Test cases
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Situation Dictionary Structure
|
||||
|
||||
PolicyEngine requires a nested dictionary defining household composition and characteristics:
|
||||
|
||||
```python
|
||||
situation = {
|
||||
"people": {
|
||||
"person_id": {
|
||||
"age": {2024: 35},
|
||||
"employment_income": {2024: 50000},
|
||||
# ... other person attributes
|
||||
}
|
||||
},
|
||||
"families": {
|
||||
"family_id": {"members": ["person_id", ...]}
|
||||
},
|
||||
"marital_units": {
|
||||
"marital_unit_id": {"members": ["person_id", ...]}
|
||||
},
|
||||
"tax_units": {
|
||||
"tax_unit_id": {"members": ["person_id", ...]}
|
||||
},
|
||||
"spm_units": {
|
||||
"spm_unit_id": {"members": ["person_id", ...]}
|
||||
},
|
||||
"households": {
|
||||
"household_id": {
|
||||
"members": ["person_id", ...],
|
||||
"state_name": {2024: "CA"}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Rules:**
|
||||
- All entities must have consistent member lists
|
||||
- Use year keys for all values: `{2024: value}`
|
||||
- State must be two-letter code (e.g., "CA", "NY", "TX")
|
||||
- All monetary values in dollars (not cents)
|
||||
|
||||
### 2. Creating Simulations
|
||||
|
||||
```python
|
||||
from policyengine_us import Simulation
|
||||
|
||||
# Create simulation from situation
|
||||
simulation = Simulation(situation=situation)
|
||||
|
||||
# Calculate variables
|
||||
income_tax = simulation.calculate("income_tax", 2024)
|
||||
eitc = simulation.calculate("eitc", 2024)
|
||||
household_net_income = simulation.calculate("household_net_income", 2024)
|
||||
```
|
||||
|
||||
**Common Variables:**
|
||||
|
||||
**Income:**
|
||||
- `employment_income` - W-2 wages
|
||||
- `self_employment_income` - 1099/business income
|
||||
- `qualified_dividend_income` - Qualified dividends
|
||||
- `capital_gains` - Capital gains
|
||||
- `interest_income` - Interest income
|
||||
- `social_security` - Social Security benefits
|
||||
- `pension_income` - Pension/retirement income
|
||||
|
||||
**Deductions:**
|
||||
- `charitable_cash_donations` - Cash charitable giving
|
||||
- `real_estate_taxes` - State and local property taxes
|
||||
- `mortgage_interest` - Mortgage interest deduction
|
||||
- `medical_expense` - Medical and dental expenses
|
||||
- `casualty_loss` - Casualty and theft losses
|
||||
|
||||
**Tax Outputs:**
|
||||
- `income_tax` - Total federal income tax
|
||||
- `payroll_tax` - FICA taxes
|
||||
- `state_income_tax` - State income tax
|
||||
- `household_tax` - Total taxes (federal + state + local)
|
||||
|
||||
**Benefits:**
|
||||
- `eitc` - Earned Income Tax Credit
|
||||
- `ctc` - Child Tax Credit
|
||||
- `snap` - SNAP benefits
|
||||
- `household_benefits` - Total benefits
|
||||
|
||||
**Summary:**
|
||||
- `household_net_income` - Income minus taxes plus benefits
|
||||
|
||||
### 3. Using Axes for Parameter Sweeps
|
||||
|
||||
To vary a parameter across multiple values:
|
||||
|
||||
```python
|
||||
situation = {
|
||||
# ... normal situation setup ...
|
||||
"axes": [[{
|
||||
"name": "employment_income",
|
||||
"count": 1001,
|
||||
"min": 0,
|
||||
"max": 200000,
|
||||
"period": 2024
|
||||
}]]
|
||||
}
|
||||
|
||||
simulation = Simulation(situation=situation)
|
||||
# Now calculate() returns arrays of 1001 values
|
||||
incomes = simulation.calculate("employment_income", 2024) # Array of 1001 values
|
||||
taxes = simulation.calculate("income_tax", 2024) # Array of 1001 values
|
||||
```
|
||||
|
||||
**Important:** Remove axes before creating single-point simulations:
|
||||
```python
|
||||
situation_single = situation.copy()
|
||||
situation_single.pop("axes", None)
|
||||
simulation = Simulation(situation=situation_single)
|
||||
```
|
||||
|
||||
### 4. Policy Reforms
|
||||
|
||||
```python
|
||||
from policyengine_us import Simulation
|
||||
|
||||
# Define a reform (modifies parameters)
|
||||
reform = {
|
||||
"gov.irs.credits.ctc.amount.base_amount": {
|
||||
"2024-01-01.2100-12-31": 5000 # Increase CTC to $5000
|
||||
}
|
||||
}
|
||||
|
||||
# Create simulation with reform
|
||||
simulation = Simulation(situation=situation, reform=reform)
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Single Household Calculation
|
||||
|
||||
```python
|
||||
from policyengine_us import Simulation
|
||||
|
||||
situation = {
|
||||
"people": {
|
||||
"parent": {
|
||||
"age": {2024: 35},
|
||||
"employment_income": {2024: 60000}
|
||||
},
|
||||
"child": {
|
||||
"age": {2024: 5}
|
||||
}
|
||||
},
|
||||
"families": {"family": {"members": ["parent", "child"]}},
|
||||
"marital_units": {"marital_unit": {"members": ["parent"]}},
|
||||
"tax_units": {"tax_unit": {"members": ["parent", "child"]}},
|
||||
"spm_units": {"spm_unit": {"members": ["parent", "child"]}},
|
||||
"households": {
|
||||
"household": {
|
||||
"members": ["parent", "child"],
|
||||
"state_name": {2024: "NY"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sim = Simulation(situation=situation)
|
||||
income_tax = sim.calculate("income_tax", 2024)[0]
|
||||
ctc = sim.calculate("ctc", 2024)[0]
|
||||
```
|
||||
|
||||
### Pattern 2: Marginal Tax Rate Analysis
|
||||
|
||||
```python
|
||||
# Create baseline with axes varying income
|
||||
situation_with_axes = {
|
||||
# ... situation setup ...
|
||||
"axes": [[{
|
||||
"name": "employment_income",
|
||||
"count": 1001,
|
||||
"min": 0,
|
||||
"max": 200000,
|
||||
"period": 2024
|
||||
}]]
|
||||
}
|
||||
|
||||
sim = Simulation(situation=situation_with_axes)
|
||||
incomes = sim.calculate("employment_income", 2024)
|
||||
taxes = sim.calculate("income_tax", 2024)
|
||||
|
||||
# Calculate marginal tax rate
|
||||
import numpy as np
|
||||
mtr = np.gradient(taxes) / np.gradient(incomes)
|
||||
```
|
||||
|
||||
### Pattern 3: Charitable Donation Impact
|
||||
|
||||
```python
|
||||
# Baseline (no donation)
|
||||
situation_baseline = create_situation(income=100000, donation=0)
|
||||
sim_baseline = Simulation(situation=situation_baseline)
|
||||
tax_baseline = sim_baseline.calculate("income_tax", 2024)[0]
|
||||
|
||||
# With donation
|
||||
situation_donation = create_situation(income=100000, donation=5000)
|
||||
sim_donation = Simulation(situation=situation_donation)
|
||||
tax_donation = sim_donation.calculate("income_tax", 2024)[0]
|
||||
|
||||
# Tax savings from donation
|
||||
tax_savings = tax_baseline - tax_donation
|
||||
effective_discount = tax_savings / 5000 # e.g., 0.24 = 24% discount
|
||||
```
|
||||
|
||||
### Pattern 4: State Comparison
|
||||
|
||||
```python
|
||||
states = ["CA", "NY", "TX", "FL"]
|
||||
results = {}
|
||||
|
||||
for state in states:
|
||||
situation = create_situation(state=state, income=75000)
|
||||
sim = Simulation(situation=situation)
|
||||
results[state] = {
|
||||
"state_income_tax": sim.calculate("state_income_tax", 2024)[0],
|
||||
"total_tax": sim.calculate("household_tax", 2024)[0]
|
||||
}
|
||||
```
|
||||
|
||||
## Helper Scripts
|
||||
|
||||
This skill includes helper scripts in the `scripts/` directory:
|
||||
|
||||
```python
|
||||
from policyengine_skills.situation_helpers import (
|
||||
create_single_filer,
|
||||
create_married_couple,
|
||||
create_family_with_children,
|
||||
add_itemized_deductions
|
||||
)
|
||||
|
||||
# Quick situation creation
|
||||
situation = create_single_filer(
|
||||
income=50000,
|
||||
state="CA",
|
||||
age=30
|
||||
)
|
||||
|
||||
# Add deductions
|
||||
situation = add_itemized_deductions(
|
||||
situation,
|
||||
charitable_donations=5000,
|
||||
mortgage_interest=10000,
|
||||
real_estate_taxes=8000
|
||||
)
|
||||
```
|
||||
|
||||
## Common Pitfalls and Solutions
|
||||
|
||||
### Pitfall 1: Member Lists Out of Sync
|
||||
**Problem:** Different entities have different members
|
||||
```python
|
||||
# WRONG
|
||||
"tax_units": {"tax_unit": {"members": ["parent"]}},
|
||||
"households": {"household": {"members": ["parent", "child"]}}
|
||||
```
|
||||
|
||||
**Solution:** Keep all entity member lists consistent:
|
||||
```python
|
||||
# CORRECT
|
||||
all_members = ["parent", "child"]
|
||||
"families": {"family": {"members": all_members}},
|
||||
"tax_units": {"tax_unit": {"members": all_members}},
|
||||
"households": {"household": {"members": all_members}}
|
||||
```
|
||||
|
||||
### Pitfall 2: Forgetting Year Keys
|
||||
**Problem:** `"age": 35` instead of `"age": {2024: 35}`
|
||||
|
||||
**Solution:** Always use year dictionary:
|
||||
```python
|
||||
"age": {2024: 35},
|
||||
"employment_income": {2024: 50000}
|
||||
```
|
||||
|
||||
### Pitfall 3: Net Taxes vs Gross Taxes
|
||||
**Problem:** Forgetting to subtract benefits from taxes
|
||||
|
||||
**Solution:** Use proper calculation:
|
||||
```python
|
||||
# Net taxes (what household actually pays)
|
||||
net_tax = sim.calculate("household_tax", 2024) - \
|
||||
sim.calculate("household_benefits", 2024)
|
||||
```
|
||||
|
||||
### Pitfall 4: Axes Persistence
|
||||
**Problem:** Axes remain in situation when creating single-point simulation
|
||||
|
||||
**Solution:** Remove axes before single-point simulation:
|
||||
```python
|
||||
situation_single = situation.copy()
|
||||
situation_single.pop("axes", None)
|
||||
```
|
||||
|
||||
### Pitfall 5: State-Specific Variables
|
||||
**Problem:** Using NYC-specific variables without `in_nyc: True`
|
||||
|
||||
**Solution:** Set NYC flag for NY residents in NYC:
|
||||
```python
|
||||
"households": {
|
||||
"household": {
|
||||
"state_name": {2024: "NY"},
|
||||
"in_nyc": {2024: True} # Required for NYC taxes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## NYC Handling
|
||||
|
||||
For New York City residents:
|
||||
```python
|
||||
situation = {
|
||||
# ... people setup ...
|
||||
"households": {
|
||||
"household": {
|
||||
"members": ["person"],
|
||||
"state_name": {2024: "NY"},
|
||||
"in_nyc": {2024: True} # Enable NYC tax calculations
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
- Always use `policyengine-us>=1.155.0` for 2024 calculations
|
||||
- Check version: `import policyengine_us; print(policyengine_us.__version__)`
|
||||
- Different years may require different package versions
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
1. **Enable tracing:**
|
||||
```python
|
||||
simulation.trace = True
|
||||
result = simulation.calculate("variable_name", 2024)
|
||||
```
|
||||
|
||||
2. **Check intermediate calculations:**
|
||||
```python
|
||||
agi = simulation.calculate("adjusted_gross_income", 2024)
|
||||
taxable_income = simulation.calculate("taxable_income", 2024)
|
||||
```
|
||||
|
||||
3. **Verify situation structure:**
|
||||
```python
|
||||
import json
|
||||
print(json.dumps(situation, indent=2))
|
||||
```
|
||||
|
||||
4. **Test with PolicyEngine web app:**
|
||||
- Go to policyengine.org/us/household
|
||||
- Enter same inputs
|
||||
- Compare results
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Documentation:** https://policyengine.org/us/docs
|
||||
- **API Reference:** https://github.com/PolicyEngine/policyengine-us
|
||||
- **Example Notebooks:** https://github.com/PolicyEngine/analysis-notebooks
|
||||
- **Variable Explorer:** https://policyengine.org/us/variables
|
||||
|
||||
## Examples Directory
|
||||
|
||||
See `examples/` for complete working examples:
|
||||
- `single_filer.yaml` - Single person household
|
||||
- `married_couple.yaml` - Married filing jointly
|
||||
- `family_with_children.yaml` - Family with dependents
|
||||
- `itemized_deductions.yaml` - Using itemized deductions
|
||||
- `donation_sweep.yaml` - Analyzing donation impacts with axes
|
||||
71
skills/policyengine-us-skill/examples/donation_sweep.yaml
Normal file
71
skills/policyengine-us-skill/examples/donation_sweep.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
# Example: Analyzing charitable donation impacts using axes
|
||||
# Married couple with 2 children in New York
|
||||
# Sweeps charitable donations from $0 to $50,000
|
||||
|
||||
people:
|
||||
parent_1:
|
||||
age:
|
||||
2024: 35
|
||||
employment_income:
|
||||
2024: 100000
|
||||
parent_2:
|
||||
age:
|
||||
2024: 35
|
||||
employment_income:
|
||||
2024: 50000
|
||||
child_1:
|
||||
age:
|
||||
2024: 8
|
||||
child_2:
|
||||
age:
|
||||
2024: 5
|
||||
|
||||
families:
|
||||
family:
|
||||
members:
|
||||
- parent_1
|
||||
- parent_2
|
||||
- child_1
|
||||
- child_2
|
||||
|
||||
marital_units:
|
||||
marital_unit:
|
||||
members:
|
||||
- parent_1
|
||||
- parent_2
|
||||
- child_1
|
||||
- child_2
|
||||
|
||||
tax_units:
|
||||
tax_unit:
|
||||
members:
|
||||
- parent_1
|
||||
- parent_2
|
||||
- child_1
|
||||
- child_2
|
||||
|
||||
spm_units:
|
||||
spm_unit:
|
||||
members:
|
||||
- parent_1
|
||||
- parent_2
|
||||
- child_1
|
||||
- child_2
|
||||
|
||||
households:
|
||||
household:
|
||||
members:
|
||||
- parent_1
|
||||
- parent_2
|
||||
- child_1
|
||||
- child_2
|
||||
state_name:
|
||||
2024: NY
|
||||
|
||||
# Axes: Vary charitable donations from $0 to $50,000
|
||||
axes:
|
||||
- - name: charitable_cash_donations
|
||||
count: 1001
|
||||
min: 0
|
||||
max: 50000
|
||||
period: 2024
|
||||
38
skills/policyengine-us-skill/examples/single_filer.yaml
Normal file
38
skills/policyengine-us-skill/examples/single_filer.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
# Example: Single tax filer in California
|
||||
# Income: $60,000, Age: 30, with charitable donations
|
||||
|
||||
people:
|
||||
person:
|
||||
age:
|
||||
2024: 30
|
||||
employment_income:
|
||||
2024: 60000
|
||||
charitable_cash_donations:
|
||||
2024: 5000
|
||||
|
||||
families:
|
||||
family:
|
||||
members:
|
||||
- person
|
||||
|
||||
marital_units:
|
||||
marital_unit:
|
||||
members:
|
||||
- person
|
||||
|
||||
tax_units:
|
||||
tax_unit:
|
||||
members:
|
||||
- person
|
||||
|
||||
spm_units:
|
||||
spm_unit:
|
||||
members:
|
||||
- person
|
||||
|
||||
households:
|
||||
household:
|
||||
members:
|
||||
- person
|
||||
state_name:
|
||||
2024: CA
|
||||
257
skills/policyengine-us-skill/scripts/situation_helpers.py
Normal file
257
skills/policyengine-us-skill/scripts/situation_helpers.py
Normal file
@@ -0,0 +1,257 @@
|
||||
"""
|
||||
Helper functions for creating PolicyEngine-US situations.
|
||||
|
||||
These utilities simplify the creation of situation dictionaries
|
||||
for common household configurations.
|
||||
"""
|
||||
|
||||
CURRENT_YEAR = 2024
|
||||
|
||||
|
||||
def create_single_filer(income, state="CA", age=35, **kwargs):
|
||||
"""
|
||||
Create a situation for a single tax filer.
|
||||
|
||||
Args:
|
||||
income (float): Employment income
|
||||
state (str): Two-letter state code (e.g., "CA", "NY")
|
||||
age (int): Person's age
|
||||
**kwargs: Additional person attributes (e.g., self_employment_income)
|
||||
|
||||
Returns:
|
||||
dict: PolicyEngine situation dictionary
|
||||
"""
|
||||
person_attrs = {
|
||||
"age": {CURRENT_YEAR: age},
|
||||
"employment_income": {CURRENT_YEAR: income},
|
||||
}
|
||||
person_attrs.update({k: {CURRENT_YEAR: v} for k, v in kwargs.items()})
|
||||
|
||||
return {
|
||||
"people": {"person": person_attrs},
|
||||
"families": {"family": {"members": ["person"]}},
|
||||
"marital_units": {"marital_unit": {"members": ["person"]}},
|
||||
"tax_units": {"tax_unit": {"members": ["person"]}},
|
||||
"spm_units": {"spm_unit": {"members": ["person"]}},
|
||||
"households": {
|
||||
"household": {
|
||||
"members": ["person"],
|
||||
"state_name": {CURRENT_YEAR: state}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def create_married_couple(
|
||||
income_1, income_2=0, state="CA", age_1=35, age_2=35, **kwargs
|
||||
):
|
||||
"""
|
||||
Create a situation for a married couple filing jointly.
|
||||
|
||||
Args:
|
||||
income_1 (float): First spouse's employment income
|
||||
income_2 (float): Second spouse's employment income
|
||||
state (str): Two-letter state code
|
||||
age_1 (int): First spouse's age
|
||||
age_2 (int): Second spouse's age
|
||||
**kwargs: Additional household attributes
|
||||
|
||||
Returns:
|
||||
dict: PolicyEngine situation dictionary
|
||||
"""
|
||||
members = ["spouse_1", "spouse_2"]
|
||||
|
||||
household_attrs = {
|
||||
"members": members,
|
||||
"state_name": {CURRENT_YEAR: state}
|
||||
}
|
||||
household_attrs.update({k: {CURRENT_YEAR: v} for k, v in kwargs.items()})
|
||||
|
||||
return {
|
||||
"people": {
|
||||
"spouse_1": {
|
||||
"age": {CURRENT_YEAR: age_1},
|
||||
"employment_income": {CURRENT_YEAR: income_1}
|
||||
},
|
||||
"spouse_2": {
|
||||
"age": {CURRENT_YEAR: age_2},
|
||||
"employment_income": {CURRENT_YEAR: income_2}
|
||||
}
|
||||
},
|
||||
"families": {"family": {"members": members}},
|
||||
"marital_units": {"marital_unit": {"members": members}},
|
||||
"tax_units": {"tax_unit": {"members": members}},
|
||||
"spm_units": {"spm_unit": {"members": members}},
|
||||
"households": {"household": household_attrs}
|
||||
}
|
||||
|
||||
|
||||
def create_family_with_children(
|
||||
parent_income,
|
||||
num_children=1,
|
||||
child_ages=None,
|
||||
state="CA",
|
||||
parent_age=35,
|
||||
married=False,
|
||||
spouse_income=0,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Create a situation for a family with children.
|
||||
|
||||
Args:
|
||||
parent_income (float): Primary parent's employment income
|
||||
num_children (int): Number of children
|
||||
child_ages (list): List of child ages (defaults to [5, 8, 12, ...])
|
||||
state (str): Two-letter state code
|
||||
parent_age (int): Parent's age
|
||||
married (bool): Whether parents are married
|
||||
spouse_income (float): Spouse's income if married
|
||||
**kwargs: Additional household attributes
|
||||
|
||||
Returns:
|
||||
dict: PolicyEngine situation dictionary
|
||||
"""
|
||||
if child_ages is None:
|
||||
child_ages = [5 + i * 3 for i in range(num_children)]
|
||||
elif len(child_ages) != num_children:
|
||||
raise ValueError("Length of child_ages must match num_children")
|
||||
|
||||
people = {
|
||||
"parent": {
|
||||
"age": {CURRENT_YEAR: parent_age},
|
||||
"employment_income": {CURRENT_YEAR: parent_income}
|
||||
}
|
||||
}
|
||||
|
||||
members = ["parent"]
|
||||
|
||||
if married:
|
||||
people["spouse"] = {
|
||||
"age": {CURRENT_YEAR: parent_age},
|
||||
"employment_income": {CURRENT_YEAR: spouse_income}
|
||||
}
|
||||
members.append("spouse")
|
||||
|
||||
for i, age in enumerate(child_ages):
|
||||
child_id = f"child_{i+1}"
|
||||
people[child_id] = {"age": {CURRENT_YEAR: age}}
|
||||
members.append(child_id)
|
||||
|
||||
household_attrs = {
|
||||
"members": members,
|
||||
"state_name": {CURRENT_YEAR: state}
|
||||
}
|
||||
household_attrs.update({k: {CURRENT_YEAR: v} for k, v in kwargs.items()})
|
||||
|
||||
return {
|
||||
"people": people,
|
||||
"families": {"family": {"members": members}},
|
||||
"marital_units": {
|
||||
"marital_unit": {
|
||||
"members": members if married else ["parent"]
|
||||
}
|
||||
},
|
||||
"tax_units": {"tax_unit": {"members": members}},
|
||||
"spm_units": {"spm_unit": {"members": members}},
|
||||
"households": {"household": household_attrs}
|
||||
}
|
||||
|
||||
|
||||
def add_itemized_deductions(
|
||||
situation,
|
||||
charitable_donations=0,
|
||||
mortgage_interest=0,
|
||||
real_estate_taxes=0,
|
||||
medical_expenses=0,
|
||||
casualty_losses=0
|
||||
):
|
||||
"""
|
||||
Add itemized deductions to an existing situation.
|
||||
|
||||
Adds deductions to the first person in the situation.
|
||||
|
||||
Args:
|
||||
situation (dict): Existing PolicyEngine situation
|
||||
charitable_donations (float): Cash charitable contributions
|
||||
mortgage_interest (float): Mortgage interest paid
|
||||
real_estate_taxes (float): State and local property taxes
|
||||
medical_expenses (float): Medical and dental expenses
|
||||
casualty_losses (float): Casualty and theft losses
|
||||
|
||||
Returns:
|
||||
dict: Updated situation with deductions
|
||||
"""
|
||||
# Get first person ID
|
||||
first_person = list(situation["people"].keys())[0]
|
||||
|
||||
# Add deductions
|
||||
if charitable_donations > 0:
|
||||
situation["people"][first_person]["charitable_cash_donations"] = {
|
||||
CURRENT_YEAR: charitable_donations
|
||||
}
|
||||
|
||||
if mortgage_interest > 0:
|
||||
situation["people"][first_person]["mortgage_interest"] = {
|
||||
CURRENT_YEAR: mortgage_interest
|
||||
}
|
||||
|
||||
if real_estate_taxes > 0:
|
||||
situation["people"][first_person]["real_estate_taxes"] = {
|
||||
CURRENT_YEAR: real_estate_taxes
|
||||
}
|
||||
|
||||
if medical_expenses > 0:
|
||||
situation["people"][first_person]["medical_expense"] = {
|
||||
CURRENT_YEAR: medical_expenses
|
||||
}
|
||||
|
||||
if casualty_losses > 0:
|
||||
situation["people"][first_person]["casualty_loss"] = {
|
||||
CURRENT_YEAR: casualty_losses
|
||||
}
|
||||
|
||||
return situation
|
||||
|
||||
|
||||
def add_axes(situation, variable_name, min_val, max_val, count=1001):
|
||||
"""
|
||||
Add axes to a situation for parameter sweeps.
|
||||
|
||||
Args:
|
||||
situation (dict): Existing PolicyEngine situation
|
||||
variable_name (str): Variable to vary (e.g., "employment_income")
|
||||
min_val (float): Minimum value
|
||||
max_val (float): Maximum value
|
||||
count (int): Number of points (default: 1001)
|
||||
|
||||
Returns:
|
||||
dict: Updated situation with axes
|
||||
"""
|
||||
situation["axes"] = [[{
|
||||
"name": variable_name,
|
||||
"count": count,
|
||||
"min": min_val,
|
||||
"max": max_val,
|
||||
"period": CURRENT_YEAR
|
||||
}]]
|
||||
|
||||
return situation
|
||||
|
||||
|
||||
def set_state_nyc(situation, in_nyc=True):
|
||||
"""
|
||||
Set state to NY and configure NYC residence.
|
||||
|
||||
Args:
|
||||
situation (dict): Existing PolicyEngine situation
|
||||
in_nyc (bool): Whether household is in NYC
|
||||
|
||||
Returns:
|
||||
dict: Updated situation
|
||||
"""
|
||||
household_id = list(situation["households"].keys())[0]
|
||||
situation["households"][household_id]["state_name"] = {CURRENT_YEAR: "NY"}
|
||||
situation["households"][household_id]["in_nyc"] = {CURRENT_YEAR: in_nyc}
|
||||
|
||||
return situation
|
||||
295
skills/policyengine-user-guide-skill/SKILL.md
Normal file
295
skills/policyengine-user-guide-skill/SKILL.md
Normal file
@@ -0,0 +1,295 @@
|
||||
---
|
||||
name: policyengine-user-guide
|
||||
description: Using PolicyEngine web apps to analyze tax and benefit policy impacts - for users of policyengine.org
|
||||
---
|
||||
|
||||
# PolicyEngine User Guide
|
||||
|
||||
This skill helps you use PolicyEngine to analyze how tax and benefit policies affect households and populations.
|
||||
|
||||
## For Users: Getting Started
|
||||
|
||||
### What is PolicyEngine?
|
||||
|
||||
PolicyEngine computes the impact of public policy on households and society. You can:
|
||||
- Calculate how policies affect your household
|
||||
- Analyze population-wide impacts of reforms
|
||||
- Create and share custom policy proposals
|
||||
- Compare different policy options
|
||||
|
||||
### Web App: policyengine.org
|
||||
|
||||
**Main features:**
|
||||
1. **Your household** - Calculate your taxes and benefits
|
||||
2. **Policy** - Design custom reforms and see impacts
|
||||
3. **Research** - Read policy analysis and blog posts
|
||||
|
||||
### Available Countries
|
||||
|
||||
- **United States** - policyengine.org/us
|
||||
- **United Kingdom** - policyengine.org/uk
|
||||
- **Canada** - policyengine.org/ca (beta)
|
||||
|
||||
## Using the Household Calculator
|
||||
|
||||
### Step 1: Navigate to Household Page
|
||||
|
||||
**US:** https://policyengine.org/us/household
|
||||
**UK:** https://policyengine.org/uk/household
|
||||
|
||||
### Step 2: Enter Your Information
|
||||
|
||||
**Income:**
|
||||
- Employment income (W-2 wages)
|
||||
- Self-employment income
|
||||
- Capital gains and dividends
|
||||
- Social Security, pensions, etc.
|
||||
|
||||
**Household composition:**
|
||||
- Adults and dependents
|
||||
- Ages
|
||||
- Marital status
|
||||
|
||||
**Location:**
|
||||
- State (US) or region (UK)
|
||||
- NYC checkbox for New York City residents
|
||||
|
||||
**Deductions (US):**
|
||||
- Charitable donations
|
||||
- Mortgage interest
|
||||
- State and local taxes (SALT)
|
||||
- Medical expenses
|
||||
|
||||
### Step 3: View Results
|
||||
|
||||
**Net income** - Your income after taxes and benefits
|
||||
|
||||
**Breakdown:**
|
||||
- Total taxes (federal + state + local)
|
||||
- Total benefits (EITC, CTC, SNAP, etc.)
|
||||
- Effective tax rate
|
||||
- Marginal tax rate
|
||||
|
||||
**Charts:**
|
||||
- Net income by earnings
|
||||
- Marginal tax rate by earnings
|
||||
|
||||
## Creating a Policy Reform
|
||||
|
||||
### Step 1: Navigate to Policy Page
|
||||
|
||||
**US:** https://policyengine.org/us/policy
|
||||
**UK:** https://policyengine.org/uk/policy
|
||||
|
||||
### Step 2: Select Parameters to Change
|
||||
|
||||
**Browse parameters by:**
|
||||
- Government department (IRS, SSA, etc.)
|
||||
- Program (EITC, CTC, SNAP)
|
||||
- Type (tax rates, benefit amounts, thresholds)
|
||||
|
||||
**Example: Increase Child Tax Credit**
|
||||
1. Navigate to gov.irs.credits.ctc.amount.base_amount
|
||||
2. Change from $2,000 to $5,000
|
||||
3. Click "Calculate economic impact"
|
||||
|
||||
### Step 3: View Population Impacts
|
||||
|
||||
**Budgetary impact:**
|
||||
- Total cost or revenue raised
|
||||
- Breakdown by program
|
||||
|
||||
**Poverty impact:**
|
||||
- Change in poverty rates
|
||||
- By age group (children, adults, seniors)
|
||||
- Deep poverty (income < 50% of threshold)
|
||||
|
||||
**Distributional impact:**
|
||||
- Average impact by income decile
|
||||
- Winners and losers by decile
|
||||
- Relative vs absolute changes
|
||||
|
||||
**Inequality impact:**
|
||||
- Gini index change
|
||||
- Top 10% and top 1% income share
|
||||
|
||||
### Step 4: Share Your Reform
|
||||
|
||||
**Share URL:**
|
||||
Every reform has a unique URL you can share:
|
||||
```
|
||||
policyengine.org/us/policy?reform=12345®ion=enhanced_us&timePeriod=2025
|
||||
```
|
||||
|
||||
**Parameters in URL:**
|
||||
- `reform=12345` - Your custom reform ID
|
||||
- `region=enhanced_us` - Geography (US, state, or congressional district)
|
||||
- `timePeriod=2025` - Year of analysis
|
||||
|
||||
## Understanding Results
|
||||
|
||||
### Metrics Explained
|
||||
|
||||
**Supplemental Poverty Measure (SPM):**
|
||||
- Accounts for taxes, benefits, and living costs
|
||||
- US Census Bureau's official alternative poverty measure
|
||||
- More comprehensive than Official Poverty Measure
|
||||
|
||||
**Gini coefficient:**
|
||||
- Measures income inequality (0 = perfect equality, 1 = perfect inequality)
|
||||
- US Gini is typically around 0.48
|
||||
- Lower values = more equal income distribution
|
||||
|
||||
**Income deciles:**
|
||||
- Population divided into 10 equal groups by income
|
||||
- Decile 1 = bottom 10% of earners
|
||||
- Decile 10 = top 10% of earners
|
||||
|
||||
**Winners and losers:**
|
||||
- Winners: Net income increases by 5% or more
|
||||
- Losers: Net income decreases by 5% or more
|
||||
- Neutral: Net income change less than 5%
|
||||
|
||||
### Reading Charts
|
||||
|
||||
**Household impact charts:**
|
||||
- X-axis: Usually income or earnings
|
||||
- Y-axis: Net income, taxes, or benefits
|
||||
- Hover to see exact values
|
||||
|
||||
**Population impact charts:**
|
||||
- Bar charts: Compare across groups (deciles, states)
|
||||
- Line charts: Show relationships (income vs impact)
|
||||
- Waterfall charts: Show components of budgetary impact
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Use Case 1: How Does Policy X Affect My Household?
|
||||
|
||||
1. Go to household calculator
|
||||
2. Enter your information
|
||||
3. Select "Reform" and choose the policy
|
||||
4. Compare baseline vs reform results
|
||||
|
||||
### Use Case 2: How Much Would Policy X Cost?
|
||||
|
||||
1. Go to policy page
|
||||
2. Create or select the reform
|
||||
3. View "Budgetary impact" section
|
||||
4. See total cost and breakdown
|
||||
|
||||
### Use Case 3: Would Policy X Reduce Poverty?
|
||||
|
||||
1. Go to policy page
|
||||
2. Create or select the reform
|
||||
3. View "Poverty impact" section
|
||||
4. See change in poverty rate by age group
|
||||
|
||||
### Use Case 4: Who Benefits from Policy X?
|
||||
|
||||
1. Go to policy page
|
||||
2. Create or select the reform
|
||||
3. View "Distributional impact" section
|
||||
4. See winners and losers by income decile
|
||||
|
||||
### Use Case 5: Compare Two Policy Proposals
|
||||
|
||||
1. Create Reform A (e.g., expand EITC)
|
||||
2. Note the URL or reform ID
|
||||
3. Create Reform B (e.g., expand CTC)
|
||||
4. Compare budgetary, poverty, and distributional impacts
|
||||
|
||||
## For Analysts: Moving Beyond the Web App
|
||||
|
||||
Once you understand the web app, you can:
|
||||
|
||||
**Use the Python client:**
|
||||
- See `policyengine-python-client-skill` for programmatic access
|
||||
- See `policyengine-us-skill` for detailed simulation patterns
|
||||
|
||||
**Create custom analyses:**
|
||||
- See `policyengine-analysis-skill` for analysis patterns
|
||||
- See `microdf-skill` for data analysis utilities
|
||||
|
||||
**Access the API directly:**
|
||||
- See `policyengine-api-skill` for API documentation
|
||||
- REST endpoints for integration
|
||||
|
||||
## For Contributors: Building PolicyEngine
|
||||
|
||||
To contribute to PolicyEngine development:
|
||||
|
||||
**Understanding the stack:**
|
||||
- See `policyengine-core-skill` for engine architecture
|
||||
- See `policyengine-us-skill` for country model patterns
|
||||
- See `policyengine-api-skill` for API development
|
||||
- See `policyengine-app-skill` for app development
|
||||
|
||||
**Development standards:**
|
||||
- See `policyengine-standards-skill` for code quality requirements
|
||||
- See `policyengine-writing-skill` for documentation style
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### How accurate is PolicyEngine?
|
||||
|
||||
PolicyEngine uses official tax and benefit rules from legislation and regulations. Calculations match official calculators (IRS, SSA, etc.) for individual households.
|
||||
|
||||
Population-level estimates use microsimulation with survey data (Current Population Survey for US, Family Resources Survey for UK).
|
||||
|
||||
### Can I use PolicyEngine for my taxes?
|
||||
|
||||
PolicyEngine is for policy analysis, not tax filing. Results are estimates based on the information you provide. For filing taxes, use IRS.gov or professional tax software.
|
||||
|
||||
### How is PolicyEngine funded?
|
||||
|
||||
PolicyEngine is a nonprofit funded by grants and donations. The platform is free to use.
|
||||
|
||||
### Can I export results?
|
||||
|
||||
Yes! Charts can be downloaded as PNG or HTML. You can also share reform URLs with others.
|
||||
|
||||
### What programs does PolicyEngine model?
|
||||
|
||||
**US (federal):**
|
||||
- Income tax, payroll tax, capital gains tax
|
||||
- EITC, CTC, ACTC
|
||||
- SNAP, WIC, ACA premium tax credits
|
||||
- Social Security, SSI, TANF
|
||||
- State income taxes (varies by state)
|
||||
|
||||
**UK:**
|
||||
- Income tax, National Insurance
|
||||
- Universal Credit, Child Benefit
|
||||
- State Pension, Pension Credit
|
||||
- Council Tax, Council Tax Support
|
||||
|
||||
For complete lists, see:
|
||||
- US: https://policyengine.org/us/parameters
|
||||
- UK: https://policyengine.org/uk/parameters
|
||||
|
||||
### How do I report a bug?
|
||||
|
||||
**If you find incorrect calculations:**
|
||||
1. Go to the household calculator
|
||||
2. Note your inputs and the incorrect result
|
||||
3. File an issue: https://github.com/PolicyEngine/policyengine-us/issues (or appropriate country repo)
|
||||
4. Include the household URL
|
||||
|
||||
**If you find app bugs:**
|
||||
1. Note what you were doing
|
||||
2. File an issue: https://github.com/PolicyEngine/policyengine-app/issues
|
||||
|
||||
## Resources
|
||||
|
||||
- **Website:** https://policyengine.org
|
||||
- **Documentation:** https://policyengine.org/us/docs
|
||||
- **Blog:** https://policyengine.org/us/research
|
||||
- **GitHub:** https://github.com/PolicyEngine
|
||||
- **Contact:** hello@policyengine.org
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **policyengine-python-client-skill** - Using PolicyEngine programmatically
|
||||
- **policyengine-us-skill** - Understanding US tax/benefit calculations
|
||||
- **policyengine-analysis-skill** - Creating custom policy analyses
|
||||
526
skills/policyengine-writing-skill/SKILL.md
Normal file
526
skills/policyengine-writing-skill/SKILL.md
Normal file
@@ -0,0 +1,526 @@
|
||||
---
|
||||
name: policyengine-writing
|
||||
description: PolicyEngine writing style for blog posts, documentation, PR descriptions, and research reports - emphasizing active voice, quantitative precision, and neutral tone
|
||||
---
|
||||
|
||||
# PolicyEngine Writing Skill
|
||||
|
||||
Use this skill when writing blog posts, documentation, PR descriptions, research reports, or any public-facing PolicyEngine content.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Writing blog posts about policy analysis
|
||||
- Creating PR descriptions
|
||||
- Drafting documentation
|
||||
- Writing research reports
|
||||
- Composing social media posts
|
||||
- Creating newsletters
|
||||
- Writing README files
|
||||
|
||||
## Core Principles
|
||||
|
||||
PolicyEngine's writing emphasizes clarity, precision, and objectivity.
|
||||
|
||||
1. **Active voice** - Prefer active constructions over passive
|
||||
2. **Direct and quantitative** - Use specific numbers, avoid vague adjectives/adverbs
|
||||
3. **Sentence case** - Use sentence case for headings, not title case
|
||||
4. **Neutral tone** - Describe what policies do, not whether they're good or bad
|
||||
5. **Precise language** - Choose exact verbs over vague modifiers
|
||||
|
||||
## Active Voice
|
||||
|
||||
Active voice makes writing clearer and more direct.
|
||||
|
||||
**✅ Correct (Active):**
|
||||
```
|
||||
Harris proposes expanding the Earned Income Tax Credit
|
||||
The reform reduces poverty by 3.2%
|
||||
PolicyEngine projects higher costs than other organizations
|
||||
We estimate the ten-year costs
|
||||
The bill lowers the state's top income tax rate
|
||||
Montana raises the EITC from 10% to 20%
|
||||
```
|
||||
|
||||
**❌ Wrong (Passive):**
|
||||
```
|
||||
The Earned Income Tax Credit is proposed to be expanded by Harris
|
||||
Poverty is reduced by 3.2% by the reform
|
||||
Higher costs are projected by PolicyEngine
|
||||
The ten-year costs are estimated
|
||||
The state's top income tax rate is lowered by the bill
|
||||
The EITC is raised from 10% to 20% by Montana
|
||||
```
|
||||
|
||||
## Quantitative and Precise
|
||||
|
||||
Replace vague modifiers with specific numbers and measurements.
|
||||
|
||||
**✅ Correct (Quantitative):**
|
||||
```
|
||||
Costs the state $245 million
|
||||
Benefits 77% of Montana residents
|
||||
Lowers the Supplemental Poverty Measure by 0.8%
|
||||
Raises net income by $252 in 2026
|
||||
The reform affects 14.3 million households
|
||||
Hours worked falls by 0.27%, or 411,000 full-time equivalent jobs
|
||||
The top decile receives an average benefit of $1,033
|
||||
PolicyEngine projects costs 40% higher than the Tax Foundation
|
||||
```
|
||||
|
||||
**❌ Wrong (Vague adjectives/adverbs):**
|
||||
```
|
||||
Significantly costs the state
|
||||
Benefits most Montana residents
|
||||
Greatly lowers poverty
|
||||
Substantially raises net income
|
||||
The reform affects many households
|
||||
Hours worked falls considerably
|
||||
High earners receive large benefits
|
||||
PolicyEngine projects much higher costs
|
||||
```
|
||||
|
||||
## Sentence Case for Headings
|
||||
|
||||
Use sentence case (capitalize only the first word and proper nouns) for all headings.
|
||||
|
||||
**✅ Correct (Sentence case):**
|
||||
```
|
||||
## The proposal
|
||||
## Nationwide impacts
|
||||
## Household impacts
|
||||
## Statewide impacts 2026
|
||||
## Case study: the End Child Poverty Act
|
||||
## Key findings
|
||||
```
|
||||
|
||||
**❌ Wrong (Title case):**
|
||||
```
|
||||
## The Proposal
|
||||
## Nationwide Impacts
|
||||
## Household Impacts
|
||||
## Statewide Impacts 2026
|
||||
## Case Study: The End Child Poverty Act
|
||||
## Key Findings
|
||||
```
|
||||
|
||||
## Neutral, Objective Tone
|
||||
|
||||
Describe what policies do without value judgments. Let readers draw their own conclusions from the data.
|
||||
|
||||
**✅ Correct (Neutral):**
|
||||
```
|
||||
The reform reduces poverty by 3.2% and raises inequality by 0.16%
|
||||
Single filers with earnings between $8,000 and $37,000 see their net incomes increase
|
||||
The tax changes raise the net income of 75.9% of residents
|
||||
PolicyEngine projects higher costs than other organizations
|
||||
The top income decile receives 42% of total benefits
|
||||
```
|
||||
|
||||
**❌ Wrong (Value judgments):**
|
||||
```
|
||||
The reform successfully reduces poverty by 3.2% but unfortunately raises inequality
|
||||
Low-income workers finally see their net incomes increase
|
||||
The tax changes benefit most residents
|
||||
PolicyEngine provides more accurate cost estimates
|
||||
The wealthiest households receive a disproportionate share of benefits
|
||||
```
|
||||
|
||||
## Precise Verbs Over Adverbs
|
||||
|
||||
Choose specific verbs instead of generic verbs modified by adverbs.
|
||||
|
||||
**✅ Correct (Precise verbs):**
|
||||
```
|
||||
The bill lowers the top rate from 5.9% to 5.4%
|
||||
The policy raises the maximum credit from $632 to $1,774
|
||||
The reform increases the phase-in rate from 7.65% to 15.3%
|
||||
This doubles Montana's EITC from 10% to 20%
|
||||
The change eliminates the age cap
|
||||
```
|
||||
|
||||
**❌ Wrong (Vague verbs + adverbs):**
|
||||
```
|
||||
The bill significantly changes the top rate
|
||||
The policy substantially increases the maximum credit
|
||||
The reform greatly boosts the phase-in rate
|
||||
This dramatically expands Montana's EITC
|
||||
The change completely removes the age cap
|
||||
```
|
||||
|
||||
## Concrete Examples
|
||||
|
||||
Always include specific household examples with precise numbers.
|
||||
|
||||
**✅ Correct:**
|
||||
```
|
||||
For a single adult with no children and $10,000 of earnings, the tax provisions
|
||||
increase their net income by $69 in 2026 and $68 in 2027, solely from the
|
||||
doubled EITC match.
|
||||
|
||||
A single parent of two kids with an annual income of $50,000 will see a $252
|
||||
increase to their net income: $179 from the expanded EITC, and $73 from the
|
||||
lower bracket threshold.
|
||||
|
||||
A married couple with no dependents and $200,000 of earnings will see their
|
||||
liability drop by $1,306 in 2027.
|
||||
```
|
||||
|
||||
**❌ Wrong:**
|
||||
```
|
||||
Low-income workers see modest increases to their net income from the
|
||||
expanded EITC.
|
||||
|
||||
Families with children benefit substantially from the tax changes.
|
||||
|
||||
High earners also see significant reductions in their tax liability.
|
||||
```
|
||||
|
||||
## Tables and Data
|
||||
|
||||
Use tables liberally to present data clearly. Always include units and context.
|
||||
|
||||
**Example 1: Tax parameters over time**
|
||||
|
||||
| Year | Phase-in rate | Max credit | Phase-out start | Phase-out rate |
|
||||
| ---- | ------------- | ---------- | --------------- | -------------- |
|
||||
| 2025 | 15.3% | $1,774 | $13,706 | 15.3% |
|
||||
| 2026 | 15.3% | $1,815 | $14,022 | 15.3% |
|
||||
| 2027 | 15.3% | $1,852 | $14,306 | 15.3% |
|
||||
|
||||
**Example 2: Household impacts**
|
||||
|
||||
| Household composition | 2026 net income change | 2027 net income change |
|
||||
| ------------------------------ | ---------------------- | ---------------------- |
|
||||
| Single, no children, $10,000 | $66 | $68 |
|
||||
| Single, two children, $50,000 | $252 | $266 |
|
||||
| Married, no children, $200,000 | $853 | $1,306 |
|
||||
|
||||
**Example 3: Ten-year costs**
|
||||
|
||||
| Year | Federal cost ($ billions) |
|
||||
| ------- | ------------------------- |
|
||||
| 2025 | 14.3 |
|
||||
| 2026 | 14.4 |
|
||||
| 2027 | 14.7 |
|
||||
| 2025-34 | 143.7 |
|
||||
|
||||
## Avoid Superlatives
|
||||
|
||||
Replace superlative claims with specific comparisons.
|
||||
|
||||
**✅ Correct:**
|
||||
```
|
||||
PolicyEngine projects costs 40% higher than the Tax Foundation
|
||||
The top decile receives an average benefit of $1,033
|
||||
The reform reduces child poverty by 3.2 percentage points
|
||||
This represents Montana's largest income tax cut since 2021
|
||||
```
|
||||
|
||||
**❌ Wrong:**
|
||||
```
|
||||
PolicyEngine provides the most accurate cost projections
|
||||
The wealthiest households receive massive benefits
|
||||
The reform dramatically slashes child poverty
|
||||
This is Montana's largest income tax cut in history
|
||||
```
|
||||
|
||||
## Structure and Organization
|
||||
|
||||
Follow a clear hierarchical structure with key findings up front.
|
||||
|
||||
**Standard blog post structure:**
|
||||
|
||||
```markdown
|
||||
# Title (H1)
|
||||
|
||||
Opening paragraph states what happened and when, with a link to PolicyEngine.
|
||||
|
||||
Key results in [year]:
|
||||
- Cost: $245 million
|
||||
- Benefits: 77% of residents
|
||||
- Poverty impact: Reduces SPM by 0.8%
|
||||
- Inequality impact: Raises Gini by 0.16%
|
||||
|
||||
## The proposal (H2)
|
||||
|
||||
Detailed description of the policy changes, often with a table showing
|
||||
the specific parameter values.
|
||||
|
||||
## Household impacts (H2)
|
||||
|
||||
Specific examples for representative household types.
|
||||
|
||||
### Example 1: Single filer (H3)
|
||||
Detailed calculation...
|
||||
|
||||
### Example 2: Family with children (H3)
|
||||
Detailed calculation...
|
||||
|
||||
## Statewide impacts (H2)
|
||||
|
||||
Population-level analysis with charts and tables.
|
||||
|
||||
### Budgetary impact (H3)
|
||||
Cost/revenue estimates...
|
||||
|
||||
### Distributional impact (H3)
|
||||
Winners/losers by income decile...
|
||||
|
||||
### Poverty and inequality (H3)
|
||||
Impact on poverty rates and inequality measures...
|
||||
|
||||
## Methodology (H2)
|
||||
|
||||
Explanation of data sources, modeling approach, and caveats.
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Opening Paragraphs
|
||||
|
||||
State the facts directly with dates, actors, and actions:
|
||||
|
||||
```
|
||||
[On April 28, 2025], Governor Greg Gianforte (R-MT) signed House Bill 337,
|
||||
a bill that amends Montana's individual income tax code.
|
||||
|
||||
Vice President Harris proposes expanding the Earned Income Tax Credit (EITC)
|
||||
for filers without qualifying dependents.
|
||||
|
||||
In her economic plan, Harris proposes to restore the expanded Earned Income
|
||||
Tax Credit for workers without children to its level under the American
|
||||
Rescue Plan Act in 2021.
|
||||
```
|
||||
|
||||
### Key Findings Format
|
||||
|
||||
Lead with bullet points of quantitative results:
|
||||
|
||||
```
|
||||
Key results in 2027:
|
||||
- Costs the state $245 million
|
||||
- Benefits 77% of Montana residents
|
||||
- Lowers the Supplemental Poverty Measure by 0.8%
|
||||
- Raises the Gini index by 0.16%
|
||||
```
|
||||
|
||||
### Methodological Transparency
|
||||
|
||||
Always specify the model, version, and assumptions:
|
||||
|
||||
```
|
||||
Based on static microsimulation modeling with PolicyEngine US (version 1.103.0),
|
||||
we project the following economic impacts for 2025.
|
||||
|
||||
Assuming no behavioral responses, we project that the EITC expansion will cost
|
||||
the federal government $14.3 billion in 2025.
|
||||
|
||||
Incorporating elasticities of labor supply used by the Congressional Budget Office
|
||||
increases the reform's cost.
|
||||
|
||||
Over the ten-year budget window, this amounts to $143.7 billion.
|
||||
```
|
||||
|
||||
### Household Examples
|
||||
|
||||
Always include the household composition, income, and specific dollar impacts:
|
||||
|
||||
```
|
||||
For a single adult with no children and $10,000 of earnings, the tax provisions
|
||||
increase their net income by $69 in 2026 and $68 in 2027.
|
||||
|
||||
A single parent of two kids with an annual income of $50,000 will see a $252
|
||||
increase to their net income due to House Bill 337: $179 from the expanded EITC,
|
||||
and $73 from the lower bracket threshold.
|
||||
```
|
||||
|
||||
## Examples in Context
|
||||
|
||||
### Blog Post Opening
|
||||
|
||||
**✅ Correct:**
|
||||
```
|
||||
On April 28, 2025, Governor Gianforte signed House Bill 337, which lowers
|
||||
Montana's top income tax rate from 5.9% to 5.4% and doubles the state EITC
|
||||
from 10% to 20% of the federal credit.
|
||||
|
||||
Key results in 2027:
|
||||
- Costs the state $245 million
|
||||
- Benefits 77% of Montana residents
|
||||
- Lowers the Supplemental Poverty Measure by 0.8%
|
||||
- Raises the Gini index by 0.16%
|
||||
|
||||
Use PolicyEngine to view the full results or calculate the effect on your
|
||||
household.
|
||||
```
|
||||
|
||||
**❌ Wrong:**
|
||||
```
|
||||
On April 28, 2025, Governor Gianforte made history by signing an amazing new
|
||||
tax cut bill that will dramatically help Montana families. House Bill 337
|
||||
significantly reduces tax rates and greatly expands the EITC.
|
||||
|
||||
This groundbreaking reform will:
|
||||
- Cost the state money
|
||||
- Help most residents
|
||||
- Reduce poverty substantially
|
||||
- Impact inequality
|
||||
|
||||
Check out PolicyEngine to see how much you could save!
|
||||
```
|
||||
|
||||
### PR Description
|
||||
|
||||
**✅ Correct:**
|
||||
```
|
||||
## Summary
|
||||
|
||||
This PR adds Claude Code plugin configuration to enable automated installation
|
||||
of agents and skills for PolicyEngine development.
|
||||
|
||||
## Changes
|
||||
|
||||
- Add plugin auto-install configuration in .claude/settings.json
|
||||
- Configure auto-install of country-models plugin from PolicyEngine/policyengine-claude
|
||||
|
||||
## Benefits
|
||||
|
||||
- Access to 15 specialized agents
|
||||
- 3 slash commands (/encode-policy, /review-pr, /fix-pr)
|
||||
- 2 skills (policyengine-us-skill, policyengine-standards-skill)
|
||||
|
||||
## Testing
|
||||
|
||||
After merging, team members trust the repo and the plugin auto-installs.
|
||||
```
|
||||
|
||||
**❌ Wrong:**
|
||||
```
|
||||
## Summary
|
||||
|
||||
This amazing PR adds incredible new Claude Code plugin support that will
|
||||
revolutionize PolicyEngine development!
|
||||
|
||||
## Changes
|
||||
|
||||
- Adds some configuration files
|
||||
- Sets up plugins and stuff
|
||||
|
||||
## Benefits
|
||||
|
||||
- Gets you lots of cool new features
|
||||
- Makes development much easier
|
||||
- Provides great new tools
|
||||
|
||||
## Testing
|
||||
|
||||
Should work great once merged!
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
**✅ Correct:**
|
||||
```
|
||||
## Installation
|
||||
|
||||
Install PolicyEngine-US from PyPI:
|
||||
|
||||
```bash
|
||||
pip install policyengine-us
|
||||
```
|
||||
|
||||
This installs version 1.103.0 or later, which includes support for 2025
|
||||
tax parameters.
|
||||
```
|
||||
|
||||
**❌ Wrong:**
|
||||
```
|
||||
## Installation
|
||||
|
||||
Simply install PolicyEngine-US:
|
||||
|
||||
```bash
|
||||
pip install policyengine-us
|
||||
```
|
||||
|
||||
This will install the latest version with all the newest features!
|
||||
```
|
||||
|
||||
## Special Cases
|
||||
|
||||
### Comparisons to Other Organizations
|
||||
|
||||
State facts neutrally without claiming superiority:
|
||||
|
||||
**✅ Correct:**
|
||||
```
|
||||
PolicyEngine projects higher costs than other organizations when considering
|
||||
behavioral responses.
|
||||
|
||||
| Organization | Cost, 2025-2034 ($ billions) |
|
||||
| ------------------------- | ---------------------------- |
|
||||
| PolicyEngine (static) | 144 |
|
||||
| PolicyEngine (dynamic) | 201 |
|
||||
| Tax Foundation | 157 |
|
||||
| Penn Wharton Budget Model | 135 |
|
||||
```
|
||||
|
||||
**❌ Wrong:**
|
||||
```
|
||||
PolicyEngine provides more accurate estimates than other organizations.
|
||||
|
||||
Unlike other models that underestimate costs, PolicyEngine correctly accounts
|
||||
for behavioral responses to project a more realistic $201 billion cost.
|
||||
```
|
||||
|
||||
### Discussing Limitations
|
||||
|
||||
Acknowledge limitations directly without hedging:
|
||||
|
||||
**✅ Correct:**
|
||||
```
|
||||
## Caveats
|
||||
|
||||
The Current Population Survey has several limitations for tax microsimulation:
|
||||
|
||||
- Truncates high incomes for privacy, underestimating tax impacts on high earners
|
||||
- Underestimates benefit receipt compared to administrative totals
|
||||
- Reflects 2020 data with 2025 policy parameters
|
||||
- Lacks detail for specific income types (assumes all capital gains are long-term)
|
||||
```
|
||||
|
||||
**❌ Wrong:**
|
||||
```
|
||||
## Caveats
|
||||
|
||||
While our model is highly sophisticated, like all models it has some potential
|
||||
limitations that users should be aware of:
|
||||
|
||||
- The data might not perfectly capture high incomes
|
||||
- Benefits may be slightly underestimated
|
||||
- We do our best to extrapolate older data to current years
|
||||
```
|
||||
|
||||
## Writing Checklist
|
||||
|
||||
Before publishing, verify:
|
||||
|
||||
- [ ] Use active voice throughout
|
||||
- [ ] Include specific numbers for all claims
|
||||
- [ ] Use sentence case for all headings
|
||||
- [ ] Maintain neutral, objective tone
|
||||
- [ ] Choose precise verbs over vague adverbs
|
||||
- [ ] Include concrete household examples
|
||||
- [ ] Present data in tables
|
||||
- [ ] Avoid all superlatives
|
||||
- [ ] Structure with clear hierarchy
|
||||
- [ ] Open with key quantitative findings
|
||||
- [ ] Specify model version and assumptions
|
||||
- [ ] Link to PolicyEngine when relevant
|
||||
- [ ] Acknowledge limitations directly
|
||||
|
||||
## Resources
|
||||
|
||||
- **Example posts**: See `policyengine-app/src/posts/articles/` for reference implementations
|
||||
- **PolicyEngine app**: https://policyengine.org for linking to analyses
|
||||
- **Microsimulation docs**: https://policyengine.org/us/docs for methodology details
|
||||
Reference in New Issue
Block a user