Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c1c636d
Scaffold the APPAM 2026 roundtable deck
MaxGhenis Nov 1, 2026
b66f2cc
Compute the APPAM deck's family, microdata and Axiom results
MaxGhenis Nov 1, 2026
e2fe775
Build the APPAM deck's slides, charts and invariants
MaxGhenis Nov 1, 2026
b74f4f3
Add the APPAM deck's microdata slide and evidence ledger
MaxGhenis Nov 1, 2026
1a34f25
Add the APPAM deck's reform slide and state baselines
MaxGhenis Nov 1, 2026
100305f
Apply the voice gate across the APPAM deck and notes
MaxGhenis Nov 1, 2026
66d2599
Format the APPAM analysis scripts
MaxGhenis Nov 1, 2026
d50751c
Lay out the APPAM closing slide so the logo clears the title
MaxGhenis Nov 1, 2026
e986abf
Add the fullscreen step to the APPAM presenting checklist
MaxGhenis Nov 1, 2026
c51ff2d
Resolve the APPAM deck's first review
MaxGhenis Nov 1, 2026
b344cae
Tighten the APPAM copy after the voice pass
MaxGhenis Nov 1, 2026
1199c42
Run the APPAM state slides on the state data file
MaxGhenis Nov 2, 2026
a25172c
State the Massachusetts formula change precisely in the APPAM ledger
MaxGhenis Nov 2, 2026
99ee5b3
Resolve round 2 review findings in the APPAM deck
MaxGhenis Nov 2, 2026
d4fa4b2
Fit slide 8 and add standard errors for its Massachusetts figures
MaxGhenis Nov 2, 2026
3009ecc
Join a staccato run in the notes and drop a reduced passive from the …
MaxGhenis Nov 2, 2026
df34413
Make every slide read from the PDF alone
MaxGhenis Nov 3, 2026
0129c2a
Trim the PDF to 16:9 and link the addresses it prints
MaxGhenis Nov 3, 2026
7ae1a4d
Resolve the round 3 review of the APPAM deck
MaxGhenis Nov 3, 2026
c1b7573
Link the TAXSIM and Axiom addresses in the PDF and test the printed code
MaxGhenis Nov 3, 2026
ed4fd56
Separate slide 5's Iowa and Idaho labels and print each cliff's raise
MaxGhenis Nov 3, 2026
07032ec
Darken slide 3's base bars and the rug marks for projection
MaxGhenis Nov 3, 2026
f82ad66
Print Massachusetts's effective sample among households with children
MaxGhenis Nov 3, 2026
69e5c51
Say when the deck's SPM cash adjustment must go
MaxGhenis Nov 3, 2026
d07f110
Give the ledger's slide 10 row the slide's wording
MaxGhenis Nov 3, 2026
986c661
Count each source household once in the APPAM effective samples
MaxGhenis Nov 3, 2026
06ef06f
Fit the microdata slide's comment and move the Axiom link box
MaxGhenis Nov 3, 2026
e07b7b8
Split the microdata slide's comment over two lines
MaxGhenis Nov 3, 2026
2fb11e9
Move the TAXSIM link box with slide 7's longer code block
MaxGhenis Nov 3, 2026
8f07254
Test the source-household clusters, and add a Questions note
MaxGhenis Nov 4, 2026
8d86641
Link slide 12's QR code and deck address, and match slide 6's citatio…
MaxGhenis Nov 4, 2026
236e809
Add slide 9 with screenshots of CliffWatch and Build
MaxGhenis Nov 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/slideshows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ import { scottishGovernment2026Config } from "@/slideshows/scottish-government-2
import { goodGrowthFoundation2026Config } from "@/slideshows/good-growth-foundation-2026/config";
import { autumnBudget2026Config } from "@/slideshows/autumn-budget-2026/config";
import { prop40Webinar2026Config } from "@/slideshows/prop40-webinar-2026/config";
import { appam2026Config } from "@/slideshows/appam-2026/config";

export const slideshows: SlideshowConfig[] = [
appam2026Config,
prop40Webinar2026Config,
autumnBudget2026Config,
goodGrowthFoundation2026Config,
Expand Down
Binary file added public/images/appam-2026/build-reform.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/appam-2026/cliffwatch-chart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/appam-2026/qr-deck.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 136 additions & 0 deletions slideshows/appam-2026/EVIDENCE.md

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions slideshows/appam-2026/SPEAKER-NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# APPAM 2026 roundtable: speaker notes

State Policy Impacts on Family Resources: Tools to Improve Policy Analysis. Thursday, November 5, 2026, 10:15 to 11:45am ET, Wellesley room, Boston. Cynthia Osborne moderates. Tara Watson and Linda Giannarelli also present. Each presenter has 10 to 12 minutes.

Thirteen slides in about 11 minutes.

## Before presenting

- Send the PDF to Cynthia's team by Wednesday evening, November 4, for the room's laptop.
- Before Thursday, scan slide 13's QR code and check that it opens this deck.
- Before the session starts, open CliffWatch's chart once to check that it loads.
- Keep these notes on your phone and the PDF on your laptop. If the room's laptop fails, present from yours.

## 1. PolicyEngine for state policy research (0:00, 15 seconds)

Thanks, Cynthia, for putting this session together, and thanks to Tara and Linda for sharing the table.

## 2. PolicyEngine computes taxes and benefits for a family or a population (0:15, 1 minute)

PolicyEngine is a nonprofit that provides free, open source software to compute the impact of public policy. We calculate federal and state income taxes and credits and the main benefit programs for one household or a whole population.

With it, researchers can compare families, measure their own samples and estimate reforms.

## 3. One family ends up with $27,711 in Ohio and $54,345 in Alaska (1:15, 1 minute 15 seconds)

Take one family under each state's 2026 rules. One parent works full time and earns $40,000, has two young children, and pays $1,500 a month for child care. We put them in each state's largest county and treat them as a new applicant for every program.

The gray base shows earnings after taxes and credits, minus the full child care bill, and benefits sit above it. Ohio and Nevada turn this family down for child care help. Both set a lower income limit for new applicants than for families already enrolled. Alaska pays each person a $1,000 dividend and $200 of energy relief, $3,600 for this family.

The gap between the top and bottom state moves with earnings: $16,404 at $30,000 and $21,670 at $60,000.

## 4. States differ most on child care subsidies, then SNAP and state taxes (2:30, 1 minute 15 seconds)

Each mark shows one state on one line of the family's budget. Federal taxes and credits vary least, and states drive the rest through child care subsidies, SNAP options and their own taxes.

With Texas's subsidy, the family gets $15,791 in child care help and loses $5,598 of SNAP. Congress lets SNAP households deduct their care costs, so this family deducts less when Texas pays for care. The family ends up $10,193 ahead.

## 5. This family loses up to $15,343 at a benefit cliff, at earnings that vary by state (3:45, 1 minute)

Each gray line follows one state as earnings rise. In Maryland, Iowa and Idaho, the family crosses the child care income limit after a $500 raise. It then loses between $14,511 and $15,343. In 46 of 51 jurisdictions, the family's largest drop comes from losing the subsidy.

Across states, the median total effective marginal tax rate falls from 63.6% at $30,000 of earnings to 24.6% at $60,000.

## 6. We record when each value took effect and cite its sources (4:45, 45 seconds)

Look at one parameter, the amount per dependent in the Massachusetts child and family credit. The Legislature raised it to $310 for 2023 and $440 for 2024. We record each value's effective date and cite the session law and tax forms behind it.

## 7. Build simulated eligibility measures on your own microdata (5:30, 1 minute 30 seconds)

To build a simulated eligibility measure, map your survey or administrative file to our variable names, set the state and run it. Keep the people and their incomes fixed and change only the state. Any difference between runs then comes from the rules in that state.

We drew 3,000 households with children from Populace US, our default microdata, and ran them through each state's 2024 rules. The same families pass SNAP's tests at rates from 17.5% in Arkansas and Tennessee to 38.3% in Maryland. Their children qualify for Medicaid or CHIP at rates from 39.8% in Idaho to 66.1% in Hawaii.

If your pipeline already runs on TAXSIM, our TAXSIM-compatible interface at policyengine.org/us/taxsim takes the same files. It runs tax years 2021 onward through PolicyEngine and sends earlier years to TAXSIM35.

## 8. We estimate Massachusetts would forgo $577 million if lawmakers doubled its child and family credit (7:00, 1 minute 30 seconds)

Take the parameter from a moment ago and double it, from $440 to $880 per dependent. We estimate Massachusetts would forgo $577 million in 2026, and that 29.4% of its households would gain and none would lose. Under the Supplemental Poverty Measure, or SPM, the poverty gap would shrink by $169 million.

We ran this on the Populace US local-area file, which we use for state and district results. It holds 36,491 Massachusetts household records, an effective sample of about 301 households, or 132 among those with children. We estimate that 21.4% of the state's children lived below the SPM threshold in 2025, and Census reports 12.8% for 2023 to 2025. We estimate that the 2026 child poverty rate would fall by about 0.2 points under the doubled credit.

On our Child Poverty Impact Dashboard, you can run child tax credit, EITC, SNAP and state tax credit reforms state by state.

## 9. You can find a family's cliffs and build a reform without writing code (8:30, 45 seconds)

CliffWatch charts a household's net income as its earnings rise and marks where a raise leaves it with less. For example, a single parent of two school-age children in Cambridge loses the state's cash assistance, TAFDC, when earnings pass $18,000.

In Build, you can open the same Massachusetts credit, set it to $880 and run a report.

## 10. Research questions and the tools that answer them (9:15, 15 seconds)

With PolicyEngine, you can also change any parameter, run your own microdata, read every rule and project current law through 2035.

## 11. What we leave out (9:30, 1 minute)

We model child care subsidies in all 51 jurisdictions from August 2026. We copy each parameter's earliest value back to 2015, so check historical runs against each year's rules. We take earnings and paid leave benefits as inputs and hold them fixed when you change minimum wage or paid leave law. We hold hours fixed unless you set a labor supply response.

Our local-area file holds thin state samples, about 108 effective households with children in the median state. Our child poverty estimates on that file miss Census's 2023 to 2025 averages by 3.2 points on average. In 29 of 51 jurisdictions, they fall outside Census's margin of error. For state child poverty rates, bring your own microdata.

## 12. The Axiom Foundation encodes rules with their source text and dates (10:30, 45 seconds)

I also lead the Axiom Foundation. We publish open, machine-readable encodings of rules, starting with tax and benefit policy. Congress sets SNAP's 20% earned income deduction, and Georgia's Division of Family and Children Services applies it in section 3612 of its SNAP manual. We record the manual's words and effective date beside each value.

When we run this rule through the Axiom engine, a family earning $30,000 a year gets a $500 monthly deduction. Ask for November 2019, before the manual section we encoded took effect, and the engine refuses.

## 13. Thank you (11:15)

Scan the QR code to open this deck. From its last slide, you can open the code behind each chart.

## Questions

**Could sampling error explain the misses against Census?** Our own sampling error could produce misses of that size. We estimate a standard error of 4.6 points on Massachusetts's 2026 child poverty rate, more than twice the 2.0-point margin of error Census reports for the state. Our file holds fewer effective households with children in the median state, about 108 against Massachusetts's 132.

**Why does CliffWatch's net income exceed slide 3's net resources?** CliffWatch counts Medicaid and CHIP in net income, and we leave health coverage out of net resources. The Cambridge family also pays nothing for child care, and slide 3's family pays $1,500 a month.
2 changes: 2 additions & 0 deletions slideshows/appam-2026/analysis/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.hypothesis/
__pycache__/
85 changes: 85 additions & 0 deletions slideshows/appam-2026/analysis/audit_sample_columns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""List the stored columns the fixed sample leaves out, and test whether they matter.

sample_families.py keeps input variables and ids, so the model computes everything
else. This script sorts the rest of the default file's columns, rebuilds the sample
with the computed variables' stored values, and reruns the states it names. Slide 7's
two shares must not move.
"""

import json
import sys
from importlib.metadata import version
from pathlib import Path

import pandas as pd
import sample_families as sf
import simulated_instrument as si
import state_baseline as sb
from policyengine_us import CountryTaxBenefitSystem

YEAR = sf.YEAR
STATES = ("TN", "OR", "HI")
SHARES = ("snap_eligible_share", "child_medicaid_chip_share")


def is_id(name):
"""sample_families.py keeps every *_id column, so only those count as ids here."""
return name.endswith("_id")


def rerun(sample):
si.families = sample.drop(
columns=[f"{name}__{YEAR}" for name in si.FINER_GEOGRAPHY], errors="ignore"
)
rows = {state: si.simulate(state) for state in STATES}
return {state: {key: row[key] for key in SHARES} for state, row in rows.items()}


def main(commit, sample_path):
variables = CountryTaxBenefitSystem().variables
inputs = {
f"{name}__{YEAR}"
for name, variable in variables.items()
if variable.is_input_variable()
}
sample, draws = sf.build(inputs)
# Strings reload under whichever storage this environment prefers.
pd.testing.assert_frame_equal(
sample,
pd.read_pickle(sample_path),
check_dtype=False,
check_column_type=False,
)
stored = {name for table in sb.tables.values() for name in table.columns}
social_security = {f"social_security_{kind}" for kind in sf.SOCIAL_SECURITY}
computed = sorted(
name
for name in stored - social_security
if name in variables and not variables[name].is_input_variable()
if not is_id(name)
)
undefined = sorted(
name for name in stored if name not in variables and not is_id(name)
)
restored, _ = sf.build(inputs | {f"{name}__{YEAR}" for name in computed})
payload = {
"policyengine_us_version": version("policyengine-us"),
"policyengine_us_commit": commit,
"draws": len(draws),
"distinct_households": len(set(draws)),
"has_parent_links": any(
name in sb.tables["person"].columns
for name in ("parent_1_id", "parent_2_id")
),
"renamed_to_reported": sorted(social_security & stored),
"computed_by_the_model": computed,
"undefined_in_this_version": undefined,
"inputs_only": rerun(sample),
"with_stored_values": rerun(restored),
}
target = Path(__file__).parent.parent / "data" / "sample_columns.json"
target.write_text(json.dumps(payload, indent=1) + "\n")


if __name__ == "__main__":
main(sys.argv[1], sys.argv[2])
106 changes: 106 additions & 0 deletions slideshows/appam-2026/analysis/axiom_ga_3612.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Run Georgia's SNAP earned income deduction on the Axiom rules engine.

Compile first, from a checkout of TheAxiomFoundation/rulespec-us:

axiom-rules-engine compile \
--program /path/to/rulespec-us/us-ga/policies/dfcs/snap/3612.yaml \
--rulespec-root /path/to/rulespec-us \
--output ga3612.compiled.json

Then: python axiom_ga_3612.py <engine binary> ga3612.compiled.json
Writes data/axiom_ga_3612.json. The cases cover slide 3's family at $30,000 a
year in Georgia, where PolicyEngine US gives it SNAP, and a month before the
rule's December 2019 effective date.
"""

import json
import subprocess
import sys
from pathlib import Path

MODULE = "us-ga:policies/dfcs/snap/3612"
RULES = (
"earned_income_deduction_rate",
"earned_income_deduction_allowed",
"earned_income_deduction",
)
MONTHLY_EARNINGS = "2500.00"
CASES = (
("June 2026", "2026-06"),
("before the effective date", "2019-11"),
)


def month_bounds(month):
number = int(month.split("-")[1])
end = 30 if number in (4, 6, 9, 11) else 31
return f"{month}-01", f"{month}-{end:02d}"


def request(month):
start, end = month_bounds(month)
interval = {"start": start, "end": end}
return {
"mode": "explain",
"dataset": {
"inputs": [
{
"name": f"{MODULE}#input.total_countable_gross_earned_income",
"entity": "Household",
"entity_id": "family",
"interval": interval,
"value": {"kind": "decimal", "value": MONTHLY_EARNINGS},
},
{
"name": f"{MODULE}#input.assistance_unit_failed_to_report_earned_income_timely",
"entity": "Household",
"entity_id": "family",
"interval": interval,
"value": {"kind": "bool", "value": False},
},
],
"relations": [],
},
"queries": [
{
"entity_id": "family",
"period": {"period_kind": "month", "start": start, "end": end},
"outputs": [f"{MODULE}#{rule}" for rule in RULES],
}
],
}


def main(engine, artifact):
version = subprocess.run(
[engine, "capabilities"], capture_output=True, text=True, check=True
).stdout
results = []
for label, month in CASES:
run = subprocess.run(
[engine, "run-compiled", "--artifact", artifact],
input=json.dumps(request(month)),
capture_output=True,
text=True,
check=False,
)
results.append(
{
"case": label,
"month": month,
"exit_code": run.returncode,
"stdout": json.loads(run.stdout) if run.stdout.strip() else None,
"stderr": run.stderr.strip(),
}
)
payload = {
"engine": json.loads(version),
"monthly_earnings": MONTHLY_EARNINGS,
"results": results,
}
path = Path(__file__).parent.parent / "data" / "axiom_ga_3612.json"
path.write_text(json.dumps(payload, indent=1) + "\n")


if __name__ == "__main__":
main(sys.argv[1], sys.argv[2])
81 changes: 81 additions & 0 deletions slideshows/appam-2026/analysis/check_slide_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Run the code on slides 7 and 8 as the slides print it.

Slide 7's loop reads a CSV of the fixed sample. Slide 8's reform dictionary runs on
the Massachusetts rows of the local-area file. content.test.ts compares the results
with the deck's own runs.
"""

import json
import sys
import tempfile
from importlib.metadata import version
from pathlib import Path

import pandas as pd
from policyengine_core.data import Dataset
from policyengine_core.reforms import Reform
from policyengine_us import Microsimulation
from simulated_instrument import FINER_GEOGRAPHY, SAMPLE_YEAR
from simulated_instrument import STATE_FIPS as ALL_STATES
from state_baseline import DATA_YEAR, load_state, state_file

STATE_FIPS = {code: ALL_STATES[code] for code in ("AR", "MD")}
MASSACHUSETTS_FIPS = 25


def slide_7(sample_path):
survey = pd.read_pickle(sample_path).drop(
columns=[f"{name}__{SAMPLE_YEAR}" for name in FINER_GEOGRAPHY], errors="ignore"
)
shares = {}
with tempfile.TemporaryDirectory() as folder:
csv = Path(folder) / "your_survey.csv"
survey.to_csv(csv, index=False)
people = pd.read_csv(csv)
for code, fips in STATE_FIPS.items():
people["state_fips__2024"] = fips
data = Dataset.from_dataframe(people, 2024)
sim = Microsimulation(dataset=data)
snap = sim.calculate("is_snap_eligible", "2024-01")
units = sim.calculate("is_child", 2024, map_to="spm_unit").values > 0
shares[code] = float(snap.values[units].mean())
return shares


def state_income_tax(sim):
return float(
(
sim.calculate("state_income_tax", 2026).values
* sim.calculate("tax_unit_weight", 2026).values
).sum()
)


def slide_8(folder):
load_state(MASSACHUSETTS_FIPS, Path(folder))
massachusetts = Dataset.from_dataframe(state_file(MASSACHUSETTS_FIPS), DATA_YEAR)
reform = Reform.from_dict({
"gov.states.ma.tax.income.credits"
".child_and_family.amount": {
"2026-01-01.2100-12-31": 880
}
}, country_id="us") # fmt: skip

baseline = Microsimulation(dataset=massachusetts)
reformed = Microsimulation(dataset=massachusetts, reform=reform)
return state_income_tax(baseline) - state_income_tax(reformed)


def main(commit, sample_path, folder):
payload = {
"policyengine_us_version": version("policyengine-us"),
"policyengine_us_commit": commit,
"slide_7_snap_eligible_share": slide_7(sample_path),
"slide_8_state_cost": slide_8(folder),
}
target = Path(__file__).parent.parent / "data" / "slide_code_check.json"
target.write_text(json.dumps(payload, indent=1) + "\n")


if __name__ == "__main__":
main(*sys.argv[1:4])
Loading
Loading