From d2c59991da29093a4428aacbb06163bbdff0b926 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 13:41:18 -0500 Subject: [PATCH 01/17] Add a California breakdown of the labor-to-capital shift analysis/california_shift.py runs the sweep's shift and fiscal accounting on the installed PolicyEngine bundle, one fresh microsimulation per level, and records what the sweep payload leaves out for California: its employee SDI contribution, its change by within-state market-income group, and federal net revenue (the all-government change less every state's net change). analysis/california_chart.py draws California against all other states from the site's sweep payload or from a run. The tests state the accounting's invariants and check them with Hypothesis. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 2 + analysis/california_chart.py | 142 ++++++++++++ analysis/california_shift.py | 287 ++++++++++++++++++++++++ analysis/tests/test_california_shift.py | 203 +++++++++++++++++ 4 files changed, 634 insertions(+) create mode 100644 analysis/california_chart.py create mode 100644 analysis/california_shift.py create mode 100644 analysis/tests/test_california_shift.py diff --git a/.gitignore b/.gitignore index bd7f787..74f915d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,8 @@ analysis/outputs/* !analysis/outputs/ai_scenarios_buildp_published.json !analysis/outputs/transfer_detail_buildp_published.json !analysis/outputs/yale_publishable_2030.xlsx +# California revenue under the labor-to-capital shift (analysis/california_shift.py). +!analysis/outputs/california_shift_pe*.json .venv/ .venv312/ .pytest_cache/ diff --git a/analysis/california_chart.py b/analysis/california_chart.py new file mode 100644 index 0000000..18b272d --- /dev/null +++ b/analysis/california_chart.py @@ -0,0 +1,142 @@ +"""Chart California's revenue change across the labor-to-capital shift. + +Reads either the site's sweep payload (``src/data/shiftSweepData.json``) or a +``california_shift`` run, and writes one PNG. + +Usage: + python -m analysis.california_chart --source sweep --out PATH.png + python -m analysis.california_chart --source run --run PATH.json --out PATH.png +""" + +from __future__ import annotations + +import argparse +import json +import os + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # noqa: E402 +from matplotlib.ticker import FuncFormatter # noqa: E402 + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SWEEP_PATH = os.path.join(REPO, "src", "data", "shiftSweepData.json") + +# Validated with the dataviz skill's palette checker (light surface #fcfcfb): +# every check passes for this pair. +CALIFORNIA = "#00908E" +OTHER_STATES = "#EB6834" +SURFACE = "#fcfcfb" +TEXT_PRIMARY = "#0b0b0b" +TEXT_SECONDARY = "#52514e" +GRID = "#e6e5e1" + + +def series_from_sweep(path=SWEEP_PATH): + data = json.load(open(path)) + xs, ca, other = [], [], [] + for row in data["scenarios"]: + states = row.get("state_deltas") or {} + xs.append(row["shift_pct"]) + if not states: + ca.append(0.0) + other.append(0.0) + continue + ca_net = states["CA"]["state_net_change_b"] + total = sum(v["state_net_change_b"] for v in states.values()) + ca.append(ca_net) + other.append(total - ca_net) + meta = data["metadata"] + note = f"policyengine-us {meta['country_model_version']}, data year {data['year']}" + return xs, ca, None, other, note + + +def series_from_run(path): + data = json.load(open(path)) + xs, ca, ca_sdi, other = [0], [0.0], [0.0], [0.0] + for row in data["levels"]: + xs.append(row["shift_pct"]) + ca.append(row["california"]["state_net_change_b"]) + ca_sdi.append(row["california"]["state_net_including_sdi_change_b"]) + other.append(row["other_states_net_change_b"]) + meta = data["metadata"] + note = f"policyengine-us {meta['policyengine_us_version']}, data year {data['year']}" + return xs, ca, ca_sdi, other, note + + +def draw(xs, ca, ca_sdi, other, out, source_note): + plt.rcParams.update( + { + "font.family": "sans-serif", + "font.sans-serif": ["Inter", "Helvetica Neue", "Helvetica", "Arial"], + "font.size": 11, + } + ) + fig, ax = plt.subplots(figsize=(8, 5), dpi=200) + fig.patch.set_facecolor(SURFACE) + ax.set_facecolor(SURFACE) + + ax.axhline(0, color=TEXT_SECONDARY, linewidth=0.8, zorder=1) + ax.plot(xs, ca, color=CALIFORNIA, linewidth=2, marker="o", markersize=5, + label="California", zorder=3) + if ca_sdi is not None: + ax.plot(xs, ca_sdi, color=CALIFORNIA, linewidth=2, linestyle=(0, (4, 3)), + marker="o", markersize=4, markerfacecolor=SURFACE, + label="California, including SDI", zorder=3) + ax.plot(xs, other, color=OTHER_STATES, linewidth=2, marker="o", markersize=5, + label="All other states combined", zorder=3) + + # Direct labels at the line ends, in text ink beside the colored mark. + ends = [(ca[-1], "California")] + if ca_sdi is not None: + ends.append((ca_sdi[-1], "California, including SDI")) + ends.append((other[-1], "All other states")) + for y, text in ends: + ax.annotate(f"{text} ${y:,.0f}B", xy=(xs[-1], y), xytext=(6, 0), + textcoords="offset points", va="center", fontsize=9.5, + color=TEXT_PRIMARY) + + ax.set_xlim(0, 100) + ax.set_xticks(range(0, 101, 20)) + ax.xaxis.set_major_formatter(FuncFormatter(lambda v, _: f"{v:.0f}%")) + ax.yaxis.set_major_formatter(FuncFormatter(lambda v, _: f"${v:,.0f}B")) + ax.set_xlabel("Share of labor income shifted to capital income", color=TEXT_SECONDARY) + ax.grid(axis="y", color=GRID, linewidth=0.8) + ax.tick_params(colors=TEXT_SECONDARY, length=0) + for spine in ax.spines.values(): + spine.set_visible(False) + ax.legend(frameon=False, loc="upper left", fontsize=9.5, labelcolor=TEXT_PRIMARY) + + fig.text(0.06, 0.955, "California gains as labor income shifts to capital", + fontsize=14, fontweight="bold", color=TEXT_PRIMARY, ha="left") + fig.text(0.06, 0.915, + "Change in state income tax net of refundable state credits and " + "state-funded benefits, $ billions", + fontsize=10, color=TEXT_SECONDARY, ha="left") + fig.text(0.06, 0.02, + "PolicyEngine static microsimulation of current law on Microcosm " + f"microdata ({source_note}). Total income held fixed; new capital " + "income follows existing holdings.", + fontsize=7.5, color=TEXT_SECONDARY, ha="left", wrap=True) + fig.subplots_adjust(left=0.1, right=0.76, top=0.86, bottom=0.16) + fig.savefig(out, facecolor=SURFACE) + return out + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--source", choices=["sweep", "run"], default="sweep") + parser.add_argument("--run", default=None, help="california_shift output JSON") + parser.add_argument("--out", required=True) + args = parser.parse_args() + if args.source == "sweep": + series = series_from_sweep() + else: + series = series_from_run(args.run) + xs, ca, ca_sdi, other, note = series + print(draw(xs, ca, ca_sdi, other, args.out, note)) + + +if __name__ == "__main__": + main() diff --git a/analysis/california_shift.py b/analysis/california_shift.py new file mode 100644 index 0000000..a903282 --- /dev/null +++ b/analysis/california_shift.py @@ -0,0 +1,287 @@ +"""California's revenue under the labor-to-capital income shift. + +Runs the sweep's shift (``labor_capital_shift._apply_shift``) and fiscal +accounting (``fiscal.revenue_components``, ``fiscal.state_revenue_components``) +on whatever PolicyEngine bundle is installed, with one fresh microsimulation +per level, the way ``compute_shift_sweep`` does. On top of the sweep payload it +records: + +* California's employee SDI contribution. The sweep's state measure, + ``household_state_tax_before_refundable_credits``, adds state income tax and + use tax only, so this wage levy sits outside it. +* California's change split by within-state market-income group, with the + labor income each group gives up and the capital income it receives. +* Federal net revenue, as the all-government total less every state's net + change. + +Usage: + python -m analysis.california_shift [--levels 0.1,0.2,0.5,1.0] [--out PATH] +""" + +from __future__ import annotations + +import argparse +import gc +import json +import os +from importlib.metadata import PackageNotFoundError, version + +import numpy as np + +from .constants import CAPITAL_INCOME_VARS, YEAR +from .fiscal import ( + net_fiscal_impact, + revenue_components, + state_revenue_components, + state_revenue_delta, +) +from .policyengine_runtime import managed_us_microsimulation, policyengine_bundle + +DEFAULT_LEVELS = [0.10, 0.20, 0.50, 1.00] +STATE = "CA" +SDI_VARIABLE = "ca_employee_state_disability_insurance_contribution" +LABOR_VARS = ["employment_income", "self_employment_income"] + +#: Within-California groups by baseline household market income, as +#: (label, lower weighted percentile, upper weighted percentile). +GROUPS = [ + ("Bottom 50%", 0.0, 0.5), + ("50th to 90th percentile", 0.5, 0.9), + ("90th to 99th percentile", 0.9, 0.99), + ("Top 1%", 0.99, 1.0000001), +] + +OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "outputs") + + +def _package_version(name): + try: + return version(name) + except PackageNotFoundError: + return None + + +def _hh(sim, var): + return np.asarray(sim.calculate(var, map_to="household", period=YEAR), dtype=float) + + +def _household_frame(sim): + """Household arrays needed for the California accounting.""" + frame = { + "state": np.asarray( + sim.calculate("state_code_str", map_to="household", period=YEAR) + ).astype(str), + "weight": np.asarray(sim.calculate("household_weight", period=YEAR), dtype=float), + "market_income": _hh(sim, "household_market_income"), + "state_tax": _hh(sim, "household_state_tax_before_refundable_credits"), + "state_credits": _hh(sim, "household_refundable_state_tax_credits"), + "state_benefits": _hh(sim, "household_state_benefits"), + "labor": sum(_hh(sim, v) for v in LABOR_VARS), + "capital": sum(_hh(sim, v) for v in CAPITAL_INCOME_VARS), + } + try: + frame["sdi"] = _hh(sim, SDI_VARIABLE) + except Exception as exc: # noqa: BLE001 - record, do not fail the run + print(f" {SDI_VARIABLE} unavailable: {exc}", flush=True) + frame["sdi"] = np.zeros_like(frame["weight"]) + frame["state_net"] = frame["state_tax"] - frame["state_credits"] - frame["state_benefits"] + return frame + + +def _weighted_rank(values, weights): + """Weighted percentile rank (0-1) of each household within its group.""" + order = np.argsort(values, kind="mergesort") + cum = np.cumsum(weights[order]) + ranks = np.empty_like(cum) + ranks[order] = (cum - weights[order] / 2) / cum[-1] + return ranks + + +def _baseline_concentration(base): + """Shares of California labor and capital income by market-income group.""" + mask = base["state"] == STATE + w = base["weight"][mask] + ranks = _weighted_rank(base["market_income"][mask], w) + labor = np.clip(base["labor"][mask], 0, None) + capital = np.clip(base["capital"][mask], 0, None) + rows = [] + for label, lo, hi in GROUPS: + g = (ranks >= lo) & (ranks < hi) + rows.append( + { + "group": label, + "households_m": float(w[g].sum() / 1e6), + "labor_income_share": float((labor[g] * w[g]).sum() / (labor * w).sum()), + "capital_income_share": float( + (capital[g] * w[g]).sum() / (capital * w).sum() + ), + "mean_market_income": float( + (base["market_income"][mask][g] * w[g]).sum() / w[g].sum() + ), + } + ) + return { + "households_m": float(w.sum() / 1e6), + "labor_income_b": float((base["labor"][mask] * w).sum() / 1e9), + "capital_income_b": float((base["capital"][mask] * w).sum() / 1e9), + "state_net_revenue_b": float((base["state_net"][mask] * w).sum() / 1e9), + "state_tax_before_refundable_credits_b": float( + (base["state_tax"][mask] * w).sum() / 1e9 + ), + "sdi_b": float((base["sdi"][mask] * w).sum() / 1e9), + "groups": rows, + } + + +def _california_rows(base, scen): + """California's change in total and by baseline market-income group, $B.""" + mask = base["state"] == STATE + assert np.array_equal(base["state"], scen["state"]), "household order changed" + w = base["weight"][mask] + ranks = _weighted_rank(base["market_income"][mask], w) + + def delta(key): + return (scen[key] - base[key])[mask] + + fields = { + "labor_income_change_b": delta("labor"), + "capital_income_change_b": delta("capital"), + "market_income_change_b": delta("market_income"), + "state_tax_before_refundable_credits_change_b": delta("state_tax"), + "state_refundable_credits_change_b": delta("state_credits"), + "state_benefits_change_b": delta("state_benefits"), + "state_net_change_b": delta("state_net"), + "sdi_change_b": delta("sdi"), + } + + def total(sel): + out = {k: float((v[sel] * w[sel]).sum() / 1e9) for k, v in fields.items()} + out["state_net_including_sdi_change_b"] = ( + out["state_net_change_b"] + out["sdi_change_b"] + ) + return out + + everyone = np.ones_like(w, dtype=bool) + groups = [] + for label, lo, hi in GROUPS: + g = (ranks >= lo) & (ranks < hi) + row = {"group": label} + row.update(total(g)) + groups.append(row) + return total(everyone), groups + + +def run(levels, microsim_factory=managed_us_microsimulation, verbose=True): + # labor_capital_shift imports policyengine_us at module load; importing it + # here keeps this module's accounting helpers testable without the model. + from .labor_capital_shift import _apply_shift + + base_sim = microsim_factory() + bundle = policyengine_bundle(base_sim) + base_rev = revenue_components(base_sim) + base_states = state_revenue_components(base_sim) + base = _household_frame(base_sim) + baseline = _baseline_concentration(base) + del base_sim + gc.collect() + if verbose: + print( + f"Baseline CA net state revenue ${baseline['state_net_revenue_b']:.1f}B, " + f"SDI ${baseline['sdi_b']:.1f}B", + flush=True, + ) + + rows = [] + for pct in levels: + sim = microsim_factory() + branch, freed = _apply_shift(sim, f"ca_{int(round(pct * 100))}", pct) + rev = revenue_components(branch) + delta = net_fiscal_impact(rev, base_rev) + states = state_revenue_delta(state_revenue_components(branch), base_states) + scen = _household_frame(branch) + ca_total, ca_groups = _california_rows(base, scen) + + state_net = {code: d["state_net_change"] / 1e9 for code, d in states.items()} + all_states = sum(state_net.values()) + other_states = all_states - state_net.get(STATE, 0.0) + gainers = sorted( + (c for c, v in state_net.items() if v > 0.005), key=lambda c: -state_net[c] + ) + losers = [c for c, v in state_net.items() if v < -0.005] + row = { + "shift_pct": int(round(pct * 100)), + "labor_income_moved_b": freed / 1e9, + "all_government_change_b": delta["total_change"] / 1e9, + "all_states_net_change_b": all_states, + "federal_net_change_b": delta["total_change"] / 1e9 - all_states, + "other_states_net_change_b": other_states, + "states_gaining": gainers, + "states_losing_count": len(losers), + "state_net_change_b": dict(sorted(state_net.items())), + "national_components_b": { + k: v / 1e9 for k, v in sorted(delta.items()) if isinstance(v, (int, float)) + }, + "california": ca_total, + "california_by_group": ca_groups, + } + rows.append(row) + if verbose: + print( + f"{row['shift_pct']:>3}% shift: CA net {ca_total['state_net_change_b']:+.2f}B " + f"(with SDI {ca_total['state_net_including_sdi_change_b']:+.2f}B); " + f"other states {other_states:+.2f}B; federal " + f"{row['federal_net_change_b']:+.1f}B; all-gov " + f"{row['all_government_change_b']:+.1f}B", + flush=True, + ) + del branch, sim, scen + gc.collect() + + return { + "year": YEAR, + "state": STATE, + "description": ( + "Positive employment and self-employment income fall by the shift " + "share; the same weighted total is added to positive capital income " + "in proportion to existing holdings. Static, current law, no " + "behavioral response. State net revenue is state income tax plus use " + "tax, less refundable state credits and state-funded benefits; " + "California's employee SDI contribution is reported separately." + ), + "metadata": { + "policyengine_version": bundle.get("policyengine_version") + or _package_version("policyengine"), + "policyengine_us_version": bundle.get("model_version") + or _package_version("policyengine-us"), + "policyengine_core_version": _package_version("policyengine-core"), + "certified_data_build_id": bundle.get("certified_data_build_id"), + "runtime_dataset_uri": bundle.get("runtime_dataset_uri"), + }, + "baseline_california": baseline, + "levels": rows, + } + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument( + "--levels", + default=",".join(str(x) for x in DEFAULT_LEVELS), + help="Comma-separated shift shares, e.g. 0.1,0.2,0.5,1.0", + ) + parser.add_argument("--out", default=None, help="Output JSON path") + args = parser.parse_args() + levels = [float(x) for x in args.levels.split(",") if x.strip()] + result = run(levels) + out = args.out or os.path.join( + OUTPUT_DIR, + f"california_shift_pe{result['metadata']['policyengine_version']}.json", + ) + os.makedirs(os.path.dirname(out), exist_ok=True) + with open(out, "w") as f: + json.dump(result, f, indent=2) + print(f"Saved {out}", flush=True) + + +if __name__ == "__main__": + main() diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py new file mode 100644 index 0000000..6c1248f --- /dev/null +++ b/analysis/tests/test_california_shift.py @@ -0,0 +1,203 @@ +"""Tests for the California accounting in ``analysis.california_shift``. + +The invariants, checked over generated household frames: + +- ``_weighted_rank`` puts every household strictly inside (0, 1), and a + household with a higher value always ranks higher; +- the four market-income groups partition California's households, so each + field's group changes add up to the state total, and the groups' shares of + labor and capital income each add up to one; +- households outside California never move California's rows; +- the change including SDI equals the net change plus the SDI change. + +None of these tests load PolicyEngine. +""" + +import json + +import numpy as np +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st + +from analysis.california_shift import ( + GROUPS, + STATE, + _baseline_concentration, + _california_rows, + _weighted_rank, +) + +VALUE_KEYS = ( + "market_income", + "state_tax", + "state_credits", + "state_benefits", + "labor", + "capital", + "sdi", +) + + +def _frame(state, weight, **columns): + n = len(weight) + frame = { + "state": np.asarray(state).astype(str), + "weight": np.asarray(weight, dtype=float), + } + for key in VALUE_KEYS: + frame[key] = np.asarray(columns.get(key, np.zeros(n)), dtype=float) + frame["state_net"] = frame["state_tax"] - frame["state_credits"] - frame["state_benefits"] + return frame + + +FLOATS = st.floats(-1e6, 1e7, allow_nan=False, allow_infinity=False) +WEIGHTS = st.floats(0.5, 5_000, allow_nan=False, allow_infinity=False) + + +@st.composite +def household_pair(draw): + """A baseline and scenario frame over the same households, at least one in CA.""" + n = draw(st.integers(1, 30)) + state = draw(st.lists(st.sampled_from([STATE, "NY", "TX"]), min_size=n, max_size=n)) + state[0] = STATE + weight = draw(st.lists(WEIGHTS, min_size=n, max_size=n)) + + def columns(): + return {key: draw(st.lists(FLOATS, min_size=n, max_size=n)) for key in VALUE_KEYS} + + return _frame(state, weight, **columns()), _frame(state, weight, **columns()) + + +def test_weighted_rank_equal_weights_are_midpoints(): + ranks = _weighted_rank(np.array([30.0, 10.0, 40.0, 20.0]), np.ones(4)) + np.testing.assert_allclose(ranks, [0.625, 0.125, 0.875, 0.375]) + + +@settings(max_examples=200, deadline=None) +@given( + st.lists(st.tuples(FLOATS, WEIGHTS), min_size=1, max_size=40), +) +def test_weighted_rank_bounds_and_order(pairs): + values = np.array([v for v, _ in pairs]) + weights = np.array([w for _, w in pairs]) + ranks = _weighted_rank(values, weights) + assert np.all(ranks > 0) and np.all(ranks < 1) + for i in range(len(values)): + for j in range(len(values)): + if values[i] < values[j]: + assert ranks[i] < ranks[j] + + +def test_groups_cover_the_unit_interval(): + edges = [(lo, hi) for _, lo, hi in GROUPS] + assert edges[0][0] == 0.0 and edges[-1][1] > 1.0 + for (_, hi), (lo, _) in zip(edges, edges[1:]): + assert hi == lo + + +@settings(max_examples=200, deadline=None) +@given(household_pair()) +def test_group_rows_add_up_to_the_state_total(pair): + base, scen = pair + total, groups = _california_rows(base, scen) + assert [g["group"] for g in groups] == [label for label, _, _ in GROUPS] + for key, value in total.items(): + parts = sum(g[key] for g in groups) + assert parts == pytest.approx(value, rel=1e-9, abs=1e-12) + assert total["state_net_including_sdi_change_b"] == pytest.approx( + total["state_net_change_b"] + total["sdi_change_b"], rel=1e-12, abs=1e-15 + ) + + +@settings(max_examples=200, deadline=None) +@given(household_pair(), st.floats(-1e6, 1e6, allow_nan=False)) +def test_other_states_never_move_california(pair, shock): + base, scen = pair + total, groups = _california_rows(base, scen) + shocked = {k: (v.copy() if isinstance(v, np.ndarray) else v) for k, v in scen.items()} + outside = shocked["state"] != STATE + for key in VALUE_KEYS + ("state_net",): + shocked[key] = np.where(outside, shocked[key] + shock, shocked[key]) + total2, groups2 = _california_rows(base, shocked) + assert total2 == total + assert groups2 == groups + + +@settings(max_examples=200, deadline=None) +@given(household_pair()) +def test_baseline_shares_add_up_to_one(pair): + base, _ = pair + mask = base["state"] == STATE + labor = (np.clip(base["labor"][mask], 0, None) * base["weight"][mask]).sum() + capital = (np.clip(base["capital"][mask], 0, None) * base["weight"][mask]).sum() + with np.errstate(invalid="ignore", divide="ignore"): + out = _baseline_concentration(base) + groups = out["groups"] + assert sum(g["households_m"] for g in groups) == pytest.approx(out["households_m"], rel=1e-9) + if labor > 0: + assert sum(g["labor_income_share"] for g in groups) == pytest.approx(1.0, rel=1e-9) + if capital > 0: + assert sum(g["capital_income_share"] for g in groups) == pytest.approx(1.0, rel=1e-9) + + +def test_california_rows_example(): + # Two California households and one in New York. The scenario moves $100 + # of labor income to $60 of capital income in the first household and + # raises its state tax by $5; New York's changes must not count. + base = _frame( + [STATE, STATE, "NY"], + [1e9, 1e9, 1e9], + market_income=[100, 200, 300], + labor=[100, 0, 50], + capital=[0, 200, 0], + state_tax=[1, 10, 3], + sdi=[1.3, 0, 0], + ) + scen = _frame( + [STATE, STATE, "NY"], + [1e9, 1e9, 1e9], + market_income=[60, 200, 999], + labor=[0, 0, 0], + capital=[60, 200, 50], + state_tax=[6, 10, 99], + sdi=[0, 0, 0], + ) + total, groups = _california_rows(base, scen) + assert total["labor_income_change_b"] == pytest.approx(-100) + assert total["capital_income_change_b"] == pytest.approx(60) + assert total["state_net_change_b"] == pytest.approx(5) + assert total["sdi_change_b"] == pytest.approx(-1.3) + assert total["state_net_including_sdi_change_b"] == pytest.approx(3.7) + + +def test_household_order_change_is_refused(): + base = _frame([STATE, "NY"], [1.0, 1.0]) + scen = _frame(["NY", STATE], [1.0, 1.0]) + with pytest.raises(AssertionError): + _california_rows(base, scen) + + +def test_chart_series_from_run(tmp_path): + pytest.importorskip("matplotlib") + from analysis.california_chart import series_from_run + + run = { + "year": 2026, + "metadata": {"policyengine_us_version": "9.9.9"}, + "levels": [ + { + "shift_pct": 10, + "other_states_net_change_b": -2.0, + "california": { + "state_net_change_b": 5.0, + "state_net_including_sdi_change_b": 3.0, + }, + } + ], + } + path = tmp_path / "run.json" + path.write_text(json.dumps(run)) + xs, ca, ca_sdi, other, note = series_from_run(str(path)) + assert xs == [0, 10] and ca == [0.0, 5.0] and ca_sdi == [0.0, 3.0] and other == [0.0, -2.0] + assert "9.9.9" in note From 02bcfac415ab60c91b2323b347477f7d63bf7180 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 13:45:56 -0500 Subject: [PATCH 02/17] Summarize California runs against the published sweep analysis/california_summary.py prints each level's California, other-state and federal changes, the federal split into income tax, payroll, credits and benefits, California's labor and capital flows and group rows, and the drift from src/data/shiftSweepData.json. A differential test checks that the federal split plus the states' lines equals fiscal.net_fiscal_impact's total. Co-Authored-By: Claude Opus 5.5 --- analysis/california_chart.py | 10 +- analysis/california_summary.py | 204 ++++++++++++++++++++++++ analysis/tests/test_california_shift.py | 100 ++++++++++++ 3 files changed, 309 insertions(+), 5 deletions(-) create mode 100644 analysis/california_summary.py diff --git a/analysis/california_chart.py b/analysis/california_chart.py index 18b272d..d523c33 100644 --- a/analysis/california_chart.py +++ b/analysis/california_chart.py @@ -83,7 +83,7 @@ def draw(xs, ca, ca_sdi, other, out, source_note): if ca_sdi is not None: ax.plot(xs, ca_sdi, color=CALIFORNIA, linewidth=2, linestyle=(0, (4, 3)), marker="o", markersize=4, markerfacecolor=SURFACE, - label="California, including SDI", zorder=3) + label="California, including SDI contributions", zorder=3) ax.plot(xs, other, color=OTHER_STATES, linewidth=2, marker="o", markersize=5, label="All other states combined", zorder=3) @@ -115,10 +115,10 @@ def draw(xs, ca, ca_sdi, other, out, source_note): "state-funded benefits, $ billions", fontsize=10, color=TEXT_SECONDARY, ha="left") fig.text(0.06, 0.02, - "PolicyEngine static microsimulation of current law on Microcosm " - f"microdata ({source_note}). Total income held fixed; new capital " - "income follows existing holdings.", - fontsize=7.5, color=TEXT_SECONDARY, ha="left", wrap=True) + "Source: PolicyEngine static microsimulation of current law on Microcosm " + f"data ({source_note}).\nWe hold total income fixed and add new capital " + "income in proportion to existing holdings.", + fontsize=7.5, color=TEXT_SECONDARY, ha="left") fig.subplots_adjust(left=0.1, right=0.76, top=0.86, bottom=0.16) fig.savefig(out, facecolor=SURFACE) return out diff --git a/analysis/california_summary.py b/analysis/california_summary.py new file mode 100644 index 0000000..cb589d2 --- /dev/null +++ b/analysis/california_summary.py @@ -0,0 +1,204 @@ +"""Summarize a ``california_shift`` run and compare it with the published sweep. + +Prints, for each shift level: + +* California's net change, with and without its employee SDI contribution; +* all other states combined, every state, and federal net revenue; +* the federal split into income tax, payroll taxes, refundable credits and + benefits; +* the labor income California households give up and the capital income they + receive; +* California's change by within-state market-income group; + +then the baseline concentration of California's labor and capital income, and +the drift from the site's published sweep at matching levels. + +Usage: + python -m analysis.california_summary PATH.json [--sweep src/data/shiftSweepData.json] +""" + +from __future__ import annotations + +import argparse +import json +import os + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SWEEP_PATH = os.path.join(REPO, "src", "data", "shiftSweepData.json") + +EMPLOYEE_PAYROLL_KEYS = ( + "employee_social_security_tax_change", + "employee_medicare_tax_change", + "self_employment_tax_change", +) + + +def federal_split(components): + """Split the federal net change ($B) into its parts. + + Federal net = all-government net less the states' net, so each federal part + is the national aggregate less its state portion. ``residual`` holds any + household tax outside federal income tax, payroll and state tax (local and + flat taxes); it is zero when the decomposition is complete. Payroll takes + the employer side from ``employer_payroll_tax``, the aggregate that + ``fiscal.net_fiscal_impact`` counts, so the parts add up to the total. + """ + c = components + employee_payroll = sum(c.get(k, 0.0) for k in EMPLOYEE_PAYROLL_KEYS) + employer_payroll = c.get("employer_payroll_tax_change", 0.0) + payroll = employee_payroll + employer_payroll + income_tax = c.get("fed_income_tax_before_refundable_credits_change", 0.0) + state_tax = c.get("state_tax_before_refundable_credits_change", 0.0) + residual = ( + c.get("household_tax_before_refundable_credits_change", 0.0) + - income_tax + - employee_payroll + - state_tax + ) + credits = c.get("household_refundable_tax_credits_change", 0.0) - c.get( + "state_refundable_credits_change", 0.0 + ) + benefits = c.get("household_benefits_change", 0.0) - c.get("state_benefits_change", 0.0) + states_net = ( + state_tax + - c.get("state_refundable_credits_change", 0.0) + - c.get("state_benefits_change", 0.0) + ) + federal = income_tax + payroll + residual - credits - benefits + return { + "income_tax": income_tax, + "payroll": payroll, + "employee_payroll": employee_payroll, + "employer_payroll": employer_payroll, + "employer_social_security_and_medicare": c.get("employer_social_security_tax_change", 0.0) + + c.get("employer_medicare_tax_change", 0.0), + "residual": residual, + "refundable_credits": credits, + "federal_refundable_credits_reported": c.get( + "income_tax_refundable_credits_change", float("nan") + ), + "benefits": benefits, + "federal_net": federal, + "states_net_national": states_net, + "total": c.get("total_change", 0.0), + } + + +def published_by_level(path=SWEEP_PATH): + data = json.load(open(path)) + out = {} + for row in data["scenarios"]: + states = row.get("state_deltas") or {} + if not states: + continue + ca = states["CA"]["state_net_change_b"] + all_states = sum(v["state_net_change_b"] for v in states.values()) + out[row["shift_pct"]] = { + "california": ca, + "other_states": all_states - ca, + "federal": row["total_rev_change_b"] - all_states, + "total": row["total_rev_change_b"], + } + return out, data["metadata"] + + +def _pct(new, old): + return float("nan") if old == 0 else 100 * (new - old) / abs(old) + + +def summarize(run, published=None): + lines = [] + meta = run["metadata"] + lines.append( + f"Run: policyengine {meta['policyengine_version']}, policyengine-us " + f"{meta['policyengine_us_version']}, year {run['year']}" + ) + base = run["baseline_california"] + lines.append( + f"California baseline: {base['households_m']:.2f}M households, labor income " + f"${base['labor_income_b']:,.0f}B, capital income ${base['capital_income_b']:,.0f}B, " + f"net state revenue ${base['state_net_revenue_b']:,.1f}B, SDI ${base['sdi_b']:,.1f}B" + ) + lines.append("Shares of California's positive labor and capital income by market-income group:") + for g in base["groups"]: + lines.append( + f" {g['group']:<26} labor {100 * g['labor_income_share']:5.1f}% " + f"capital {100 * g['capital_income_share']:5.1f}% " + f"mean market income ${g['mean_market_income']:,.0f}" + ) + for row in run["levels"]: + ca = row["california"] + split = federal_split(row["national_components_b"]) + lines.append("") + lines.append(f"{row['shift_pct']}% shift (${row['labor_income_moved_b']:,.0f}B of labor income moves)") + lines.append( + f" California net {ca['state_net_change_b']:+.2f}B; with SDI " + f"{ca['state_net_including_sdi_change_b']:+.2f}B (SDI {ca['sdi_change_b']:+.2f}B)" + ) + lines.append( + f" California parts: state tax {ca['state_tax_before_refundable_credits_change_b']:+.2f}B, " + f"refundable credits {ca['state_refundable_credits_change_b']:+.2f}B, " + f"benefits {ca['state_benefits_change_b']:+.2f}B" + ) + lines.append( + f" California households: labor {ca['labor_income_change_b']:+.1f}B, capital " + f"{ca['capital_income_change_b']:+.1f}B, market income {ca['market_income_change_b']:+.1f}B" + ) + pipeline_ca = row["state_net_change_b"].get("CA") + lines.append( + f" Check: pipeline CA {pipeline_ca:+.4f}B vs household frame " + f"{ca['state_net_change_b']:+.4f}B; per-state sum {row['all_states_net_change_b']:+.3f}B vs " + f"national state lines {split['states_net_national']:+.3f}B" + ) + lines.append( + f" Other states {row['other_states_net_change_b']:+.2f}B; all states " + f"{row['all_states_net_change_b']:+.2f}B; gaining: {', '.join(row['states_gaining'])}; " + f"{row['states_losing_count']} losing" + ) + lines.append( + f" Federal net {row['federal_net_change_b']:+.1f}B = income tax {split['income_tax']:+.1f}B " + f"+ payroll {split['payroll']:+.1f}B + other household tax {split['residual']:+.2f}B " + f"- refundable credits {split['refundable_credits']:+.1f}B - benefits {split['benefits']:+.1f}B " + f"(sum {split['federal_net']:+.1f}B; reported federal refundable credits " + f"{split['federal_refundable_credits_reported']:+.1f}B)" + ) + lines.append( + f" Payroll: employee and self-employment {split['employee_payroll']:+.1f}B, employer " + f"{split['employer_payroll']:+.1f}B (employer Social Security and Medicare lines " + f"{split['employer_social_security_and_medicare']:+.1f}B)" + ) + lines.append(f" All governments {row['all_government_change_b']:+.1f}B") + total_tax = ca["state_tax_before_refundable_credits_change_b"] + for g in row["california_by_group"]: + share = float("nan") if total_tax == 0 else 100 * g["state_tax_before_refundable_credits_change_b"] / total_tax + lines.append( + f" {g['group']:<26} state tax {g['state_tax_before_refundable_credits_change_b']:+.2f}B " + f"({share:5.1f}% of the change), net {g['state_net_change_b']:+.2f}B, labor " + f"{g['labor_income_change_b']:+.1f}B, capital {g['capital_income_change_b']:+.1f}B" + ) + if published and row["shift_pct"] in published: + p = published[row["shift_pct"]] + lines.append( + f" Published: California {p['california']:+.2f}B ({_pct(ca['state_net_change_b'], p['california']):+.1f}%), " + f"other states {p['other_states']:+.2f}B, federal {p['federal']:+.1f}B " + f"({_pct(row['federal_net_change_b'], p['federal']):+.1f}%)" + ) + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("run") + parser.add_argument("--sweep", default=SWEEP_PATH) + args = parser.parse_args() + run = json.load(open(args.run)) + published, meta = published_by_level(args.sweep) + print(summarize(run, published)) + print( + f"\nPublished sweep: policyengine-us {meta['country_model_version']}, " + f"policyengine {meta['policyengine_version']}" + ) + + +if __name__ == "__main__": + main() diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 6c1248f..3dc536d 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -201,3 +201,103 @@ def test_chart_series_from_run(tmp_path): xs, ca, ca_sdi, other, note = series_from_run(str(path)) assert xs == [0, 10] and ca == [0.0, 5.0] and ca_sdi == [0.0, 3.0] and other == [0.0, -2.0] assert "9.9.9" in note + + +COMPONENT_KEYS = ( + "household_tax_before_refundable_credits", + "household_refundable_tax_credits", + "household_benefits", + "employer_payroll_tax", + "fed_income_tax_before_refundable_credits", + "employee_social_security_tax", + "employee_medicare_tax", + "employer_social_security_tax", + "employer_medicare_tax", + "self_employment_tax", + "state_tax_before_refundable_credits", + "state_refundable_credits", + "state_benefits", + "income_tax_refundable_credits", + "household_market_income", + "household_net_income", +) + + +@settings(max_examples=200, deadline=None) +@given( + st.lists(FLOATS, min_size=len(COMPONENT_KEYS), max_size=len(COMPONENT_KEYS)), + st.lists(FLOATS, min_size=len(COMPONENT_KEYS), max_size=len(COMPONENT_KEYS)), +) +def test_federal_and_state_parts_add_up_to_the_total(scenario, baseline): + # Differential check: the summary's federal split, built from components, + # plus the national state lines must equal fiscal.net_fiscal_impact's total. + from analysis.california_summary import federal_split + from analysis.fiscal import net_fiscal_impact + + delta = net_fiscal_impact( + dict(zip(COMPONENT_KEYS, scenario)), dict(zip(COMPONENT_KEYS, baseline)) + ) + split = federal_split(delta) + assert split["federal_net"] + split["states_net_national"] == pytest.approx( + delta["total_change"], rel=1e-9, abs=1e-3 + ) + assert split["payroll"] == pytest.approx( + split["employee_payroll"] + split["employer_payroll"], rel=1e-12, abs=1e-9 + ) + + +def test_summary_reads_a_run(): + from analysis.california_summary import summarize + + ca = { + "labor_income_change_b": -150.0, + "capital_income_change_b": 170.0, + "market_income_change_b": 20.0, + "state_tax_before_refundable_credits_change_b": 5.5, + "state_refundable_credits_change_b": 0.2, + "state_benefits_change_b": 0.3, + "state_net_change_b": 5.0, + "sdi_change_b": -2.0, + "state_net_including_sdi_change_b": 3.0, + } + group = dict(ca, group="Top 1%") + run = { + "year": 2026, + "metadata": {"policyengine_version": "6.16.0", "policyengine_us_version": "2.2.1"}, + "baseline_california": { + "households_m": 14.0, + "labor_income_b": 1500.0, + "capital_income_b": 400.0, + "state_net_revenue_b": 120.0, + "sdi_b": 20.0, + "groups": [ + { + "group": "Top 1%", + "households_m": 0.14, + "labor_income_share": 0.15, + "capital_income_share": 0.5, + "mean_market_income": 3e6, + } + ], + }, + "levels": [ + { + "shift_pct": 10, + "labor_income_moved_b": 1200.0, + "all_government_change_b": -135.0, + "all_states_net_change_b": 3.0, + "federal_net_change_b": -138.0, + "other_states_net_change_b": -2.0, + "states_gaining": ["CA", "NY"], + "states_losing_count": 40, + "state_net_change_b": {"CA": 5.0, "NY": 2.0, "TX": -4.0}, + "national_components_b": {"total_change": -135.0}, + "california": ca, + "california_by_group": [group], + } + ], + } + published = {10: {"california": 5.0, "other_states": -2.1, "federal": -138.2, "total": -135.3}} + text = summarize(run, published) + assert "California net +5.00B; with SDI +3.00B" in text + assert "Published: California +5.00B (+0.0%)" in text From a0732f6f7206a88eab1ee0036704a81b30edeaf6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 15:31:41 -0500 Subject: [PATCH 03/17] Add a within-state variant of the labor-to-capital shift The sweep scales every positive capital-income dollar in the country by one factor, so a state whose share of national capital income exceeds its share of labor income gains market income from other states. The variant gives each state's freed labor income to that state's own capital holders, so each state's market income stays fixed and its revenue change reflects only the movement of income within the state. california_shift runs it at 10% and 50% by default (--within-state-levels), and california_summary splits California's gain into the within-state part and the part from income moving in from other states. Tests: per-state conservation, positive-only scaling, refusal for a state with labor but no capital, a hand-checked example, a closed-form match to the national shift when every record sits in one state, a differential match to labor_capital_shift._apply_shift (when policyengine_us is installed), and a wiring test of run() with faked PolicyEngine calls. Co-Authored-By: Claude Opus 5.5 --- analysis/california_shift.py | 248 +++++++++++++++++---- analysis/california_summary.py | 49 +++++ analysis/tests/test_california_shift.py | 275 +++++++++++++++++++++++- 3 files changed, 527 insertions(+), 45 deletions(-) diff --git a/analysis/california_shift.py b/analysis/california_shift.py index a903282..a4de38e 100644 --- a/analysis/california_shift.py +++ b/analysis/california_shift.py @@ -13,9 +13,17 @@ labor income each group gives up and the capital income it receives. * Federal net revenue, as the all-government total less every state's net change. +* A within-state variant of the shift. The sweep scales every positive + capital-income dollar in the country by one factor, so a state whose share of + national capital income exceeds its share of labor income gains market + income from other states. The variant gives each state's freed labor income + to that state's own capital holders, in proportion to their holdings, so + every state's market income stays fixed and only the movement of income + within each state changes its revenue. Usage: - python -m analysis.california_shift [--levels 0.1,0.2,0.5,1.0] [--out PATH] + python -m analysis.california_shift [--levels 0.1,0.2,0.5,1.0] + [--within-state-levels 0.1,0.5] [--out PATH] """ from __future__ import annotations @@ -38,6 +46,7 @@ from .policyengine_runtime import managed_us_microsimulation, policyengine_bundle DEFAULT_LEVELS = [0.10, 0.20, 0.50, 1.00] +DEFAULT_WITHIN_STATE_LEVELS = [0.10, 0.50] STATE = "CA" SDI_VARIABLE = "ca_employee_state_disability_insurance_contribution" LABOR_VARS = ["employment_income", "self_employment_income"] @@ -88,6 +97,116 @@ def _household_frame(sim): return frame +def _freed_labor(values, shift_pct): + """Labor income each record gives up: ``shift_pct`` of its positive amount. + + Matches ``labor_capital_shift._apply_shift``: losses free nothing. + """ + values = np.asarray(values, dtype=float) + return np.where(values > 0, values * shift_pct, 0.0) + + +def _weighted_sum_by(keys, values, weights): + """Weighted sum of ``values`` for each distinct key, as {key: total}.""" + keys = np.asarray(keys).astype(str) + values = np.asarray(values, dtype=float) + weights = np.asarray(weights, dtype=float) + return {str(k): float((values[keys == k] * weights[keys == k]).sum()) for k in np.unique(keys)} + + +def _within_state_scales(freed_by_state, positive_capital_by_state): + """Factor on each state's positive capital income that absorbs its freed labor. + + A state that frees no labor income keeps a factor of one. A state that frees + labor income but holds no positive capital income has nowhere to put it, so + the shift refuses rather than drop or move those dollars. + """ + scales = {} + for state, freed in freed_by_state.items(): + if freed <= 0: + scales[state] = 1.0 + continue + capital = positive_capital_by_state.get(state, 0.0) + if capital <= 0: + raise ValueError( + f"{state} frees ${freed:,.0f} of labor income but holds no positive " + "capital income to receive it" + ) + scales[state] = 1.0 + freed / capital + return scales + + +def _scale_by_state(values, states, scales): + """Multiply each positive value by its state's factor; leave the rest as they are.""" + values = np.asarray(values, dtype=float) + states = np.asarray(states).astype(str) + factor = np.ones_like(values) + for state, scale in scales.items(): + factor[states == state] = scale + return np.where(values > 0, values * factor, values) + + +def _states_for(sim, variable): + """State code of each record of ``variable``'s entity.""" + entity = sim.tax_benefit_system.variables[variable].entity.key + return np.asarray(sim.calculate("state_code_str", period=YEAR, map_to=entity)).astype(str) + + +def _apply_within_state_shift(baseline, branch_name, shift_pct): + """Branch with each state's freed labor income added to its own capital income. + + The labor cut matches ``labor_capital_shift._apply_shift``: positive + employment and self-employment income fall by ``shift_pct``. Each state's + freed total then scales that state's positive capital income, in every + capital variable by the same factor, so each state's capital income rises by + exactly what its residents gave up in labor income. + + Returns the branch, the national total freed and the total freed by state. + """ + branch = baseline.get_branch(branch_name) + + freed_by_state = {} + total_freed = 0.0 + for var in LABOR_VARS: + result = baseline.calculate(var, period=YEAR) + raw = np.asarray(result, dtype=float) + weights = np.asarray(result.weights, dtype=float) + cut = _freed_labor(raw, shift_pct) + branch.set_input(var, YEAR, raw - cut) + for state, freed in _weighted_sum_by(_states_for(baseline, var), cut, weights).items(): + freed_by_state[state] = freed_by_state.get(state, 0.0) + freed + total_freed += float((cut * weights).sum()) + + capital = {} + capital_by_state = {} + for var in CAPITAL_INCOME_VARS: + result = baseline.calculate(var, period=YEAR) + raw = np.asarray(result, dtype=float) + weights = np.asarray(result.weights, dtype=float) + states = _states_for(baseline, var) + capital[var] = (raw, states, weights) + for state, held in _weighted_sum_by(states, np.clip(raw, 0, None), weights).items(): + capital_by_state[state] = capital_by_state.get(state, 0.0) + held + + scales = _within_state_scales(freed_by_state, capital_by_state) + added_by_state = {} + for var, (raw, states, weights) in capital.items(): + scaled = _scale_by_state(raw, states, scales) + branch.set_input(var, YEAR, scaled) + for state, added in _weighted_sum_by(states, scaled - raw, weights).items(): + added_by_state[state] = added_by_state.get(state, 0.0) + added + + # Rounding in raw * scale - raw grows with the capital a state holds, so the + # tolerance does too. + for state, freed in freed_by_state.items(): + added = added_by_state.get(state, 0.0) + tolerance = 1e-9 * (abs(freed) + capital_by_state.get(state, 0.0)) + 1e-6 + assert abs(added - freed) <= tolerance, ( + f"{state}: capital income rose by {added:,.0f}, labor income fell by {freed:,.0f}" + ) + return branch, total_freed, freed_by_state + + def _weighted_rank(values, weights): """Weighted percentile rank (0-1) of each household within its group.""" order = np.argsort(values, kind="mergesort") @@ -171,11 +290,67 @@ def total(sel): return total(everyone), groups -def run(levels, microsim_factory=managed_us_microsimulation, verbose=True): +def _level_row(branch, freed, pct, base, base_rev, base_states, allocation): + """Fiscal and California accounting for one shifted branch.""" + rev = revenue_components(branch) + delta = net_fiscal_impact(rev, base_rev) + states = state_revenue_delta(state_revenue_components(branch), base_states) + scen = _household_frame(branch) + ca_total, ca_groups = _california_rows(base, scen) + + state_net = {code: d["state_net_change"] / 1e9 for code, d in states.items()} + all_states = sum(state_net.values()) + other_states = all_states - state_net.get(STATE, 0.0) + gainers = sorted( + (c for c, v in state_net.items() if v > 0.005), key=lambda c: -state_net[c] + ) + losers = [c for c, v in state_net.items() if v < -0.005] + return { + "shift_pct": int(round(pct * 100)), + "allocation": allocation, + "labor_income_moved_b": freed / 1e9, + "all_government_change_b": delta["total_change"] / 1e9, + "all_states_net_change_b": all_states, + "federal_net_change_b": delta["total_change"] / 1e9 - all_states, + "other_states_net_change_b": other_states, + "states_gaining": gainers, + "states_losing_count": len(losers), + "state_net_change_b": dict(sorted(state_net.items())), + "national_components_b": { + k: v / 1e9 for k, v in sorted(delta.items()) if isinstance(v, (int, float)) + }, + "california": ca_total, + "california_by_group": ca_groups, + } + + +def _report(row): + ca = row["california"] + print( + f"{row['shift_pct']:>3}% shift ({row['allocation']}): CA net " + f"{ca['state_net_change_b']:+.2f}B (with SDI " + f"{ca['state_net_including_sdi_change_b']:+.2f}B; market income " + f"{ca['market_income_change_b']:+.1f}B); other states " + f"{row['other_states_net_change_b']:+.2f}B; federal " + f"{row['federal_net_change_b']:+.1f}B; all-gov " + f"{row['all_government_change_b']:+.1f}B", + flush=True, + ) + + +def run( + levels, + microsim_factory=managed_us_microsimulation, + verbose=True, + within_state_levels=None, +): # labor_capital_shift imports policyengine_us at module load; importing it # here keeps this module's accounting helpers testable without the model. from .labor_capital_shift import _apply_shift + if within_state_levels is None: + within_state_levels = DEFAULT_WITHIN_STATE_LEVELS + base_sim = microsim_factory() bundle = policyengine_bundle(base_sim) base_rev = revenue_components(base_sim) @@ -195,46 +370,27 @@ def run(levels, microsim_factory=managed_us_microsimulation, verbose=True): for pct in levels: sim = microsim_factory() branch, freed = _apply_shift(sim, f"ca_{int(round(pct * 100))}", pct) - rev = revenue_components(branch) - delta = net_fiscal_impact(rev, base_rev) - states = state_revenue_delta(state_revenue_components(branch), base_states) - scen = _household_frame(branch) - ca_total, ca_groups = _california_rows(base, scen) - - state_net = {code: d["state_net_change"] / 1e9 for code, d in states.items()} - all_states = sum(state_net.values()) - other_states = all_states - state_net.get(STATE, 0.0) - gainers = sorted( - (c for c, v in state_net.items() if v > 0.005), key=lambda c: -state_net[c] + row = _level_row(branch, freed, pct, base, base_rev, base_states, "national") + rows.append(row) + if verbose: + _report(row) + del branch, sim + gc.collect() + + within_rows = [] + for pct in within_state_levels: + sim = microsim_factory() + branch, freed, freed_by_state = _apply_within_state_shift( + sim, f"ca_within_{int(round(pct * 100))}", pct ) - losers = [c for c, v in state_net.items() if v < -0.005] - row = { - "shift_pct": int(round(pct * 100)), - "labor_income_moved_b": freed / 1e9, - "all_government_change_b": delta["total_change"] / 1e9, - "all_states_net_change_b": all_states, - "federal_net_change_b": delta["total_change"] / 1e9 - all_states, - "other_states_net_change_b": other_states, - "states_gaining": gainers, - "states_losing_count": len(losers), - "state_net_change_b": dict(sorted(state_net.items())), - "national_components_b": { - k: v / 1e9 for k, v in sorted(delta.items()) if isinstance(v, (int, float)) - }, - "california": ca_total, - "california_by_group": ca_groups, + row = _level_row(branch, freed, pct, base, base_rev, base_states, "within_state") + row["labor_income_moved_by_state_b"] = { + state: value / 1e9 for state, value in sorted(freed_by_state.items()) } - rows.append(row) + within_rows.append(row) if verbose: - print( - f"{row['shift_pct']:>3}% shift: CA net {ca_total['state_net_change_b']:+.2f}B " - f"(with SDI {ca_total['state_net_including_sdi_change_b']:+.2f}B); " - f"other states {other_states:+.2f}B; federal " - f"{row['federal_net_change_b']:+.1f}B; all-gov " - f"{row['all_government_change_b']:+.1f}B", - flush=True, - ) - del branch, sim, scen + _report(row) + del branch, sim gc.collect() return { @@ -246,7 +402,10 @@ def run(levels, microsim_factory=managed_us_microsimulation, verbose=True): "in proportion to existing holdings. Static, current law, no " "behavioral response. State net revenue is state income tax plus use " "tax, less refundable state credits and state-funded benefits; " - "California's employee SDI contribution is reported separately." + "California's employee SDI contribution is reported separately. " + "'levels' allocate the new capital income across the country, as the " + "published sweep does; 'within_state_levels' give each state's freed " + "labor income to that state's own capital holders." ), "metadata": { "policyengine_version": bundle.get("policyengine_version") @@ -259,6 +418,7 @@ def run(levels, microsim_factory=managed_us_microsimulation, verbose=True): }, "baseline_california": baseline, "levels": rows, + "within_state_levels": within_rows, } @@ -269,10 +429,16 @@ def main(): default=",".join(str(x) for x in DEFAULT_LEVELS), help="Comma-separated shift shares, e.g. 0.1,0.2,0.5,1.0", ) + parser.add_argument( + "--within-state-levels", + default=",".join(str(x) for x in DEFAULT_WITHIN_STATE_LEVELS), + help="Shift shares for the within-state variant; empty to skip it", + ) parser.add_argument("--out", default=None, help="Output JSON path") args = parser.parse_args() levels = [float(x) for x in args.levels.split(",") if x.strip()] - result = run(levels) + within = [float(x) for x in args.within_state_levels.split(",") if x.strip()] + result = run(levels, within_state_levels=within) out = args.out or os.path.join( OUTPUT_DIR, f"california_shift_pe{result['metadata']['policyengine_version']}.json", diff --git a/analysis/california_summary.py b/analysis/california_summary.py index cb589d2..7c746e0 100644 --- a/analysis/california_summary.py +++ b/analysis/california_summary.py @@ -183,9 +183,58 @@ def summarize(run, published=None): f"other states {p['other_states']:+.2f}B, federal {p['federal']:+.1f}B " f"({_pct(row['federal_net_change_b'], p['federal']):+.1f}%)" ) + lines.extend(_within_state_lines(run)) return "\n".join(lines) +def _within_state_lines(run): + """The within-state variant, and how much of California's gain it keeps.""" + within = run.get("within_state_levels") or [] + if not within: + return [] + national = {row["shift_pct"]: row for row in run["levels"]} + lines = ["", "Within-state variant: each state's freed labor income goes to its own capital holders"] + for row in within: + ca = row["california"] + lines.append("") + lines.append( + f"{row['shift_pct']}% shift: California net {ca['state_net_change_b']:+.2f}B; with SDI " + f"{ca['state_net_including_sdi_change_b']:+.2f}B; households: labor " + f"{ca['labor_income_change_b']:+.1f}B, capital {ca['capital_income_change_b']:+.1f}B, " + f"market income {ca['market_income_change_b']:+.2f}B" + ) + lines.append( + f" Other states {row['other_states_net_change_b']:+.2f}B; gaining: " + f"{', '.join(row['states_gaining'])}; {row['states_losing_count']} losing; federal " + f"{row['federal_net_change_b']:+.1f}B; all governments {row['all_government_change_b']:+.1f}B" + ) + n = national.get(row["shift_pct"]) + if n: + nat = n["california"]["state_net_change_b"] + lines.append( + f" California under the national allocation {nat:+.2f}B: " + f"{ca['state_net_change_b']:+.2f}B from income moving within the state and " + f"{nat - ca['state_net_change_b']:+.2f}B from income moving in from other states " + f"(market income {n['california']['market_income_change_b']:+.1f}B)" + ) + total_tax = ca["state_tax_before_refundable_credits_change_b"] + for g in row["california_by_group"]: + share = float("nan") if total_tax == 0 else 100 * g["state_tax_before_refundable_credits_change_b"] / total_tax + lines.append( + f" {g['group']:<26} state tax {g['state_tax_before_refundable_credits_change_b']:+.2f}B " + f"({share:5.1f}% of the change), labor {g['labor_income_change_b']:+.1f}B, capital " + f"{g['capital_income_change_b']:+.1f}B" + ) + ranked = sorted(row["state_net_change_b"].items(), key=lambda kv: kv[1]) + lines.append( + " Five lowest changes: " + ", ".join(f"{s} {v:+.2f}B" for s, v in ranked[:5]) + ) + lines.append( + " Five highest changes: " + ", ".join(f"{s} {v:+.2f}B" for s, v in ranked[::-1][:5]) + ) + return lines + + def main(): parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("run") diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 3dc536d..753bafb 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -8,24 +8,40 @@ field's group changes add up to the state total, and the groups' shares of labor and capital income each add up to one; - households outside California never move California's rows; -- the change including SDI equals the net change plus the SDI change. - -None of these tests load PolicyEngine. +- the change including SDI equals the net change plus the SDI change; +- the within-state shift conserves every state's market income: each state's + capital income rises by exactly the labor income its residents give up, only + positive capital income moves, and the national labor cut matches the + published sweep's; +- with every record in one state, the within-state shift sets the same inputs + as the published sweep's national shift (a differential check, run when + policyengine_us is installed, plus a closed-form check that always runs). + +Only the differential check against ``_apply_shift`` loads PolicyEngine. """ import json +import sys +from types import SimpleNamespace import numpy as np import pytest -from hypothesis import given, settings +from hypothesis import assume, given, settings from hypothesis import strategies as st from analysis.california_shift import ( + CAPITAL_INCOME_VARS, GROUPS, + LABOR_VARS, STATE, + _apply_within_state_shift, _baseline_concentration, _california_rows, + _freed_labor, + _scale_by_state, _weighted_rank, + _weighted_sum_by, + _within_state_scales, ) VALUE_KEYS = ( @@ -301,3 +317,254 @@ def test_summary_reads_a_run(): text = summarize(run, published) assert "California net +5.00B; with SDI +3.00B" in text assert "Published: California +5.00B (+0.0%)" in text + assert "Within-state variant" not in text + + within_ca = dict(ca, state_net_change_b=3.5, market_income_change_b=0.0) + run["within_state_levels"] = [ + dict( + run["levels"][0], + allocation="within_state", + california=within_ca, + california_by_group=[dict(within_ca, group="Top 1%")], + ) + ] + text = summarize(run, published) + assert "Within-state variant" in text + assert "+3.50B from income moving within the state and +1.50B from income moving in" in text + assert "Five lowest changes: TX -4.00B" in text + + +# --- Within-state shift ------------------------------------------------------- + +INCOME_VARS = tuple(LABOR_VARS) + tuple(CAPITAL_INCOME_VARS) + + +class _Result: + """Stands in for a MicroSeries: array values plus ``weights``.""" + + def __init__(self, values, weights): + self.values = np.asarray(values) + self.weights = np.asarray(weights, dtype=float) + + def __array__(self, dtype=None, copy=None): + return self.values if dtype is None else self.values.astype(dtype) + + +class _Branch: + def __init__(self): + self.inputs = {} + + def set_input(self, variable, period, values): + self.inputs[variable] = np.array(values, dtype=float) + + +class _Sim: + """A person-level microsimulation with the calls the shift functions make.""" + + def __init__(self, state, weight, columns): + self.state = np.asarray(state).astype(str) + self.weight = np.asarray(weight, dtype=float) + self.columns = {k: np.asarray(v, dtype=float) for k, v in columns.items()} + person = SimpleNamespace(entity=SimpleNamespace(key="person")) + self.tax_benefit_system = SimpleNamespace(variables={v: person for v in INCOME_VARS}) + self.branches = [] + + def get_branch(self, name): + branch = _Branch() + self.branches.append(branch) + return branch + + def calculate(self, variable, period=None, map_to=None): + if variable == "state_code_str": + assert map_to == "person" + return _Result(self.state, self.weight) + return _Result(self.columns[variable], self.weight) + + +INCOMES = st.floats(-2e5, 2e6, allow_nan=False, allow_infinity=False) +SHIFTS = st.floats(0.0, 1.0, allow_nan=False) + + +@st.composite +def person_sims(draw, states=("CA", "NY", "TX")): + n = draw(st.integers(1, 25)) + state = draw(st.lists(st.sampled_from(list(states)), min_size=n, max_size=n)) + weight = draw(st.lists(WEIGHTS, min_size=n, max_size=n)) + columns = {v: draw(st.lists(INCOMES, min_size=n, max_size=n)) for v in INCOME_VARS} + return _Sim(state, weight, columns) + + +def _by_state(sim, values): + return _weighted_sum_by(sim.state, values, sim.weight) + + +def _positive_capital(sim): + return sum(np.clip(sim.columns[v], 0, None) for v in CAPITAL_INCOME_VARS) + + +@settings(max_examples=300, deadline=None) +@given(person_sims(), SHIFTS) +def test_within_state_shift_conserves_each_state(sim, pct): + freed = sum(_freed_labor(sim.columns[v], pct) for v in LABOR_VARS) + freed_by_state = _by_state(sim, freed) + capital_by_state = _by_state(sim, _positive_capital(sim)) + if any(f > 0 and capital_by_state.get(s, 0.0) <= 0 for s, f in freed_by_state.items()): + with pytest.raises(ValueError): + _apply_within_state_shift(sim, "t", pct) + return + + branch, total, reported = _apply_within_state_shift(sim, "t", pct) + out = branch.inputs + assert set(out) == set(INCOME_VARS) + assert total == pytest.approx(float((freed * sim.weight).sum()), rel=1e-12, abs=1e-6) + assert reported == pytest.approx(freed_by_state, rel=1e-12, abs=1e-6) + + labor_change = sum(out[v] - sim.columns[v] for v in LABOR_VARS) + capital_change = sum(out[v] - sim.columns[v] for v in CAPITAL_INCOME_VARS) + held = _positive_capital(sim) + for state in np.unique(sim.state): + m = sim.state == state + lost = float((labor_change[m] * sim.weight[m]).sum()) + gained = float((capital_change[m] * sim.weight[m]).sum()) + # Rounding scales with the dollars involved, capital included. + size = float(((np.abs(labor_change[m]) + held[m]) * sim.weight[m]).sum()) + assert lost + gained == pytest.approx(0.0, abs=1e-9 * size + 1e-6) + + for v in CAPITAL_INCOME_VARS: + before, after = sim.columns[v], out[v] + # Zero and negative values stay put; positive ones never fall. + np.testing.assert_array_equal(after[before <= 0], before[before <= 0]) + assert np.all(after[before > 0] >= before[before > 0]) + + +@settings(max_examples=300, deadline=None) +@given(person_sims(), SHIFTS) +def test_within_state_scales_are_shared_across_capital_types(sim, pct): + freed = _by_state(sim, sum(_freed_labor(sim.columns[v], pct) for v in LABOR_VARS)) + capital = _by_state(sim, _positive_capital(sim)) + assume(all(f <= 0 or capital.get(s, 0.0) > 0 for s, f in freed.items())) + scales = _within_state_scales(freed, capital) + assert all(s >= 1.0 for s in scales.values()) + for v in CAPITAL_INCOME_VARS: + raw = sim.columns[v] + scaled = _scale_by_state(raw, sim.state, scales) + for state, scale in scales.items(): + m = (sim.state == state) & (raw > 0) + np.testing.assert_allclose(scaled[m], raw[m] * scale, rtol=1e-12) + + +def test_within_state_scales_refuse_a_state_without_capital(): + with pytest.raises(ValueError, match="NY"): + _within_state_scales({"CA": 10.0, "NY": 5.0}, {"CA": 100.0}) + assert _within_state_scales({"CA": 0.0}, {}) == {"CA": 1.0} + + +def test_within_state_shift_example(): + # A California worker gives up $100 of a $1,000 wage; the two California + # capital holders split it 3:1 by holdings. New York's worker frees $50, + # which goes to New York's only holder, not to California. + sim = _Sim( + ["CA", "CA", "CA", "NY", "NY"], + [1.0, 1.0, 1.0, 1.0, 1.0], + { + "employment_income": [1000, 0, 0, 500, 0], + "self_employment_income": [0, 0, 0, 0, 0], + "long_term_capital_gains": [0, 300, 100, 0, 40], + "short_term_capital_gains": [0, 0, 0, 0, 0], + "taxable_interest_income": [0, 0, 0, 0, 10], + "qualified_dividend_income": [0, 0, 0, 0, 0], + "non_qualified_dividend_income": [0, 0, 0, 0, 0], + "rental_income": [0, 0, -50, 0, 0], + }, + ) + branch, total, by_state = _apply_within_state_shift(sim, "t", 0.1) + assert total == pytest.approx(150) + assert by_state == pytest.approx({"CA": 100, "NY": 50}) + np.testing.assert_allclose(branch.inputs["employment_income"], [900, 0, 0, 450, 0]) + np.testing.assert_allclose(branch.inputs["long_term_capital_gains"], [0, 375, 125, 0, 80]) + np.testing.assert_allclose(branch.inputs["taxable_interest_income"], [0, 0, 0, 0, 20]) + np.testing.assert_allclose(branch.inputs["rental_income"], [0, 0, -50, 0, 0]) + + +def _national_closed_form(sim, pct): + """The published sweep's shift in closed form: one factor on all positive capital.""" + freed = sum(_freed_labor(sim.columns[v], pct) for v in LABOR_VARS) + total_freed = float((freed * sim.weight).sum()) + total_capital = float((_positive_capital(sim) * sim.weight).sum()) + scale = 1.0 + total_freed / total_capital + out = {v: sim.columns[v] - _freed_labor(sim.columns[v], pct) for v in LABOR_VARS} + for v in CAPITAL_INCOME_VARS: + raw = sim.columns[v] + out[v] = np.where(raw > 0, raw * scale, raw) + return out + + +@settings(max_examples=200, deadline=None) +@given(person_sims(states=("CA",)), SHIFTS) +def test_one_state_matches_the_national_shift_in_closed_form(sim, pct): + assume(float((_positive_capital(sim) * sim.weight).sum()) > 0) + branch, _, _ = _apply_within_state_shift(sim, "t", pct) + expected = _national_closed_form(sim, pct) + for v in INCOME_VARS: + np.testing.assert_allclose(branch.inputs[v], expected[v], rtol=1e-12, atol=1e-9) + + +@settings(max_examples=100, deadline=None) +@given(person_sims(states=("CA",)), SHIFTS) +def test_one_state_matches_the_published_apply_shift(sim, pct): + # Differential check against the code that produced the published sweep. + pytest.importorskip("policyengine_us") + from analysis.labor_capital_shift import _apply_shift + + assume(float((_positive_capital(sim) * sim.weight).sum()) > 0) + within, total_w, _ = _apply_within_state_shift(sim, "w", pct) + national, total_n = _apply_shift(sim, "n", pct) + assert total_w == pytest.approx(total_n, rel=1e-12, abs=1e-9) + for v in INCOME_VARS: + expected = national.inputs.get(v, sim.columns[v]) + np.testing.assert_allclose(within.inputs[v], expected, rtol=1e-12, atol=1e-9) + + +def test_run_records_both_allocations(monkeypatch): + # Wiring check for run(): fake every PolicyEngine call and confirm the output + # carries national and within-state rows with the accounting fields. + import analysis.california_shift as cs + + frame = _frame([STATE, "NY"], [1e9, 1e9], market_income=[10, 20], labor=[5, 5], capital=[2, 3]) + + def fake_national(sim, name, pct): + return _Branch(), 100e9 * pct + + def fake_within(sim, name, pct): + return _Branch(), 100e9 * pct, {STATE: 60e9 * pct, "NY": 40e9 * pct} + + monkeypatch.setitem( + sys.modules, "analysis.labor_capital_shift", SimpleNamespace(_apply_shift=fake_national) + ) + monkeypatch.setattr(cs, "_apply_within_state_shift", fake_within) + monkeypatch.setattr(cs, "policyengine_bundle", lambda sim: {"policyengine_version": "test"}) + monkeypatch.setattr(cs, "revenue_components", lambda sim: {"total": 1.0}) + monkeypatch.setattr(cs, "net_fiscal_impact", lambda rev, base: {"total_change": -5e9}) + monkeypatch.setattr(cs, "state_revenue_components", lambda sim: {}) + monkeypatch.setattr( + cs, + "state_revenue_delta", + lambda scen, base: {STATE: {"state_net_change": 2e9}, "NY": {"state_net_change": -1e9}}, + ) + monkeypatch.setattr(cs, "_household_frame", lambda sim: frame) + + # One California household leaves three of the four groups empty, so their + # mean income divides by zero; the real data fills every group. + with np.errstate(invalid="ignore", divide="ignore"): + out = cs.run([0.1, 0.5], microsim_factory=lambda: object(), verbose=True, + within_state_levels=[0.1]) + assert [r["shift_pct"] for r in out["levels"]] == [10, 50] + assert [r["allocation"] for r in out["levels"]] == ["national", "national"] + (row,) = out["within_state_levels"] + assert row["allocation"] == "within_state" and row["shift_pct"] == 10 + assert row["labor_income_moved_b"] == pytest.approx(10.0) + assert row["labor_income_moved_by_state_b"] == pytest.approx({STATE: 6.0, "NY": 4.0}) + assert row["federal_net_change_b"] == pytest.approx(-5.0 - 1.0) + assert row["other_states_net_change_b"] == pytest.approx(-1.0) + assert row["states_gaining"] == [STATE] + assert out["metadata"]["policyengine_version"] == "test" From 876e71e5668b0316d2362d978d291d8ec2f71c29 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 16:27:51 -0500 Subject: [PATCH 04/17] Label the chart's measure as state tax revenue and its year as the simulation year Co-Authored-By: Claude Opus 5.5 --- analysis/california_chart.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/analysis/california_chart.py b/analysis/california_chart.py index d523c33..5feb560 100644 --- a/analysis/california_chart.py +++ b/analysis/california_chart.py @@ -48,7 +48,7 @@ def series_from_sweep(path=SWEEP_PATH): ca.append(ca_net) other.append(total - ca_net) meta = data["metadata"] - note = f"policyengine-us {meta['country_model_version']}, data year {data['year']}" + note = f"policyengine-us {meta['country_model_version']}, {data['year']}" return xs, ca, None, other, note @@ -61,7 +61,7 @@ def series_from_run(path): ca_sdi.append(row["california"]["state_net_including_sdi_change_b"]) other.append(row["other_states_net_change_b"]) meta = data["metadata"] - note = f"policyengine-us {meta['policyengine_us_version']}, data year {data['year']}" + note = f"policyengine-us {meta['policyengine_us_version']}, {data['year']}" return xs, ca, ca_sdi, other, note @@ -111,7 +111,7 @@ def draw(xs, ca, ca_sdi, other, out, source_note): fig.text(0.06, 0.955, "California gains as labor income shifts to capital", fontsize=14, fontweight="bold", color=TEXT_PRIMARY, ha="left") fig.text(0.06, 0.915, - "Change in state income tax net of refundable state credits and " + "Change in state tax revenue, net of refundable state credits and " "state-funded benefits, $ billions", fontsize=10, color=TEXT_SECONDARY, ha="left") fig.text(0.06, 0.02, From e9b07805cb0c4a9d419a38322c576d8f680468b5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 18:12:20 -0500 Subject: [PATCH 05/17] Record the California rerun on policyengine 6.16.0 Output of `python -m analysis.california_shift` on policyengine 6.16.0 (policyengine-us 2.2.1, the 2026-09-15 Microcosm national release) for 2026: the national allocation at 10, 20, 50 and 100 percent and the within-state allocation at 10 and 50 percent. Aggregates only. Co-Authored-By: Claude Opus 5.5 --- .../outputs/california_shift_pe6.16.0.json | 1218 +++++++++++++++++ 1 file changed, 1218 insertions(+) create mode 100644 analysis/outputs/california_shift_pe6.16.0.json diff --git a/analysis/outputs/california_shift_pe6.16.0.json b/analysis/outputs/california_shift_pe6.16.0.json new file mode 100644 index 0000000..89ce62c --- /dev/null +++ b/analysis/outputs/california_shift_pe6.16.0.json @@ -0,0 +1,1218 @@ +{ + "year": 2026, + "state": "CA", + "description": "Positive employment and self-employment income fall by the shift share; the same weighted total is added to positive capital income in proportion to existing holdings. Static, current law, no behavioral response. State net revenue is state income tax plus use tax, less refundable state credits and state-funded benefits; California's employee SDI contribution is reported separately. 'levels' allocate the new capital income across the country, as the published sweep does; 'within_state_levels' give each state's freed labor income to that state's own capital holders.", + "metadata": { + "policyengine_version": "6.16.0", + "policyengine_us_version": "2.2.1", + "policyengine_core_version": "3.33.3", + "certified_data_build_id": "populace-us-2024-spm-20260915", + "runtime_dataset_uri": "hf://policyengine/populace-us/populace_us_2024.h5@populace-us-2024-spm-20260915" + }, + "baseline_california": { + "households_m": 13.808544553134501, + "labor_income_b": 1799.4463104844283, + "capital_income_b": 378.85956074411666, + "state_net_revenue_b": 139.83791264480345, + "state_tax_before_refundable_credits_b": 146.57802063237833, + "sdi_b": 22.521579236494752, + "groups": [ + { + "group": "Bottom 50%", + "households_m": 6.904237991577316, + "labor_income_share": 0.14566525771657926, + "capital_income_share": 0.03515932174318084, + "mean_market_income": 42801.04758578183 + }, + { + "group": "50th to 90th percentile", + "households_m": 5.522240338942084, + "labor_income_share": 0.4819600529113467, + "capital_income_share": 0.15836669439991793, + "mean_market_income": 211968.10824030847 + }, + { + "group": "90th to 99th percentile", + "households_m": 1.2439704277950518, + "labor_income_share": 0.3089739456274271, + "capital_income_share": 0.40870763588608616, + "mean_market_income": 704295.0256718536 + }, + { + "group": "Top 1%", + "households_m": 0.13809579482004902, + "labor_income_share": 0.06340074374464696, + "capital_income_share": 0.3977663479708151, + "mean_market_income": 2468854.9013992785 + } + ] + }, + "levels": [ + { + "shift_pct": 10, + "allocation": "national", + "labor_income_moved_b": 1241.2958648972376, + "all_government_change_b": -128.3202290050426, + "all_states_net_change_b": 2.935411415642451, + "federal_net_change_b": -131.25564042068504, + "other_states_net_change_b": -2.1043900160405467, + "states_gaining": [ + "CA", + "NY", + "WA", + "MA", + "CT", + "DC", + "MT", + "VT", + "HI" + ], + "states_losing_count": 33, + "state_net_change_b": { + "AK": -0.0018910200188786686, + "AL": -0.1263968604000864, + "AR": -0.025099278949883463, + "AZ": -0.08166439852860889, + "CA": 5.0398014316829975, + "CO": -0.06070681398334026, + "CT": 0.41651087668758024, + "DC": 0.14132477656309855, + "DE": -0.07080597075446611, + "FL": 0.0, + "GA": -0.33402922112945793, + "HI": 0.014210770050646514, + "IA": -0.06101501447513433, + "ID": -0.03844281097763181, + "IL": -0.47187000761784853, + "IN": -0.24789678567523846, + "KS": -0.083220933401813, + "KY": -0.17074822632612227, + "LA": -0.08179197504043988, + "MA": 0.7225977344170723, + "MD": -0.6170744692364339, + "ME": -0.03901763025109136, + "MI": -0.4877292735632546, + "MN": -0.37472572774553625, + "MO": -0.5172870002738177, + "MS": -0.10423069946344066, + "MT": 0.0779609128560824, + "NC": -0.334679661664051, + "ND": -0.005395447582594216, + "NE": -0.027988221950396276, + "NH": 0.0, + "NJ": -0.2262439334782281, + "NM": -0.03564449270050979, + "NV": 0.0, + "NY": 1.9468955849709864, + "OH": -0.2858043992376404, + "OK": -0.07599352735785224, + "OR": -0.09559607063792933, + "PA": -0.38024396809826183, + "RI": -0.004179059189240866, + "SC": -0.21249240591354848, + "SD": 0.0, + "TN": 0.0, + "TX": 0.0, + "UT": -0.027876588999423028, + "VA": -0.4346690389152745, + "VT": 0.026919647088846057, + "WA": 1.1102113890302001, + "WI": -0.3341611081218943, + "WV": -0.08440966604569042, + "WY": 0.0 + }, + "national_components_b": { + "_identity_residual": -3.708355239868164e-06, + "eitc_change": 6.763975618969422, + "employee_medicare_tax_change": -17.300927508987215, + "employee_social_security_tax_change": -52.55146390560706, + "employer_medicare_tax_change": -17.300927508987215, + "employer_payroll_tax_change": -71.51300997710877, + "employer_social_security_tax_change": -52.55146390560706, + "fed_alternative_minimum_tax_change": 2.6073408440253143, + "fed_capital_gains_tax_change": 153.30576208936836, + "fed_income_tax_before_refundable_credits_change": 43.60628673608105, + "fed_income_tax_main_rates_change": -155.443282802145, + "fed_net_investment_income_tax_change": 38.115400875872176, + "fed_nonrefundable_credits_change": -5.021063920889222, + "fed_other_income_tax_items_change": 0.0, + "health_benefits_change": 0.0, + "household_benefits_change": 12.186044780831299, + "household_market_income_change": -8.03573671875e-05, + "household_net_income_change": 56.807142378921874, + "household_refundable_tax_credits_change": 10.406023079190902, + "household_tax_before_refundable_credits_change": -34.21515116791162, + "income_tax_refundable_credits_change": 9.117663549023407, + "refundable_ctc_change": 2.2964054787175066, + "self_employment_tax_change": -5.570886517814468, + "snap_change": 8.56349976508905, + "ssi_change": 0.855591230624794, + "state_benefits_change": 0.3181483921817093, + "state_refundable_credits_change": 1.2883596216184043, + "state_tax_before_refundable_credits_change": 4.541919429442627, + "tanf_change": 0.1271162918525219, + "total_change": -128.3202290050426, + "wic_change": 0.5924254459370251 + }, + "california": { + "labor_income_change_b": -179.94462918295312, + "capital_income_change_b": 176.87994775373528, + "market_income_change_b": -3.0646802352988605, + "state_tax_before_refundable_credits_change_b": 5.39631934632345, + "state_refundable_credits_change_b": 0.2403037746718769, + "state_benefits_change_b": 0.11621413996855041, + "state_net_change_b": 5.039801431683023, + "sdi_change_b": -2.2521579438558437, + "state_net_including_sdi_change_b": 2.7876434878271796 + }, + "california_by_group": [ + { + "group": "Bottom 50%", + "labor_income_change_b": -26.211679848868705, + "capital_income_change_b": 6.453648066045201, + "market_income_change_b": -19.75803230633572, + "state_tax_before_refundable_credits_change_b": -0.42185069909310197, + "state_refundable_credits_change_b": 0.2155656132361496, + "state_benefits_change_b": 0.04422697393432171, + "state_net_change_b": -0.6816432862635734, + "sdi_change_b": -0.30307941405147765, + "state_net_including_sdi_change_b": -0.984722700315051 + }, + { + "group": "50th to 90th percentile", + "labor_income_change_b": -86.72612153726766, + "capital_income_change_b": 28.105026708660986, + "market_income_change_b": -58.621100133348996, + "state_tax_before_refundable_credits_change_b": -4.401760779545172, + "state_refundable_credits_change_b": 0.023381412888317438, + "state_benefits_change_b": 0.07198716603422868, + "state_net_change_b": -4.497129358467718, + "sdi_change_b": -1.0884794777788802, + "state_net_including_sdi_change_b": -5.585608836246598 + }, + { + "group": "90th to 99th percentile", + "labor_income_change_b": -55.59820425705453, + "capital_income_change_b": 72.84575975768374, + "market_income_change_b": 17.247560872731345, + "state_tax_before_refundable_credits_change_b": 2.2981185508074415, + "state_refundable_credits_change_b": 0.0013289896034107758, + "state_benefits_change_b": 0.0, + "state_net_change_b": 2.2967895612040308, + "sdi_change_b": -0.7122869353925602, + "state_net_including_sdi_change_b": 1.5845026258114707 + }, + { + "group": "Top 1%", + "labor_income_change_b": -11.408623539762246, + "capital_income_change_b": 69.47551322134537, + "market_income_change_b": 58.06689133165453, + "state_tax_before_refundable_credits_change_b": 7.921812274154283, + "state_refundable_credits_change_b": 2.775894399909064e-05, + "state_benefits_change_b": 0.0, + "state_net_change_b": 7.921784515210284, + "sdi_change_b": -0.14831211663292593, + "state_net_including_sdi_change_b": 7.773472398577358 + } + ] + }, + { + "shift_pct": 20, + "allocation": "national", + "labor_income_moved_b": 2482.591729794475, + "all_government_change_b": -241.74890843283436, + "all_states_net_change_b": 12.10397536873566, + "federal_net_change_b": -253.85288380157002, + "other_states_net_change_b": 1.3917346428401487, + "states_gaining": [ + "CA", + "NY", + "WA", + "MA", + "CT", + "DC", + "MT", + "VT", + "HI", + "RI" + ], + "states_losing_count": 34, + "state_net_change_b": { + "AK": -0.007156259996970661, + "AL": -0.2733519225511227, + "AR": -0.053381314780248164, + "AZ": -0.1480840365577189, + "CA": 10.712240725895512, + "CO": -0.0055929381630215646, + "CT": 0.8313647271015612, + "DC": 0.2917206839698865, + "DE": -0.12679457894435833, + "FL": 0.0, + "GA": -0.6233249217678146, + "HI": 0.04778094038203454, + "IA": -0.11788860413686224, + "ID": -0.07074292217639172, + "IL": -0.89358979357645, + "IN": -0.4987452323928204, + "KS": -0.16120271978751127, + "KY": -0.33252164330514716, + "LA": -0.15814094779071378, + "MA": 1.4801384032699214, + "MD": -0.9540156610726691, + "ME": -0.07461719715157494, + "MI": -0.9580965659079059, + "MN": -0.6293220987796614, + "MO": -1.0104493389298461, + "MS": -0.20104983889269828, + "MT": 0.15845272223638251, + "NC": -0.6296061404720306, + "ND": -0.005622036416945458, + "NE": -0.05835682330087945, + "NH": 0.0, + "NJ": -0.4849948598317089, + "NM": -0.050677030936250866, + "NV": 0.0, + "NY": 8.547996239633038, + "OH": -0.6015316565036087, + "OK": -0.14566473493807067, + "OR": -0.10563053712899, + "PA": -0.7977383648330847, + "RI": 0.005291331583160907, + "SC": -0.4036188968645191, + "SD": 0.0, + "TN": 0.0, + "TX": 0.0, + "UT": -0.05225394508487988, + "VA": -0.8824998997865019, + "VT": 0.062297288465273666, + "WA": 2.2959153929844214, + "WI": -0.6543328622677396, + "WV": -0.15862676175881463, + "WY": 0.0 + }, + "national_components_b": { + "_identity_residual": 5.72486669921875e-06, + "eitc_change": 13.251508491958084, + "employee_medicare_tax_change": -34.601854885330106, + "employee_social_security_tax_change": -107.03839464418402, + "employer_medicare_tax_change": -34.601854885330106, + "employer_payroll_tax_change": -145.07865099463368, + "employer_social_security_tax_change": -107.03839464418402, + "fed_alternative_minimum_tax_change": 4.071102858005413, + "fed_capital_gains_tax_change": 308.1912700328199, + "fed_income_tax_before_refundable_credits_change": 101.4215474781748, + "fed_income_tax_main_rates_change": -299.0515171039409, + "fed_net_investment_income_tax_change": 76.99604074771887, + "fed_nonrefundable_credits_change": -11.214653671423157, + "fed_other_income_tax_items_change": 0.0, + "health_benefits_change": 0.0, + "household_benefits_change": 26.513765112390868, + "household_market_income_change": 0.0001681133984375, + "household_net_income_change": 96.67041982673243, + "household_refundable_tax_credits_change": 20.826008507153063, + "household_tax_before_refundable_credits_change": -49.33048381865674, + "income_tax_refundable_credits_change": 18.04397242910858, + "refundable_ctc_change": 4.70622488567392, + "self_employment_tax_change": -11.138475708396331, + "snap_change": 18.73557774855487, + "ssi_change": 2.067479289948776, + "state_benefits_change": 0.7220293720362282, + "state_refundable_credits_change": 2.7820361702908745, + "state_tax_before_refundable_credits_change": 15.608040911062867, + "tanf_change": 0.40389179696234323, + "total_change": -241.74890843283436, + "wic_change": 1.0690528654655724 + }, + "california": { + "labor_income_change_b": -359.88925973446646, + "capital_income_change_b": 353.75993531852686, + "market_income_change_b": -6.129319382930992, + "state_tax_before_refundable_credits_change_b": 11.729854562621638, + "state_refundable_credits_change_b": 0.5816074750111029, + "state_benefits_change_b": 0.43600636171499674, + "state_net_change_b": 10.712240725895539, + "sdi_change_b": -4.504315837466379, + "state_net_including_sdi_change_b": 6.20792488842916 + }, + "california_by_group": [ + { + "group": "Bottom 50%", + "labor_income_change_b": -52.42336141389948, + "capital_income_change_b": 12.90729753169806, + "market_income_change_b": -39.51606367757846, + "state_tax_before_refundable_credits_change_b": -0.6880553316316795, + "state_refundable_credits_change_b": 0.5151495634301718, + "state_benefits_change_b": 0.29203194019479634, + "state_net_change_b": -1.4952368352566476, + "sdi_change_b": -0.6061588478416385, + "state_net_including_sdi_change_b": -2.101395683098286 + }, + { + "group": "50th to 90th percentile", + "labor_income_change_b": -173.45224678469756, + "capital_income_change_b": 56.21006189069981, + "market_income_change_b": -117.24219162591551, + "state_tax_before_refundable_credits_change_b": -8.41858302075647, + "state_refundable_credits_change_b": 0.06649079025959294, + "state_benefits_change_b": 0.14397442152020037, + "state_net_change_b": -8.629048232536265, + "sdi_change_b": -2.1769590308521356, + "state_net_including_sdi_change_b": -10.8060072633884 + }, + { + "group": "90th to 99th percentile", + "labor_income_change_b": -111.19640497409466, + "capital_income_change_b": 145.6915355002647, + "market_income_change_b": 34.495130534942746, + "state_tax_before_refundable_credits_change_b": 4.985899057551191, + "state_refundable_credits_change_b": -3.0751973128435205e-05, + "state_benefits_change_b": 0.0, + "state_net_change_b": 4.985929809524321, + "sdi_change_b": -1.4245737551903215, + "state_net_including_sdi_change_b": 3.5613560543339995 + }, + { + "group": "Top 1%", + "labor_income_change_b": -22.8172465617748, + "capital_income_change_b": 138.9510403958643, + "market_income_change_b": 116.13380538562022, + "state_tax_before_refundable_credits_change_b": 15.850593857458597, + "state_refundable_credits_change_b": -2.1267055334723847e-06, + "state_benefits_change_b": 0.0, + "state_net_change_b": 15.850595984164132, + "sdi_change_b": -0.2966242035822835, + "state_net_including_sdi_change_b": 15.553971780581849 + } + ] + }, + { + "shift_pct": 50, + "allocation": "national", + "labor_income_moved_b": 6206.4793244861885, + "all_government_change_b": -538.3643757954468, + "all_states_net_change_b": 37.67221500875059, + "federal_net_change_b": -576.0365908041974, + "other_states_net_change_b": 4.334469113781374, + "states_gaining": [ + "CA", + "NY", + "WA", + "MA", + "CT", + "DC", + "MT", + "HI", + "VT", + "RI", + "ND" + ], + "states_losing_count": 32, + "state_net_change_b": { + "AK": -0.0035937875129989087, + "AL": -0.7585454455868569, + "AR": -0.09244942703809166, + "AZ": -0.29540477200920795, + "CA": 33.33774589496922, + "CO": -0.053305754130928515, + "CT": 2.125593510645269, + "DC": 0.7965476680380743, + "DE": -0.27352238263808404, + "FL": 0.0, + "GA": -1.1658291891867967, + "HI": 0.20594359875842086, + "IA": -0.23723184107044035, + "ID": -0.08951524023385644, + "IL": -2.1434239473285186, + "IN": -1.4953501785343766, + "KS": -0.30939513811365116, + "KY": -0.8078947734140024, + "LA": -0.3544322110645854, + "MA": 3.865018319046742, + "MD": -2.3752330057452897, + "ME": -0.1714627342155431, + "MI": -2.4820884431023447, + "MN": -1.4098073651362404, + "MO": -2.352075538541522, + "MS": -0.40305244356277276, + "MT": 0.4253527103470452, + "NC": -1.8744257246049065, + "ND": 0.02128833554178035, + "NE": -0.19990558857639198, + "NH": 0.0, + "NJ": -0.2302231514269123, + "NM": -0.05850239659949624, + "NV": 0.0, + "NY": 17.61063201660578, + "OH": -1.43986001420442, + "OK": -0.3049804730618925, + "OR": -0.12873003135054564, + "PA": -1.8152376557466112, + "RI": 0.05227437404289879, + "SC": -0.7616399744929886, + "SD": 0.0, + "TN": 0.0, + "TX": 0.0, + "UT": -0.011761252594205856, + "VA": -1.9385259781557513, + "VT": 0.19326895212420142, + "WA": 6.685295948814244, + "WI": -1.231675592305532, + "WV": -0.37766486889731843, + "WY": 0.0 + }, + "national_components_b": { + "_identity_residual": 2.2876407165527344e-05, + "eitc_change": 32.14777771501349, + "employee_medicare_tax_change": -86.50463796254392, + "employee_social_security_tax_change": -283.4822814731632, + "employer_medicare_tax_change": -86.50463796254392, + "employer_payroll_tax_change": -380.9961954764811, + "employer_social_security_tax_change": -283.4822814731632, + "fed_alternative_minimum_tax_change": 10.705279284056674, + "fed_capital_gains_tax_change": 777.9974516830972, + "fed_income_tax_before_refundable_credits_change": 371.2437304411592, + "fed_income_tax_main_rates_change": -648.02516927002, + "fed_net_investment_income_tax_change": 194.5247967890259, + "fed_nonrefundable_credits_change": -36.04136401740068, + "fed_other_income_tax_items_change": 0.0, + "health_benefits_change": 0.0, + "household_benefits_change": 95.46779674769923, + "household_market_income_change": 0.000181752, + "household_net_income_change": 157.3683391945586, + "household_refundable_tax_credits_change": 52.024695127743605, + "household_tax_before_refundable_credits_change": -9.875688443522948, + "income_tax_refundable_credits_change": 43.69362121073502, + "refundable_ctc_change": 11.417518081703896, + "self_employment_tax_change": -28.50575880564992, + "snap_change": 67.51869914564361, + "ssi_change": 7.09768759961007, + "state_benefits_change": 2.8913163227645455, + "state_refundable_credits_change": 8.331073892325787, + "state_tax_before_refundable_credits_change": 48.89460522384094, + "tanf_change": 1.881322009245634, + "total_change": -538.3643757954468, + "wic_change": 2.8507232494222423 + }, + "california": { + "labor_income_change_b": -899.7231568847778, + "capital_income_change_b": 884.3998259169749, + "market_income_change_b": -15.323338188496903, + "state_tax_before_refundable_credits_change_b": 36.384832686758, + "state_refundable_credits_change_b": 2.128559874277, + "state_benefits_change_b": 0.9185269175117781, + "state_net_change_b": 33.33774589496923, + "sdi_change_b": -11.260789618247376, + "state_net_including_sdi_change_b": 22.076956276721855 + }, + "california_by_group": [ + { + "group": "Bottom 50%", + "labor_income_change_b": -131.05840528193096, + "capital_income_change_b": 32.26824296350594, + "market_income_change_b": -98.79016284897277, + "state_tax_before_refundable_credits_change_b": -0.7622201448529897, + "state_refundable_credits_change_b": 1.8606310797570838, + "state_benefits_change_b": 0.6990473022265064, + "state_net_change_b": -3.3218985268365797, + "sdi_change_b": -1.5153971262117865, + "state_net_including_sdi_change_b": -4.837295653048367 + }, + { + "group": "50th to 90th percentile", + "labor_income_change_b": -433.6306195061013, + "capital_income_change_b": 140.52514931003253, + "market_income_change_b": -293.10547459754287, + "state_tax_before_refundable_credits_change_b": -17.383315972978693, + "state_refundable_credits_change_b": 0.26292298949316517, + "state_benefits_change_b": 0.21947961465094262, + "state_net_change_b": -17.865718577122802, + "sdi_change_b": -5.44239753615176, + "state_net_including_sdi_change_b": -23.30811611327456 + }, + { + "group": "90th to 99th percentile", + "labor_income_change_b": -277.9910148901088, + "capital_income_change_b": 364.2288353358433, + "market_income_change_b": 86.23782171642489, + "state_tax_before_refundable_credits_change_b": 15.085382764875824, + "state_refundable_credits_change_b": 0.005097588503135205, + "state_benefits_change_b": 6.343292924935895e-10, + "state_net_change_b": 15.080285175738357, + "sdi_change_b": -3.5614344420745723, + "state_net_including_sdi_change_b": 11.518850733663784 + }, + { + "group": "Top 1%", + "labor_income_change_b": -57.043117206636836, + "capital_income_change_b": 347.3775983075932, + "market_income_change_b": 290.33447754159374, + "state_tax_before_refundable_credits_change_b": 39.44498603971386, + "state_refundable_credits_change_b": -9.178347638389696e-05, + "state_benefits_change_b": 0.0, + "state_net_change_b": 39.445077823190246, + "sdi_change_b": -0.7415605138092564, + "state_net_including_sdi_change_b": 38.70351730938099 + } + ] + }, + { + "shift_pct": 100, + "allocation": "national", + "labor_income_moved_b": 12412.958648972377, + "all_government_change_b": -655.5925875467775, + "all_states_net_change_b": 184.68446363677913, + "federal_net_change_b": -840.2770511835566, + "other_states_net_change_b": 86.95349448139117, + "states_gaining": [ + "CA", + "NY", + "WA", + "MA", + "CT", + "NJ", + "DC", + "OR", + "HI", + "UT", + "MT", + "CO", + "VT", + "MN", + "NM", + "KS", + "ME", + "RI", + "AR", + "NE", + "IA", + "AZ", + "ID", + "OK", + "WI", + "GA", + "ND" + ], + "states_losing_count": 16, + "state_net_change_b": { + "AK": -0.05987815306414271, + "AL": -0.818882418579669, + "AR": 0.4401893482118077, + "AZ": 0.3555181195880325, + "CA": 97.73096915538795, + "CO": 1.1153349890273019, + "CT": 6.175607468535935, + "DC": 2.474070580737307, + "DE": -0.0782979255025322, + "FL": 0.0, + "GA": 0.11258957412116158, + "HI": 1.234724715632579, + "IA": 0.3709255229474559, + "ID": 0.27775121800493596, + "IL": -1.552993811352232, + "IN": -1.7845084598556982, + "KS": 0.5511482648661881, + "KY": -0.9420201427022161, + "LA": 0.0024138712232447714, + "MA": 10.373001341015737, + "MD": -0.18592121463769185, + "ME": 0.5095298549930756, + "MI": -3.4817086327310798, + "MN": 0.6173204865843013, + "MO": -3.1585701101592125, + "MS": -0.16167778201573754, + "MT": 1.2026495661348449, + "NC": -2.6015730206319776, + "ND": 0.10867488228308665, + "NE": 0.3965123090431535, + "NH": 0.0, + "NJ": 6.145664837456626, + "NM": 0.57124480041525, + "NV": 0.0, + "NY": 52.12523155521271, + "OH": -0.8946254715354424, + "OK": 0.17380735379055912, + "OR": 2.060764025631767, + "PA": -2.0838418605195503, + "RI": 0.4454540662877673, + "SC": -0.10395773322823906, + "SD": 0.0, + "TN": 0.0, + "TX": 0.0, + "UT": 1.2340733463179092, + "VA": -0.25679243962313525, + "VT": 0.7219094126532757, + "WA": 15.654904164667885, + "WI": 0.12947012812704828, + "WV": -0.46174214598121566, + "WY": 0.0 + }, + "national_components_b": { + "_identity_residual": 1.9556870849609374e-05, + "eitc_change": -64.49903897693396, + "employee_medicare_tax_change": -173.00927592508785, + "employee_social_security_tax_change": -623.9699959913409, + "employer_medicare_tax_change": -173.00927592508785, + "employer_payroll_tax_change": -865.4879711032152, + "employer_social_security_tax_change": -623.9699959913409, + "fed_alternative_minimum_tax_change": 19.806632976334324, + "fed_capital_gains_tax_change": 1564.7028432849556, + "fed_income_tax_before_refundable_credits_change": 1234.6731516275684, + "fed_income_tax_main_rates_change": -815.0108624199042, + "fed_net_investment_income_tax_change": 385.01593875553186, + "fed_nonrefundable_credits_change": -80.15858866925531, + "fed_other_income_tax_items_change": 0.0, + "health_benefits_change": 0.0, + "household_benefits_change": 410.3304784042629, + "household_market_income_change": 0.0003720635, + "household_net_income_change": -209.89503104980858, + "household_refundable_tax_credits_change": -102.70960349510452, + "household_tax_before_refundable_credits_change": 517.5162584655961, + "income_tax_refundable_credits_change": -102.45721242524593, + "refundable_ctc_change": -38.10118620947614, + "self_employment_tax_change": -58.14741555532982, + "snap_change": 330.10036509758015, + "ssi_change": 21.82785080596483, + "state_benefits_change": 8.801013827791138, + "state_refundable_credits_change": -0.25239107169264985, + "state_tax_before_refundable_credits_change": 193.23308639287757, + "tanf_change": 8.024421456210268, + "total_change": -655.5925875467775, + "wic_change": 5.360402101499574 + }, + "california": { + "labor_income_change_b": -1799.4463137695557, + "capital_income_change_b": 1768.799650449747, + "market_income_change_b": -30.646678149203705, + "state_tax_before_refundable_credits_change_b": 101.30562568390637, + "state_refundable_credits_change_b": -0.12512064244271562, + "state_benefits_change_b": 3.699777170961141, + "state_net_change_b": 97.73096915538795, + "sdi_change_b": -22.521579236494752, + "state_net_including_sdi_change_b": 75.20938991889321 + }, + "california_by_group": [ + { + "group": "Bottom 50%", + "labor_income_change_b": -262.1168105638619, + "capital_income_change_b": 64.53648586386407, + "market_income_change_b": -197.58032398962942, + "state_tax_before_refundable_credits_change_b": 0.5069681574061998, + "state_refundable_credits_change_b": -0.5090986921118821, + "state_benefits_change_b": 1.9109382155892227, + "state_net_change_b": -0.8948713660711405, + "sdi_change_b": -3.030794252423573, + "state_net_including_sdi_change_b": -3.9256656184947136 + }, + { + "group": "50th to 90th percentile", + "labor_income_change_b": -867.2612390122026, + "capital_income_change_b": 281.0502995547615, + "market_income_change_b": -586.2109434767776, + "state_tax_before_refundable_credits_change_b": -17.286773730960874, + "state_refundable_credits_change_b": 0.3596086235026163, + "state_benefits_change_b": 1.4612476203792863, + "state_net_change_b": -19.107629974842773, + "sdi_change_b": -10.88479507230352, + "state_net_including_sdi_change_b": -29.99242504714629 + }, + { + "group": "90th to 99th percentile", + "labor_income_change_b": -555.9820297802175, + "capital_income_change_b": 728.4576724718384, + "market_income_change_b": 172.4756453650971, + "state_tax_before_refundable_credits_change_b": 39.44166580158406, + "state_refundable_credits_change_b": 0.025517961247900196, + "state_benefits_change_b": 0.32759133499263254, + "state_net_change_b": 39.08855650534352, + "sdi_change_b": -7.1228688841491445, + "state_net_including_sdi_change_b": 31.965687621194373 + }, + { + "group": "Top 1%", + "labor_income_change_b": -114.08623441327367, + "capital_income_change_b": 694.755192559283, + "market_income_change_b": 580.6689439521062, + "state_tax_before_refundable_credits_change_b": 78.643765455877, + "state_refundable_credits_change_b": -0.001148535081349965, + "state_benefits_change_b": 0.0, + "state_net_change_b": 78.64491399095834, + "sdi_change_b": -1.4831210276185127, + "state_net_including_sdi_change_b": 77.16179296333983 + } + ] + } + ], + "within_state_levels": [ + { + "shift_pct": 10, + "allocation": "within_state", + "labor_income_moved_b": 1241.2958648972376, + "all_government_change_b": -125.26436439233863, + "all_states_net_change_b": 7.913263383006146, + "federal_net_change_b": -133.17762777534477, + "other_states_net_change_b": 2.520201348574501, + "states_gaining": [ + "CA", + "WA", + "NJ", + "MA", + "NY", + "MD", + "MN", + "CT", + "OH", + "OR", + "RI", + "VT", + "DC", + "ME", + "NM", + "ID", + "GA", + "HI", + "NE", + "ND" + ], + "states_losing_count": 18, + "state_net_change_b": { + "AK": -0.0010001636160389036, + "AL": 0.0013896771990938187, + "AR": -0.059399669265742305, + "AZ": -0.06460382228043804, + "CA": 5.393062034431645, + "CO": -0.2124574610442686, + "CT": 0.09204463899140322, + "DC": 0.032600186391528486, + "DE": -0.007085432371897644, + "FL": 0.0, + "GA": 0.01650745750332974, + "HI": 0.011017531763328701, + "IA": 0.0038490348273049744, + "ID": 0.01814860132099128, + "IL": -0.13042160787025464, + "IN": -0.0054988782831175585, + "KS": -0.00041111788275676966, + "KY": -0.025379995833544732, + "LA": 0.0021455494226873665, + "MA": 0.6050703803157403, + "MD": 0.3032133892773857, + "ME": 0.023490650411194088, + "MI": -0.1400858203210299, + "MN": 0.2513267120694355, + "MO": -0.4397434328513943, + "MS": -0.005409541850484371, + "MT": -0.027865532362223448, + "NC": -0.00585492270089221, + "ND": 0.00678784943534708, + "NE": 0.01097409716372574, + "NH": 0.0, + "NJ": 0.7990923997788806, + "NM": 0.022127173536007048, + "NV": 0.0, + "NY": 0.41376895768113325, + "OH": 0.07305905195708656, + "OK": -0.016071151950888648, + "OR": 0.05483933748887584, + "PA": -0.07225669176805102, + "RI": 0.045826249205616655, + "SC": -0.10700575427823734, + "SD": 0.0, + "TN": 0.0, + "TX": 0.0, + "UT": -0.05114343273535156, + "VA": -0.008697095787728697, + "VT": 0.037468579759875255, + "WA": 1.1109413435975288, + "WI": -0.03544130896609038, + "WV": 0.00034533349743168896, + "WY": 0.0 + }, + "national_components_b": { + "_identity_residual": -4.374418563842774e-06, + "eitc_change": 6.729188016873032, + "employee_medicare_tax_change": -17.300927508987215, + "employee_social_security_tax_change": -52.55146390560706, + "employer_medicare_tax_change": -17.300927508987215, + "employer_payroll_tax_change": -71.51300997710877, + "employer_social_security_tax_change": -52.55146390560706, + "fed_alternative_minimum_tax_change": 2.8154100604136962, + "fed_capital_gains_tax_change": 150.41199389455866, + "fed_income_tax_before_refundable_credits_change": 41.48823820576855, + "fed_income_tax_main_rates_change": -154.5070249086958, + "fed_net_investment_income_tax_change": 37.62904860535333, + "fed_nonrefundable_credits_change": -5.138814764533936, + "fed_other_income_tax_items_change": 0.0, + "health_benefits_change": 0.0, + "household_benefits_change": 12.020978310958741, + "household_market_income_change": 1.396251171875e-05, + "household_net_income_change": 53.75137275216016, + "household_refundable_tax_credits_change": 10.352463128370744, + "household_tax_before_refundable_credits_change": -31.37791297590039, + "income_tax_refundable_credits_change": 9.076418252026, + "refundable_ctc_change": 2.2908059714598235, + "self_employment_tax_change": -5.570886517814468, + "snap_change": 8.46946450199199, + "ssi_change": 0.8550276494390641, + "state_benefits_change": 0.30788852483324053, + "state_refundable_credits_change": 1.2760449828070641, + "state_tax_before_refundable_credits_change": 9.497196890646485, + "tanf_change": 0.11880633463968182, + "total_change": -125.26436439233863, + "wic_change": 0.5935199471899671 + }, + "california": { + "labor_income_change_b": -179.94462918295312, + "capital_income_change_b": 179.9446292048434, + "market_income_change_b": 7.861856775283813e-06, + "state_tax_before_refundable_credits_change_b": 5.748144934935302, + "state_refundable_credits_change_b": 0.2402459669675764, + "state_benefits_change_b": 0.11483693353605211, + "state_net_change_b": 5.393062034431675, + "sdi_change_b": -2.2521579438558437, + "state_net_including_sdi_change_b": 3.140904090575831 + }, + "california_by_group": [ + { + "group": "Bottom 50%", + "labor_income_change_b": -26.211679848868705, + "capital_income_change_b": 6.56546615433297, + "market_income_change_b": -19.646214467310497, + "state_tax_before_refundable_credits_change_b": -0.4182455700189991, + "state_refundable_credits_change_b": 0.21551678488593734, + "state_benefits_change_b": 0.04284976760733898, + "state_net_change_b": -0.6766121225122754, + "sdi_change_b": -0.30307941405147765, + "state_net_including_sdi_change_b": -0.9796915365637531 + }, + { + "group": "50th to 90th percentile", + "labor_income_change_b": -86.72612153726766, + "capital_income_change_b": 28.59198452746559, + "market_income_change_b": -58.13413873967788, + "state_tax_before_refundable_credits_change_b": -4.358702357898429, + "state_refundable_credits_change_b": 0.02337243353449629, + "state_benefits_change_b": 0.07198716592871314, + "state_net_change_b": -4.454061957361639, + "sdi_change_b": -1.0884794777788802, + "state_net_including_sdi_change_b": -5.542541435140519 + }, + { + "group": "90th to 99th percentile", + "labor_income_change_b": -55.59820425705453, + "capital_income_change_b": 74.10791088438187, + "market_income_change_b": 18.509714427505873, + "state_tax_before_refundable_credits_change_b": 2.439380812044302, + "state_refundable_credits_change_b": 0.001328989603143694, + "state_benefits_change_b": 0.0, + "state_net_change_b": 2.4380518224411585, + "sdi_change_b": -0.7122869353925602, + "state_net_including_sdi_change_b": 1.7257648870485984 + }, + { + "group": "Top 1%", + "labor_income_change_b": -11.408623539762246, + "capital_income_change_b": 70.67926763866299, + "market_income_change_b": 59.27064664133929, + "state_tax_before_refundable_credits_change_b": 8.085712050808429, + "state_refundable_credits_change_b": 2.775894399909064e-05, + "state_benefits_change_b": 0.0, + "state_net_change_b": 8.08568429186443, + "sdi_change_b": -0.14831211663292593, + "state_net_including_sdi_change_b": 7.937372175231504 + } + ], + "labor_income_moved_by_state_b": { + "AK": 2.521538154628764, + "AL": 13.565450541466157, + "AR": 7.9015235890507824, + "AZ": 23.61963366168758, + "CA": 179.94463137695556, + "CO": 25.129929604959415, + "CT": 17.993157944661675, + "DC": 3.9972809901654074, + "DE": 3.4786235569621993, + "FL": 77.14741264194939, + "GA": 35.432555197100015, + "HI": 4.726746487444649, + "IA": 10.15783524666676, + "ID": 5.692732862043959, + "IL": 49.33457747060286, + "IN": 21.74810707240535, + "KS": 9.494653830963681, + "KY": 12.276695378625437, + "LA": 12.093852439829964, + "MA": 36.050163833311004, + "MD": 26.337377638811432, + "ME": 4.598468371218562, + "MI": 32.031967559401316, + "MN": 22.938264459890107, + "MO": 19.157726122680668, + "MS": 6.845376766845728, + "MT": 3.345930638487266, + "NC": 35.61990782201757, + "ND": 3.0822507101493395, + "NE": 6.63509846288188, + "NH": 6.523574711772908, + "NJ": 45.21062868403341, + "NM": 5.398404132844462, + "NV": 10.6864911434394, + "NY": 85.12041573328673, + "OH": 38.10301744929049, + "OK": 10.56998923094113, + "OR": 14.686559060196998, + "PA": 46.92827123600335, + "RI": 4.172942358122625, + "SC": 15.671578158479457, + "SD": 2.8694373505044743, + "TN": 23.12772375660853, + "TX": 106.99501194129907, + "UT": 11.921521411683353, + "VA": 35.40469897371058, + "VT": 2.25303081150049, + "WA": 35.84089624843171, + "WI": 20.542410508772893, + "WV": 4.477394375585429, + "WY": 1.8923971868658216 + } + }, + { + "shift_pct": 50, + "allocation": "within_state", + "labor_income_moved_b": 6206.4793244861885, + "all_government_change_b": -518.2930917660808, + "all_states_net_change_b": 62.99733679603278, + "federal_net_change_b": -581.2904285621136, + "other_states_net_change_b": 27.83119568666188, + "states_gaining": [ + "CA", + "NY", + "WA", + "NJ", + "MA", + "MD", + "MN", + "GA", + "OR", + "CT", + "WI", + "OH", + "RI", + "NM", + "VT", + "DC", + "VA", + "ID", + "HI", + "ME", + "KS", + "MS", + "IA", + "ND", + "LA", + "DE", + "WV" + ], + "states_losing_count": 15, + "state_net_change_b": { + "AK": -0.00025726358800537137, + "AL": -0.11858608132779122, + "AR": -0.2655640640300379, + "AZ": -0.20894129513900686, + "CA": 35.166141109370905, + "CO": -0.8693680637429371, + "CT": 0.510084477231754, + "DC": 0.22615620272152687, + "DE": 0.049862250550842376, + "FL": 0.0, + "GA": 0.6314579942054609, + "HI": 0.18966957329089465, + "IA": 0.09549162228533788, + "ID": 0.197059918567868, + "IL": -0.44553842743666994, + "IN": -0.2891100755710161, + "KS": 0.10585501599225092, + "KY": -0.07747406644715023, + "LA": 0.06656804369325685, + "MA": 3.250416409551159, + "MD": 2.553458502497449, + "ME": 0.16237621177251924, + "MI": -0.715946006424873, + "MN": 1.8990312924121862, + "MO": -1.9657084209392288, + "MS": 0.10517712091433525, + "MT": -0.1122326219117023, + "NC": -0.22649935644481564, + "ND": 0.09471775642944819, + "NE": -0.004768520915495873, + "NH": 0.0, + "NJ": 5.425104517941073, + "NM": 0.25454600598888555, + "NV": 0.0, + "NY": 9.194866304748977, + "OH": 0.2959853649332886, + "OK": -0.015187227422411383, + "OR": 0.5975983404064942, + "PA": -0.2770256892193844, + "RI": 0.2707105026079905, + "SC": -0.23386955762566566, + "SD": 0.0, + "TN": 0.0, + "TX": 0.0, + "UT": -0.12808728375798417, + "VA": 0.19917485125730292, + "VT": 0.24818330923242754, + "WA": 6.6904154369121684, + "WI": 0.42331654086038506, + "WV": 0.04807614160076848, + "WY": 0.0 + }, + "national_components_b": { + "_identity_residual": 1.4037480529785157e-05, + "eitc_change": 32.052335559085144, + "employee_medicare_tax_change": -86.50463796254392, + "employee_social_security_tax_change": -283.4822814731632, + "employer_medicare_tax_change": -86.50463796254392, + "employer_payroll_tax_change": -380.9961954764811, + "employer_social_security_tax_change": -283.4822814731632, + "fed_alternative_minimum_tax_change": 12.140251303617298, + "fed_capital_gains_tax_change": 767.9431140066079, + "fed_income_tax_before_refundable_credits_change": 365.46464762285353, + "fed_income_tax_main_rates_change": -643.3633241327026, + "fed_net_investment_income_tax_change": 192.85873104169664, + "fed_nonrefundable_credits_change": -35.88587008792402, + "fed_other_income_tax_items_change": 0.0, + "health_benefits_change": 0.0, + "household_benefits_change": 95.21206053430664, + "household_market_income_change": -2.3104859375e-05, + "household_net_income_change": 137.29685914725977, + "household_refundable_tax_credits_change": 51.60664904361675, + "household_tax_before_refundable_credits_change": 9.521813288323731, + "income_tax_refundable_credits_change": 43.411500767380616, + "refundable_ctc_change": 11.230572226252221, + "self_employment_tax_change": -28.50575880564992, + "snap_change": 67.44048118640079, + "ssi_change": 7.0948036445380405, + "state_benefits_change": 2.87870333757243, + "state_refundable_credits_change": 8.195148284033078, + "state_tax_before_refundable_credits_change": 74.07118841763831, + "tanf_change": 1.873954611441908, + "total_change": -518.2930917660808, + "wic_change": 2.7927354330325977 + }, + "california": { + "labor_income_change_b": -899.7231568847778, + "capital_income_change_b": 899.7231581831453, + "market_income_change_b": 1.2181578811645508e-05, + "state_tax_before_refundable_credits_change_b": 38.208239930026735, + "state_refundable_credits_change_b": 2.1276283674584833, + "state_benefits_change_b": 0.9144704531973168, + "state_net_change_b": 35.16614110937093, + "sdi_change_b": -11.260789618247376, + "state_net_including_sdi_change_b": 23.905351491123557 + }, + "california_by_group": [ + { + "group": "Bottom 50%", + "labor_income_change_b": -131.05840528193096, + "capital_income_change_b": 32.82733090555146, + "market_income_change_b": -98.231075273647, + "state_tax_before_refundable_credits_change_b": -0.7362924841283481, + "state_refundable_credits_change_b": 1.85971916722936, + "state_benefits_change_b": 0.6951572944972174, + "state_net_change_b": -3.2911689458549263, + "sdi_change_b": -1.5153971262117865, + "state_net_including_sdi_change_b": -4.806566072066713 + }, + { + "group": "50th to 90th percentile", + "labor_income_change_b": -433.6306195061013, + "capital_income_change_b": 142.95992210076025, + "market_income_change_b": -290.6707044219503, + "state_tax_before_refundable_credits_change_b": -17.169114071139298, + "state_refundable_credits_change_b": 0.2629033952059039, + "state_benefits_change_b": 0.21931315819159833, + "state_net_change_b": -17.6513306245368, + "sdi_change_b": -5.44239753615176, + "state_net_including_sdi_change_b": -23.09372816068856 + }, + { + "group": "90th to 99th percentile", + "labor_income_change_b": -277.9910148901088, + "capital_income_change_b": 370.5395538036873, + "market_income_change_b": 92.54853935680102, + "state_tax_before_refundable_credits_change_b": 15.867143110989112, + "state_refundable_credits_change_b": 0.005097588499603407, + "state_benefits_change_b": 5.085010380302037e-10, + "state_net_change_b": 15.862045521981006, + "sdi_change_b": -3.5614344420745723, + "state_net_including_sdi_change_b": 12.300611079906433 + }, + { + "group": "Top 1%", + "labor_income_change_b": -57.043117206636836, + "capital_income_change_b": 353.39635137314616, + "market_income_change_b": 296.35325252037507, + "state_tax_before_refundable_credits_change_b": 40.24650337430528, + "state_refundable_credits_change_b": -9.178347638389696e-05, + "state_benefits_change_b": 0.0, + "state_net_change_b": 40.24659515778166, + "sdi_change_b": -0.7415605138092564, + "state_net_including_sdi_change_b": 39.50503464397241 + } + ], + "labor_income_moved_by_state_b": { + "AK": 12.607690773143817, + "AL": 67.82725270733077, + "AR": 39.507617945253905, + "AZ": 118.09816830843788, + "CA": 899.723156884778, + "CO": 125.64964802479707, + "CT": 89.96578972330836, + "DC": 19.986404950827033, + "DE": 17.393117784810997, + "FL": 385.7370632097469, + "GA": 177.16277598550002, + "HI": 23.633732437223244, + "IA": 50.7891762333338, + "ID": 28.463664310219794, + "IL": 246.6728873530143, + "IN": 108.74053536202672, + "KS": 47.473269154818404, + "KY": 61.38347689312719, + "LA": 60.46926219914981, + "MA": 180.25081916655503, + "MD": 131.68688819405713, + "ME": 22.992341856092814, + "MI": 160.15983779700656, + "MN": 114.69132229945052, + "MO": 95.78863061340336, + "MS": 34.22688383422864, + "MT": 16.729653192436327, + "NC": 178.09953911008782, + "ND": 15.411253550746698, + "NE": 33.1754923144094, + "NH": 32.61787355886454, + "NJ": 226.05314342016706, + "NM": 26.99202066422231, + "NV": 53.43245571719698, + "NY": 425.6020786664336, + "OH": 190.51508724645245, + "OK": 52.84994615470565, + "OR": 73.43279530098499, + "PA": 234.64135618001677, + "RI": 20.86471179061312, + "SC": 78.3578907923973, + "SD": 14.347186752522369, + "TN": 115.63861878304266, + "TX": 534.9750597064952, + "UT": 59.60760705841676, + "VA": 177.0234948685529, + "VT": 11.265154057502444, + "WA": 179.20448124215855, + "WI": 102.71205254386447, + "WV": 22.386971877927145, + "WY": 9.461985934329109 + } + } + ] +} \ No newline at end of file From ba5e0ea3ca65e25d06419fba591774d9d138e04a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 18:15:02 -0500 Subject: [PATCH 06/17] Let the California chart drop the other-states line The all-other-states path depends on where new capital income lands, so `--california-only` charts California with and without SDI alone. End markers no longer clip at 0% and 100%, and the note says the shift holds national income fixed. Co-Authored-By: Claude Opus 5.5 --- analysis/california_chart.py | 26 +++++++++++++++++-------- analysis/tests/test_california_shift.py | 15 ++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/analysis/california_chart.py b/analysis/california_chart.py index 5feb560..2fa3e76 100644 --- a/analysis/california_chart.py +++ b/analysis/california_chart.py @@ -5,7 +5,7 @@ Usage: python -m analysis.california_chart --source sweep --out PATH.png - python -m analysis.california_chart --source run --run PATH.json --out PATH.png + python -m analysis.california_chart --source run --run PATH.json --out PATH.png [--california-only] """ from __future__ import annotations @@ -78,20 +78,23 @@ def draw(xs, ca, ca_sdi, other, out, source_note): ax.set_facecolor(SURFACE) ax.axhline(0, color=TEXT_SECONDARY, linewidth=0.8, zorder=1) + # clip_on=False keeps the end markers at 0% and 100% whole. ax.plot(xs, ca, color=CALIFORNIA, linewidth=2, marker="o", markersize=5, - label="California", zorder=3) + label="California", zorder=3, clip_on=False) if ca_sdi is not None: ax.plot(xs, ca_sdi, color=CALIFORNIA, linewidth=2, linestyle=(0, (4, 3)), marker="o", markersize=4, markerfacecolor=SURFACE, - label="California, including SDI contributions", zorder=3) - ax.plot(xs, other, color=OTHER_STATES, linewidth=2, marker="o", markersize=5, - label="All other states combined", zorder=3) + label="California, including SDI contributions", zorder=3, clip_on=False) + if other is not None: + ax.plot(xs, other, color=OTHER_STATES, linewidth=2, marker="o", markersize=5, + label="All other states combined", zorder=3, clip_on=False) # Direct labels at the line ends, in text ink beside the colored mark. ends = [(ca[-1], "California")] if ca_sdi is not None: ends.append((ca_sdi[-1], "California, including SDI")) - ends.append((other[-1], "All other states")) + if other is not None: + ends.append((other[-1], "All other states")) for y, text in ends: ax.annotate(f"{text} ${y:,.0f}B", xy=(xs[-1], y), xytext=(6, 0), textcoords="offset points", va="center", fontsize=9.5, @@ -116,8 +119,8 @@ def draw(xs, ca, ca_sdi, other, out, source_note): fontsize=10, color=TEXT_SECONDARY, ha="left") fig.text(0.06, 0.02, "Source: PolicyEngine static microsimulation of current law on Microcosm " - f"data ({source_note}).\nWe hold total income fixed and add new capital " - "income in proportion to existing holdings.", + f"data ({source_note}).\nWe hold national income fixed and add new capital " + "income in proportion to existing holdings nationwide.", fontsize=7.5, color=TEXT_SECONDARY, ha="left") fig.subplots_adjust(left=0.1, right=0.76, top=0.86, bottom=0.16) fig.savefig(out, facecolor=SURFACE) @@ -129,12 +132,19 @@ def main(): parser.add_argument("--source", choices=["sweep", "run"], default="sweep") parser.add_argument("--run", default=None, help="california_shift output JSON") parser.add_argument("--out", required=True) + parser.add_argument( + "--california-only", + action="store_true", + help="omit the all-other-states line, whose path depends on where new capital income lands", + ) args = parser.parse_args() if args.source == "sweep": series = series_from_sweep() else: series = series_from_run(args.run) xs, ca, ca_sdi, other, note = series + if args.california_only: + other = None print(draw(xs, ca, ca_sdi, other, args.out, note)) diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 753bafb..322dd4a 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -219,6 +219,21 @@ def test_chart_series_from_run(tmp_path): assert "9.9.9" in note +def test_chart_draws_california_only(tmp_path): + pytest.importorskip("matplotlib") + import matplotlib.pyplot as plt + + from analysis.california_chart import draw + + out = tmp_path / "chart.png" + draw([0, 10, 50], [0.0, 5.0, 33.0], [0.0, 3.0, 22.0], None, str(out), "policyengine-us 9.9.9, 2026") + fig = plt.gcf() + labels = [line.get_label() for line in fig.axes[0].get_lines() if not line.get_label().startswith("_")] + plt.close(fig) + assert out.stat().st_size > 0 + assert labels == ["California", "California, including SDI contributions"] + + COMPONENT_KEYS = ( "household_tax_before_refundable_credits", "household_refundable_tax_credits", From 1c16a8c55b64779decd9decf12731b73a869eb4f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 18:16:52 -0500 Subject: [PATCH 07/17] Label California's cross-state part as income moving between states Under the national allocation California's market income falls, so its cross-state part comes from income leaving the state, not arriving. Co-Authored-By: Claude Opus 5.5 --- analysis/california_summary.py | 2 +- analysis/tests/test_california_shift.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/analysis/california_summary.py b/analysis/california_summary.py index 7c746e0..9aff9f4 100644 --- a/analysis/california_summary.py +++ b/analysis/california_summary.py @@ -214,7 +214,7 @@ def _within_state_lines(run): lines.append( f" California under the national allocation {nat:+.2f}B: " f"{ca['state_net_change_b']:+.2f}B from income moving within the state and " - f"{nat - ca['state_net_change_b']:+.2f}B from income moving in from other states " + f"{nat - ca['state_net_change_b']:+.2f}B from income moving between states " f"(market income {n['california']['market_income_change_b']:+.1f}B)" ) total_tax = ca["state_tax_before_refundable_credits_change_b"] diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 322dd4a..45a7ad6 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -345,7 +345,7 @@ def test_summary_reads_a_run(): ] text = summarize(run, published) assert "Within-state variant" in text - assert "+3.50B from income moving within the state and +1.50B from income moving in" in text + assert "+3.50B from income moving within the state and +1.50B from income moving between states" in text assert "Five lowest changes: TX -4.00B" in text From 697a753d17a243a2fcde75b0d7cbfad796ff1874 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 14:09:09 -0500 Subject: [PATCH 08/17] Describe the California chart's allocation and netting as the code runs them The note said the shift adds capital income "in proportion to existing holdings"; `_apply_shift` scales the interest, dividends, capital gains and rent people already receive. The subtitle's "state-funded benefits" read as every state program, but California's net subtracts only its refundable credits and the nine programs `household_state_benefits` lists for it, none of them Medi-Cal or CalWORKs; the California-only chart now says so. The x-axis label drops a reduced passive. Co-Authored-By: Claude Opus 5.5 --- analysis/california_chart.py | 39 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/analysis/california_chart.py b/analysis/california_chart.py index 2fa3e76..cb6814f 100644 --- a/analysis/california_chart.py +++ b/analysis/california_chart.py @@ -32,6 +32,19 @@ TEXT_SECONDARY = "#52514e" GRID = "#e6e5e1" +# The shift scales the six variables in ``constants.CAPITAL_INCOME_VARS``. +ALLOCATION_NOTE = ( + "We move labor income to capital income nationwide, in proportion to the interest, " + "dividends, capital gains and rent people already receive." +) +# ``fiscal.state_revenue_components`` nets California's refundable credits +# (gov.states.ca.tax.income.credits.refundable) and its entries in +# gov.household.household_state_benefits, which hold no CalWORKs or Medi-Cal. +CALIFORNIA_MEASURE_NOTE = ( + "We net out refundable credits like CalEITC and benefits like the SSI supplement, " + "and leave out Medi-Cal and CalWORKs." +) + def series_from_sweep(path=SWEEP_PATH): data = json.load(open(path)) @@ -65,7 +78,7 @@ def series_from_run(path): return xs, ca, ca_sdi, other, note -def draw(xs, ca, ca_sdi, other, out, source_note): +def draw(xs, ca, ca_sdi, other, out, source_note, measure_note=None): plt.rcParams.update( { "font.family": "sans-serif", @@ -104,7 +117,7 @@ def draw(xs, ca, ca_sdi, other, out, source_note): ax.set_xticks(range(0, 101, 20)) ax.xaxis.set_major_formatter(FuncFormatter(lambda v, _: f"{v:.0f}%")) ax.yaxis.set_major_formatter(FuncFormatter(lambda v, _: f"${v:,.0f}B")) - ax.set_xlabel("Share of labor income shifted to capital income", color=TEXT_SECONDARY) + ax.set_xlabel("Share of labor income that moves to capital income", color=TEXT_SECONDARY) ax.grid(axis="y", color=GRID, linewidth=0.8) ax.tick_params(colors=TEXT_SECONDARY, length=0) for spine in ax.spines.values(): @@ -115,14 +128,18 @@ def draw(xs, ca, ca_sdi, other, out, source_note): fontsize=14, fontweight="bold", color=TEXT_PRIMARY, ha="left") fig.text(0.06, 0.915, "Change in state tax revenue, net of refundable state credits and " - "state-funded benefits, $ billions", + "selected state benefits, $ billions", fontsize=10, color=TEXT_SECONDARY, ha="left") - fig.text(0.06, 0.02, - "Source: PolicyEngine static microsimulation of current law on Microcosm " - f"data ({source_note}).\nWe hold national income fixed and add new capital " - "income in proportion to existing holdings nationwide.", - fontsize=7.5, color=TEXT_SECONDARY, ha="left") - fig.subplots_adjust(left=0.1, right=0.76, top=0.86, bottom=0.16) + notes = [ + "Source: PolicyEngine static microsimulation of current law on Microcosm " + f"data ({source_note}).", + ALLOCATION_NOTE, + ] + if measure_note: + notes.append(measure_note) + fig.text(0.06, 0.02, "\n".join(notes), fontsize=7.5, color=TEXT_SECONDARY, + ha="left", linespacing=1.4) + fig.subplots_adjust(left=0.1, right=0.76, top=0.86, bottom=0.14 + 0.025 * len(notes)) fig.savefig(out, facecolor=SURFACE) return out @@ -143,9 +160,11 @@ def main(): else: series = series_from_run(args.run) xs, ca, ca_sdi, other, note = series + measure_note = None if args.california_only: other = None - print(draw(xs, ca, ca_sdi, other, args.out, note)) + measure_note = CALIFORNIA_MEASURE_NOTE + print(draw(xs, ca, ca_sdi, other, args.out, note, measure_note)) if __name__ == "__main__": From 0aaed3246e9d4fa92d8ae820cc5682dc9d921f54 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 14:09:40 -0500 Subject: [PATCH 09/17] Say which capital income and state benefits the run description means Co-Authored-By: Claude Opus 5.5 --- analysis/california_shift.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/analysis/california_shift.py b/analysis/california_shift.py index a4de38e..94a2193 100644 --- a/analysis/california_shift.py +++ b/analysis/california_shift.py @@ -399,10 +399,14 @@ def run( "description": ( "Positive employment and self-employment income fall by the shift " "share; the same weighted total is added to positive capital income " - "in proportion to existing holdings. Static, current law, no " + "(gains, interest, dividends and rent) in proportion to the capital " + "income each person already receives. Static, current law, no " "behavioral response. State net revenue is state income tax plus use " - "tax, less refundable state credits and state-funded benefits; " - "California's employee SDI contribution is reported separately. " + "tax, less refundable state credits and the state benefits in " + "gov.household.household_state_benefits (for California: SSP, CAPI, " + "child care subsidies, CVRP, CARE, FERA and three Los Angeles " + "programs; no Medi-Cal or CalWORKs); California's employee SDI " + "contribution is reported separately. " "'levels' allocate the new capital income across the country, as the " "published sweep does; 'within_state_levels' give each state's freed " "labor income to that state's own capital holders." From 90fb3671b2eb07e87dcfdd14df453af8737b56b4 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 14:17:02 -0500 Subject: [PATCH 10/17] Chart subtitle: "some state benefits"; name state_revenue_delta in the comment "Selected" read as a reduced passive in the chart subtitle. The comment above CALIFORNIA_MEASURE_NOTE now names fiscal.state_revenue_delta, which subtracts the refundable credits and state benefits. Co-Authored-By: Claude Opus 5.5 --- analysis/california_chart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analysis/california_chart.py b/analysis/california_chart.py index cb6814f..715de66 100644 --- a/analysis/california_chart.py +++ b/analysis/california_chart.py @@ -37,7 +37,7 @@ "We move labor income to capital income nationwide, in proportion to the interest, " "dividends, capital gains and rent people already receive." ) -# ``fiscal.state_revenue_components`` nets California's refundable credits +# ``fiscal.state_revenue_delta`` nets California's refundable credits # (gov.states.ca.tax.income.credits.refundable) and its entries in # gov.household.household_state_benefits, which hold no CalWORKs or Medi-Cal. CALIFORNIA_MEASURE_NOTE = ( @@ -128,7 +128,7 @@ def draw(xs, ca, ca_sdi, other, out, source_note, measure_note=None): fontsize=14, fontweight="bold", color=TEXT_PRIMARY, ha="left") fig.text(0.06, 0.915, "Change in state tax revenue, net of refundable state credits and " - "selected state benefits, $ billions", + "some state benefits, $ billions", fontsize=10, color=TEXT_SECONDARY, ha="left") notes = [ "Source: PolicyEngine static microsimulation of current law on Microcosm " From e512038d560206b7fefd330b750b1b9999a35933 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 14:51:12 -0500 Subject: [PATCH 11/17] Count Medicaid, CHIP, ACA credits and TANF in the California accounting The sweep's state measure leaves out Medi-Cal and CalWORKs. Household net income excludes health benefits (include_health_benefits_in_net_income is false), so Medicaid, CHIP, the Medicare Savings Programs and ACA premium tax credits sit in no government's total, and tanf sits in household_benefits but in no state's line, so the sweep books CalWORKs as federal. The new *_with_health_tanf fields charge each state its share of Medicaid, CHIP and MSP costs under policyengine-us's federal matching rules (state_benefit_cost), the whole cost of coverage it funds alone for people whose immigration status rules out federal matching (policyengine-us books a federal share for them), its own health subsidies and its whole TANF change, since the federal TANF grant is fixed. The all-government total adds every program in household_health_benefits, and federal is what the states leave. The run refuses a model whose health lists differ from the ones classified here, and checks that the federal and state shares add up to the costs. Property tests cover conservation of health spending and the federal-plus- states identity; differential tests check the immigration-status mirror and the state-only move against policyengine-us 2.2.1. Co-Authored-By: Claude Opus 5.5 --- analysis/california_chart.py | 70 +++++- analysis/california_shift.py | 235 ++++++++++++++++- analysis/california_summary.py | 32 ++- analysis/tests/test_california_shift.py | 322 +++++++++++++++++++++++- 4 files changed, 635 insertions(+), 24 deletions(-) diff --git a/analysis/california_chart.py b/analysis/california_chart.py index 715de66..5fa9523 100644 --- a/analysis/california_chart.py +++ b/analysis/california_chart.py @@ -44,6 +44,42 @@ "We net out refundable credits like CalEITC and benefits like the SSI supplement, " "and leave out Medi-Cal and CalWORKs." ) +# ``california_shift._health_tanf_accounting`` adds California's share of +# Medi-Cal, CHIP and Medicare Savings Program costs, the coverage it funds +# alone, its premium subsidy and CalWORKs. +CALIFORNIA_HEALTH_TANF_NOTE = ( + "We net out refundable credits like CalEITC, benefits like the SSI supplement, " + "CalWORKs, and California's share of Medi-Cal costs." +) + +#: What each measure reads from a run (California, California with SDI, all +#: other states) and how the chart describes it. +MEASURES = { + "study": { + "fields": ( + "state_net_change_b", + "state_net_including_sdi_change_b", + "other_states_net_change_b", + ), + "subtitle": ( + "Change in state tax revenue, net of refundable state credits and " + "some state benefits, $ billions" + ), + "california_note": CALIFORNIA_MEASURE_NOTE, + }, + "with_health_tanf": { + "fields": ( + "state_net_with_health_tanf_change_b", + "state_net_with_health_tanf_including_sdi_change_b", + "other_states_net_with_health_tanf_change_b", + ), + "subtitle": ( + "Change in state tax revenue, net of credits, benefits, Medi-Cal and " + "CalWORKs, $ billions" + ), + "california_note": CALIFORNIA_HEALTH_TANF_NOTE, + }, +} def series_from_sweep(path=SWEEP_PATH): @@ -65,20 +101,22 @@ def series_from_sweep(path=SWEEP_PATH): return xs, ca, None, other, note -def series_from_run(path): +def series_from_run(path, measure="with_health_tanf"): + ca_key, ca_sdi_key, other_key = MEASURES[measure]["fields"] data = json.load(open(path)) xs, ca, ca_sdi, other = [0], [0.0], [0.0], [0.0] for row in data["levels"]: xs.append(row["shift_pct"]) - ca.append(row["california"]["state_net_change_b"]) - ca_sdi.append(row["california"]["state_net_including_sdi_change_b"]) - other.append(row["other_states_net_change_b"]) + ca.append(row["california"][ca_key]) + ca_sdi.append(row["california"][ca_sdi_key]) + other.append(row[other_key]) meta = data["metadata"] note = f"policyengine-us {meta['policyengine_us_version']}, {data['year']}" return xs, ca, ca_sdi, other, note -def draw(xs, ca, ca_sdi, other, out, source_note, measure_note=None): +def draw(xs, ca, ca_sdi, other, out, source_note, measure_note=None, + subtitle=MEASURES["study"]["subtitle"]): plt.rcParams.update( { "font.family": "sans-serif", @@ -126,10 +164,7 @@ def draw(xs, ca, ca_sdi, other, out, source_note, measure_note=None): fig.text(0.06, 0.955, "California gains as labor income shifts to capital", fontsize=14, fontweight="bold", color=TEXT_PRIMARY, ha="left") - fig.text(0.06, 0.915, - "Change in state tax revenue, net of refundable state credits and " - "some state benefits, $ billions", - fontsize=10, color=TEXT_SECONDARY, ha="left") + fig.text(0.06, 0.915, subtitle, fontsize=10, color=TEXT_SECONDARY, ha="left") notes = [ "Source: PolicyEngine static microsimulation of current law on Microcosm " f"data ({source_note}).", @@ -154,17 +189,28 @@ def main(): action="store_true", help="omit the all-other-states line, whose path depends on where new capital income lands", ) + parser.add_argument( + "--measure", + choices=sorted(MEASURES), + default=None, + help="state measure to chart: a run defaults to with_health_tanf; the sweep has only study", + ) args = parser.parse_args() if args.source == "sweep": + measure = args.measure or "study" + if measure != "study": + parser.error("the sweep payload holds only the study measure") series = series_from_sweep() else: - series = series_from_run(args.run) + measure = args.measure or "with_health_tanf" + series = series_from_run(args.run, measure) xs, ca, ca_sdi, other, note = series measure_note = None if args.california_only: other = None - measure_note = CALIFORNIA_MEASURE_NOTE - print(draw(xs, ca, ca_sdi, other, args.out, note, measure_note)) + measure_note = MEASURES[measure]["california_note"] + print(draw(xs, ca, ca_sdi, other, args.out, note, measure_note, + subtitle=MEASURES[measure]["subtitle"])) if __name__ == "__main__": diff --git a/analysis/california_shift.py b/analysis/california_shift.py index 94a2193..8e7f242 100644 --- a/analysis/california_shift.py +++ b/analysis/california_shift.py @@ -20,6 +20,20 @@ to that state's own capital holders, in proportion to their holdings, so every state's market income stays fixed and only the movement of income within each state changes its revenue. +* Health spending and TANF, which the sweep's state measure leaves out + (``*_with_health_tanf`` fields). Household net income excludes health + benefits (``gov.simulation.include_health_benefits_in_net_income`` is + false), so Medicaid, CHIP, the Medicare Savings Programs and ACA premium tax + credits sit in no government's total, and ``tanf`` sits in + ``household_benefits`` but in no state's line, so the sweep books it federal. + These fields charge each state its share of Medicaid, CHIP and Medicare + Savings Program costs under federal matching rules + (``state_benefit_cost``, from ``gov.hhs.medicaid.cost_share``), the whole + cost of coverage it funds alone for people whose immigration status rules + out federal matching, its own health subsidies, and its whole TANF change, + because the federal TANF block grant is a fixed amount. The all-government + total adds every program in ``gov.household.household_health_benefits``; + federal net revenue is again what the states leave. Usage: python -m analysis.california_shift [--levels 0.1,0.2,0.5,1.0] @@ -51,6 +65,30 @@ SDI_VARIABLE = "ca_employee_state_disability_insurance_contribution" LABOR_VARS = ["employment_income", "self_employment_income"] +#: The programs in ``gov.household.household_health_benefits``, by who pays. +#: policyengine-us splits these three between federal and state through +#: ``federal_benefit_cost`` and ``state_benefit_cost``. +FMAP_SPLIT_HEALTH = ("medicaid_cost", "chip", "msp_cost") +#: Federal: ACA premium tax credits, and the Basic Health Program, which +#: federal pass-through funds (95% of the credits enrollees would have +#: received) pay for mostly. +FEDERAL_HEALTH = ("assigned_aca_ptc", "basic_health_program") +#: State-funded health subsidies. +STATE_HEALTH = ( + "assigned_ca_premium_subsidy", + "co_omnisalud", + "assigned_co_premium_assistance", + "md_premium_assistance", + "assigned_nm_premium_assistance", + "or_healthier_oregon_cost", +) +#: The members of ``gov.household.federal_benefit_cost`` and +#: ``gov.household.state_benefit_cost`` this accounting expects. +FEDERAL_SHARE_COSTS = ("medicaid_federal_cost", "chip_federal_cost", "msp_federal_cost") +STATE_SHARE_COSTS = ("medicaid_state_cost", "chip_state_cost", "msp_state_cost") +#: Every state TANF program; California's is CalWORKs (``ca_tanf``). +TANF_VARIABLE = "tanf" + #: Within-California groups by baseline household market income, as #: (label, lower weighted percentile, upper weighted percentile). GROUPS = [ @@ -74,8 +112,114 @@ def _hh(sim, var): return np.asarray(sim.calculate(var, map_to="household", period=YEAR), dtype=float) +def _parameters(sim): + return sim.tax_benefit_system.parameters(f"{YEAR}-01-01") + + +def _check_health_lists(parameters): + """Refuse a model whose health accounting differs from what this module classifies. + + The ``*_with_health_tanf`` fields assume health benefits sit outside + household net income, that every program in + ``gov.household.household_health_benefits`` has a payer here, and that the + federal and state benefit-cost lists hold the shares of the same three + programs. + """ + gov = parameters.gov + if gov.simulation.include_health_benefits_in_net_income: + raise ValueError( + "health benefits are in household net income, so household_benefits " + "already counts them; the health accounting would count them twice" + ) + expected = { + "household_health_benefits": set(FMAP_SPLIT_HEALTH) | set(FEDERAL_HEALTH) | set(STATE_HEALTH), + "federal_benefit_cost": set(FEDERAL_SHARE_COSTS), + "state_benefit_cost": set(STATE_SHARE_COSTS), + } + for name, want in expected.items(): + listed = set(getattr(gov.household, name)) + if listed != want: + raise ValueError( + f"gov.household.{name} changed: unclassified {sorted(listed - want)}, " + f"missing {sorted(want - listed)}" + ) + + +def _federally_eligible_status(status, years_since_entry, p): + """Whether federal Medicaid can match coverage, given each person's immigration status. + + Mirrors the federal branch of policyengine-us's + ``is_medicaid_immigration_status_eligible``: citizens, and qualified + statuses that are exempt from the five-year bar or past it. ``p`` is + ``gov.hhs.medicaid.eligibility``. + """ + status = np.asarray(status).astype(str) + citizen = status == "CITIZEN" + qualified = np.isin(status, list(p.eligible_immigration_statuses)) + bar_exempt = np.isin(status, list(p.bar_exempt_immigration_statuses)) + past_bar = np.asarray(years_since_entry, dtype=float) >= p.five_year_bar_years + return citizen | (qualified & (bar_exempt | past_bar)) + + +def _state_only_coverage(sim): + """Federal share policyengine-us books for coverage only a state pays for, by household. + + ``medicaid_federal_share`` and ``chip_federal_share`` apply federal matching + rates to every enrollee, including people a state covers whatever their + immigration status, such as California's Medi-Cal for undocumented, DACA + and TPS residents. Federal funds pay for such people only in emergencies, + which ``medicaid_cost`` leaves out, so their whole cost is the state's. + """ + p = _parameters(sim).gov.hhs.medicaid.eligibility + status = sim.calculate("immigration_status", period=YEAR, decode_enums=True) + years = sim.calculate("years_since_us_entry", period=YEAR) + federal = _federally_eligible_status(np.asarray(status), np.asarray(years), p) + federal_share = sum( + np.asarray(sim.calculate(v, period=YEAR), dtype=float) for v in FEDERAL_SHARE_COSTS + ) + moved = np.where(federal, 0.0, federal_share) + return np.asarray(sim.map_result(moved, "person", "household"), dtype=float) + + +def _with_derived(frame): + """Add the measures built from a frame's stored arrays. + + ``state_health`` is each household's health spending that falls on its + state and ``federal_health`` the part that falls on the federal + government; between them they hold every program in + ``gov.household.household_health_benefits``. + """ + frame["state_net"] = frame["state_tax"] - frame["state_credits"] - frame["state_benefits"] + frame["state_health"] = ( + frame["state_health_split"] + frame["state_only_coverage"] + frame["state_health_programs"] + ) + frame["federal_health"] = ( + frame["federal_health_split"] - frame["state_only_coverage"] + frame["federal_health_other"] + ) + frame["state_net_with_health_tanf"] = ( + frame["state_net"] - frame["state_health"] - frame["tanf"] + ) + return frame + + +def _check_health_split(frame): + """policyengine-us's federal and state shares must add up to the costs they split.""" + residual = frame["fmap_split_cost"] - frame["federal_health_split"] - frame["state_health_split"] + worst = float(np.abs(residual).max()) if residual.size else 0.0 + weighted = float((residual * frame["weight"]).sum()) + if worst >= 1.0 or abs(weighted) >= 1e6: + raise AssertionError( + "federal_benefit_cost + state_benefit_cost differ from Medicaid, CHIP and " + f"MSP cost by up to ${worst:,.2f} a household (${weighted:,.0f} weighted)" + ) + moved = frame["state_only_coverage"] + if (moved < -1e-6).any() or (moved > frame["federal_health_split"] + 1e-6).any(): + raise AssertionError("state-only coverage moves more than the federal share it comes from") + + def _household_frame(sim): """Household arrays needed for the California accounting.""" + _check_health_lists(_parameters(sim)) frame = { "state": np.asarray( sim.calculate("state_code_str", map_to="household", period=YEAR) @@ -87,14 +231,21 @@ def _household_frame(sim): "state_benefits": _hh(sim, "household_state_benefits"), "labor": sum(_hh(sim, v) for v in LABOR_VARS), "capital": sum(_hh(sim, v) for v in CAPITAL_INCOME_VARS), + "federal_health_split": _hh(sim, "federal_benefit_cost"), + "state_health_split": _hh(sim, "state_benefit_cost"), + "fmap_split_cost": sum(_hh(sim, v) for v in FMAP_SPLIT_HEALTH), + "federal_health_other": sum(_hh(sim, v) for v in FEDERAL_HEALTH), + "state_health_programs": sum(_hh(sim, v) for v in STATE_HEALTH), + "state_only_coverage": _state_only_coverage(sim), + "tanf": _hh(sim, TANF_VARIABLE), } try: frame["sdi"] = _hh(sim, SDI_VARIABLE) except Exception as exc: # noqa: BLE001 - record, do not fail the run print(f" {SDI_VARIABLE} unavailable: {exc}", flush=True) frame["sdi"] = np.zeros_like(frame["weight"]) - frame["state_net"] = frame["state_tax"] - frame["state_credits"] - frame["state_benefits"] - return frame + _check_health_split(frame) + return _with_derived(frame) def _freed_labor(values, shift_pct): @@ -248,6 +399,14 @@ def _baseline_concentration(base): (base["state_tax"][mask] * w).sum() / 1e9 ), "sdi_b": float((base["sdi"][mask] * w).sum() / 1e9), + "state_health_b": float((base["state_health"][mask] * w).sum() / 1e9), + "state_health_split_b": float((base["state_health_split"][mask] * w).sum() / 1e9), + "state_only_coverage_b": float((base["state_only_coverage"][mask] * w).sum() / 1e9), + "state_health_programs_b": float((base["state_health_programs"][mask] * w).sum() / 1e9), + "tanf_b": float((base["tanf"][mask] * w).sum() / 1e9), + "state_net_with_health_tanf_b": float( + (base["state_net_with_health_tanf"][mask] * w).sum() / 1e9 + ), "groups": rows, } @@ -271,6 +430,12 @@ def delta(key): "state_benefits_change_b": delta("state_benefits"), "state_net_change_b": delta("state_net"), "sdi_change_b": delta("sdi"), + "state_health_change_b": delta("state_health"), + "state_health_split_change_b": delta("state_health_split"), + "state_only_coverage_change_b": delta("state_only_coverage"), + "state_health_programs_change_b": delta("state_health_programs"), + "tanf_change_b": delta("tanf"), + "state_net_with_health_tanf_change_b": delta("state_net_with_health_tanf"), } def total(sel): @@ -278,6 +443,9 @@ def total(sel): out["state_net_including_sdi_change_b"] = ( out["state_net_change_b"] + out["sdi_change_b"] ) + out["state_net_with_health_tanf_including_sdi_change_b"] = ( + out["state_net_with_health_tanf_change_b"] + out["sdi_change_b"] + ) return out everyone = np.ones_like(w, dtype=bool) @@ -290,6 +458,47 @@ def total(sel): return total(everyone), groups +def _health_tanf_accounting(base, scen, state_net, all_government): + """Each government's change once health spending and TANF count, $B. + + ``state_net`` ($B by state) and ``all_government`` ($B) follow the sweep: + ``tanf`` sits in all-government benefits but in no state's line, and health + spending sits in neither. Here each state also pays its health share + (``state_health``) and its whole TANF change, the all-government total pays + every health dollar, and federal net revenue is what the states leave, so + TANF moves from the federal line to the states'. + """ + assert np.array_equal(base["state"], scen["state"]), "household order changed" + w = base["weight"] + + def by_state(key): + return _weighted_sum_by(base["state"], scen[key] - base[key], w) + + state_health = by_state("state_health") + tanf = by_state("tanf") + federal_health = float(((scen["federal_health"] - base["federal_health"]) * w).sum()) + states = { + code: state_net.get(code, 0.0) + - (state_health.get(code, 0.0) + tanf.get(code, 0.0)) / 1e9 + for code in set(state_net) | set(state_health) | set(tanf) + } + all_states = sum(states.values()) + all_government_with_health = ( + all_government - (federal_health + sum(state_health.values())) / 1e9 + ) + return { + "health_federal_change_b": federal_health / 1e9, + "health_state_change_b": sum(state_health.values()) / 1e9, + "tanf_change_b": sum(tanf.values()) / 1e9, + "all_government_with_health_change_b": all_government_with_health, + "all_states_net_with_health_tanf_change_b": all_states, + "federal_net_with_health_tanf_change_b": all_government_with_health - all_states, + "other_states_net_with_health_tanf_change_b": all_states - states.get(STATE, 0.0), + "states_losing_with_health_tanf_count": sum(1 for v in states.values() if v < -0.005), + "state_net_with_health_tanf_change_b": dict(sorted(states.items())), + } + + def _level_row(branch, freed, pct, base, base_rev, base_states, allocation): """Fiscal and California accounting for one shifted branch.""" rev = revenue_components(branch) @@ -305,7 +514,8 @@ def _level_row(branch, freed, pct, base, base_rev, base_states, allocation): (c for c, v in state_net.items() if v > 0.005), key=lambda c: -state_net[c] ) losers = [c for c, v in state_net.items() if v < -0.005] - return { + health_tanf = _health_tanf_accounting(base, scen, state_net, delta["total_change"] / 1e9) + return health_tanf | { "shift_pct": int(round(pct * 100)), "allocation": allocation, "labor_income_moved_b": freed / 1e9, @@ -336,6 +546,15 @@ def _report(row): f"{row['all_government_change_b']:+.1f}B", flush=True, ) + print( + f" with health and TANF: CA {ca['state_net_with_health_tanf_change_b']:+.2f}B " + f"(state health {ca['state_health_change_b']:+.2f}B, of which state-only coverage " + f"{ca['state_only_coverage_change_b']:+.2f}B; CalWORKs {ca['tanf_change_b']:+.2f}B); " + f"other states {row['other_states_net_with_health_tanf_change_b']:+.2f}B; federal " + f"{row['federal_net_with_health_tanf_change_b']:+.1f}B; all-gov " + f"{row['all_government_with_health_change_b']:+.1f}B", + flush=True, + ) def run( @@ -406,7 +625,15 @@ def run( "gov.household.household_state_benefits (for California: SSP, CAPI, " "child care subsidies, CVRP, CARE, FERA and three Los Angeles " "programs; no Medi-Cal or CalWORKs); California's employee SDI " - "contribution is reported separately. " + "contribution is reported separately. The *_with_health_tanf fields " + "also charge each state its share of Medicaid, CHIP and Medicare " + "Savings Program costs under federal matching rules (state_benefit_cost), " + "the whole cost of coverage it funds alone for people whose immigration " + "status rules out federal matching, its own health subsidies (for " + "California, the Covered California premium subsidy) and its whole TANF " + "change (CalWORKs for California), since the federal TANF block grant is " + "fixed; the all-government total adds every program in " + "gov.household.household_health_benefits. " "'levels' allocate the new capital income across the country, as the " "published sweep does; 'within_state_levels' give each state's freed " "labor income to that state's own capital holders." diff --git a/analysis/california_summary.py b/analysis/california_summary.py index 9aff9f4..c3a9f5d 100644 --- a/analysis/california_summary.py +++ b/analysis/california_summary.py @@ -3,6 +3,10 @@ Prints, for each shift level: * California's net change, with and without its employee SDI contribution; +* the same once health spending and TANF count: California's share of + Medi-Cal, CHIP and Medicare Savings Program costs, the state-only coverage it + pays for in full, its premium subsidy and CalWORKs, with the other states and + federal lines on the same basis (runs without these fields skip the lines); * all other states combined, every state, and federal net revenue; * the federal split into income tax, payroll taxes, refundable credits and benefits; @@ -140,6 +144,7 @@ def summarize(run, published=None): f"refundable credits {ca['state_refundable_credits_change_b']:+.2f}B, " f"benefits {ca['state_benefits_change_b']:+.2f}B" ) + lines.extend(_health_tanf_lines(row)) lines.append( f" California households: labor {ca['labor_income_change_b']:+.1f}B, capital " f"{ca['capital_income_change_b']:+.1f}B, market income {ca['market_income_change_b']:+.1f}B" @@ -171,10 +176,12 @@ def summarize(run, published=None): total_tax = ca["state_tax_before_refundable_credits_change_b"] for g in row["california_by_group"]: share = float("nan") if total_tax == 0 else 100 * g["state_tax_before_refundable_credits_change_b"] / total_tax + full = g.get("state_net_with_health_tanf_change_b") lines.append( f" {g['group']:<26} state tax {g['state_tax_before_refundable_credits_change_b']:+.2f}B " - f"({share:5.1f}% of the change), net {g['state_net_change_b']:+.2f}B, labor " - f"{g['labor_income_change_b']:+.1f}B, capital {g['capital_income_change_b']:+.1f}B" + f"({share:5.1f}% of the change), net {g['state_net_change_b']:+.2f}B" + + ("" if full is None else f" (with health and TANF {full:+.2f}B)") + + f", labor {g['labor_income_change_b']:+.1f}B, capital {g['capital_income_change_b']:+.1f}B" ) if published and row["shift_pct"] in published: p = published[row["shift_pct"]] @@ -187,6 +194,26 @@ def summarize(run, published=None): return "\n".join(lines) +def _health_tanf_lines(row): + """California, the other states and federal once health spending and TANF count.""" + ca = row["california"] + if "state_net_with_health_tanf_change_b" not in ca: + return [] + return [ + f" California with health and TANF {ca['state_net_with_health_tanf_change_b']:+.2f}B; with SDI " + f"{ca['state_net_with_health_tanf_including_sdi_change_b']:+.2f}B. Spending changes: state health " + f"{ca['state_health_change_b']:+.2f}B (matched shares {ca['state_health_split_change_b']:+.2f}B, " + f"state-only coverage {ca['state_only_coverage_change_b']:+.2f}B, premium subsidy " + f"{ca['state_health_programs_change_b']:+.2f}B), CalWORKs {ca['tanf_change_b']:+.2f}B", + f" With health and TANF: other states {row['other_states_net_with_health_tanf_change_b']:+.2f}B " + f"({row['states_losing_with_health_tanf_count']} losing); federal " + f"{row['federal_net_with_health_tanf_change_b']:+.1f}B; all governments " + f"{row['all_government_with_health_change_b']:+.1f}B. Health spending changes: federal " + f"{row['health_federal_change_b']:+.1f}B, states {row['health_state_change_b']:+.2f}B; TANF " + f"{row['tanf_change_b']:+.2f}B moves from the federal line to the states'", + ] + + def _within_state_lines(run): """The within-state variant, and how much of California's gain it keeps.""" within = run.get("within_state_levels") or [] @@ -208,6 +235,7 @@ def _within_state_lines(run): f"{', '.join(row['states_gaining'])}; {row['states_losing_count']} losing; federal " f"{row['federal_net_change_b']:+.1f}B; all governments {row['all_government_change_b']:+.1f}B" ) + lines.extend(_health_tanf_lines(row)) n = national.get(row["shift_pct"]) if n: nat = n["california"]["state_net_change_b"] diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 45a7ad6..6ea5dd4 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -15,9 +15,22 @@ published sweep's; - with every record in one state, the within-state shift sets the same inputs as the published sweep's national shift (a differential check, run when - policyengine_us is installed, plus a closed-form check that always runs). - -Only the differential check against ``_apply_shift`` loads PolicyEngine. + policyengine_us is installed, plus a closed-form check that always runs); +- the health accounting conserves health spending: each household's state and + federal health spending add up to every program in + ``household_health_benefits``, and moving state-only coverage changes who + pays, not how much; +- the federal line with health and TANF plus every state's line equals the + all-government total with health, and TANF moves dollar for dollar from the + federal line to the state's; +- the federal immigration-status test matches policyengine-us's + ``is_medicaid_immigration_status_eligible`` where no state coverage applies, + and the state-only adjustment moves exactly the federal share policyengine-us + books for a California child covered without satisfactory status (both run + when policyengine_us is installed). + +The differential checks against ``_apply_shift`` and the immigration rules are +the only tests that load PolicyEngine. """ import json @@ -31,16 +44,26 @@ from analysis.california_shift import ( CAPITAL_INCOME_VARS, + FEDERAL_HEALTH, + FEDERAL_SHARE_COSTS, + FMAP_SPLIT_HEALTH, GROUPS, LABOR_VARS, STATE, + STATE_HEALTH, + STATE_SHARE_COSTS, _apply_within_state_shift, _baseline_concentration, _california_rows, + _check_health_lists, + _check_health_split, + _federally_eligible_status, _freed_labor, + _health_tanf_accounting, _scale_by_state, _weighted_rank, _weighted_sum_by, + _with_derived, _within_state_scales, ) @@ -52,6 +75,13 @@ "labor", "capital", "sdi", + "federal_health_split", + "state_health_split", + "fmap_split_cost", + "federal_health_other", + "state_health_programs", + "state_only_coverage", + "tanf", ) @@ -63,8 +93,7 @@ def _frame(state, weight, **columns): } for key in VALUE_KEYS: frame[key] = np.asarray(columns.get(key, np.zeros(n)), dtype=float) - frame["state_net"] = frame["state_tax"] - frame["state_credits"] - frame["state_benefits"] - return frame + return _with_derived(frame) FLOATS = st.floats(-1e6, 1e7, allow_nan=False, allow_infinity=False) @@ -205,18 +234,23 @@ def test_chart_series_from_run(tmp_path): { "shift_pct": 10, "other_states_net_change_b": -2.0, + "other_states_net_with_health_tanf_change_b": -2.5, "california": { "state_net_change_b": 5.0, "state_net_including_sdi_change_b": 3.0, + "state_net_with_health_tanf_change_b": 4.0, + "state_net_with_health_tanf_including_sdi_change_b": 2.0, }, } ], } path = tmp_path / "run.json" path.write_text(json.dumps(run)) - xs, ca, ca_sdi, other, note = series_from_run(str(path)) + xs, ca, ca_sdi, other, note = series_from_run(str(path), measure="study") assert xs == [0, 10] and ca == [0.0, 5.0] and ca_sdi == [0.0, 3.0] and other == [0.0, -2.0] assert "9.9.9" in note + xs, ca, ca_sdi, other, _ = series_from_run(str(path)) + assert xs == [0, 10] and ca == [0.0, 4.0] and ca_sdi == [0.0, 2.0] and other == [0.0, -2.5] def test_chart_draws_california_only(tmp_path): @@ -290,6 +324,13 @@ def test_summary_reads_a_run(): "state_net_change_b": 5.0, "sdi_change_b": -2.0, "state_net_including_sdi_change_b": 3.0, + "state_health_change_b": 0.6, + "state_health_split_change_b": 0.4, + "state_only_coverage_change_b": 0.15, + "state_health_programs_change_b": 0.05, + "tanf_change_b": 0.4, + "state_net_with_health_tanf_change_b": 4.0, + "state_net_with_health_tanf_including_sdi_change_b": 2.0, } group = dict(ca, group="Top 1%") run = { @@ -325,12 +366,23 @@ def test_summary_reads_a_run(): "national_components_b": {"total_change": -135.0}, "california": ca, "california_by_group": [group], + "other_states_net_with_health_tanf_change_b": -3.0, + "states_losing_with_health_tanf_count": 42, + "federal_net_with_health_tanf_change_b": -140.0, + "all_government_with_health_change_b": -139.0, + "health_federal_change_b": 3.0, + "health_state_change_b": 1.0, + "tanf_change_b": 1.0, } ], } published = {10: {"california": 5.0, "other_states": -2.1, "federal": -138.2, "total": -135.3}} text = summarize(run, published) assert "California net +5.00B; with SDI +3.00B" in text + assert "California with health and TANF +4.00B; with SDI +2.00B" in text + assert "state-only coverage +0.15B" in text and "CalWORKs +0.40B" in text + assert "With health and TANF: other states -3.00B (42 losing); federal -140.0B" in text + assert "(with health and TANF +4.00B)" in text assert "Published: California +5.00B (+0.0%)" in text assert "Within-state variant" not in text @@ -583,3 +635,261 @@ def fake_within(sim, name, pct): assert row["other_states_net_change_b"] == pytest.approx(-1.0) assert row["states_gaining"] == [STATE] assert out["metadata"]["policyengine_version"] == "test" + # The fake changes no health spending or TANF, so those fields match the sweep's. + assert row["federal_net_with_health_tanf_change_b"] == pytest.approx(row["federal_net_change_b"]) + assert row["other_states_net_with_health_tanf_change_b"] == pytest.approx(-1.0) + assert row["california"]["state_net_with_health_tanf_change_b"] == pytest.approx(0.0) + assert "state_net_with_health_tanf_b" in out["baseline_california"] + + +# --- Health spending and TANF ------------------------------------------------- + +HEALTH_KEYS = ( + "federal_health_split", + "state_health_split", + "federal_health_other", + "state_health_programs", + "tanf", +) +SPENDING = st.floats(0, 5e4, allow_nan=False, allow_infinity=False) +SHARES = st.floats(0, 1, allow_nan=False) + + +@st.composite +def health_pair(draw): + """Baseline and scenario frames with non-negative health spending, at least one in CA.""" + n = draw(st.integers(1, 30)) + state = draw(st.lists(st.sampled_from([STATE, "NY", "TX"]), min_size=n, max_size=n)) + state[0] = STATE + weight = draw(st.lists(WEIGHTS, min_size=n, max_size=n)) + + def columns(): + cols = { + key: np.asarray(draw(st.lists(SPENDING, min_size=n, max_size=n))) for key in HEALTH_KEYS + } + # State-only coverage moves part of the federal share, never more. + share = np.asarray(draw(st.lists(SHARES, min_size=n, max_size=n))) + cols["state_only_coverage"] = cols["federal_health_split"] * share + cols["fmap_split_cost"] = cols["federal_health_split"] + cols["state_health_split"] + cols["state_tax"] = draw(st.lists(FLOATS, min_size=n, max_size=n)) + return cols + + return _frame(state, weight, **columns()), _frame(state, weight, **columns()) + + +@settings(max_examples=300, deadline=None) +@given(health_pair()) +def test_health_spending_is_conserved(pair): + for frame in pair: + every_program = ( + frame["federal_health_split"] + + frame["state_health_split"] + + frame["federal_health_other"] + + frame["state_health_programs"] + ) + np.testing.assert_allclose( + frame["state_health"] + frame["federal_health"], every_program, rtol=1e-12, atol=1e-6 + ) + np.testing.assert_allclose( + frame["state_net_with_health_tanf"], + frame["state_net"] - frame["state_health"] - frame["tanf"], + rtol=1e-12, + atol=1e-6, + ) + # Generated frames satisfy the run's own checks. + _check_health_split(frame) + + +@settings(max_examples=300, deadline=None) +@given( + health_pair(), + st.floats(-50, 50, allow_nan=False), + st.floats(-500, 500, allow_nan=False), + st.floats(-50, 50, allow_nan=False), +) +def test_health_tanf_accounting_adds_up(pair, ca_net, all_government, ny_net): + base, scen = pair + out = _health_tanf_accounting(base, scen, {STATE: ca_net, "NY": ny_net}, all_government) + states = out["state_net_with_health_tanf_change_b"] + assert out["federal_net_with_health_tanf_change_b"] + sum(states.values()) == pytest.approx( + out["all_government_with_health_change_b"], rel=1e-9, abs=1e-9 + ) + assert out["all_states_net_with_health_tanf_change_b"] == pytest.approx( + sum(states.values()), rel=1e-12, abs=1e-12 + ) + # California's line is the sweep's net less California's health and TANF + # changes, as its household rows count them. + total, _ = _california_rows(base, scen) + assert states[STATE] == pytest.approx( + ca_net - total["state_health_change_b"] - total["tanf_change_b"], rel=1e-9, abs=1e-9 + ) + # Every health dollar leaves the all-government total; TANF already sits in it. + w = base["weight"] + health = lambda f: f["state_health"] + f["federal_health"] # noqa: E731 + health_change = float(((health(scen) - health(base)) * w).sum()) / 1e9 + assert out["all_government_with_health_change_b"] == pytest.approx( + all_government - health_change, rel=1e-9, abs=1e-9 + ) + + +def test_tanf_moves_from_the_federal_line_to_the_state(): + # CalWORKs rises $1B and nothing else changes. The sweep counts the $1B in + # all-government benefits but in no state's line, so its federal line falls + # $1B; here California pays it and the federal line does not move. + base = _frame([STATE, "NY"], [1e9, 1e9]) + scen = _frame([STATE, "NY"], [1e9, 1e9], tanf=[1.0, 0.0]) + out = _health_tanf_accounting(base, scen, {STATE: 0.0, "NY": 0.0}, -1.0) + assert out["state_net_with_health_tanf_change_b"][STATE] == pytest.approx(-1.0) + assert out["federal_net_with_health_tanf_change_b"] == pytest.approx(0.0) + assert out["tanf_change_b"] == pytest.approx(1.0) + + +def test_state_only_coverage_moves_to_the_state(): + # A California child newly covered by state-funded Medi-Cal costs $1,000. + # policyengine-us books $500 federal and $500 state at the 50% matching + # rate; the child's status rules out federal matching, so California pays + # all of it and the federal line does not move. + base = _frame([STATE], [1e9]) + scen = _frame( + [STATE], + [1e9], + fmap_split_cost=[1000.0], + federal_health_split=[500.0], + state_health_split=[500.0], + state_only_coverage=[500.0], + ) + _check_health_split(scen) + out = _health_tanf_accounting(base, scen, {STATE: 0.0}, 0.0) + assert out["state_net_with_health_tanf_change_b"][STATE] == pytest.approx(-1000.0) + assert out["health_federal_change_b"] == pytest.approx(0.0) + assert out["federal_net_with_health_tanf_change_b"] == pytest.approx(0.0) + assert out["all_government_with_health_change_b"] == pytest.approx(-1000.0) + + +def test_check_health_split_refuses_gaps(): + gap = _frame( + [STATE], [1.0], fmap_split_cost=[100.0], federal_health_split=[50.0], state_health_split=[40.0] + ) + with pytest.raises(AssertionError, match="differ"): + _check_health_split(gap) + overmoved = _frame( + [STATE], + [1.0], + fmap_split_cost=[100.0], + federal_health_split=[50.0], + state_health_split=[50.0], + state_only_coverage=[60.0], + ) + with pytest.raises(AssertionError, match="moves more"): + _check_health_split(overmoved) + + +def _health_parameters(flag=False, health=None, federal=None, state=None): + household = SimpleNamespace( + household_health_benefits=health + or list(FMAP_SPLIT_HEALTH) + list(FEDERAL_HEALTH) + list(STATE_HEALTH), + federal_benefit_cost=federal or list(FEDERAL_SHARE_COSTS), + state_benefit_cost=state or list(STATE_SHARE_COSTS), + ) + simulation = SimpleNamespace(include_health_benefits_in_net_income=flag) + return SimpleNamespace(gov=SimpleNamespace(household=household, simulation=simulation)) + + +def test_check_health_lists(): + _check_health_lists(_health_parameters()) + with pytest.raises(ValueError, match="twice"): + _check_health_lists(_health_parameters(flag=True)) + with pytest.raises(ValueError, match="new_program"): + _check_health_lists( + _health_parameters( + health=list(FMAP_SPLIT_HEALTH) + list(FEDERAL_HEALTH) + list(STATE_HEALTH) + ["new_program"] + ) + ) + with pytest.raises(ValueError, match="state_benefit_cost"): + _check_health_lists(_health_parameters(state=["medicaid_state_cost"])) + + +def test_federally_eligible_status_examples(): + p = SimpleNamespace( + eligible_immigration_statuses=["CITIZEN", "LEGAL_PERMANENT_RESIDENT", "REFUGEE"], + bar_exempt_immigration_statuses=["REFUGEE"], + five_year_bar_years=5, + ) + statuses = [ + "CITIZEN", + "LEGAL_PERMANENT_RESIDENT", + "LEGAL_PERMANENT_RESIDENT", + "REFUGEE", + "UNDOCUMENTED", + "DACA", + "TPS", + ] + years = [0, 5, 2, 0, 20, 20, 20] + got = _federally_eligible_status(statuses, years, p) + assert got.tolist() == [True, True, False, True, False, False, False] + + +def _situation(state, people): + return { + "people": people, + "households": {"household": {"members": list(people), "state_code": {2026: state}}}, + } + + +def test_federally_eligible_status_matches_policyengine(): + pytest.importorskip("policyengine_us") + from policyengine_us import Simulation + + statuses = [ + "CITIZEN", + "LEGAL_PERMANENT_RESIDENT", + "LEGAL_PERMANENT_RESIDENT", + "REFUGEE", + "ASYLEE", + "UNDOCUMENTED", + "DACA", + "TPS", + "PAROLED_ONE_YEAR", + "PAROLED_ONE_YEAR", + ] + years = [0, 7, 1, 0, 2, 12, 12, 12, 6, 2] + people = { + f"p{i}": { + "age": {2026: 40}, + "immigration_status": {2026: s}, + "years_since_us_entry": {2026: y}, + } + for i, (s, y) in enumerate(zip(statuses, years)) + } + # Texas covers no one without satisfactory status, so policyengine-us's + # variable reduces to its federal branch there. + sim = Simulation(situation=_situation("TX", people)) + expected = np.asarray(sim.calculate("is_medicaid_immigration_status_eligible", 2026)) + p = sim.tax_benefit_system.parameters("2026-01-01").gov.hhs.medicaid.eligibility + got = _federally_eligible_status(statuses, years, p) + assert got.tolist() == expected.tolist() + assert got.any() and not got.all() + + +def test_state_only_coverage_matches_policyengine(): + pytest.importorskip("policyengine_us") + from policyengine_us import Simulation + + from analysis.california_shift import _state_only_coverage + + # A California household with no income: a citizen parent, a citizen child + # and an undocumented child whom state-funded Medi-Cal covers. The + # adjustment moves exactly the federal share policyengine-us books for the + # undocumented child, and nothing for the citizens. + people = { + "parent": {"age": {2026: 30}, "immigration_status": {2026: "CITIZEN"}}, + "citizen_child": {"age": {2026: 5}, "immigration_status": {2026: "CITIZEN"}}, + "undocumented_child": {"age": {2026: 6}, "immigration_status": {2026: "UNDOCUMENTED"}}, + } + for person in people.values(): + person["takes_up_medicaid_if_eligible"] = {2026: True} + sim = Simulation(situation=_situation("CA", people)) + federal = sum(np.asarray(sim.calculate(v, 2026), dtype=float) for v in FEDERAL_SHARE_COSTS) + assert federal[2] > 0, "the undocumented child should be enrolled with a federal share booked" + assert federal[1] > 0, "the citizen child should be enrolled with a federal share booked" + assert _state_only_coverage(sim).tolist() == pytest.approx([federal[2]]) From 1e75a35d014f115d335666b452744f42d1b5119e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 14:55:28 -0500 Subject: [PATCH 12/17] Split California's use tax and mental health services tax out of state tax household_state_tax_before_refundable_credits adds state income tax and use tax, and California's income tax includes the 1% tax on taxable income above $1 million (ca_mental_health_services_tax), whose revenue funds behavioral health services rather than the General Fund. The California rows now report both, so a reader can see how much of the change each fund receives. Co-Authored-By: Claude Opus 5.5 --- analysis/california_shift.py | 6 ++++++ analysis/california_summary.py | 8 +++++++- analysis/tests/test_california_shift.py | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/analysis/california_shift.py b/analysis/california_shift.py index 8e7f242..146ff81 100644 --- a/analysis/california_shift.py +++ b/analysis/california_shift.py @@ -227,6 +227,10 @@ def _household_frame(sim): "weight": np.asarray(sim.calculate("household_weight", period=YEAR), dtype=float), "market_income": _hh(sim, "household_market_income"), "state_tax": _hh(sim, "household_state_tax_before_refundable_credits"), + # Both sit inside state_tax: use tax beside income tax, and California's + # 1% tax on taxable income above $1 million inside its income tax. + "state_use_tax": _hh(sim, "state_use_tax"), + "mental_health_services_tax": _hh(sim, "ca_mental_health_services_tax"), "state_credits": _hh(sim, "household_refundable_state_tax_credits"), "state_benefits": _hh(sim, "household_state_benefits"), "labor": sum(_hh(sim, v) for v in LABOR_VARS), @@ -426,6 +430,8 @@ def delta(key): "capital_income_change_b": delta("capital"), "market_income_change_b": delta("market_income"), "state_tax_before_refundable_credits_change_b": delta("state_tax"), + "state_use_tax_change_b": delta("state_use_tax"), + "mental_health_services_tax_change_b": delta("mental_health_services_tax"), "state_refundable_credits_change_b": delta("state_credits"), "state_benefits_change_b": delta("state_benefits"), "state_net_change_b": delta("state_net"), diff --git a/analysis/california_summary.py b/analysis/california_summary.py index c3a9f5d..02ffc43 100644 --- a/analysis/california_summary.py +++ b/analysis/california_summary.py @@ -139,8 +139,14 @@ def summarize(run, published=None): f" California net {ca['state_net_change_b']:+.2f}B; with SDI " f"{ca['state_net_including_sdi_change_b']:+.2f}B (SDI {ca['sdi_change_b']:+.2f}B)" ) + inside = "" + if "mental_health_services_tax_change_b" in ca: + inside = ( + f" (of which use tax {ca['state_use_tax_change_b']:+.2f}B and the 1% mental health " + f"services tax {ca['mental_health_services_tax_change_b']:+.2f}B)" + ) lines.append( - f" California parts: state tax {ca['state_tax_before_refundable_credits_change_b']:+.2f}B, " + f" California parts: state tax {ca['state_tax_before_refundable_credits_change_b']:+.2f}B{inside}, " f"refundable credits {ca['state_refundable_credits_change_b']:+.2f}B, " f"benefits {ca['state_benefits_change_b']:+.2f}B" ) diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 6ea5dd4..edc1106 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -70,6 +70,8 @@ VALUE_KEYS = ( "market_income", "state_tax", + "state_use_tax", + "mental_health_services_tax", "state_credits", "state_benefits", "labor", From 78789527cd877b90432168fe260703d00b6cf639 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 15:15:22 -0500 Subject: [PATCH 13/17] Keep CHIP's unborn-child match federal in the state-only adjustment _state_only_coverage moved every federal share policyengine-us books for people whose immigration status rules out federal matching. That included CHIP coverage through the From-Conception-to-End-of-Pregnancy option, which policyengine-us gives pregnant people regardless of status (is_chip_fcep_eligible_person) and which federal CHIP funds match, since the unborn child is the beneficiary (42 CFR 457.10). In California every CHIP enrollee in the model is an FCEP enrollee, as the state's separate CHIP child limit is -inf. _state_only_federal_share now leaves the CHIP share in place for chip_category PREGNANT_FCEP. New tests: an example, a property test (nothing moves for federally eligible people, never more than the booked share, never the FCEP CHIP share), and a policyengine-us check that an undocumented pregnant woman in Texas keeps her federal CHIP match. Co-Authored-By: Claude Opus 5.5 --- analysis/california_shift.py | 43 +++++++++++-- analysis/tests/test_california_shift.py | 83 ++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 9 deletions(-) diff --git a/analysis/california_shift.py b/analysis/california_shift.py index 146ff81..5018bdc 100644 --- a/analysis/california_shift.py +++ b/analysis/california_shift.py @@ -30,8 +30,9 @@ Savings Program costs under federal matching rules (``state_benefit_cost``, from ``gov.hhs.medicaid.cost_share``), the whole cost of coverage it funds alone for people whose immigration status rules - out federal matching, its own health subsidies, and its whole TANF change, - because the federal TANF block grant is a fixed amount. The all-government + out federal matching (CHIP's unborn-child coverage keeps its federal match), + its own health subsidies, and its whole TANF change, because the federal + TANF block grant is a fixed amount. The all-government total adds every program in ``gov.household.household_health_benefits``; federal net revenue is again what the states leave. @@ -86,6 +87,11 @@ #: ``gov.household.state_benefit_cost`` this accounting expects. FEDERAL_SHARE_COSTS = ("medicaid_federal_cost", "chip_federal_cost", "msp_federal_cost") STATE_SHARE_COSTS = ("medicaid_state_cost", "chip_state_cost", "msp_state_cost") +#: ``chip_category`` for a pregnant person covered through CHIP's +#: From-Conception-to-End-of-Pregnancy option. The unborn child is the +#: beneficiary (42 CFR 457.10), so federal CHIP funds match the coverage +#: whatever the parent's immigration status. +FCEP_CATEGORY = "PREGNANT_FCEP" #: Every state TANF program; California's is CalWORKs (``ca_tanf``). TANF_VARIABLE = "tanf" @@ -161,6 +167,31 @@ def _federally_eligible_status(status, years_since_entry, p): return citizen | (qualified & (bar_exempt | past_bar)) +def _state_only_federal_share(federal, shares, chip_category): + """Federal share to move to the state, by person. + + ``federal`` says whether each person's immigration status lets federal + funds match their coverage, ``shares`` maps each name in + ``FEDERAL_SHARE_COSTS`` to the federal share policyengine-us books for each + person, and ``chip_category`` holds each person's decoded ``chip_category``. + For people federal matching can't cover, the Medicaid, Medicare Savings + Program and CHIP shares move, except CHIP coverage through the + unborn-child (FCEP) option, which federal funds match whatever the parent's + status. + """ + chip = np.where( + np.asarray(chip_category).astype(str) == FCEP_CATEGORY, + 0.0, + np.asarray(shares["chip_federal_cost"], dtype=float), + ) + movable = ( + np.asarray(shares["medicaid_federal_cost"], dtype=float) + + chip + + np.asarray(shares["msp_federal_cost"], dtype=float) + ) + return np.where(np.asarray(federal, dtype=bool), 0.0, movable) + + def _state_only_coverage(sim): """Federal share policyengine-us books for coverage only a state pays for, by household. @@ -169,15 +200,15 @@ def _state_only_coverage(sim): immigration status, such as California's Medi-Cal for undocumented, DACA and TPS residents. Federal funds pay for such people only in emergencies, which ``medicaid_cost`` leaves out, so their whole cost is the state's. + CHIP's unborn-child coverage is the exception (``_state_only_federal_share``). """ p = _parameters(sim).gov.hhs.medicaid.eligibility status = sim.calculate("immigration_status", period=YEAR, decode_enums=True) years = sim.calculate("years_since_us_entry", period=YEAR) federal = _federally_eligible_status(np.asarray(status), np.asarray(years), p) - federal_share = sum( - np.asarray(sim.calculate(v, period=YEAR), dtype=float) for v in FEDERAL_SHARE_COSTS - ) - moved = np.where(federal, 0.0, federal_share) + shares = {v: np.asarray(sim.calculate(v, period=YEAR), dtype=float) for v in FEDERAL_SHARE_COSTS} + chip_category = np.asarray(sim.calculate("chip_category", period=YEAR, decode_enums=True)) + moved = _state_only_federal_share(federal, shares, chip_category) return np.asarray(sim.map_result(moved, "person", "household"), dtype=float) diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index edc1106..79a0504 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -23,11 +23,15 @@ - the federal line with health and TANF plus every state's line equals the all-government total with health, and TANF moves dollar for dollar from the federal line to the state's; +- the state-only adjustment moves nothing for people whose status allows + federal matching, never moves more than the federal share policyengine-us + books, and never moves CHIP's unborn-child (FCEP) match; - the federal immigration-status test matches policyengine-us's ``is_medicaid_immigration_status_eligible`` where no state coverage applies, - and the state-only adjustment moves exactly the federal share policyengine-us - books for a California child covered without satisfactory status (both run - when policyengine_us is installed). + the state-only adjustment moves exactly the federal share policyengine-us + books for a California child covered without satisfactory status, and it + leaves the federal CHIP match for an undocumented pregnant woman's FCEP + coverage in place (all run when policyengine_us is installed). The differential checks against ``_apply_shift`` and the immigration rules are the only tests that load PolicyEngine. @@ -44,6 +48,7 @@ from analysis.california_shift import ( CAPITAL_INCOME_VARS, + FCEP_CATEGORY, FEDERAL_HEALTH, FEDERAL_SHARE_COSTS, FMAP_SPLIT_HEALTH, @@ -61,6 +66,7 @@ _freed_labor, _health_tanf_accounting, _scale_by_state, + _state_only_federal_share, _weighted_rank, _weighted_sum_by, _with_derived, @@ -895,3 +901,74 @@ def test_state_only_coverage_matches_policyengine(): assert federal[2] > 0, "the undocumented child should be enrolled with a federal share booked" assert federal[1] > 0, "the citizen child should be enrolled with a federal share booked" assert _state_only_coverage(sim).tolist() == pytest.approx([federal[2]]) + + +def test_state_only_federal_share_examples(): + # A citizen on Medicaid, an undocumented woman covered through CHIP's + # unborn-child option, a DACA child on CHIP and an undocumented adult on + # state-funded Medicaid with a Medicare Savings Program share. + federal = [True, False, False, False] + shares = { + "medicaid_federal_cost": [100.0, 0.0, 0.0, 900.0], + "chip_federal_cost": [0.0, 300.0, 200.0, 0.0], + "msp_federal_cost": [0.0, 0.0, 0.0, 10.0], + } + chip_category = ["NONE", FCEP_CATEGORY, "CHILD", "NONE"] + got = _state_only_federal_share(federal, shares, chip_category) + assert got.tolist() == [0.0, 0.0, 200.0, 910.0] + + +@settings(max_examples=300, deadline=None) +@given(st.data()) +def test_state_only_federal_share_bounds(data): + n = data.draw(st.integers(1, 30)) + federal = np.asarray(data.draw(st.lists(st.booleans(), min_size=n, max_size=n))) + shares = { + v: np.asarray(data.draw(st.lists(SPENDING, min_size=n, max_size=n))) + for v in FEDERAL_SHARE_COSTS + } + chip_category = np.asarray( + data.draw( + st.lists( + st.sampled_from(["NONE", "CHILD", "PREGNANT_STANDARD", FCEP_CATEGORY]), + min_size=n, + max_size=n, + ) + ) + ) + moved = _state_only_federal_share(federal, shares, chip_category) + total = sum(shares.values()) + assert (moved >= 0).all() + assert (moved <= total + 1e-9).all() + assert (moved[federal] == 0).all() + fcep = chip_category == FCEP_CATEGORY + np.testing.assert_allclose( + moved[~federal & fcep], + (shares["medicaid_federal_cost"] + shares["msp_federal_cost"])[~federal & fcep], + ) + np.testing.assert_allclose(moved[~federal & ~fcep], total[~federal & ~fcep]) + + +def test_state_only_coverage_keeps_fcep_match_federal(): + pytest.importorskip("policyengine_us") + from policyengine_us import Simulation + + from analysis.california_shift import _state_only_coverage + + # Texas covers no undocumented adults in Medicaid, so an undocumented + # pregnant woman with no income qualifies only through CHIP's unborn-child + # option, which federal CHIP funds match whatever her status. + people = { + "mother": { + "age": {2026: 28}, + "immigration_status": {2026: "UNDOCUMENTED"}, + "is_pregnant": {2026: True}, + "takes_up_medicaid_if_eligible": {2026: True}, + "takes_up_chip_if_eligible": {2026: True}, + } + } + sim = Simulation(situation=_situation("TX", people)) + category = sim.calculate("chip_category", 2026, decode_enums=True) + assert np.asarray(category).astype(str).tolist() == [FCEP_CATEGORY] + assert float(np.asarray(sim.calculate("chip_federal_cost", 2026))[0]) > 0 + assert _state_only_coverage(sim).tolist() == pytest.approx([0.0]) From c916f7f490f776e70b07177fc320225a5ffd53a2 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 16:49:01 -0500 Subject: [PATCH 14/17] Price Medicaid at the baseline and keep state payroll and local taxes off the federal line This addresses the round-1 review of #49: - Each shifted branch takes the baseline's medicaid_slcsp_state_denominator, so state Medicaid totals move with enrollment. Outside reform mode, policyengine-us spreads each state's spending over the branch's own enrollees, which holds every state's total fixed. - Federal net revenue leaves out state payroll taxes (employee and employer) and local taxes, which the all-government total counts but no state line holds. federal_net_published_definition_change_b keeps the site's definition for the drift comparison. The summary's federal split adds the additional Medicare tax and FUTA and reports state payroll and local lines. - A state whose line already holds its TANF program (Massachusetts's TAFDC in policyengine-us 2.2.1) pays it once, and the all-government total with health counts it once. - Healthier Oregon's cost drops for people policyengine-us also enrolls in Medicaid. - The five state premium subsidies that healthcare_benefit_value counts (CT, MA, NJ, VT, WA) join state health spending. The classification check reads healthcare_benefit_value and accepts household_health_benefits with or without them, as later policyengine-us releases list them there. - CHIP's unborn-child carve-out keys on is_chip_fcep_eligible_person and chip_enrolled, and the docstring restates the emergency-Medicaid caveat. - The chart's title follows the sign of California's change, its subtitle turns general when other states appear, and small ranges get decimal ticks. Tests: - On a four-household PolicyEngine dataset, a branch's Medicaid total stays fixed on its own and rises with enrollment at the baseline price. - Property tests cover the federal split's completeness, the TANF charge, and the all-government identity with state payroll and local taxes. - Each fix also has an example test. Co-Authored-By: Claude Opus 5.5 --- analysis/california_chart.py | 44 ++- analysis/california_shift.py | 360 ++++++++++++++++---- analysis/california_summary.py | 108 ++++-- analysis/tests/test_california_shift.py | 435 +++++++++++++++++++++--- 4 files changed, 788 insertions(+), 159 deletions(-) diff --git a/analysis/california_chart.py b/analysis/california_chart.py index 5fa9523..a469b88 100644 --- a/analysis/california_chart.py +++ b/analysis/california_chart.py @@ -48,8 +48,8 @@ # Medi-Cal, CHIP and Medicare Savings Program costs, the coverage it funds # alone, its premium subsidy and CalWORKs. CALIFORNIA_HEALTH_TANF_NOTE = ( - "We net out refundable credits like CalEITC, benefits like the SSI supplement, " - "CalWORKs, and California's share of Medi-Cal costs." + "We net out refundable credits like CalEITC, benefits like the SSI supplement, CalWORKs, " + "and California's Medi-Cal and premium subsidy spending." ) #: What each measure reads from a run (California, California with SDI, all @@ -77,11 +77,40 @@ "Change in state tax revenue, net of credits, benefits, Medi-Cal and " "CalWORKs, $ billions" ), + "subtitle_all_states": ( + "Change in state tax revenue, net of credits, benefits, Medicaid and " + "TANF, $ billions" + ), "california_note": CALIFORNIA_HEALTH_TANF_NOTE, }, } +def subtitle_for(measure, california_only): + """The measure's subtitle, naming California's programs only when California stands alone.""" + entry = MEASURES[measure] + if california_only: + return entry["subtitle"] + return entry.get("subtitle_all_states", entry["subtitle"]) + + +def title_for(ca): + """A title that says which way California's revenue moves.""" + moved = [v for v in ca if v != 0] + if moved and all(v > 0 for v in moved): + return "California gains as labor income shifts to capital" + if moved and all(v < 0 for v in moved): + return "California loses as labor income shifts to capital" + return "California's revenue as labor income shifts to capital" + + +def dollar_format(values): + """Format for $B labels: one decimal when the values span less than $5 billion.""" + span = max(values) - min(values) if values else 0.0 + decimals = 1 if span < 5 else 0 + return lambda v: f"${v:,.{decimals}f}B" + + def series_from_sweep(path=SWEEP_PATH): data = json.load(open(path)) xs, ca, other = [], [], [] @@ -140,6 +169,9 @@ def draw(xs, ca, ca_sdi, other, out, source_note, measure_note=None, ax.plot(xs, other, color=OTHER_STATES, linewidth=2, marker="o", markersize=5, label="All other states combined", zorder=3, clip_on=False) + drawn = list(ca) + list(ca_sdi or []) + list(other or []) + dollars = dollar_format(drawn) + # Direct labels at the line ends, in text ink beside the colored mark. ends = [(ca[-1], "California")] if ca_sdi is not None: @@ -147,14 +179,14 @@ def draw(xs, ca, ca_sdi, other, out, source_note, measure_note=None, if other is not None: ends.append((other[-1], "All other states")) for y, text in ends: - ax.annotate(f"{text} ${y:,.0f}B", xy=(xs[-1], y), xytext=(6, 0), + ax.annotate(f"{text} {dollars(y)}", xy=(xs[-1], y), xytext=(6, 0), textcoords="offset points", va="center", fontsize=9.5, color=TEXT_PRIMARY) ax.set_xlim(0, 100) ax.set_xticks(range(0, 101, 20)) ax.xaxis.set_major_formatter(FuncFormatter(lambda v, _: f"{v:.0f}%")) - ax.yaxis.set_major_formatter(FuncFormatter(lambda v, _: f"${v:,.0f}B")) + ax.yaxis.set_major_formatter(FuncFormatter(lambda v, _: dollars(v))) ax.set_xlabel("Share of labor income that moves to capital income", color=TEXT_SECONDARY) ax.grid(axis="y", color=GRID, linewidth=0.8) ax.tick_params(colors=TEXT_SECONDARY, length=0) @@ -162,7 +194,7 @@ def draw(xs, ca, ca_sdi, other, out, source_note, measure_note=None, spine.set_visible(False) ax.legend(frameon=False, loc="upper left", fontsize=9.5, labelcolor=TEXT_PRIMARY) - fig.text(0.06, 0.955, "California gains as labor income shifts to capital", + fig.text(0.06, 0.955, title_for(ca), fontsize=14, fontweight="bold", color=TEXT_PRIMARY, ha="left") fig.text(0.06, 0.915, subtitle, fontsize=10, color=TEXT_SECONDARY, ha="left") notes = [ @@ -210,7 +242,7 @@ def main(): other = None measure_note = MEASURES[measure]["california_note"] print(draw(xs, ca, ca_sdi, other, args.out, note, measure_note, - subtitle=MEASURES[measure]["subtitle"])) + subtitle=subtitle_for(measure, args.california_only))) if __name__ == "__main__": diff --git a/analysis/california_shift.py b/analysis/california_shift.py index 5018bdc..4f30e15 100644 --- a/analysis/california_shift.py +++ b/analysis/california_shift.py @@ -11,8 +11,11 @@ use tax only, so this wage levy sits outside it. * California's change split by within-state market-income group, with the labor income each group gives up and the capital income it receives. -* Federal net revenue, as the all-government total less every state's net - change. +* Federal net revenue: the all-government total less every state's net + change and less the state payroll taxes (``STATE_PAYROLL_TAXES``) and local + taxes (``LOCAL_TAXES``) that the all-government total counts but no state's + line holds. The published site's federal line keeps those taxes; + ``federal_net_published_definition_change_b`` reproduces it. * A within-state variant of the shift. The sweep scales every positive capital-income dollar in the country by one factor, so a state whose share of national capital income exceeds its share of labor income gains market @@ -23,18 +26,27 @@ * Health spending and TANF, which the sweep's state measure leaves out (``*_with_health_tanf`` fields). Household net income excludes health benefits (``gov.simulation.include_health_benefits_in_net_income`` is - false), so Medicaid, CHIP, the Medicare Savings Programs and ACA premium tax - credits sit in no government's total, and ``tanf`` sits in - ``household_benefits`` but in no state's line, so the sweep books it federal. - These fields charge each state its share of Medicaid, CHIP and Medicare - Savings Program costs under federal matching rules + false), so Medicaid, CHIP, the Medicare Savings Programs, ACA premium tax + credits and state premium subsidies sit in no government's total. ``tanf`` + sits in ``household_benefits`` but in no state's line, so the sweep books it + federal; the exception is a state whose program + ``gov.household.household_state_benefits`` also lists (Massachusetts's + TAFDC in policyengine-us 2.2.1, which later releases drop from that list), + which the state's line holds and the all-government total counts twice. These fields charge each state its share of Medicaid, CHIP and + Medicare Savings Program costs under federal matching rules (``state_benefit_cost``, from ``gov.hhs.medicaid.cost_share``), the whole cost of coverage it funds alone for people whose immigration status rules out federal matching (CHIP's unborn-child coverage keeps its federal match), - its own health subsidies, and its whole TANF change, because the federal - TANF block grant is a fixed amount. The all-government - total adds every program in ``gov.household.household_health_benefits``; - federal net revenue is again what the states leave. + its own health subsidies, and the TANF change its line doesn't already hold, + because the federal TANF block grant is a fixed amount. The all-government + total adds every program in ``healthcare_benefit_value`` and counts TANF + once; federal net revenue is again what the states and localities leave. +* Medicaid at the baseline's cost per enrollee. policyengine-us spreads each + state's fixed Medicaid spending over the enrollees in each simulation + (``medicaid_slcsp_state_denominator``), so on its own a shifted branch that + adds enrollees cuts every enrollee's cost and keeps the state's total. Each + branch here takes the baseline's denominator, as policyengine-us's reform + branches do, so a new enrollee adds cost. Usage: python -m analysis.california_shift [--levels 0.1,0.2,0.5,1.0] @@ -83,17 +95,55 @@ "assigned_nm_premium_assistance", "or_healthier_oregon_cost", ) +#: State premium subsidies that ``healthcare_benefit_value`` counts and +#: policyengine-us 2.2.1's ``gov.household.household_health_benefits`` leaves +#: out; later releases list them there (PolicyEngine/policyengine-us#9514). +#: Each state pays insurers these amounts on top of the federal premium tax +#: credit. +STATE_HEALTH_UNLISTED = ( + "ct_covered_connecticut", + "ma_connector_care", + "nj_njhps", + "vt_premium_assistance", + "wa_cascade_care_savings", +) +#: policyengine-us's household value of health coverage. Its ``adds`` list +#: every health program, including those ``household_health_benefits`` omits. +HEALTH_VALUE_VARIABLE = "healthcare_benefit_value" +#: Healthier Oregon covers people federal Medicaid can't. Its cost has no +#: enrollment test, and policyengine-us also enrolls Oregon's undocumented +#: residents in Medicaid (``gov.hhs.medicaid.eligibility.undocumented_immigrant`` +#: is true for Oregon), so it prices their coverage twice. Their Medicaid cost +#: stays here and their Healthier Oregon cost goes. +HEALTHIER_OREGON = "or_healthier_oregon_cost" #: The members of ``gov.household.federal_benefit_cost`` and #: ``gov.household.state_benefit_cost`` this accounting expects. FEDERAL_SHARE_COSTS = ("medicaid_federal_cost", "chip_federal_cost", "msp_federal_cost") STATE_SHARE_COSTS = ("medicaid_state_cost", "chip_state_cost", "msp_state_cost") -#: ``chip_category`` for a pregnant person covered through CHIP's -#: From-Conception-to-End-of-Pregnancy option. The unborn child is the -#: beneficiary (42 CFR 457.10), so federal CHIP funds match the coverage -#: whatever the parent's immigration status. -FCEP_CATEGORY = "PREGNANT_FCEP" +#: Whether CHIP's From-Conception-to-End-of-Pregnancy option covers a +#: pregnant person. The unborn child is the beneficiary (42 CFR 457.10), so +#: federal CHIP funds match the coverage whatever the parent's immigration +#: status. +FCEP_ELIGIBLE = "is_chip_fcep_eligible_person" +CHIP_ENROLLED = "chip_enrolled" #: Every state TANF program; California's is CalWORKs (``ca_tanf``). TANF_VARIABLE = "tanf" +#: Spreads each state's Medicaid spending over that state's enrollees. +MEDICAID_DENOMINATOR = "medicaid_slcsp_state_denominator" +#: Taxes the all-government total counts that go to states and localities but +#: sit outside the sweep's state measure: payroll taxes such as California's +#: SDI withholding and state unemployment insurance, and city wage taxes. +STATE_PAYROLL_TAXES = ("employee_state_payroll_tax", "employer_state_payroll_tax") +LOCAL_TAXES = ( + "local_income_tax_before_refundable_credits", + "local_occupational_tax", + "employer_local_payroll_tax", +) +#: Federal payroll taxes that ``fiscal.revenue_components`` doesn't list, +#: which the summary's federal split needs to add up. +FEDERAL_PAYROLL_UNLISTED = ("additional_medicare_tax", "employer_federal_unemployment_tax") +#: The taxes above, kept by variable in each household frame. +OTHER_TAX_VARIABLES = STATE_PAYROLL_TAXES + LOCAL_TAXES + FEDERAL_PAYROLL_UNLISTED #: Within-California groups by baseline household market income, as #: (label, lower weighted percentile, upper weighted percentile). @@ -122,14 +172,17 @@ def _parameters(sim): return sim.tax_benefit_system.parameters(f"{YEAR}-01-01") -def _check_health_lists(parameters): +def _check_health_lists(parameters, health_value_programs): """Refuse a model whose health accounting differs from what this module classifies. The ``*_with_health_tanf`` fields assume health benefits sit outside household net income, that every program in - ``gov.household.household_health_benefits`` has a payer here, and that the - federal and state benefit-cost lists hold the shares of the same three - programs. + ``gov.household.household_health_benefits`` and in + ``healthcare_benefit_value``'s ``adds`` (``health_value_programs``) has a + payer here, that the federal and state benefit-cost lists hold the shares + of the same three programs, and that no health program sits in + ``gov.household.household_state_benefits``, which the state measure already + nets. """ gov = parameters.gov if gov.simulation.include_health_benefits_in_net_income: @@ -137,18 +190,46 @@ def _check_health_lists(parameters): "health benefits are in household net income, so household_benefits " "already counts them; the health accounting would count them twice" ) + listed_health = set(FMAP_SPLIT_HEALTH) | set(FEDERAL_HEALTH) | set(STATE_HEALTH) + every_health = listed_health | set(STATE_HEALTH_UNLISTED) expected = { - "household_health_benefits": set(FMAP_SPLIT_HEALTH) | set(FEDERAL_HEALTH) | set(STATE_HEALTH), - "federal_benefit_cost": set(FEDERAL_SHARE_COSTS), - "state_benefit_cost": set(STATE_SHARE_COSTS), + # Releases after 2.2.1 also list the unlisted state subsidies here. + "gov.household.household_health_benefits": ( + set(gov.household.household_health_benefits) - set(STATE_HEALTH_UNLISTED), + listed_health, + ), + f"{HEALTH_VALUE_VARIABLE}'s adds": (set(health_value_programs), every_health), + "gov.household.federal_benefit_cost": ( + set(gov.household.federal_benefit_cost), + set(FEDERAL_SHARE_COSTS), + ), + "gov.household.state_benefit_cost": ( + set(gov.household.state_benefit_cost), + set(STATE_SHARE_COSTS), + ), } - for name, want in expected.items(): - listed = set(getattr(gov.household, name)) + for name, (listed, want) in expected.items(): if listed != want: raise ValueError( - f"gov.household.{name} changed: unclassified {sorted(listed - want)}, " + f"{name} changed: unclassified {sorted(listed - want)}, " f"missing {sorted(want - listed)}" ) + in_state_benefits = every_health & set(gov.household.household_state_benefits) + if in_state_benefits: + raise ValueError( + "gov.household.household_state_benefits lists health programs " + f"{sorted(in_state_benefits)}, which the health accounting would count twice" + ) + + +def _tanf_states_in_state_benefits(state_tanf_variables, state_benefits): + """States whose TANF program ``household_state_benefits`` already lists. + + ``state_tanf_variables`` holds every state TANF program, each named with its + state's code first (``ca_tanf``, ``ma_tafdc``). + """ + listed = set(state_benefits) + return sorted({v.split("_", 1)[0].upper() for v in state_tanf_variables if v in listed}) def _federally_eligible_status(status, years_since_entry, p): @@ -167,20 +248,19 @@ def _federally_eligible_status(status, years_since_entry, p): return citizen | (qualified & (bar_exempt | past_bar)) -def _state_only_federal_share(federal, shares, chip_category): +def _state_only_federal_share(federal, shares, fcep): """Federal share to move to the state, by person. ``federal`` says whether each person's immigration status lets federal funds match their coverage, ``shares`` maps each name in ``FEDERAL_SHARE_COSTS`` to the federal share policyengine-us books for each - person, and ``chip_category`` holds each person's decoded ``chip_category``. - For people federal matching can't cover, the Medicaid, Medicare Savings - Program and CHIP shares move, except CHIP coverage through the - unborn-child (FCEP) option, which federal funds match whatever the parent's - status. + person, and ``fcep`` says whether CHIP's unborn-child (FCEP) option covers + each person. For people federal matching can't cover, the Medicaid, + Medicare Savings Program and CHIP shares move, except CHIP for people the + FCEP option covers, which federal funds match whatever the parent's status. """ chip = np.where( - np.asarray(chip_category).astype(str) == FCEP_CATEGORY, + np.asarray(fcep, dtype=bool), 0.0, np.asarray(shares["chip_federal_cost"], dtype=float), ) @@ -198,27 +278,55 @@ def _state_only_coverage(sim): ``medicaid_federal_share`` and ``chip_federal_share`` apply federal matching rates to every enrollee, including people a state covers whatever their immigration status, such as California's Medi-Cal for undocumented, DACA - and TPS residents. Federal funds pay for such people only in emergencies, - which ``medicaid_cost`` leaves out, so their whole cost is the state's. - CHIP's unborn-child coverage is the exception (``_state_only_federal_share``). + and TPS residents. Federal law matches only emergency care for such people + (42 USC 1396b(v)), so this moves their whole federal share to the state. + If the state totals that ``medicaid_cost`` spreads (KFF and MACPAC totals of + Medicaid spending) count that federally matched emergency care, the move + overstates the state's cost by the federal share of their emergency care, + an amount this accounting can't measure. CHIP's unborn-child coverage keeps + its federal match (``_state_only_federal_share``). """ p = _parameters(sim).gov.hhs.medicaid.eligibility status = sim.calculate("immigration_status", period=YEAR, decode_enums=True) years = sim.calculate("years_since_us_entry", period=YEAR) federal = _federally_eligible_status(np.asarray(status), np.asarray(years), p) shares = {v: np.asarray(sim.calculate(v, period=YEAR), dtype=float) for v in FEDERAL_SHARE_COSTS} - chip_category = np.asarray(sim.calculate("chip_category", period=YEAR, decode_enums=True)) - moved = _state_only_federal_share(federal, shares, chip_category) + fcep = np.asarray(sim.calculate(FCEP_ELIGIBLE, period=YEAR), dtype=bool) & np.asarray( + sim.calculate(CHIP_ENROLLED, period=YEAR), dtype=bool + ) + moved = _state_only_federal_share(federal, shares, fcep) return np.asarray(sim.map_result(moved, "person", "household"), dtype=float) +def _healthier_oregon_overlap(sim): + """Healthier Oregon cost for people policyengine-us also enrolls in Medicaid, by household.""" + cost = np.asarray(sim.calculate(HEALTHIER_OREGON, period=YEAR), dtype=float) + enrolled = np.asarray(sim.calculate("medicaid_enrolled", period=YEAR), dtype=bool) + return np.asarray( + sim.map_result(np.where(enrolled, cost, 0.0), "person", "household"), dtype=float + ) + + +def _adds(sim, variable): + """The variables ``variable`` adds, from its list or the parameter it names.""" + adds = sim.tax_benefit_system.variables[variable].adds + if isinstance(adds, str): + node = _parameters(sim) + for part in adds.split("."): + node = getattr(node, part) + adds = node + return list(adds) + + def _with_derived(frame): """Add the measures built from a frame's stored arrays. ``state_health`` is each household's health spending that falls on its state and ``federal_health`` the part that falls on the federal government; between them they hold every program in - ``gov.household.household_health_benefits``. + ``healthcare_benefit_value`` once. ``tanf_charge`` is the TANF its state's + line doesn't already hold. ``state_payroll_tax`` and ``local_tax`` are the + taxes the all-government total counts outside every state's line. """ frame["state_net"] = frame["state_tax"] - frame["state_credits"] - frame["state_benefits"] frame["state_health"] = ( @@ -227,9 +335,12 @@ def _with_derived(frame): frame["federal_health"] = ( frame["federal_health_split"] - frame["state_only_coverage"] + frame["federal_health_other"] ) + frame["tanf_charge"] = frame["tanf"] - frame["tanf_in_state_benefits"] frame["state_net_with_health_tanf"] = ( - frame["state_net"] - frame["state_health"] - frame["tanf"] + frame["state_net"] - frame["state_health"] - frame["tanf_charge"] ) + frame["state_payroll_tax"] = sum(frame[v] for v in STATE_PAYROLL_TAXES) + frame["local_tax"] = sum(frame[v] for v in LOCAL_TAXES) return frame @@ -250,11 +361,21 @@ def _check_health_split(frame): def _household_frame(sim): """Household arrays needed for the California accounting.""" - _check_health_lists(_parameters(sim)) + # Imported here, as in run(), so the accounting helpers load without the model. + from policyengine_us.variables.gov.hhs.tanf.cash.tanf_if_takes_up import ( + STATE_TANF_VARIABLES, + ) + + parameters = _parameters(sim) + _check_health_lists(parameters, _adds(sim, HEALTH_VALUE_VARIABLE)) + state = np.asarray(sim.calculate("state_code_str", map_to="household", period=YEAR)).astype(str) + tanf = _hh(sim, TANF_VARIABLE) + tanf_states = _tanf_states_in_state_benefits( + STATE_TANF_VARIABLES, parameters.gov.household.household_state_benefits + ) + overlap = _healthier_oregon_overlap(sim) frame = { - "state": np.asarray( - sim.calculate("state_code_str", map_to="household", period=YEAR) - ).astype(str), + "state": state, "weight": np.asarray(sim.calculate("household_weight", period=YEAR), dtype=float), "market_income": _hh(sim, "household_market_income"), "state_tax": _hh(sim, "household_state_tax_before_refundable_credits"), @@ -270,10 +391,15 @@ def _household_frame(sim): "state_health_split": _hh(sim, "state_benefit_cost"), "fmap_split_cost": sum(_hh(sim, v) for v in FMAP_SPLIT_HEALTH), "federal_health_other": sum(_hh(sim, v) for v in FEDERAL_HEALTH), - "state_health_programs": sum(_hh(sim, v) for v in STATE_HEALTH), + "state_health_programs": sum(_hh(sim, v) for v in STATE_HEALTH + STATE_HEALTH_UNLISTED) + - overlap, + "healthier_oregon_overlap": overlap, "state_only_coverage": _state_only_coverage(sim), - "tanf": _hh(sim, TANF_VARIABLE), + "tanf": tanf, + "tanf_in_state_benefits": np.where(np.isin(state, tanf_states), tanf, 0.0), } + for variable in OTHER_TAX_VARIABLES: + frame[variable] = _hh(sim, variable) try: frame["sdi"] = _hh(sim, SDI_VARIABLE) except Exception as exc: # noqa: BLE001 - record, do not fail the run @@ -439,6 +565,7 @@ def _baseline_concentration(base): "state_only_coverage_b": float((base["state_only_coverage"][mask] * w).sum() / 1e9), "state_health_programs_b": float((base["state_health_programs"][mask] * w).sum() / 1e9), "tanf_b": float((base["tanf"][mask] * w).sum() / 1e9), + "state_payroll_tax_b": float((base["state_payroll_tax"][mask] * w).sum() / 1e9), "state_net_with_health_tanf_b": float( (base["state_net_with_health_tanf"][mask] * w).sum() / 1e9 ), @@ -471,8 +598,11 @@ def delta(key): "state_health_split_change_b": delta("state_health_split"), "state_only_coverage_change_b": delta("state_only_coverage"), "state_health_programs_change_b": delta("state_health_programs"), - "tanf_change_b": delta("tanf"), + # TANF the state's line doesn't already hold: for California, CalWORKs. + "tanf_change_b": delta("tanf_charge"), "state_net_with_health_tanf_change_b": delta("state_net_with_health_tanf"), + "state_payroll_tax_change_b": delta("state_payroll_tax"), + "local_tax_change_b": delta("local_tax"), } def total(sel): @@ -495,15 +625,40 @@ def total(sel): return total(everyone), groups +def _outside_state_measure(base, scen): + """Changes in taxes the all-government total counts outside every state's line, $B.""" + assert np.array_equal(base["state"], scen["state"]), "household order changed" + w = base["weight"] + + def total(key): + return float(((scen[key] - base[key]) * w).sum()) / 1e9 + + return { + "state_payroll_tax_change_b": total("state_payroll_tax"), + "local_tax_change_b": total("local_tax"), + } + + +def _other_tax_components_b(base, scen): + """National change in each of ``OTHER_TAX_VARIABLES``, $B, keyed as ``fiscal`` keys its deltas.""" + w = base["weight"] + return { + f"{v}_change": float(((scen[v] - base[v]) * w).sum()) / 1e9 for v in OTHER_TAX_VARIABLES + } + + def _health_tanf_accounting(base, scen, state_net, all_government): """Each government's change once health spending and TANF count, $B. ``state_net`` ($B by state) and ``all_government`` ($B) follow the sweep: - ``tanf`` sits in all-government benefits but in no state's line, and health - spending sits in neither. Here each state also pays its health share - (``state_health``) and its whole TANF change, the all-government total pays - every health dollar, and federal net revenue is what the states leave, so - TANF moves from the federal line to the states'. + ``tanf`` sits in all-government benefits but in no state's line, except a + program ``household_state_benefits`` also lists, which sits in its state's + line and twice in the all-government total; health spending sits in + neither. Here each state also pays its health share (``state_health``) and + the TANF change its line doesn't already hold, the all-government total + pays every health dollar and counts TANF once, and federal net revenue is + what the states and localities leave, so TANF moves from the federal line to + the states'. """ assert np.array_equal(base["state"], scen["state"]), "household order changed" w = base["weight"] @@ -511,25 +666,35 @@ def _health_tanf_accounting(base, scen, state_net, all_government): def by_state(key): return _weighted_sum_by(base["state"], scen[key] - base[key], w) + def total(key): + return float(((scen[key] - base[key]) * w).sum()) + state_health = by_state("state_health") - tanf = by_state("tanf") - federal_health = float(((scen["federal_health"] - base["federal_health"]) * w).sum()) + tanf_charge = by_state("tanf_charge") + federal_health = total("federal_health") + counted_twice = total("tanf_in_state_benefits") states = { code: state_net.get(code, 0.0) - - (state_health.get(code, 0.0) + tanf.get(code, 0.0)) / 1e9 - for code in set(state_net) | set(state_health) | set(tanf) + - (state_health.get(code, 0.0) + tanf_charge.get(code, 0.0)) / 1e9 + for code in set(state_net) | set(state_health) | set(tanf_charge) } all_states = sum(states.values()) all_government_with_health = ( - all_government - (federal_health + sum(state_health.values())) / 1e9 + all_government - (federal_health + sum(state_health.values()) - counted_twice) / 1e9 ) + outside = _outside_state_measure(base, scen) return { "health_federal_change_b": federal_health / 1e9, "health_state_change_b": sum(state_health.values()) / 1e9, - "tanf_change_b": sum(tanf.values()) / 1e9, + "healthier_oregon_overlap_change_b": total("healthier_oregon_overlap") / 1e9, + "tanf_change_b": total("tanf") / 1e9, + "tanf_counted_twice_change_b": counted_twice / 1e9, "all_government_with_health_change_b": all_government_with_health, "all_states_net_with_health_tanf_change_b": all_states, - "federal_net_with_health_tanf_change_b": all_government_with_health - all_states, + "federal_net_with_health_tanf_change_b": all_government_with_health + - all_states + - outside["state_payroll_tax_change_b"] + - outside["local_tax_change_b"], "other_states_net_with_health_tanf_change_b": all_states - states.get(STATE, 0.0), "states_losing_with_health_tanf_count": sum(1 for v in states.values() if v < -0.005), "state_net_with_health_tanf_change_b": dict(sorted(states.items())), @@ -551,21 +716,28 @@ def _level_row(branch, freed, pct, base, base_rev, base_states, allocation): (c for c, v in state_net.items() if v > 0.005), key=lambda c: -state_net[c] ) losers = [c for c, v in state_net.items() if v < -0.005] - health_tanf = _health_tanf_accounting(base, scen, state_net, delta["total_change"] / 1e9) - return health_tanf | { + all_government = delta["total_change"] / 1e9 + outside = _outside_state_measure(base, scen) + health_tanf = _health_tanf_accounting(base, scen, state_net, all_government) + components = {k: v / 1e9 for k, v in sorted(delta.items()) if isinstance(v, (int, float))} + components.update(_other_tax_components_b(base, scen)) + return health_tanf | outside | { "shift_pct": int(round(pct * 100)), "allocation": allocation, "labor_income_moved_b": freed / 1e9, - "all_government_change_b": delta["total_change"] / 1e9, + "all_government_change_b": all_government, "all_states_net_change_b": all_states, - "federal_net_change_b": delta["total_change"] / 1e9 - all_states, + "federal_net_change_b": all_government + - all_states + - outside["state_payroll_tax_change_b"] + - outside["local_tax_change_b"], + # The published site's federal line: all governments less the states. + "federal_net_published_definition_change_b": all_government - all_states, "other_states_net_change_b": other_states, "states_gaining": gainers, "states_losing_count": len(losers), "state_net_change_b": dict(sorted(state_net.items())), - "national_components_b": { - k: v / 1e9 for k, v in sorted(delta.items()) if isinstance(v, (int, float)) - }, + "national_components_b": dict(sorted(components.items())), "california": ca_total, "california_by_group": ca_groups, } @@ -579,8 +751,9 @@ def _report(row): f"{ca['state_net_including_sdi_change_b']:+.2f}B; market income " f"{ca['market_income_change_b']:+.1f}B); other states " f"{row['other_states_net_change_b']:+.2f}B; federal " - f"{row['federal_net_change_b']:+.1f}B; all-gov " - f"{row['all_government_change_b']:+.1f}B", + f"{row['federal_net_change_b']:+.1f}B; state payroll " + f"{row['state_payroll_tax_change_b']:+.2f}B; local {row['local_tax_change_b']:+.2f}B; " + f"all-gov {row['all_government_change_b']:+.1f}B", flush=True, ) print( @@ -594,6 +767,31 @@ def _report(row): ) +def _medicaid_denominator(sim): + """Each person's ``medicaid_slcsp_state_denominator`` in ``sim``.""" + return np.asarray(sim.calculate(MEDICAID_DENOMINATOR, period=YEAR), dtype=float) + + +def _price_medicaid_at_baseline(branch, denominator): + """Price a shifted branch's Medicaid enrollees at the baseline's cost per enrollee. + + ``medicaid_cost_if_enrolled`` is the state's spending times the person's + cost index over ``medicaid_slcsp_state_denominator``, the weighted sum of + the cost indices of the state's enrollees in the same simulation. Left to + itself, a branch that adds enrollees raises its own denominator, so every + enrollee's cost falls and the state's total stays fixed. With the + baseline's denominator, each enrollee costs what an enrollee with that cost + index costs in the baseline, so the total moves with enrollment, as in + policyengine-us's reform branches. + """ + if branch.get_holder(MEDICAID_DENOMINATOR).get_known_periods(): + raise RuntimeError( + f"{MEDICAID_DENOMINATOR} is already calculated in this branch; set the " + "baseline's before calculating anything that depends on it" + ) + branch.set_input(MEDICAID_DENOMINATOR, YEAR, np.asarray(denominator, dtype=float)) + + def run( levels, microsim_factory=managed_us_microsimulation, @@ -613,6 +811,7 @@ def run( base_states = state_revenue_components(base_sim) base = _household_frame(base_sim) baseline = _baseline_concentration(base) + medicaid_denominator = _medicaid_denominator(base_sim) del base_sim gc.collect() if verbose: @@ -626,6 +825,7 @@ def run( for pct in levels: sim = microsim_factory() branch, freed = _apply_shift(sim, f"ca_{int(round(pct * 100))}", pct) + _price_medicaid_at_baseline(branch, medicaid_denominator) row = _level_row(branch, freed, pct, base, base_rev, base_states, "national") rows.append(row) if verbose: @@ -639,6 +839,7 @@ def run( branch, freed, freed_by_state = _apply_within_state_shift( sim, f"ca_within_{int(round(pct * 100))}", pct ) + _price_medicaid_at_baseline(branch, medicaid_denominator) row = _level_row(branch, freed, pct, base, base_rev, base_states, "within_state") row["labor_income_moved_by_state_b"] = { state: value / 1e9 for state, value in sorted(freed_by_state.items()) @@ -662,15 +863,26 @@ def run( "gov.household.household_state_benefits (for California: SSP, CAPI, " "child care subsidies, CVRP, CARE, FERA and three Los Angeles " "programs; no Medi-Cal or CalWORKs); California's employee SDI " - "contribution is reported separately. The *_with_health_tanf fields " + "contribution is reported separately. Federal net revenue is the " + "all-government change less every state's net change and less state " + "payroll taxes (employee and employer) and local taxes, which the " + "all-government total counts but no state's line holds; " + "federal_net_published_definition_change_b keeps them, as the published " + "site does. Each shifted branch prices Medicaid enrollees at the " + "baseline's cost per enrollee (the baseline's " + "medicaid_slcsp_state_denominator), so state Medicaid totals move with " + "enrollment. The *_with_health_tanf fields " "also charge each state its share of Medicaid, CHIP and Medicare " "Savings Program costs under federal matching rules (state_benefit_cost), " "the whole cost of coverage it funds alone for people whose immigration " - "status rules out federal matching, its own health subsidies (for " - "California, the Covered California premium subsidy) and its whole TANF " - "change (CalWORKs for California), since the federal TANF block grant is " - "fixed; the all-government total adds every program in " - "gov.household.household_health_benefits. " + "status rules out federal matching (CHIP coverage through the " + "unborn-child option keeps its federal match), its own health subsidies " + "(for California, the Covered California premium subsidy; Healthier " + "Oregon only for people not also enrolled in Medicaid) and the TANF " + "change its line doesn't already hold (CalWORKs for California; " + "Massachusetts's TAFDC already sits in its state benefits), since the " + "federal TANF block grant is fixed; the all-government total adds every " + "program in healthcare_benefit_value and counts TANF once. " "'levels' allocate the new capital income across the country, as the " "published sweep does; 'within_state_levels' give each state's freed " "labor income to that state's own capital holders." diff --git a/analysis/california_summary.py b/analysis/california_summary.py index 02ffc43..63f3a22 100644 --- a/analysis/california_summary.py +++ b/analysis/california_summary.py @@ -8,8 +8,9 @@ pays for in full, its premium subsidy and CalWORKs, with the other states and federal lines on the same basis (runs without these fields skip the lines); * all other states combined, every state, and federal net revenue; -* the federal split into income tax, payroll taxes, refundable credits and - benefits; +* the federal split into income tax, federal payroll taxes, refundable credits + and benefits, and the state payroll and local taxes that the all-government + total counts outside every state's line and the federal line; * the labor income California households give up and the capital income they receive; * California's change by within-state market-income group; @@ -30,53 +31,86 @@ REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SWEEP_PATH = os.path.join(REPO, "src", "data", "shiftSweepData.json") +#: Federal payroll taxes on the employee and self-employed side, inside +#: ``household_tax_before_refundable_credits``. EMPLOYEE_PAYROLL_KEYS = ( "employee_social_security_tax_change", "employee_medicare_tax_change", + "additional_medicare_tax_change", "self_employment_tax_change", ) +#: Federal payroll taxes on the employer side, inside ``employer_payroll_tax``. +EMPLOYER_PAYROLL_KEYS = ( + "employer_social_security_tax_change", + "employer_medicare_tax_change", + "employer_federal_unemployment_tax_change", +) +#: State payroll and local taxes, with the aggregate each sits in. +HOUSEHOLD_STATE_LOCAL_KEYS = ( + "employee_state_payroll_tax_change", + "local_income_tax_before_refundable_credits_change", + "local_occupational_tax_change", +) +EMPLOYER_STATE_LOCAL_KEYS = ( + "employer_state_payroll_tax_change", + "employer_local_payroll_tax_change", +) def federal_split(components): """Split the federal net change ($B) into its parts. - Federal net = all-government net less the states' net, so each federal part - is the national aggregate less its state portion. ``residual`` holds any - household tax outside federal income tax, payroll and state tax (local and - flat taxes); it is zero when the decomposition is complete. Payroll takes - the employer side from ``employer_payroll_tax``, the aggregate that - ``fiscal.net_fiscal_impact`` counts, so the parts add up to the total. + Federal net = all-government net less the states' net and less the state + payroll and local taxes no state's line holds, so each federal part is the + national aggregate less its state and local portions. ``payroll`` holds + federal payroll taxes only. ``residual`` holds household tax outside + federal income tax, federal payroll, state tax, state payroll and local + taxes (the flat tax), and ``employer_residual`` employer payroll tax outside + its federal, state and local parts; both are zero when the decomposition is + complete. A run that predates the state payroll and local lines reports + none of them, so its residuals hold those taxes and its federal line is the + published site's. """ c = components - employee_payroll = sum(c.get(k, 0.0) for k in EMPLOYEE_PAYROLL_KEYS) - employer_payroll = c.get("employer_payroll_tax_change", 0.0) + + def get(key): + return c.get(key, 0.0) + + employee_payroll = sum(get(k) for k in EMPLOYEE_PAYROLL_KEYS) + employer_payroll = sum(get(k) for k in EMPLOYER_PAYROLL_KEYS) payroll = employee_payroll + employer_payroll - income_tax = c.get("fed_income_tax_before_refundable_credits_change", 0.0) - state_tax = c.get("state_tax_before_refundable_credits_change", 0.0) + household_state_local = sum(get(k) for k in HOUSEHOLD_STATE_LOCAL_KEYS) + employer_state_local = sum(get(k) for k in EMPLOYER_STATE_LOCAL_KEYS) + income_tax = get("fed_income_tax_before_refundable_credits_change") + state_tax = get("state_tax_before_refundable_credits_change") residual = ( - c.get("household_tax_before_refundable_credits_change", 0.0) + get("household_tax_before_refundable_credits_change") - income_tax - employee_payroll - state_tax + - household_state_local ) - credits = c.get("household_refundable_tax_credits_change", 0.0) - c.get( - "state_refundable_credits_change", 0.0 + employer_residual = ( + get("employer_payroll_tax_change") - employer_payroll - employer_state_local ) - benefits = c.get("household_benefits_change", 0.0) - c.get("state_benefits_change", 0.0) - states_net = ( - state_tax - - c.get("state_refundable_credits_change", 0.0) - - c.get("state_benefits_change", 0.0) - ) - federal = income_tax + payroll + residual - credits - benefits + credits = get("household_refundable_tax_credits_change") - get("state_refundable_credits_change") + benefits = get("household_benefits_change") - get("state_benefits_change") + states_net = state_tax - get("state_refundable_credits_change") - get("state_benefits_change") + federal = income_tax + payroll + residual + employer_residual - credits - benefits return { "income_tax": income_tax, "payroll": payroll, "employee_payroll": employee_payroll, "employer_payroll": employer_payroll, - "employer_social_security_and_medicare": c.get("employer_social_security_tax_change", 0.0) - + c.get("employer_medicare_tax_change", 0.0), + "employer_social_security_and_medicare": get("employer_social_security_tax_change") + + get("employer_medicare_tax_change"), "residual": residual, + "employer_residual": employer_residual, + "state_payroll": get("employee_state_payroll_tax_change") + + get("employer_state_payroll_tax_change"), + "local": get("local_income_tax_before_refundable_credits_change") + + get("local_occupational_tax_change") + + get("employer_local_payroll_tax_change"), "refundable_credits": credits, "federal_refundable_credits_reported": c.get( "income_tax_refundable_credits_change", float("nan") @@ -168,16 +202,21 @@ def summarize(run, published=None): ) lines.append( f" Federal net {row['federal_net_change_b']:+.1f}B = income tax {split['income_tax']:+.1f}B " - f"+ payroll {split['payroll']:+.1f}B + other household tax {split['residual']:+.2f}B " + f"+ payroll {split['payroll']:+.1f}B + other tax " + f"{split['residual'] + split['employer_residual']:+.2f}B " f"- refundable credits {split['refundable_credits']:+.1f}B - benefits {split['benefits']:+.1f}B " f"(sum {split['federal_net']:+.1f}B; reported federal refundable credits " f"{split['federal_refundable_credits_reported']:+.1f}B)" ) lines.append( - f" Payroll: employee and self-employment {split['employee_payroll']:+.1f}B, employer " - f"{split['employer_payroll']:+.1f}B (employer Social Security and Medicare lines " + f" Federal payroll: employee and self-employment {split['employee_payroll']:+.1f}B, employer " + f"{split['employer_payroll']:+.1f}B (employer Social Security and Medicare " f"{split['employer_social_security_and_medicare']:+.1f}B)" ) + lines.append( + f" Outside every state's line and the federal line: state payroll taxes " + f"{split['state_payroll']:+.2f}B, local taxes {split['local']:+.2f}B" + ) lines.append(f" All governments {row['all_government_change_b']:+.1f}B") total_tax = ca["state_tax_before_refundable_credits_change_b"] for g in row["california_by_group"]: @@ -191,10 +230,14 @@ def summarize(run, published=None): ) if published and row["shift_pct"] in published: p = published[row["shift_pct"]] + # The site's federal line is all governments less the states. + federal_published = row.get( + "federal_net_published_definition_change_b", row["federal_net_change_b"] + ) lines.append( f" Published: California {p['california']:+.2f}B ({_pct(ca['state_net_change_b'], p['california']):+.1f}%), " - f"other states {p['other_states']:+.2f}B, federal {p['federal']:+.1f}B " - f"({_pct(row['federal_net_change_b'], p['federal']):+.1f}%)" + f"other states {p['other_states']:+.2f}B, federal on the site's definition {p['federal']:+.1f}B " + f"({_pct(federal_published, p['federal']):+.1f}%)" ) lines.extend(_within_state_lines(run)) return "\n".join(lines) @@ -205,6 +248,9 @@ def _health_tanf_lines(row): ca = row["california"] if "state_net_with_health_tanf_change_b" not in ca: return [] + tanf = row["tanf_change_b"] + twice = row.get("tanf_counted_twice_change_b", 0.0) + oregon = row.get("healthier_oregon_overlap_change_b", 0.0) return [ f" California with health and TANF {ca['state_net_with_health_tanf_change_b']:+.2f}B; with SDI " f"{ca['state_net_with_health_tanf_including_sdi_change_b']:+.2f}B. Spending changes: state health " @@ -216,7 +262,9 @@ def _health_tanf_lines(row): f"{row['federal_net_with_health_tanf_change_b']:+.1f}B; all governments " f"{row['all_government_with_health_change_b']:+.1f}B. Health spending changes: federal " f"{row['health_federal_change_b']:+.1f}B, states {row['health_state_change_b']:+.2f}B; TANF " - f"{row['tanf_change_b']:+.2f}B moves from the federal line to the states'", + f"{tanf:+.2f}B leaves the federal line: {tanf - twice:+.2f}B to the states and {twice:+.2f}B " + f"(TANF household_state_benefits already counts) out of the all-government total; " + f"Healthier Oregon cost for Medicaid enrollees dropped {oregon:+.3f}B", ] diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 79a0504..4d5af00 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -17,24 +17,36 @@ as the published sweep's national shift (a differential check, run when policyengine_us is installed, plus a closed-form check that always runs); - the health accounting conserves health spending: each household's state and - federal health spending add up to every program in - ``household_health_benefits``, and moving state-only coverage changes who - pays, not how much; -- the federal line with health and TANF plus every state's line equals the - all-government total with health, and TANF moves dollar for dollar from the - federal line to the state's; + federal health spending add up to every health program it counts, and moving + state-only coverage changes who pays, not how much; +- the federal line with health and TANF, every state's line, and the state + payroll and local taxes add up to the all-government total with health; + TANF moves dollar for dollar from the federal line to the state's, and TANF + a state's line already holds is charged once; +- the federal line, every state's line, and the state payroll and local taxes + add up to the all-government total, and the summary's federal split adds up + to the same total, with no residual when the aggregates hold only the parts + policyengine-us builds them from; - the state-only adjustment moves nothing for people whose status allows federal matching, never moves more than the federal share policyengine-us books, and never moves CHIP's unborn-child (FCEP) match; +- a shifted branch prices Medicaid at the baseline's cost per enrollee, so new + enrollees add cost, and the pricing refuses a branch that already + calculated its own; - the federal immigration-status test matches policyengine-us's ``is_medicaid_immigration_status_eligible`` where no state coverage applies, the state-only adjustment moves exactly the federal share policyengine-us - books for a California child covered without satisfactory status, and it + books for a California child covered without satisfactory status, it leaves the federal CHIP match for an undocumented pregnant woman's FCEP - coverage in place (all run when policyengine_us is installed). - -The differential checks against ``_apply_shift`` and the immigration rules are -the only tests that load PolicyEngine. + coverage in place, Medicaid priced at the baseline rises with enrollment + over a four-household dataset where policyengine-us alone holds the state's + total fixed, and the household frame drops Healthier Oregon's cost for an + adult policyengine-us also enrolls in Medicaid (all run when + policyengine_us is installed). + +The differential checks against ``_apply_shift``, the immigration rules, +Medicaid pricing, the household frame and the tax aggregates are the only tests +that load PolicyEngine. """ import json @@ -48,14 +60,18 @@ from analysis.california_shift import ( CAPITAL_INCOME_VARS, - FCEP_CATEGORY, FEDERAL_HEALTH, FEDERAL_SHARE_COSTS, FMAP_SPLIT_HEALTH, GROUPS, LABOR_VARS, + LOCAL_TAXES, + MEDICAID_DENOMINATOR, + OTHER_TAX_VARIABLES, STATE, STATE_HEALTH, + STATE_HEALTH_UNLISTED, + STATE_PAYROLL_TAXES, STATE_SHARE_COSTS, _apply_within_state_shift, _baseline_concentration, @@ -65,8 +81,11 @@ _federally_eligible_status, _freed_labor, _health_tanf_accounting, + _outside_state_measure, + _price_medicaid_at_baseline, _scale_by_state, _state_only_federal_share, + _tanf_states_in_state_benefits, _weighted_rank, _weighted_sum_by, _with_derived, @@ -89,8 +108,10 @@ "federal_health_other", "state_health_programs", "state_only_coverage", + "healthier_oregon_overlap", "tanf", -) + "tanf_in_state_benefits", +) + OTHER_TAX_VARIABLES def _frame(state, weight, **columns): @@ -276,6 +297,20 @@ def test_chart_draws_california_only(tmp_path): assert labels == ["California", "California, including SDI contributions"] +def test_chart_titles_subtitles_and_formats(): + pytest.importorskip("matplotlib") + from analysis.california_chart import dollar_format, subtitle_for, title_for + + assert title_for([0.0, 1.0, 5.0]) == "California gains as labor income shifts to capital" + assert title_for([0.0, -1.0, -5.0]) == "California loses as labor income shifts to capital" + assert title_for([0.0, 1.0, -5.0]) == "California's revenue as labor income shifts to capital" + assert "Medi-Cal" in subtitle_for("with_health_tanf", True) + assert "Medicaid and TANF" in subtitle_for("with_health_tanf", False) + assert subtitle_for("study", False) == subtitle_for("study", True) + assert dollar_format([0.0, 1.2, 3.4])(1.25) == "$1.2B" + assert dollar_format([0.0, 12.0, 40.0])(12.4) == "$12B" + + COMPONENT_KEYS = ( "household_tax_before_refundable_credits", "household_refundable_tax_credits", @@ -293,6 +328,13 @@ def test_chart_draws_california_only(tmp_path): "income_tax_refundable_credits", "household_market_income", "household_net_income", + "additional_medicare_tax", + "employer_federal_unemployment_tax", + "employee_state_payroll_tax", + "employer_state_payroll_tax", + "employer_local_payroll_tax", + "local_income_tax_before_refundable_credits", + "local_occupational_tax", ) @@ -311,14 +353,108 @@ def test_federal_and_state_parts_add_up_to_the_total(scenario, baseline): dict(zip(COMPONENT_KEYS, scenario)), dict(zip(COMPONENT_KEYS, baseline)) ) split = federal_split(delta) - assert split["federal_net"] + split["states_net_national"] == pytest.approx( - delta["total_change"], rel=1e-9, abs=1e-3 - ) + assert split["federal_net"] + split["states_net_national"] + split["state_payroll"] + split[ + "local" + ] == pytest.approx(delta["total_change"], rel=1e-9, abs=1e-3) assert split["payroll"] == pytest.approx( split["employee_payroll"] + split["employer_payroll"], rel=1e-12, abs=1e-9 ) +#: The parts of household tax and employer payroll tax, as policyengine-us +#: builds them (``test_federal_split_parts_match_policyengine`` checks this). +HOUSEHOLD_TAX_PARTS = ( + "fed_income_tax_before_refundable_credits", + "employee_social_security_tax", + "employee_medicare_tax", + "additional_medicare_tax", + "employee_state_payroll_tax", + "self_employment_tax", + "state_tax_before_refundable_credits", + "local_income_tax_before_refundable_credits", + "local_occupational_tax", +) +EMPLOYER_TAX_PARTS = ( + "employer_social_security_tax", + "employer_medicare_tax", + "employer_federal_unemployment_tax", + "employer_state_payroll_tax", + "employer_local_payroll_tax", +) + + +@settings(max_examples=200, deadline=None) +@given( + st.lists(FLOATS, min_size=len(HOUSEHOLD_TAX_PARTS + EMPLOYER_TAX_PARTS), + max_size=len(HOUSEHOLD_TAX_PARTS + EMPLOYER_TAX_PARTS)), + st.lists(FLOATS, min_size=4, max_size=4), +) +def test_federal_split_leaves_no_residual(parts, transfers): + # When household tax and employer payroll tax hold only their parts, the + # split puts every dollar in a named line, and the federal line is the + # all-government total less the states, state payroll and local taxes. + from analysis.california_summary import federal_split + + c = {f"{k}_change": v for k, v in zip(HOUSEHOLD_TAX_PARTS + EMPLOYER_TAX_PARTS, parts)} + c["household_tax_before_refundable_credits_change"] = sum( + c[f"{k}_change"] for k in HOUSEHOLD_TAX_PARTS + ) + c["employer_payroll_tax_change"] = sum(c[f"{k}_change"] for k in EMPLOYER_TAX_PARTS) + credits, state_credits, benefits, state_benefits = transfers + c.update( + household_refundable_tax_credits_change=credits, + state_refundable_credits_change=state_credits, + household_benefits_change=benefits, + state_benefits_change=state_benefits, + ) + total = ( + c["household_tax_before_refundable_credits_change"] + + c["employer_payroll_tax_change"] + - credits + - benefits + ) + split = federal_split(c) + scale = 1e-9 * (sum(abs(v) for v in parts) + sum(abs(v) for v in transfers)) + 1e-6 + assert abs(split["residual"]) <= scale and abs(split["employer_residual"]) <= scale + federal = total - split["states_net_national"] - split["state_payroll"] - split["local"] + assert split["federal_net"] == pytest.approx(federal, rel=1e-9, abs=scale) + + +def test_federal_split_parts_match_policyengine(): + # Differential check: the parts the summary's federal split assumes are + # exactly those policyengine-us adds into its tax aggregates. + pytest.importorskip("policyengine_us") + from policyengine_us.variables.gov.irs.tax.payroll.employee_payroll_tax import ( + EMPLOYEE_PAYROLL_TAX_COMPONENTS, + ) + from policyengine_us.variables.gov.irs.tax.payroll.employer_payroll_tax import ( + EMPLOYER_PAYROLL_TAX_COMPONENTS, + ) + from policyengine_us.variables.household.income.household.household_tax_before_refundable_credits import ( + household_tax_before_refundable_credits, + ) + + assert set(household_tax_before_refundable_credits.adds) == { + "employee_payroll_tax", + "self_employment_tax", + "income_tax_before_refundable_credits", + "flat_tax", # zero under current law; the split's residual holds it + "household_state_tax_before_refundable_credits", + "local_income_tax_before_refundable_credits", + "local_occupational_tax", + } + assert set(EMPLOYEE_PAYROLL_TAX_COMPONENTS) == { + "employee_social_security_tax", + "employee_medicare_tax", + "additional_medicare_tax", + "employee_state_payroll_tax", + } + assert set(EMPLOYER_PAYROLL_TAX_COMPONENTS) == set(EMPLOYER_TAX_PARTS) + assert set(STATE_PAYROLL_TAXES) | set(LOCAL_TAXES) <= set(HOUSEHOLD_TAX_PARTS) | set( + EMPLOYER_TAX_PARTS + ) + + def test_summary_reads_a_run(): from analysis.california_summary import summarize @@ -432,6 +568,10 @@ def __init__(self): def set_input(self, variable, period, values): self.inputs[variable] = np.array(values, dtype=float) + def get_holder(self, variable): + known = [period for period in ["2026"] if variable in self.inputs] + return SimpleNamespace(get_known_periods=lambda: known) + class _Sim: """A person-level microsimulation with the calls the shift functions make.""" @@ -606,12 +746,15 @@ def test_run_records_both_allocations(monkeypatch): import analysis.california_shift as cs frame = _frame([STATE, "NY"], [1e9, 1e9], market_income=[10, 20], labor=[5, 5], capital=[2, 3]) + branches = [] def fake_national(sim, name, pct): - return _Branch(), 100e9 * pct + branches.append(_Branch()) + return branches[-1], 100e9 * pct def fake_within(sim, name, pct): - return _Branch(), 100e9 * pct, {STATE: 60e9 * pct, "NY": 40e9 * pct} + branches.append(_Branch()) + return branches[-1], 100e9 * pct, {STATE: 60e9 * pct, "NY": 40e9 * pct} monkeypatch.setitem( sys.modules, "analysis.labor_capital_shift", SimpleNamespace(_apply_shift=fake_national) @@ -627,6 +770,7 @@ def fake_within(sim, name, pct): lambda scen, base: {STATE: {"state_net_change": 2e9}, "NY": {"state_net_change": -1e9}}, ) monkeypatch.setattr(cs, "_household_frame", lambda sim: frame) + monkeypatch.setattr(cs, "_medicaid_denominator", lambda sim: np.array([7.0, 8.0, 9.0])) # One California household leaves three of the four groups empty, so their # mean income divides by zero; the real data fills every group. @@ -648,6 +792,48 @@ def fake_within(sim, name, pct): assert row["other_states_net_with_health_tanf_change_b"] == pytest.approx(-1.0) assert row["california"]["state_net_with_health_tanf_change_b"] == pytest.approx(0.0) assert "state_net_with_health_tanf_b" in out["baseline_california"] + # Every shifted branch, national and within-state, prices Medicaid at the baseline. + assert len(branches) == 3 + for branch in branches: + np.testing.assert_array_equal(branch.inputs[MEDICAID_DENOMINATOR], [7.0, 8.0, 9.0]) + + +def test_level_row_keeps_state_payroll_and_local_taxes_off_the_federal_line(monkeypatch): + # California's employer UI tax rises $1B, a New York city wage tax $0.5B and + # the additional Medicare tax $0.25B. The all-government total counts all + # three, no state's line holds the first two, and only the last is federal. + import analysis.california_shift as cs + + base = _frame([STATE, "NY"], [1e9, 1e9]) + scen = _frame( + [STATE, "NY"], + [1e9, 1e9], + employer_state_payroll_tax=[1.0, 0.0], + local_income_tax_before_refundable_credits=[0.0, 0.5], + additional_medicare_tax=[0.25, 0.0], + ) + monkeypatch.setattr(cs, "revenue_components", lambda sim: {}) + monkeypatch.setattr(cs, "net_fiscal_impact", lambda rev, base: {"total_change": 1.75e9}) + monkeypatch.setattr(cs, "state_revenue_components", lambda sim: {}) + monkeypatch.setattr( + cs, + "state_revenue_delta", + lambda scen_states, base_states: { + STATE: {"state_net_change": 0.0}, + "NY": {"state_net_change": 0.0}, + }, + ) + monkeypatch.setattr(cs, "_household_frame", lambda sim: scen) + with np.errstate(invalid="ignore", divide="ignore"): + row = cs._level_row(object(), 0.0, 0.1, base, {}, {}, "national") + assert row["state_payroll_tax_change_b"] == pytest.approx(1.0) + assert row["local_tax_change_b"] == pytest.approx(0.5) + assert row["federal_net_change_b"] == pytest.approx(0.25) + assert row["federal_net_published_definition_change_b"] == pytest.approx(1.75) + assert row["federal_net_with_health_tanf_change_b"] == pytest.approx(0.25) + assert row["national_components_b"]["additional_medicare_tax_change"] == pytest.approx(0.25) + assert row["california"]["state_payroll_tax_change_b"] == pytest.approx(1.0) + assert row["california"]["local_tax_change_b"] == pytest.approx(0.0) # --- Health spending and TANF ------------------------------------------------- @@ -667,7 +853,8 @@ def fake_within(sim, name, pct): def health_pair(draw): """Baseline and scenario frames with non-negative health spending, at least one in CA.""" n = draw(st.integers(1, 30)) - state = draw(st.lists(st.sampled_from([STATE, "NY", "TX"]), min_size=n, max_size=n)) + # Massachusetts's state benefits already hold its TAFDC. + state = draw(st.lists(st.sampled_from([STATE, "NY", "TX", "MA"]), min_size=n, max_size=n)) state[0] = STATE weight = draw(st.lists(WEIGHTS, min_size=n, max_size=n)) @@ -680,6 +867,9 @@ def columns(): cols["state_only_coverage"] = cols["federal_health_split"] * share cols["fmap_split_cost"] = cols["federal_health_split"] + cols["state_health_split"] cols["state_tax"] = draw(st.lists(FLOATS, min_size=n, max_size=n)) + cols["tanf_in_state_benefits"] = np.where(np.asarray(state) == "MA", cols["tanf"], 0.0) + for key in OTHER_TAX_VARIABLES: + cols[key] = draw(st.lists(FLOATS, min_size=n, max_size=n)) return cols return _frame(state, weight, **columns()), _frame(state, weight, **columns()) @@ -698,9 +888,12 @@ def test_health_spending_is_conserved(pair): np.testing.assert_allclose( frame["state_health"] + frame["federal_health"], every_program, rtol=1e-12, atol=1e-6 ) + # A state's line pays the TANF it doesn't already hold, and only that. np.testing.assert_allclose( frame["state_net_with_health_tanf"], - frame["state_net"] - frame["state_health"] - frame["tanf"], + frame["state_net"] + - frame["state_health"] + - np.where(frame["state"] == "MA", 0.0, frame["tanf"]), rtol=1e-12, atol=1e-6, ) @@ -719,8 +912,11 @@ def test_health_tanf_accounting_adds_up(pair, ca_net, all_government, ny_net): base, scen = pair out = _health_tanf_accounting(base, scen, {STATE: ca_net, "NY": ny_net}, all_government) states = out["state_net_with_health_tanf_change_b"] - assert out["federal_net_with_health_tanf_change_b"] + sum(states.values()) == pytest.approx( - out["all_government_with_health_change_b"], rel=1e-9, abs=1e-9 + outside = _outside_state_measure(base, scen) + assert out["federal_net_with_health_tanf_change_b"] + sum(states.values()) + outside[ + "state_payroll_tax_change_b" + ] + outside["local_tax_change_b"] == pytest.approx( + out["all_government_with_health_change_b"], rel=1e-9, abs=1e-6 ) assert out["all_states_net_with_health_tanf_change_b"] == pytest.approx( sum(states.values()), rel=1e-12, abs=1e-12 @@ -731,13 +927,16 @@ def test_health_tanf_accounting_adds_up(pair, ca_net, all_government, ny_net): assert states[STATE] == pytest.approx( ca_net - total["state_health_change_b"] - total["tanf_change_b"], rel=1e-9, abs=1e-9 ) - # Every health dollar leaves the all-government total; TANF already sits in it. + # Every health dollar leaves the all-government total; TANF already sits in + # it, twice where a state's line also holds it, and once here. w = base["weight"] health = lambda f: f["state_health"] + f["federal_health"] # noqa: E731 health_change = float(((health(scen) - health(base)) * w).sum()) / 1e9 + twice = float(((scen["tanf_in_state_benefits"] - base["tanf_in_state_benefits"]) * w).sum()) / 1e9 assert out["all_government_with_health_change_b"] == pytest.approx( - all_government - health_change, rel=1e-9, abs=1e-9 + all_government - health_change + twice, rel=1e-9, abs=1e-6 ) + assert out["tanf_counted_twice_change_b"] == pytest.approx(twice, rel=1e-9, abs=1e-9) def test_tanf_moves_from_the_federal_line_to_the_state(): @@ -752,6 +951,29 @@ def test_tanf_moves_from_the_federal_line_to_the_state(): assert out["tanf_change_b"] == pytest.approx(1.0) +def test_tanf_a_state_line_already_holds_is_charged_once(): + # Massachusetts's TAFDC rises $1B. Its state benefits hold TAFDC, so the + # sweep's Massachusetts line already falls $1B, and the all-government total + # counts the $1B twice (in tanf and in state benefits). Here Massachusetts + # pays it once, the all-government total counts it once, and the federal + # line does not move. + base = _frame(["MA", STATE], [1e9, 1e9]) + scen = _frame(["MA", STATE], [1e9, 1e9], tanf=[1.0, 0.0], tanf_in_state_benefits=[1.0, 0.0]) + out = _health_tanf_accounting(base, scen, {"MA": -1.0, STATE: 0.0}, -2.0) + assert out["state_net_with_health_tanf_change_b"]["MA"] == pytest.approx(-1.0) + assert out["all_government_with_health_change_b"] == pytest.approx(-1.0) + assert out["federal_net_with_health_tanf_change_b"] == pytest.approx(0.0) + assert out["tanf_change_b"] == pytest.approx(1.0) + assert out["tanf_counted_twice_change_b"] == pytest.approx(1.0) + + +def test_tanf_states_in_state_benefits(): + programs = ["ca_tanf", "ma_tafdc", "ny_tanf", "vt_reach_up"] + assert _tanf_states_in_state_benefits(programs, ["ma_tafdc", "ca_capi", "ma_eaedc"]) == ["MA"] + assert _tanf_states_in_state_benefits(programs, ["ca_capi"]) == [] + assert _tanf_states_in_state_benefits(programs, ["vt_reach_up", "ma_tafdc"]) == ["MA", "VT"] + + def test_state_only_coverage_moves_to_the_state(): # A California child newly covered by state-funded Medi-Cal costs $1,000. # policyengine-us books $500 federal and $500 state at the 50% matching @@ -792,29 +1014,42 @@ def test_check_health_split_refuses_gaps(): _check_health_split(overmoved) -def _health_parameters(flag=False, health=None, federal=None, state=None): +LISTED_HEALTH = list(FMAP_SPLIT_HEALTH) + list(FEDERAL_HEALTH) + list(STATE_HEALTH) +EVERY_HEALTH = LISTED_HEALTH + list(STATE_HEALTH_UNLISTED) + + +def _health_parameters(flag=False, health=None, federal=None, state=None, state_benefits=None): household = SimpleNamespace( - household_health_benefits=health - or list(FMAP_SPLIT_HEALTH) + list(FEDERAL_HEALTH) + list(STATE_HEALTH), + household_health_benefits=health or LISTED_HEALTH, federal_benefit_cost=federal or list(FEDERAL_SHARE_COSTS), state_benefit_cost=state or list(STATE_SHARE_COSTS), + household_state_benefits=state_benefits or ["ca_capi", "ma_tafdc"], ) simulation = SimpleNamespace(include_health_benefits_in_net_income=flag) return SimpleNamespace(gov=SimpleNamespace(household=household, simulation=simulation)) def test_check_health_lists(): - _check_health_lists(_health_parameters()) + _check_health_lists(_health_parameters(), EVERY_HEALTH) + # Releases after policyengine-us 2.2.1 list the five state subsidies in + # household_health_benefits too; the classification is the same. + _check_health_lists(_health_parameters(health=EVERY_HEALTH), EVERY_HEALTH) with pytest.raises(ValueError, match="twice"): - _check_health_lists(_health_parameters(flag=True)) + _check_health_lists(_health_parameters(flag=True), EVERY_HEALTH) with pytest.raises(ValueError, match="new_program"): + _check_health_lists(_health_parameters(health=LISTED_HEALTH + ["new_program"]), EVERY_HEALTH) + # A health program policyengine-us values but this module doesn't classify. + with pytest.raises(ValueError, match="healthcare_benefit_value.*new_subsidy"): + _check_health_lists(_health_parameters(), EVERY_HEALTH + ["new_subsidy"]) + with pytest.raises(ValueError, match="missing.*wa_cascade_care_savings"): + _check_health_lists(_health_parameters(), EVERY_HEALTH[:-1]) + with pytest.raises(ValueError, match="state_benefit_cost"): + _check_health_lists(_health_parameters(state=["medicaid_state_cost"]), EVERY_HEALTH) + # A health program the state measure already nets would count twice. + with pytest.raises(ValueError, match="ma_connector_care"): _check_health_lists( - _health_parameters( - health=list(FMAP_SPLIT_HEALTH) + list(FEDERAL_HEALTH) + list(STATE_HEALTH) + ["new_program"] - ) + _health_parameters(state_benefits=["ca_capi", "ma_connector_care"]), EVERY_HEALTH ) - with pytest.raises(ValueError, match="state_benefit_cost"): - _check_health_lists(_health_parameters(state=["medicaid_state_cost"])) def test_federally_eligible_status_examples(): @@ -913,8 +1148,8 @@ def test_state_only_federal_share_examples(): "chip_federal_cost": [0.0, 300.0, 200.0, 0.0], "msp_federal_cost": [0.0, 0.0, 0.0, 10.0], } - chip_category = ["NONE", FCEP_CATEGORY, "CHILD", "NONE"] - got = _state_only_federal_share(federal, shares, chip_category) + fcep = [False, True, False, False] + got = _state_only_federal_share(federal, shares, fcep) assert got.tolist() == [0.0, 0.0, 200.0, 910.0] @@ -927,21 +1162,12 @@ def test_state_only_federal_share_bounds(data): v: np.asarray(data.draw(st.lists(SPENDING, min_size=n, max_size=n))) for v in FEDERAL_SHARE_COSTS } - chip_category = np.asarray( - data.draw( - st.lists( - st.sampled_from(["NONE", "CHILD", "PREGNANT_STANDARD", FCEP_CATEGORY]), - min_size=n, - max_size=n, - ) - ) - ) - moved = _state_only_federal_share(federal, shares, chip_category) + fcep = np.asarray(data.draw(st.lists(st.booleans(), min_size=n, max_size=n))) + moved = _state_only_federal_share(federal, shares, fcep) total = sum(shares.values()) assert (moved >= 0).all() assert (moved <= total + 1e-9).all() assert (moved[federal] == 0).all() - fcep = chip_category == FCEP_CATEGORY np.testing.assert_allclose( moved[~federal & fcep], (shares["medicaid_federal_cost"] + shares["msp_federal_cost"])[~federal & fcep], @@ -968,7 +1194,118 @@ def test_state_only_coverage_keeps_fcep_match_federal(): } } sim = Simulation(situation=_situation("TX", people)) - category = sim.calculate("chip_category", 2026, decode_enums=True) - assert np.asarray(category).astype(str).tolist() == [FCEP_CATEGORY] + assert np.asarray(sim.calculate("is_chip_fcep_eligible_person", 2026)).tolist() == [True] + assert np.asarray(sim.calculate("chip_enrolled", 2026)).tolist() == [True] assert float(np.asarray(sim.calculate("chip_federal_cost", 2026))[0]) > 0 assert _state_only_coverage(sim).tolist() == pytest.approx([0.0]) + + +# --- Medicaid priced at the baseline ------------------------------------------ + + +def test_price_medicaid_at_baseline_sets_the_baseline_denominator(): + branch = _Branch() + _price_medicaid_at_baseline(branch, [3.0, 4.0]) + np.testing.assert_array_equal(branch.inputs[MEDICAID_DENOMINATOR], [3.0, 4.0]) + # A branch that already holds its own denominator has priced Medicaid + # without the baseline's, so the pricing refuses it. + with pytest.raises(RuntimeError, match="already calculated"): + _price_medicaid_at_baseline(branch, [3.0, 4.0]) + + +def _california_dataset(employment_income, weights): + """A flat dataset of one-person California households, as a DataFrame.""" + import pandas as pd + + n = len(weights) + columns = { + f"person_{entity}_id__2026": np.arange(n) + for entity in ("household", "tax_unit", "spm_unit", "family", "marital_unit") + } + columns.update( + { + "person_id__2026": np.arange(n), + "age__2026": np.full(n, 35), + "employment_income__2026": np.asarray(employment_income, dtype=float), + "state_fips__2026": np.full(n, 6), + "household_weight__2026": np.asarray(weights, dtype=float), + "takes_up_medicaid_if_eligible__2026": np.ones(n, dtype=bool), + } + ) + return pd.DataFrame(columns) + + +def test_medicaid_at_the_baseline_price_rises_with_enrollment(): + # Two low-income Californians are on Medi-Cal and two higher-income ones are + # not. The branch cuts the higher incomes so all four enroll. On its own, + # policyengine-us spreads California's fixed Medicaid spending over the four, + # so the state's total stays put; at the baseline's price the two new + # enrollees add their cost and the two old ones cost what they did. + pytest.importorskip("policyengine_us") + from policyengine_us import Microsimulation + + weights = np.asarray([1_000.0, 2_000.0, 1_500.0, 500.0]) + dataset = _california_dataset([0.0, 5_000.0, 60_000.0, 90_000.0], weights) + base = Microsimulation(dataset=dataset) + assert base.is_over_dataset + assert np.asarray(base.calculate("medicaid_enrolled", 2026)).tolist() == [True, True, False, False] + base_cost = np.asarray(base.calculate("medicaid_cost", 2026), dtype=float) + base_index = np.asarray(base.calculate("medicaid_slcsp_cost_index_filled", 2026), dtype=float) + denominator = np.asarray(base.calculate(MEDICAID_DENOMINATOR, 2026), dtype=float) + income = np.asarray(base.calculate("employment_income", 2026), dtype=float) + cut = np.where(income > 50_000, 10_000.0, income) + + def shifted(name, price_at_baseline): + branch = base.get_branch(name) + branch.set_input("employment_income", 2026, cut) + if price_at_baseline: + _price_medicaid_at_baseline(branch, denominator) + assert np.asarray(branch.calculate("medicaid_enrolled", 2026)).all() + return branch + + # policyengine-us stores float32 values, so totals agree to about 1e-7. + alone = np.asarray(shifted("alone", False).calculate("medicaid_cost", 2026), dtype=float) + assert (alone * weights).sum() == pytest.approx((base_cost * weights).sum(), rel=1e-6) + + branch = shifted("priced", True) + priced = np.asarray(branch.calculate("medicaid_cost", 2026), dtype=float) + index = np.asarray(branch.calculate("medicaid_slcsp_cost_index_filled", 2026), dtype=float) + np.testing.assert_allclose(priced[:2], base_cost[:2], rtol=1e-6) + # Every enrollee costs the same amount per unit of cost index as in the baseline. + np.testing.assert_allclose(priced / index, base_cost[0] / base_index[0], rtol=1e-6) + expected_ratio = (index * weights).sum() / (base_index[:2] * weights[:2]).sum() + assert expected_ratio > 1.5 + assert (priced * weights).sum() == pytest.approx( + (base_cost * weights).sum() * expected_ratio, rel=1e-6 + ) + + +def test_household_frame_drops_healthier_oregon_for_medicaid_enrollees(): + # policyengine-us enrolls an undocumented Oregon adult with no income in + # Medicaid and also books the adult's Healthier Oregon cost. The frame keeps + # the Medicaid cost and drops the Healthier Oregon one. Building the frame + # also checks the health classification against policyengine-us's own lists. + pytest.importorskip("policyengine_us") + from policyengine_us import Simulation + + from analysis.california_shift import _household_frame + + people = { + "adult": { + "age": {2026: 35}, + "immigration_status": {2026: "UNDOCUMENTED"}, + "takes_up_medicaid_if_eligible": {2026: True}, + } + } + sim = Simulation(situation=_situation("OR", people)) + enrolled = bool(np.asarray(sim.calculate("medicaid_enrolled", 2026))[0]) + oregon = float(np.asarray(sim.calculate("or_healthier_oregon_cost", 2026))[0]) + assert enrolled and oregon > 0, "policyengine-us no longer prices this adult twice" + frame = _household_frame(sim) + assert frame["healthier_oregon_overlap"].tolist() == pytest.approx([oregon]) + programs = sum( + np.asarray(sim.calculate(v, 2026, map_to="household"), dtype=float) + for v in STATE_HEALTH + STATE_HEALTH_UNLISTED + ) + assert frame["state_health_programs"].tolist() == pytest.approx((programs - oregon).tolist()) + assert frame["state_health"][0] > 0 and frame["tanf_in_state_benefits"].tolist() == [0.0] From 629561597dbc496d496cf94f8d146a8a7ff8ccf1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 9 Nov 2026 03:38:37 -0500 Subject: [PATCH 15/17] Record the rerun with health and TANF, and state where the shift misses TANF - The output now holds the full rerun on policyengine 6.16.0 from c916f7f's code: Medicaid at the baseline price, the federal line without state payroll and local taxes, and the *_with_health_tanf fields. Its description field takes the new RUN_DESCRIPTION; no other field differs from the run. - The module docstring, run description and summary now say that the shift sets employment_income and self_employment_income on each branch, so programs that read the *_before_lsr inputs (the default TANF earned-income list, and Massachusetts's, Montana's and South Carolina's) see pre-shift earnings. California's CalWORKs list reads the shifted variables. - A new test ties the five unlisted state subsidies to the frame, using a Massachusetts adult on ConnectorCare. The Medicaid pricing test now asserts positive costs. - Comments: wrap one long docstring line, and stop saying every state pays insurers, which Connecticut's documentation doesn't state. Co-Authored-By: Claude Opus 5.5 --- analysis/california_shift.py | 97 +- analysis/california_summary.py | 12 + .../outputs/california_shift_pe6.16.0.json | 849 +++++++++++++++++- analysis/tests/test_california_shift.py | 37 + 4 files changed, 920 insertions(+), 75 deletions(-) diff --git a/analysis/california_shift.py b/analysis/california_shift.py index 4f30e15..c91d294 100644 --- a/analysis/california_shift.py +++ b/analysis/california_shift.py @@ -32,8 +32,9 @@ federal; the exception is a state whose program ``gov.household.household_state_benefits`` also lists (Massachusetts's TAFDC in policyengine-us 2.2.1, which later releases drop from that list), - which the state's line holds and the all-government total counts twice. These fields charge each state its share of Medicaid, CHIP and - Medicare Savings Program costs under federal matching rules + which the state's line holds and the all-government total counts twice. + These fields charge each state its share of Medicaid, CHIP and Medicare + Savings Program costs under federal matching rules (``state_benefit_cost``, from ``gov.hhs.medicaid.cost_share``), the whole cost of coverage it funds alone for people whose immigration status rules out federal matching (CHIP's unborn-child coverage keeps its federal match), @@ -48,6 +49,16 @@ branch here takes the baseline's denominator, as policyengine-us's reform branches do, so a new enrollee adds cost. +The shift sets ``employment_income`` and ``self_employment_income`` on each +branch, so anything that reads their ``*_before_lsr`` inputs sees pre-shift +earnings. policyengine-us's default TANF earned-income list reads those inputs, +as do Massachusetts's, Montana's and South Carolina's lists, so those states' +TANF benefits don't respond and the TANF, other-states and all-government lines +miss that response. SNAP's gross self-employment income reads them too. +California's CalWORKs list reads the shifted variables, so California's line +holds the response. The published sweep's shift (``_apply_shift``) has the same +limit. + Usage: python -m analysis.california_shift [--levels 0.1,0.2,0.5,1.0] [--within-state-levels 0.1,0.5] [--out PATH] @@ -98,8 +109,8 @@ #: State premium subsidies that ``healthcare_benefit_value`` counts and #: policyengine-us 2.2.1's ``gov.household.household_health_benefits`` leaves #: out; later releases list them there (PolicyEngine/policyengine-us#9514). -#: Each state pays insurers these amounts on top of the federal premium tax -#: credit. +#: Each lowers premiums on top of the federal premium tax credit, and this +#: accounting charges it to the state. STATE_HEALTH_UNLISTED = ( "ct_covered_connecticut", "ma_connector_care", @@ -156,6 +167,49 @@ OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "outputs") +#: What a run measures, saved with its output. +RUN_DESCRIPTION = ( + "Positive employment and self-employment income fall by the shift " + "share; the same weighted total is added to positive capital income " + "(gains, interest, dividends and rent) in proportion to the capital " + "income each person already receives. Static, current law, no " + "behavioral response. State net revenue is state income tax plus use " + "tax, less refundable state credits and the state benefits in " + "gov.household.household_state_benefits (for California: SSP, CAPI, " + "child care subsidies, CVRP, CARE, FERA and three Los Angeles " + "programs; no Medi-Cal or CalWORKs); California's employee SDI " + "contribution is reported separately. Federal net revenue is the " + "all-government change less every state's net change and less state " + "payroll taxes (employee and employer) and local taxes, which the " + "all-government total counts but no state's line holds; " + "federal_net_published_definition_change_b keeps them, as the published " + "site does. Each shifted branch prices Medicaid enrollees at the " + "baseline's cost per enrollee (the baseline's " + "medicaid_slcsp_state_denominator), so state Medicaid totals move with " + "enrollment. The *_with_health_tanf fields " + "also charge each state its share of Medicaid, CHIP and Medicare " + "Savings Program costs under federal matching rules (state_benefit_cost), " + "the whole cost of coverage it funds alone for people whose immigration " + "status rules out federal matching (CHIP coverage through the " + "unborn-child option keeps its federal match), its own health subsidies " + "(for California, the Covered California premium subsidy; Healthier " + "Oregon only for people not also enrolled in Medicaid) and the TANF " + "change its line doesn't already hold (CalWORKs for California; " + "Massachusetts's TAFDC already sits in its state benefits), since the " + "federal TANF block grant is fixed; the all-government total adds every " + "program in healthcare_benefit_value and counts TANF once. " + "The shift sets employment_income and self_employment_income on each " + "branch, so anything that reads their *_before_lsr inputs sees pre-shift " + "earnings: the default TANF earned-income list and Massachusetts's, " + "Montana's and South Carolina's lists, so those states' TANF benefits " + "don't respond and the TANF, other-states and all-government lines miss " + "that response (SNAP's gross self-employment income reads them too). " + "California's CalWORKs list reads the shifted variables. " + "'levels' allocate the new capital income across the country, as the " + "published sweep does; 'within_state_levels' give each state's freed " + "labor income to that state's own capital holders." +) + def _package_version(name): try: @@ -853,40 +907,7 @@ def run( return { "year": YEAR, "state": STATE, - "description": ( - "Positive employment and self-employment income fall by the shift " - "share; the same weighted total is added to positive capital income " - "(gains, interest, dividends and rent) in proportion to the capital " - "income each person already receives. Static, current law, no " - "behavioral response. State net revenue is state income tax plus use " - "tax, less refundable state credits and the state benefits in " - "gov.household.household_state_benefits (for California: SSP, CAPI, " - "child care subsidies, CVRP, CARE, FERA and three Los Angeles " - "programs; no Medi-Cal or CalWORKs); California's employee SDI " - "contribution is reported separately. Federal net revenue is the " - "all-government change less every state's net change and less state " - "payroll taxes (employee and employer) and local taxes, which the " - "all-government total counts but no state's line holds; " - "federal_net_published_definition_change_b keeps them, as the published " - "site does. Each shifted branch prices Medicaid enrollees at the " - "baseline's cost per enrollee (the baseline's " - "medicaid_slcsp_state_denominator), so state Medicaid totals move with " - "enrollment. The *_with_health_tanf fields " - "also charge each state its share of Medicaid, CHIP and Medicare " - "Savings Program costs under federal matching rules (state_benefit_cost), " - "the whole cost of coverage it funds alone for people whose immigration " - "status rules out federal matching (CHIP coverage through the " - "unborn-child option keeps its federal match), its own health subsidies " - "(for California, the Covered California premium subsidy; Healthier " - "Oregon only for people not also enrolled in Medicaid) and the TANF " - "change its line doesn't already hold (CalWORKs for California; " - "Massachusetts's TAFDC already sits in its state benefits), since the " - "federal TANF block grant is fixed; the all-government total adds every " - "program in healthcare_benefit_value and counts TANF once. " - "'levels' allocate the new capital income across the country, as the " - "published sweep does; 'within_state_levels' give each state's freed " - "labor income to that state's own capital holders." - ), + "description": RUN_DESCRIPTION, "metadata": { "policyengine_version": bundle.get("policyengine_version") or _package_version("policyengine"), diff --git a/analysis/california_summary.py b/analysis/california_summary.py index 63f3a22..8a78795 100644 --- a/analysis/california_summary.py +++ b/analysis/california_summary.py @@ -57,6 +57,16 @@ ) +#: The limit on the TANF lines, printed with any run that reports them. +BEFORE_LSR_NOTE = ( + "Note: the shift sets employment_income and self_employment_income on each branch, so " + "programs that read their *_before_lsr inputs see pre-shift earnings. The default TANF " + "earned-income list and Massachusetts's, Montana's and South Carolina's lists read them, " + "so those states' TANF response is missing from the TANF, other-states and all-government " + "lines. California's CalWORKs list reads the shifted variables." +) + + def federal_split(components): """Split the federal net change ($B) into its parts. @@ -240,6 +250,8 @@ def summarize(run, published=None): f"({_pct(federal_published, p['federal']):+.1f}%)" ) lines.extend(_within_state_lines(run)) + if any("state_net_with_health_tanf_change_b" in r["california"] for r in run["levels"]): + lines.extend(["", BEFORE_LSR_NOTE]) return "\n".join(lines) diff --git a/analysis/outputs/california_shift_pe6.16.0.json b/analysis/outputs/california_shift_pe6.16.0.json index 89ce62c..75c9445 100644 --- a/analysis/outputs/california_shift_pe6.16.0.json +++ b/analysis/outputs/california_shift_pe6.16.0.json @@ -1,7 +1,7 @@ { "year": 2026, "state": "CA", - "description": "Positive employment and self-employment income fall by the shift share; the same weighted total is added to positive capital income in proportion to existing holdings. Static, current law, no behavioral response. State net revenue is state income tax plus use tax, less refundable state credits and state-funded benefits; California's employee SDI contribution is reported separately. 'levels' allocate the new capital income across the country, as the published sweep does; 'within_state_levels' give each state's freed labor income to that state's own capital holders.", + "description": "Positive employment and self-employment income fall by the shift share; the same weighted total is added to positive capital income (gains, interest, dividends and rent) in proportion to the capital income each person already receives. Static, current law, no behavioral response. State net revenue is state income tax plus use tax, less refundable state credits and the state benefits in gov.household.household_state_benefits (for California: SSP, CAPI, child care subsidies, CVRP, CARE, FERA and three Los Angeles programs; no Medi-Cal or CalWORKs); California's employee SDI contribution is reported separately. Federal net revenue is the all-government change less every state's net change and less state payroll taxes (employee and employer) and local taxes, which the all-government total counts but no state's line holds; federal_net_published_definition_change_b keeps them, as the published site does. Each shifted branch prices Medicaid enrollees at the baseline's cost per enrollee (the baseline's medicaid_slcsp_state_denominator), so state Medicaid totals move with enrollment. The *_with_health_tanf fields also charge each state its share of Medicaid, CHIP and Medicare Savings Program costs under federal matching rules (state_benefit_cost), the whole cost of coverage it funds alone for people whose immigration status rules out federal matching (CHIP coverage through the unborn-child option keeps its federal match), its own health subsidies (for California, the Covered California premium subsidy; Healthier Oregon only for people not also enrolled in Medicaid) and the TANF change its line doesn't already hold (CalWORKs for California; Massachusetts's TAFDC already sits in its state benefits), since the federal TANF block grant is fixed; the all-government total adds every program in healthcare_benefit_value and counts TANF once. The shift sets employment_income and self_employment_income on each branch, so anything that reads their *_before_lsr inputs sees pre-shift earnings: the default TANF earned-income list and Massachusetts's, Montana's and South Carolina's lists, so those states' TANF benefits don't respond and the TANF, other-states and all-government lines miss that response (SNAP's gross self-employment income reads them too). California's CalWORKs list reads the shifted variables. 'levels' allocate the new capital income across the country, as the published sweep does; 'within_state_levels' give each state's freed labor income to that state's own capital holders.", "metadata": { "policyengine_version": "6.16.0", "policyengine_us_version": "2.2.1", @@ -16,6 +16,13 @@ "state_net_revenue_b": 139.83791264480345, "state_tax_before_refundable_credits_b": 146.57802063237833, "sdi_b": 22.521579236494752, + "state_health_b": 48.97543675467673, + "state_health_split_b": 48.802162631163206, + "state_only_coverage_b": 0.06716012457670517, + "state_health_programs_b": 0.1061139989368147, + "tanf_b": 1.2637433313083362, + "state_payroll_tax_b": 27.17270032800943, + "state_net_with_health_tanf_b": 89.59873255881841, "groups": [ { "group": "Bottom 50%", @@ -49,12 +56,78 @@ }, "levels": [ { + "health_federal_change_b": 48.89978571244694, + "health_state_change_b": 17.606535726454972, + "healthier_oregon_overlap_change_b": 0.0, + "tanf_change_b": 0.12711629185252052, + "tanf_counted_twice_change_b": 0.0, + "all_government_with_health_change_b": -194.8265504439445, + "all_states_net_with_health_tanf_change_b": -14.798240602665041, + "federal_net_with_health_tanf_change_b": -174.9859606398374, + "other_states_net_with_health_tanf_change_b": -16.90215885300351, + "states_losing_with_health_tanf_count": 45, + "state_net_with_health_tanf_change_b": { + "AK": -0.03800394334751303, + "AL": -0.2463500258947398, + "AR": -0.2871300460388668, + "AZ": -0.20508433981920499, + "CA": 2.103918250338469, + "CO": -0.14820577628990256, + "CT": 0.2742711586678943, + "DC": 0.05659483701899906, + "DE": -0.12524355039864876, + "FL": -0.45929928125335173, + "GA": -0.5347719571572395, + "HI": -0.09751623200002459, + "IA": -0.15555658577726186, + "ID": -0.1474742720498424, + "IL": -1.4338771653199072, + "IN": -0.44802550233131266, + "KS": -0.5522790824803416, + "KY": -0.3922825385990385, + "LA": -0.19522942525818884, + "MA": 0.21558509656593894, + "MD": -0.8443807158109116, + "ME": -0.16627700132555712, + "MI": -1.1017713847305453, + "MN": -0.9416413781262514, + "MO": -0.7518046834060179, + "MS": -0.18378259247910755, + "MT": 0.05730724835369165, + "NC": -0.8501627364160647, + "ND": -0.01651346871059771, + "NE": -0.07339163215969745, + "NH": -0.028776081160411363, + "NJ": -0.4808481853117662, + "NM": -0.08452878134767335, + "NV": -0.1141051509964269, + "NY": -0.21261282482575306, + "OH": -0.665825770076429, + "OK": -0.17406514377833743, + "OR": -0.36249791680766685, + "PA": -0.8654467263904986, + "RI": -0.10007354858283335, + "SC": -0.299604913329712, + "SD": -0.028905555778612244, + "TN": -0.4113199563360447, + "TX": -2.0090197948160116, + "UT": -0.1757918246454528, + "VA": -0.7198494158795439, + "VT": -0.005476318909395993, + "WA": 0.3763559137662281, + "WI": -0.6336641632539015, + "WV": -0.10302064559176084, + "WY": -0.01078507237789532 + }, + "state_payroll_tax_change_b": -4.514476771468066, + "local_tax_change_b": -0.5278724299740131, "shift_pct": 10, "allocation": "national", "labor_income_moved_b": 1241.2958648972376, "all_government_change_b": -128.3202290050426, "all_states_net_change_b": 2.935411415642451, - "federal_net_change_b": -131.25564042068504, + "federal_net_change_b": -126.21329121924296, + "federal_net_published_definition_change_b": -131.25564042068504, "other_states_net_change_b": -2.1043900160405467, "states_gaining": [ "CA", @@ -123,12 +196,17 @@ }, "national_components_b": { "_identity_residual": -3.708355239868164e-06, + "additional_medicare_tax_change": -3.522066677719645, "eitc_change": 6.763975618969422, "employee_medicare_tax_change": -17.300927508987215, "employee_social_security_tax_change": -52.55146390560706, + "employee_state_payroll_tax_change": -3.3381664426563673, + "employer_federal_unemployment_tax_change": -0.036280424739482425, + "employer_local_payroll_tax_change": -0.4480277481945145, "employer_medicare_tax_change": -17.300927508987215, "employer_payroll_tax_change": -71.51300997710877, "employer_social_security_tax_change": -52.55146390560706, + "employer_state_payroll_tax_change": -1.1763103288116983, "fed_alternative_minimum_tax_change": 2.6073408440253143, "fed_capital_gains_tax_change": 153.30576208936836, "fed_income_tax_before_refundable_credits_change": 43.60628673608105, @@ -143,6 +221,8 @@ "household_refundable_tax_credits_change": 10.406023079190902, "household_tax_before_refundable_credits_change": -34.21515116791162, "income_tax_refundable_credits_change": 9.117663549023407, + "local_income_tax_before_refundable_credits_change": 0.0, + "local_occupational_tax_change": -0.07984468177949863, "refundable_ctc_change": 2.2964054787175066, "self_employment_tax_change": -5.570886517814468, "snap_change": 8.56349976508905, @@ -159,11 +239,22 @@ "capital_income_change_b": 176.87994775373528, "market_income_change_b": -3.0646802352988605, "state_tax_before_refundable_credits_change_b": 5.39631934632345, + "state_use_tax_change_b": -0.0006844706765249536, + "mental_health_services_tax_change_b": 0.7940835282657179, "state_refundable_credits_change_b": 0.2403037746718769, "state_benefits_change_b": 0.11621413996855041, "state_net_change_b": 5.039801431683023, "sdi_change_b": -2.2521579438558437, - "state_net_including_sdi_change_b": 2.7876434878271796 + "state_health_change_b": 2.7728263429564364, + "state_health_split_change_b": 2.804222780143111, + "state_only_coverage_change_b": 1.554421553014472e-07, + "state_health_programs_change_b": -0.031396592628829635, + "tanf_change_b": 0.1630568383880927, + "state_net_with_health_tanf_change_b": 2.103918250338495, + "state_payroll_tax_change_b": -2.2698918539739656, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 2.7876434878271796, + "state_net_with_health_tanf_including_sdi_change_b": -0.14823969351734867 }, "california_by_group": [ { @@ -172,11 +263,22 @@ "capital_income_change_b": 6.453648066045201, "market_income_change_b": -19.75803230633572, "state_tax_before_refundable_credits_change_b": -0.42185069909310197, + "state_use_tax_change_b": -0.0019450048686574033, + "mental_health_services_tax_change_b": 0.0, "state_refundable_credits_change_b": 0.2155656132361496, "state_benefits_change_b": 0.04422697393432171, "state_net_change_b": -0.6816432862635734, "sdi_change_b": -0.30307941405147765, - "state_net_including_sdi_change_b": -0.984722700315051 + "state_health_change_b": 1.641884117081132, + "state_health_split_change_b": 1.6780010715083713, + "state_only_coverage_change_b": 1.554421553014472e-07, + "state_health_programs_change_b": -0.03611710986939473, + "tanf_change_b": 0.1630568383880927, + "state_net_with_health_tanf_change_b": -2.4865842417327975, + "state_payroll_tax_change_b": -0.31352583115627064, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -0.984722700315051, + "state_net_with_health_tanf_including_sdi_change_b": -2.789663655784275 }, { "group": "50th to 90th percentile", @@ -184,11 +286,22 @@ "capital_income_change_b": 28.105026708660986, "market_income_change_b": -58.621100133348996, "state_tax_before_refundable_credits_change_b": -4.401760779545172, + "state_use_tax_change_b": -0.006334380290184522, + "mental_health_services_tax_change_b": 0.0, "state_refundable_credits_change_b": 0.023381412888317438, "state_benefits_change_b": 0.07198716603422868, "state_net_change_b": -4.497129358467718, "sdi_change_b": -1.0884794777788802, - "state_net_including_sdi_change_b": -5.585608836246598 + "state_health_change_b": 0.995588433804718, + "state_health_split_change_b": 0.9882301300951696, + "state_only_coverage_change_b": 0.0, + "state_health_programs_change_b": 0.007358303709548279, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": -5.492717792272436, + "state_payroll_tax_change_b": -1.0946259806453726, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -5.585608836246598, + "state_net_with_health_tanf_including_sdi_change_b": -6.581197270051316 }, { "group": "90th to 99th percentile", @@ -196,11 +309,22 @@ "capital_income_change_b": 72.84575975768374, "market_income_change_b": 17.247560872731345, "state_tax_before_refundable_credits_change_b": 2.2981185508074415, + "state_use_tax_change_b": 0.0017821193575474912, + "mental_health_services_tax_change_b": 0.19810272033631737, "state_refundable_credits_change_b": 0.0013289896034107758, "state_benefits_change_b": 0.0, "state_net_change_b": 2.2967895612040308, "sdi_change_b": -0.7122869353925602, - "state_net_including_sdi_change_b": 1.5845026258114707 + "state_health_change_b": 0.13130271557398274, + "state_health_split_change_b": 0.13130271557398274, + "state_only_coverage_change_b": 0.0, + "state_health_programs_change_b": 0.0, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 2.1654868456300482, + "state_payroll_tax_change_b": -0.7133342346901824, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 1.5845026258114707, + "state_net_with_health_tanf_including_sdi_change_b": 1.4531999102374882 }, { "group": "Top 1%", @@ -208,21 +332,98 @@ "capital_income_change_b": 69.47551322134537, "market_income_change_b": 58.06689133165453, "state_tax_before_refundable_credits_change_b": 7.921812274154283, + "state_use_tax_change_b": 0.005812795124769479, + "mental_health_services_tax_change_b": 0.5959808079294008, "state_refundable_credits_change_b": 2.775894399909064e-05, "state_benefits_change_b": 0.0, "state_net_change_b": 7.921784515210284, "sdi_change_b": -0.14831211663292593, - "state_net_including_sdi_change_b": 7.773472398577358 + "state_health_change_b": 0.004051076496604264, + "state_health_split_change_b": 0.0066888629655874375, + "state_only_coverage_change_b": 0.0, + "state_health_programs_change_b": -0.0026377864689831735, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 7.917733438713681, + "state_payroll_tax_change_b": -0.14840580748214005, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 7.773472398577358, + "state_net_with_health_tanf_including_sdi_change_b": 7.769421322080754 } ] }, { + "health_federal_change_b": 100.5252412229056, + "health_state_change_b": 33.74232421435085, + "healthier_oregon_overlap_change_b": 1.620787785310313e-08, + "tanf_change_b": 0.4038917969623421, + "tanf_counted_twice_change_b": 0.0, + "all_government_with_health_change_b": -376.01647387009086, + "all_states_net_with_health_tanf_change_b": -22.042240642577532, + "federal_net_with_health_tanf_change_b": -343.7324411136589, + "other_states_net_with_health_tanf_change_b": -26.662003165199167, + "states_losing_with_health_tanf_count": 42, + "state_net_with_health_tanf_change_b": { + "AK": -0.16217708387679625, + "AL": -0.4427651759104321, + "AR": -0.43905134411763963, + "AZ": -0.7503975514502057, + "CA": 4.619762522621636, + "CO": -0.229510020235587, + "CT": 0.6091761771408821, + "DC": 0.17250212521677055, + "DE": -0.27858314633716363, + "FL": -0.9477141330773292, + "GA": -1.1501563620399993, + "HI": -0.20273095338715266, + "IA": -0.4017282474290204, + "ID": -0.2764984688568949, + "IL": -2.5617317609272527, + "IN": -0.8051676570982262, + "KS": -0.7218039479910257, + "KY": -0.8329012904009596, + "LA": -0.38753045368830785, + "MA": 0.8569827845721035, + "MD": -0.9158274582077864, + "ME": -0.3505236865683149, + "MI": -2.0573075800217913, + "MN": -1.3330690274297727, + "MO": -1.4401276835402754, + "MS": -0.3847154869522575, + "MT": 0.061267652064681766, + "NC": -1.3963328602480054, + "ND": -0.070172201528519, + "NE": -0.1475227917699791, + "NH": -0.003457841792442091, + "NJ": -1.3676720002107097, + "NM": -0.11544748016857709, + "NV": -0.21620766110016035, + "NY": 4.977477601323772, + "OH": -2.466175030551619, + "OK": -0.5451528609103065, + "OR": -0.4552100448462877, + "PA": -2.2171733032002487, + "RI": -0.15415357164950258, + "SC": -0.6467130521627618, + "SD": -0.05430204562771791, + "TN": -0.6777341750030481, + "TX": -3.837279093481773, + "UT": -0.3121149754322675, + "VA": -1.289926337031575, + "VT": 0.0008012213260977577, + "WA": 1.0828175721022146, + "WI": -1.116912022841093, + "WV": -0.21856127810228201, + "WY": -0.04278915174262476 + }, + "state_payroll_tax_change_b": -9.240641329019853, + "local_tax_change_b": -1.00115078483452, "shift_pct": 20, "allocation": "national", "labor_income_moved_b": 2482.591729794475, "all_government_change_b": -241.74890843283436, "all_states_net_change_b": 12.10397536873566, - "federal_net_change_b": -253.85288380157002, + "federal_net_change_b": -243.61109168771563, + "federal_net_published_definition_change_b": -253.85288380157002, "other_states_net_change_b": 1.3917346428401487, "states_gaining": [ "CA", @@ -292,12 +493,17 @@ }, "national_components_b": { "_identity_residual": 5.72486669921875e-06, + "additional_medicare_tax_change": -6.699273842535811, "eitc_change": 13.251508491958084, "employee_medicare_tax_change": -34.601854885330106, "employee_social_security_tax_change": -107.03839464418402, + "employee_state_payroll_tax_change": -6.722385045389452, + "employer_federal_unemployment_tax_change": -0.07868550984702603, + "employer_local_payroll_tax_change": -0.8414614267307149, "employer_medicare_tax_change": -34.601854885330106, "employer_payroll_tax_change": -145.07865099463368, "employer_social_security_tax_change": -107.03839464418402, + "employer_state_payroll_tax_change": -2.518256283630402, "fed_alternative_minimum_tax_change": 4.071102858005413, "fed_capital_gains_tax_change": 308.1912700328199, "fed_income_tax_before_refundable_credits_change": 101.4215474781748, @@ -312,6 +518,8 @@ "household_refundable_tax_credits_change": 20.826008507153063, "household_tax_before_refundable_credits_change": -49.33048381865674, "income_tax_refundable_credits_change": 18.04397242910858, + "local_income_tax_before_refundable_credits_change": 0.0, + "local_occupational_tax_change": -0.1596893581038052, "refundable_ctc_change": 4.70622488567392, "self_employment_tax_change": -11.138475708396331, "snap_change": 18.73557774855487, @@ -328,11 +536,22 @@ "capital_income_change_b": 353.75993531852686, "market_income_change_b": -6.129319382930992, "state_tax_before_refundable_credits_change_b": 11.729854562621638, + "state_use_tax_change_b": -0.00018975509516630694, + "mental_health_services_tax_change_b": 1.6755968473668685, "state_refundable_credits_change_b": 0.5816074750111029, "state_benefits_change_b": 0.43600636171499674, "state_net_change_b": 10.712240725895539, "sdi_change_b": -4.504315837466379, - "state_net_including_sdi_change_b": 6.20792488842916 + "state_health_change_b": 5.6479157871821934, + "state_health_split_change_b": 5.56756779570821, + "state_only_coverage_change_b": 0.041970933198261604, + "state_health_programs_change_b": 0.03837705827572277, + "tanf_change_b": 0.444562416091682, + "state_net_with_health_tanf_change_b": 4.619762522621664, + "state_payroll_tax_change_b": -4.542035483876528, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 6.20792488842916, + "state_net_with_health_tanf_including_sdi_change_b": 0.11544668515528489 }, "california_by_group": [ { @@ -341,11 +560,22 @@ "capital_income_change_b": 12.90729753169806, "market_income_change_b": -39.51606367757846, "state_tax_before_refundable_credits_change_b": -0.6880553316316795, + "state_use_tax_change_b": -0.003497057030959657, + "mental_health_services_tax_change_b": 0.0, "state_refundable_credits_change_b": 0.5151495634301718, "state_benefits_change_b": 0.29203194019479634, "state_net_change_b": -1.4952368352566476, "sdi_change_b": -0.6061588478416385, - "state_net_including_sdi_change_b": -2.101395683098286 + "state_health_change_b": 3.4502644434657976, + "state_health_split_change_b": 3.430855835150976, + "state_only_coverage_change_b": 1.554421553014472e-07, + "state_health_programs_change_b": 0.019408452872665738, + "tanf_change_b": 0.3766369749913707, + "state_net_with_health_tanf_change_b": -5.322138253713816, + "state_payroll_tax_change_b": -0.628491271889687, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -2.101395683098286, + "state_net_with_health_tanf_including_sdi_change_b": -5.928297101555454 }, { "group": "50th to 90th percentile", @@ -353,11 +583,22 @@ "capital_income_change_b": 56.21006189069981, "market_income_change_b": -117.24219162591551, "state_tax_before_refundable_credits_change_b": -8.41858302075647, + "state_use_tax_change_b": -0.011786828312865972, + "mental_health_services_tax_change_b": 0.0, "state_refundable_credits_change_b": 0.06649079025959294, "state_benefits_change_b": 0.14397442152020037, "state_net_change_b": -8.629048232536265, "sdi_change_b": -2.1769590308521356, - "state_net_including_sdi_change_b": -10.8060072633884 + "state_health_change_b": 1.9909523720068287, + "state_health_split_change_b": 1.9743740421720046, + "state_only_coverage_change_b": 2.45393465925099e-07, + "state_health_programs_change_b": 0.01657808444135779, + "tanf_change_b": 0.06792544110031128, + "state_net_with_health_tanf_change_b": -10.687926045643405, + "state_payroll_tax_change_b": -2.1900635072862604, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -10.8060072633884, + "state_net_with_health_tanf_including_sdi_change_b": -12.86488507649554 }, { "group": "90th to 99th percentile", @@ -365,11 +606,22 @@ "capital_income_change_b": 145.6915355002647, "market_income_change_b": 34.495130534942746, "state_tax_before_refundable_credits_change_b": 4.985899057551191, + "state_use_tax_change_b": 0.0034738695114104024, + "mental_health_services_tax_change_b": 0.4806948255854332, "state_refundable_credits_change_b": -3.0751973128435205e-05, "state_benefits_change_b": 0.0, "state_net_change_b": 4.985929809524321, "sdi_change_b": -1.4245737551903215, - "state_net_including_sdi_change_b": 3.5613560543339995 + "state_health_change_b": 0.16271206827109627, + "state_health_split_change_b": 0.15768376084041383, + "state_only_coverage_change_b": 0.0, + "state_health_programs_change_b": 0.00502830743068242, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 4.823217741253224, + "state_payroll_tax_change_b": -1.4266690930682868, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 3.5613560543339995, + "state_net_with_health_tanf_including_sdi_change_b": 3.3986439860629023 }, { "group": "Top 1%", @@ -377,21 +629,98 @@ "capital_income_change_b": 138.9510403958643, "market_income_change_b": 116.13380538562022, "state_tax_before_refundable_credits_change_b": 15.850593857458597, + "state_use_tax_change_b": 0.011620260737248922, + "mental_health_services_tax_change_b": 1.1949020217814352, "state_refundable_credits_change_b": -2.1267055334723847e-06, "state_benefits_change_b": 0.0, "state_net_change_b": 15.850595984164132, "sdi_change_b": -0.2966242035822835, - "state_net_including_sdi_change_b": 15.553971780581849 + "state_health_change_b": 0.04398690343847209, + "state_health_split_change_b": 0.004654157544814885, + "state_only_coverage_change_b": 0.04197053236264038, + "state_health_programs_change_b": -0.0026377864689831735, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 15.806609080725659, + "state_payroll_tax_change_b": -0.29681161163229264, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 15.553971780581849, + "state_net_with_health_tanf_including_sdi_change_b": 15.509984877143376 } ] }, { + "health_federal_change_b": 317.7550652884425, + "health_state_change_b": 113.75851324728036, + "healthier_oregon_overlap_change_b": 0.13349572583447514, + "tanf_change_b": 1.8813220092456335, + "tanf_counted_twice_change_b": 0.0, + "all_government_with_health_change_b": -969.8779543311697, + "all_states_net_with_health_tanf_change_b": -77.9676202477754, + "federal_net_with_health_tanf_change_b": -863.5150827645224, + "other_states_net_with_health_tanf_change_b": -93.32865089366359, + "states_losing_with_health_tanf_count": 44, + "state_net_with_health_tanf_change_b": { + "AK": -0.3943426710195816, + "AL": -1.3539507042422523, + "AR": -1.2553878976148976, + "AZ": -2.814731393408935, + "CA": 15.3610306458882, + "CO": -1.9305879392916168, + "CT": 1.2072413623158886, + "DC": 0.33925393479442156, + "DE": -0.6993121938636859, + "FL": -4.272348863803233, + "GA": -2.511252360250622, + "HI": -0.44637418385754846, + "IA": -1.090671961608236, + "ID": -0.8929909614394325, + "IL": -7.2784115959339015, + "IN": -3.0769120014693443, + "KS": -1.335418108150233, + "KY": -2.4234636062223696, + "LA": -1.683020983757503, + "MA": 0.319979715529366, + "MD": -4.025674689400749, + "ME": -0.7242993556791353, + "MI": -4.583071972191122, + "MN": -4.636613260314472, + "MO": -3.60554978619304, + "MS": -0.8169568428196654, + "MT": 0.13071263302266073, + "NC": -4.770019624084506, + "ND": -0.24440448101868154, + "NE": -0.7252763407286453, + "NH": -0.27661691188500165, + "NJ": -3.920891993426109, + "NM": -0.6497062206571647, + "NV": -0.666544183099956, + "NY": 5.033576851993217, + "OH": -6.49039988807175, + "OK": -1.630234154765328, + "OR": -1.701709244998869, + "PA": -6.750826736306555, + "RI": -0.39075011044953917, + "SC": -1.5437380851776914, + "SD": -0.21624381481970262, + "TN": -1.4251067918232312, + "TX": -11.000624507614388, + "UT": -1.1261717393167616, + "VA": -5.299495728358302, + "VT": -0.023821379633332, + "WA": 4.1652801792793195, + "WI": -2.837524154846933, + "WV": -0.8385193917351718, + "WY": -0.14472675524927855 + }, + "state_payroll_tax_change_b": -25.88104437978211, + "local_tax_change_b": -2.514206939089775, "shift_pct": 50, "allocation": "national", "labor_income_moved_b": 6206.4793244861885, "all_government_change_b": -538.3643757954468, "all_states_net_change_b": 37.67221500875059, - "federal_net_change_b": -576.0365908041974, + "federal_net_change_b": -547.6413394853255, + "federal_net_published_definition_change_b": -576.0365908041974, "other_states_net_change_b": 4.334469113781374, "states_gaining": [ "CA", @@ -462,12 +791,17 @@ }, "national_components_b": { "_identity_residual": 2.2876407165527344e-05, + "additional_medicare_tax_change": -13.834986791054886, "eitc_change": 32.14777771501349, "employee_medicare_tax_change": -86.50463796254392, "employee_social_security_tax_change": -283.4822814731632, + "employee_state_payroll_tax_change": -17.287141363153832, + "employer_federal_unemployment_tax_change": -0.30038932372186833, + "employer_local_payroll_tax_change": -2.1149835499351375, "employer_medicare_tax_change": -86.50463796254392, "employer_payroll_tax_change": -380.9961954764811, "employer_social_security_tax_change": -283.4822814731632, + "employer_state_payroll_tax_change": -8.593903016628278, "fed_alternative_minimum_tax_change": 10.705279284056674, "fed_capital_gains_tax_change": 777.9974516830972, "fed_income_tax_before_refundable_credits_change": 371.2437304411592, @@ -482,6 +816,8 @@ "household_refundable_tax_credits_change": 52.024695127743605, "household_tax_before_refundable_credits_change": -9.875688443522948, "income_tax_refundable_credits_change": 43.69362121073502, + "local_income_tax_before_refundable_credits_change": 0.0, + "local_occupational_tax_change": -0.399223389154638, "refundable_ctc_change": 11.417518081703896, "self_employment_tax_change": -28.50575880564992, "snap_change": 67.51869914564361, @@ -498,11 +834,22 @@ "capital_income_change_b": 884.3998259169749, "market_income_change_b": -15.323338188496903, "state_tax_before_refundable_credits_change_b": 36.384832686758, + "state_use_tax_change_b": -0.0006097436521935678, + "mental_health_services_tax_change_b": 4.703599002107185, "state_refundable_credits_change_b": 2.128559874277, "state_benefits_change_b": 0.9185269175117781, "state_net_change_b": 33.33774589496923, "sdi_change_b": -11.260789618247376, - "state_net_including_sdi_change_b": 22.076956276721855 + "state_health_change_b": 16.446270358763574, + "state_health_split_change_b": 16.155222552195738, + "state_only_coverage_change_b": 0.18880121151452806, + "state_health_programs_change_b": 0.10224659505330876, + "tanf_change_b": 1.5304448903174437, + "state_net_with_health_tanf_change_b": 15.361030645888212, + "state_payroll_tax_change_b": -11.404116982476815, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 22.076956276721855, + "state_net_with_health_tanf_including_sdi_change_b": 4.100241027640836 }, "california_by_group": [ { @@ -511,11 +858,22 @@ "capital_income_change_b": 32.26824296350594, "market_income_change_b": -98.79016284897277, "state_tax_before_refundable_credits_change_b": -0.7622201448529897, + "state_use_tax_change_b": -0.008224480126905893, + "mental_health_services_tax_change_b": 0.0, "state_refundable_credits_change_b": 1.8606310797570838, "state_benefits_change_b": 0.6990473022265064, "state_net_change_b": -3.3218985268365797, "sdi_change_b": -1.5153971262117865, - "state_net_including_sdi_change_b": -4.837295653048367 + "state_health_change_b": 9.19476428250088, + "state_health_split_change_b": 9.096276148290071, + "state_only_coverage_change_b": 0.08705528962717433, + "state_health_programs_change_b": 0.01143284458363391, + "tanf_change_b": 1.3556071764055104, + "state_net_with_health_tanf_change_b": -13.87226998574297, + "state_payroll_tax_change_b": -1.5964346702850116, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -4.837295653048367, + "state_net_with_health_tanf_including_sdi_change_b": -15.387667111954755 }, { "group": "50th to 90th percentile", @@ -523,11 +881,22 @@ "capital_income_change_b": 140.52514931003253, "market_income_change_b": -293.10547459754287, "state_tax_before_refundable_credits_change_b": -17.383315972978693, + "state_use_tax_change_b": -0.030099726342723922, + "mental_health_services_tax_change_b": 1.3716139357131875e-05, "state_refundable_credits_change_b": 0.26292298949316517, "state_benefits_change_b": 0.21947961465094262, "state_net_change_b": -17.865718577122802, "sdi_change_b": -5.44239753615176, - "state_net_including_sdi_change_b": -23.30811611327456 + "state_health_change_b": 6.694856476398882, + "state_health_split_change_b": 6.564524256539011, + "state_only_coverage_change_b": 0.058192137999507725, + "state_health_programs_change_b": 0.07214008186036328, + "tanf_change_b": 0.174837713911933, + "state_net_with_health_tanf_change_b": -24.735412767433612, + "state_payroll_tax_change_b": -5.496413028258118, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -23.30811611327456, + "state_net_with_health_tanf_including_sdi_change_b": -30.17781030358537 }, { "group": "90th to 99th percentile", @@ -535,11 +904,22 @@ "capital_income_change_b": 364.2288353358433, "market_income_change_b": 86.23782171642489, "state_tax_before_refundable_credits_change_b": 15.085382764875824, + "state_use_tax_change_b": 0.00867470977458991, + "mental_health_services_tax_change_b": 1.6932420136754518, "state_refundable_credits_change_b": 0.005097588503135205, "state_benefits_change_b": 6.343292924935895e-10, "state_net_change_b": 15.080285175738357, "sdi_change_b": -3.5614344420745723, - "state_net_including_sdi_change_b": 11.518850733663784 + "state_health_change_b": 0.5107619069587235, + "state_health_split_change_b": 0.48945045188042874, + "state_only_coverage_change_b": 0.0, + "state_health_programs_change_b": 0.021311455078294753, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 14.569523268779637, + "state_payroll_tax_change_b": -3.5692402140747936, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 11.518850733663784, + "state_net_with_health_tanf_including_sdi_change_b": 11.008088826705064 }, { "group": "Top 1%", @@ -547,21 +927,98 @@ "capital_income_change_b": 347.3775983075932, "market_income_change_b": 290.33447754159374, "state_tax_before_refundable_credits_change_b": 39.44498603971386, + "state_use_tax_change_b": 0.029039753042846336, + "mental_health_services_tax_change_b": 3.0103432722923764, "state_refundable_credits_change_b": -9.178347638389696e-05, "state_benefits_change_b": 0.0, "state_net_change_b": 39.445077823190246, "sdi_change_b": -0.7415605138092564, - "state_net_including_sdi_change_b": 38.70351730938099 + "state_health_change_b": 0.04588769290508968, + "state_health_split_change_b": 0.0049716954862268565, + "state_only_coverage_change_b": 0.04355378388784599, + "state_health_programs_change_b": -0.0026377864689831735, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 39.399190130285156, + "state_payroll_tax_change_b": -0.7420290698588924, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 38.70351730938099, + "state_net_with_health_tanf_including_sdi_change_b": 38.6576296164759 } ] }, { + "health_federal_change_b": 949.4721962838183, + "health_state_change_b": 372.66412529470836, + "healthier_oregon_overlap_change_b": 0.2392156626592612, + "tanf_change_b": 8.024421456210268, + "tanf_counted_twice_change_b": 0.0, + "all_government_with_health_change_b": -1977.7289091253042, + "all_states_net_with_health_tanf_change_b": -196.0040831141395, + "federal_net_with_health_tanf_change_b": -1684.4388840989757, + "other_states_net_with_health_tanf_change_b": -243.19395126502587, + "states_losing_with_health_tanf_count": 44, + "state_net_with_health_tanf_change_b": { + "AK": -1.0440461854365666, + "AL": -2.7843756265210002, + "AR": -3.322976348578409, + "AZ": -6.183903950049932, + "CA": 47.1898681508864, + "CO": -6.929481671115937, + "CT": 2.5551083626914917, + "DC": 1.2001609039436163, + "DE": -1.3514079577830926, + "FL": -11.671008711493398, + "GA": -3.7477885427379625, + "HI": -0.08856927046667762, + "IA": -2.363259382255723, + "ID": -2.0823993916100445, + "IL": -15.460601334226949, + "IN": -7.004093061783034, + "KS": -1.6158174826400917, + "KY": -6.070672247362117, + "LA": -4.2906049630890335, + "MA": -3.7797102179683026, + "MD": -6.680402444104645, + "ME": -0.6463731382066877, + "MI": -11.13959525987223, + "MN": -7.354651899143863, + "MO": -7.6685636863521145, + "MS": -1.2824991235843626, + "MT": 0.11948119195717344, + "NC": -10.075797479198151, + "ND": -1.5269634571131225, + "NE": -1.483680714178488, + "NH": -1.6694570888030436, + "NJ": -8.967265588631268, + "NM": -1.1023052726502902, + "NV": -2.399981591708745, + "NY": 16.32329233446861, + "OH": -15.60886996606911, + "OK": -3.1999625939662253, + "OR": -3.7731927704509904, + "PA": -22.66970212382202, + "RI": -0.6326111480317587, + "SC": -1.713412775594385, + "SD": -0.42852403021451807, + "TN": -2.073582161218203, + "TX": -49.975137162081936, + "UT": -3.672296110344151, + "VA": -13.45564380150689, + "VT": 0.08098104062978739, + "WA": 2.2767815537674423, + "WI": -4.280952430000203, + "WV": -2.0608871488825002, + "WY": -0.4167293416358542 + }, + "state_payroll_tax_change_b": -92.80848357835698, + "local_tax_change_b": -4.477458333831823, "shift_pct": 100, "allocation": "national", "labor_income_moved_b": 12412.958648972377, "all_government_change_b": -655.5925875467775, "all_states_net_change_b": 184.68446363677913, - "federal_net_change_b": -840.2770511835566, + "federal_net_change_b": -742.9911092713678, + "federal_net_published_definition_change_b": -840.2770511835566, "other_states_net_change_b": 86.95349448139117, "states_gaining": [ "CA", @@ -648,12 +1105,17 @@ }, "national_components_b": { "_identity_residual": 1.9556870849609374e-05, + "additional_medicare_tax_change": -18.07118854538588, "eitc_change": -64.49903897693396, "employee_medicare_tax_change": -173.00927592508785, "employee_social_security_tax_change": -623.9699959913409, + "employee_state_payroll_tax_change": -36.39365164967897, + "employer_federal_unemployment_tax_change": -8.414854677294823, + "employer_local_payroll_tax_change": -3.6790115555225467, "employer_medicare_tax_change": -173.00927592508785, "employer_payroll_tax_change": -865.4879711032152, "employer_social_security_tax_change": -623.9699959913409, + "employer_state_payroll_tax_change": -56.41483192867801, "fed_alternative_minimum_tax_change": 19.806632976334324, "fed_capital_gains_tax_change": 1564.7028432849556, "fed_income_tax_before_refundable_credits_change": 1234.6731516275684, @@ -668,6 +1130,8 @@ "household_refundable_tax_credits_change": -102.70960349510452, "household_tax_before_refundable_credits_change": 517.5162584655961, "income_tax_refundable_credits_change": -102.45721242524593, + "local_income_tax_before_refundable_credits_change": 0.0, + "local_occupational_tax_change": -0.798446778309276, "refundable_ctc_change": -38.10118620947614, "self_employment_tax_change": -58.14741555532982, "snap_change": 330.10036509758015, @@ -684,11 +1148,22 @@ "capital_income_change_b": 1768.799650449747, "market_income_change_b": -30.646678149203705, "state_tax_before_refundable_credits_change_b": 101.30562568390637, + "state_use_tax_change_b": 0.014531218454444949, + "mental_health_services_tax_change_b": 10.123198754382962, "state_refundable_credits_change_b": -0.12512064244271562, "state_benefits_change_b": 3.699777170961141, "state_net_change_b": 97.73096915538795, "sdi_change_b": -22.521579236494752, - "state_net_including_sdi_change_b": 75.20938991889321 + "state_health_change_b": 45.60577225052336, + "state_health_split_change_b": 45.03381490015019, + "state_only_coverage_change_b": 0.6533432708365142, + "state_health_programs_change_b": -0.08138592046334038, + "tanf_change_b": 4.935328753978193, + "state_net_with_health_tanf_change_b": 47.1898681508864, + "state_payroll_tax_change_b": -27.17270032800943, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 75.20938991889321, + "state_net_with_health_tanf_including_sdi_change_b": 24.668288914391646 }, "california_by_group": [ { @@ -697,11 +1172,22 @@ "capital_income_change_b": 64.53648586386407, "market_income_change_b": -197.58032398962942, "state_tax_before_refundable_credits_change_b": 0.5069681574061998, + "state_use_tax_change_b": -0.01159105677144176, + "mental_health_services_tax_change_b": 3.365064684212208e-06, "state_refundable_credits_change_b": -0.5090986921118821, "state_benefits_change_b": 1.9109382155892227, "state_net_change_b": -0.8948713660711405, "sdi_change_b": -3.030794252423573, - "state_net_including_sdi_change_b": -3.9256656184947136 + "state_health_change_b": 14.059183225856426, + "state_health_split_change_b": 13.980838357440849, + "state_only_coverage_change_b": 0.16555635389658496, + "state_health_programs_change_b": -0.08721148548100911, + "tanf_change_b": 2.986910335481688, + "state_net_with_health_tanf_change_b": -17.940964927409254, + "state_payroll_tax_change_b": -4.646080634949898, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -3.9256656184947136, + "state_net_with_health_tanf_including_sdi_change_b": -20.971759179832826 }, { "group": "50th to 90th percentile", @@ -709,11 +1195,22 @@ "capital_income_change_b": 281.0502995547615, "market_income_change_b": -586.2109434767776, "state_tax_before_refundable_credits_change_b": -17.286773730960874, + "state_use_tax_change_b": -0.05162927601991507, + "mental_health_services_tax_change_b": 0.027175685534089205, "state_refundable_credits_change_b": 0.3596086235026163, "state_benefits_change_b": 1.4612476203792863, "state_net_change_b": -19.107629974842773, "sdi_change_b": -10.88479507230352, - "state_net_including_sdi_change_b": -29.99242504714629 + "state_health_change_b": 27.52230469410797, + "state_health_split_change_b": 27.069617206130154, + "state_only_coverage_change_b": 0.44422413649116693, + "state_health_programs_change_b": 0.008463351486651881, + "tanf_change_b": 1.8258211757093077, + "state_net_with_health_tanf_change_b": -48.455755844660054, + "state_payroll_tax_change_b": -13.260107777084233, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -29.99242504714629, + "state_net_with_health_tanf_including_sdi_change_b": -59.34055091696357 }, { "group": "90th to 99th percentile", @@ -721,11 +1218,22 @@ "capital_income_change_b": 728.4576724718384, "market_income_change_b": 172.4756453650971, "state_tax_before_refundable_credits_change_b": 39.44166580158406, + "state_use_tax_change_b": 0.01954241618373256, + "mental_health_services_tax_change_b": 4.079186922196026, "state_refundable_credits_change_b": 0.025517961247900196, "state_benefits_change_b": 0.32759133499263254, "state_net_change_b": 39.08855650534352, "sdi_change_b": -7.1228688841491445, - "state_net_including_sdi_change_b": 31.965687621194373 + "state_health_change_b": 3.9326842337648342, + "state_health_split_change_b": 3.932675237203918, + "state_only_coverage_change_b": 8.996560916346702e-06, + "state_health_programs_change_b": 0.0, + "tanf_change_b": 0.1185197187066496, + "state_net_with_health_tanf_change_b": 35.037352552872036, + "state_payroll_tax_change_b": -7.744160387182013, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 31.965687621194373, + "state_net_with_health_tanf_including_sdi_change_b": 27.91448366872289 }, { "group": "Top 1%", @@ -733,23 +1241,100 @@ "capital_income_change_b": 694.755192559283, "market_income_change_b": 580.6689439521062, "state_tax_before_refundable_credits_change_b": 78.643765455877, + "state_use_tax_change_b": 0.05820913506206923, + "mental_health_services_tax_change_b": 6.016832781588161, "state_refundable_credits_change_b": -0.001148535081349965, "state_benefits_change_b": 0.0, "state_net_change_b": 78.64491399095834, "sdi_change_b": -1.4831210276185127, - "state_net_including_sdi_change_b": 77.16179296333983 + "state_health_change_b": 0.09160009679413134, + "state_health_split_change_b": 0.050684099375268514, + "state_only_coverage_change_b": 0.04355378388784599, + "state_health_programs_change_b": -0.0026377864689831735, + "tanf_change_b": 0.004077524080547563, + "state_net_with_health_tanf_change_b": 78.54923637008366, + "state_payroll_tax_change_b": -1.5223515287932876, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 77.16179296333983, + "state_net_with_health_tanf_including_sdi_change_b": 77.06611534246515 } ] } ], "within_state_levels": [ { + "health_federal_change_b": 48.372689299233, + "health_state_change_b": 17.01036008454223, + "healthier_oregon_overlap_change_b": 0.0, + "tanf_change_b": 0.11880633463968086, + "tanf_counted_twice_change_b": 0.0, + "all_government_with_health_change_b": -190.64741377611386, + "all_states_net_with_health_tanf_change_b": -9.215903036175769, + "federal_net_with_health_tanf_change_b": -176.389161538496, + "other_states_net_with_health_tanf_change_b": -11.673098217149121, + "states_losing_with_health_tanf_count": 44, + "state_net_with_health_tanf_change_b": { + "AK": -0.03711201815774792, + "AL": -0.11856348829555959, + "AR": -0.3392785305490589, + "AZ": -0.18802376357103412, + "CA": 2.4571951809733523, + "CO": -0.2999564254563598, + "CT": -0.050195085696704916, + "DC": -0.052129753152571005, + "DE": -0.05022582537694684, + "FL": -0.45929932607103907, + "GA": -0.18423527852445182, + "HI": -0.1007094702873424, + "IA": -0.0906793532180383, + "ID": -0.0908828597512193, + "IL": -1.092428736563651, + "IN": -0.20562753207916062, + "KS": -0.46946926696128544, + "KY": -0.24691430810646098, + "LA": -0.1112919007950616, + "MA": 0.09857618563551951, + "MD": 0.3054035913745166, + "ME": -0.10376872066327167, + "MI": -0.7020994259173233, + "MN": -0.31529724914214924, + "MO": -0.6742563698486084, + "MS": -0.08496142821246916, + "MT": -0.048519200008255936, + "NC": -0.5213379974529058, + "ND": -0.004328465054605213, + "NE": -0.034429313045575435, + "NH": -0.028776090603079865, + "NJ": 0.8060136373262726, + "NM": -0.026797702948500004, + "NV": -0.1141051900484546, + "NY": -1.7457400044246407, + "OH": -0.30696216859372605, + "OK": -0.11414276837137383, + "OR": -0.2120625086808617, + "PA": -0.557459440459905, + "RI": -0.05006824018797582, + "SC": -0.19384428193617576, + "SD": -0.028905555778612244, + "TN": -0.4113199563360447, + "TX": -2.0090197948160116, + "UT": -0.1990586683813813, + "VA": -0.22468081208150353, + "VT": 0.005068235215277238, + "WA": 0.37708586833355684, + "WI": -0.3349443640980975, + "WV": -0.01826563766700879, + "WY": -0.01307145766205276 + }, + "state_payroll_tax_change_b": -4.514476771468066, + "local_tax_change_b": -0.5278724299740131, "shift_pct": 10, "allocation": "within_state", "labor_income_moved_b": 1241.2958648972376, "all_government_change_b": -125.26436439233863, "all_states_net_change_b": 7.913263383006146, - "federal_net_change_b": -133.17762777534477, + "federal_net_change_b": -128.1352785739027, + "federal_net_published_definition_change_b": -133.17762777534477, "other_states_net_change_b": 2.520201348574501, "states_gaining": [ "CA", @@ -829,12 +1414,17 @@ }, "national_components_b": { "_identity_residual": -4.374418563842774e-06, + "additional_medicare_tax_change": -3.522066677719645, "eitc_change": 6.729188016873032, "employee_medicare_tax_change": -17.300927508987215, "employee_social_security_tax_change": -52.55146390560706, + "employee_state_payroll_tax_change": -3.3381664426563673, + "employer_federal_unemployment_tax_change": -0.036280424739482425, + "employer_local_payroll_tax_change": -0.4480277481945145, "employer_medicare_tax_change": -17.300927508987215, "employer_payroll_tax_change": -71.51300997710877, "employer_social_security_tax_change": -52.55146390560706, + "employer_state_payroll_tax_change": -1.1763103288116983, "fed_alternative_minimum_tax_change": 2.8154100604136962, "fed_capital_gains_tax_change": 150.41199389455866, "fed_income_tax_before_refundable_credits_change": 41.48823820576855, @@ -849,6 +1439,8 @@ "household_refundable_tax_credits_change": 10.352463128370744, "household_tax_before_refundable_credits_change": -31.37791297590039, "income_tax_refundable_credits_change": 9.076418252026, + "local_income_tax_before_refundable_credits_change": 0.0, + "local_occupational_tax_change": -0.07984468177949863, "refundable_ctc_change": 2.2908059714598235, "self_employment_tax_change": -5.570886517814468, "snap_change": 8.46946450199199, @@ -865,11 +1457,22 @@ "capital_income_change_b": 179.9446292048434, "market_income_change_b": 7.861856775283813e-06, "state_tax_before_refundable_credits_change_b": 5.748144934935302, + "state_use_tax_change_b": -0.0003539704513595009, + "mental_health_services_tax_change_b": 0.8116263494150259, "state_refundable_credits_change_b": 0.2402459669675764, "state_benefits_change_b": 0.11483693353605211, "state_net_change_b": 5.393062034431675, "sdi_change_b": -2.2521579438558437, - "state_net_including_sdi_change_b": 3.140904090575831 + "state_health_change_b": 2.7728909447476884, + "state_health_split_change_b": 2.804222780143111, + "state_only_coverage_change_b": 1.554421553014472e-07, + "state_health_programs_change_b": -0.03133199083757791, + "tanf_change_b": 0.16297590871060427, + "state_net_with_health_tanf_change_b": 2.457195180973381, + "state_payroll_tax_change_b": -2.2698918539739656, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 3.140904090575831, + "state_net_with_health_tanf_including_sdi_change_b": 0.20503723711753752 }, "california_by_group": [ { @@ -878,11 +1481,22 @@ "capital_income_change_b": 6.56546615433297, "market_income_change_b": -19.646214467310497, "state_tax_before_refundable_credits_change_b": -0.4182455700189991, + "state_use_tax_change_b": -0.0019450046675225138, + "mental_health_services_tax_change_b": 0.0, "state_refundable_credits_change_b": 0.21551678488593734, "state_benefits_change_b": 0.04284976760733898, "state_net_change_b": -0.6766121225122754, "sdi_change_b": -0.30307941405147765, - "state_net_including_sdi_change_b": -0.9796915365637531 + "state_health_change_b": 1.6419312857023864, + "state_health_split_change_b": 1.6780010715083713, + "state_only_coverage_change_b": 1.554421553014472e-07, + "state_health_programs_change_b": -0.03606994124814035, + "tanf_change_b": 0.16297590871060427, + "state_net_with_health_tanf_change_b": -2.481519316925266, + "state_payroll_tax_change_b": -0.31352583115627064, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -0.9796915365637531, + "state_net_with_health_tanf_including_sdi_change_b": -2.784598730976744 }, { "group": "50th to 90th percentile", @@ -890,11 +1504,22 @@ "capital_income_change_b": 28.59198452746559, "market_income_change_b": -58.13413873967788, "state_tax_before_refundable_credits_change_b": -4.358702357898429, + "state_use_tax_change_b": -0.006249588094616163, + "mental_health_services_tax_change_b": 0.0, "state_refundable_credits_change_b": 0.02337243353449629, "state_benefits_change_b": 0.07198716592871314, "state_net_change_b": -4.454061957361639, "sdi_change_b": -1.0884794777788802, - "state_net_including_sdi_change_b": -5.542541435140519 + "state_health_change_b": 0.9956058669747152, + "state_health_split_change_b": 0.9882301300951696, + "state_only_coverage_change_b": 0.0, + "state_health_programs_change_b": 0.007375736879545613, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": -5.449667824336354, + "state_payroll_tax_change_b": -1.0946259806453726, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -5.542541435140519, + "state_net_with_health_tanf_including_sdi_change_b": -6.538147302115234 }, { "group": "90th to 99th percentile", @@ -902,11 +1527,22 @@ "capital_income_change_b": 74.10791088438187, "market_income_change_b": 18.509714427505873, "state_tax_before_refundable_credits_change_b": 2.439380812044302, + "state_use_tax_change_b": 0.0019074515096614638, + "mental_health_services_tax_change_b": 0.2033272845917139, "state_refundable_credits_change_b": 0.001328989603143694, "state_benefits_change_b": 0.0, "state_net_change_b": 2.4380518224411585, "sdi_change_b": -0.7122869353925602, - "state_net_including_sdi_change_b": 1.7257648870485984 + "state_health_change_b": 0.13130271557398274, + "state_health_split_change_b": 0.13130271557398274, + "state_only_coverage_change_b": 0.0, + "state_health_programs_change_b": 0.0, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 2.3067491068671755, + "state_payroll_tax_change_b": -0.7133342346901824, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 1.7257648870485984, + "state_net_with_health_tanf_including_sdi_change_b": 1.5944621714746154 }, { "group": "Top 1%", @@ -914,11 +1550,22 @@ "capital_income_change_b": 70.67926763866299, "market_income_change_b": 59.27064664133929, "state_tax_before_refundable_credits_change_b": 8.085712050808429, + "state_use_tax_change_b": 0.005933170801117714, + "mental_health_services_tax_change_b": 0.608299064823312, "state_refundable_credits_change_b": 2.775894399909064e-05, "state_benefits_change_b": 0.0, "state_net_change_b": 8.08568429186443, "sdi_change_b": -0.14831211663292593, - "state_net_including_sdi_change_b": 7.937372175231504 + "state_health_change_b": 0.004051076496604264, + "state_health_split_change_b": 0.0066888629655874375, + "state_only_coverage_change_b": 0.0, + "state_health_programs_change_b": -0.0026377864689831735, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 8.081633215367825, + "state_payroll_tax_change_b": -0.14840580748214005, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 7.937372175231504, + "state_net_with_health_tanf_including_sdi_change_b": 7.933321098734899 } ], "labor_income_moved_by_state_b": { @@ -976,12 +1623,78 @@ } }, { + "health_federal_change_b": 314.5177481611792, + "health_state_change_b": 113.40689262336716, + "healthier_oregon_overlap_change_b": 0.13349572583447514, + "tanf_change_b": 1.8739546114419066, + "tanf_counted_twice_change_b": 0.0, + "all_government_with_health_change_b": -946.2177325506273, + "all_states_net_with_health_tanf_change_b": -52.28351043877628, + "federal_net_with_health_tanf_change_b": -865.5389707929791, + "other_states_net_with_health_tanf_change_b": -69.47319168573179, + "states_losing_with_health_tanf_count": 46, + "state_net_with_health_tanf_change_b": { + "AK": -0.3699491920918182, + "AL": -0.7129587162647625, + "AR": -1.4464082986056452, + "AZ": -2.7282679165387336, + "CA": 17.189681246955512, + "CO": -2.745958075715585, + "CT": -0.44509697834973316, + "DC": -0.23220154349806277, + "DE": -0.3133270754034973, + "FL": -4.3974866442743314, + "GA": -0.673079390680536, + "HI": -0.46264820932507467, + "IA": -0.7492963535521489, + "ID": -0.6064152976375484, + "IL": -5.558702543891606, + "IN": -1.8706684122065216, + "KS": -0.8877254894929113, + "KY": -1.6425988141104682, + "LA": -1.2620206982959625, + "MA": -0.30606460885415787, + "MD": 0.9204924320693164, + "ME": -0.3825654810401842, + "MI": -2.793965850282637, + "MN": -1.080933518672366, + "MO": -3.1686418204308593, + "MS": -0.2762879951454668, + "MT": -0.4068729498932592, + "NC": -3.0236913233614424, + "ND": -0.17097079192989179, + "NE": -0.5301392730677492, + "NH": -0.2766171966828415, + "NJ": 1.8080914125688818, + "NM": -0.2874468355569624, + "NV": -0.7188055081094975, + "NY": -3.520098169249133, + "OH": -4.69547018969113, + "OK": -1.2532709287786512, + "OR": -0.975380614797311, + "PA": -5.16966060004487, + "RI": -0.17231397942022952, + "SC": -1.0147768119501315, + "SD": -0.2391326952466232, + "TN": -1.4251067918232312, + "TX": -11.358064899166825, + "UT": -1.2424977765098366, + "VA": -3.12998569275902, + "VT": 0.031071799911958103, + "WA": 4.170399667356074, + "WI": -1.117928220889873, + "WV": -0.37868795311887327, + "WY": -0.18305887123002387 + }, + "state_payroll_tax_change_b": -25.88104437978211, + "local_tax_change_b": -2.514206939089775, "shift_pct": 50, "allocation": "within_state", "labor_income_moved_b": 6206.4793244861885, "all_government_change_b": -518.2930917660808, "all_states_net_change_b": 62.99733679603278, - "federal_net_change_b": -581.2904285621136, + "federal_net_change_b": -552.8951772432416, + "federal_net_published_definition_change_b": -581.2904285621136, "other_states_net_change_b": 27.83119568666188, "states_gaining": [ "CA", @@ -1068,12 +1781,17 @@ }, "national_components_b": { "_identity_residual": 1.4037480529785157e-05, + "additional_medicare_tax_change": -13.834986791054886, "eitc_change": 32.052335559085144, "employee_medicare_tax_change": -86.50463796254392, "employee_social_security_tax_change": -283.4822814731632, + "employee_state_payroll_tax_change": -17.287141363153832, + "employer_federal_unemployment_tax_change": -0.30038932372186833, + "employer_local_payroll_tax_change": -2.1149835499351375, "employer_medicare_tax_change": -86.50463796254392, "employer_payroll_tax_change": -380.9961954764811, "employer_social_security_tax_change": -283.4822814731632, + "employer_state_payroll_tax_change": -8.593903016628278, "fed_alternative_minimum_tax_change": 12.140251303617298, "fed_capital_gains_tax_change": 767.9431140066079, "fed_income_tax_before_refundable_credits_change": 365.46464762285353, @@ -1088,6 +1806,8 @@ "household_refundable_tax_credits_change": 51.60664904361675, "household_tax_before_refundable_credits_change": 9.521813288323731, "income_tax_refundable_credits_change": 43.411500767380616, + "local_income_tax_before_refundable_credits_change": 0.0, + "local_occupational_tax_change": -0.399223389154638, "refundable_ctc_change": 11.230572226252221, "self_employment_tax_change": -28.50575880564992, "snap_change": 67.44048118640079, @@ -1104,11 +1824,22 @@ "capital_income_change_b": 899.7231581831453, "market_income_change_b": 1.2181578811645508e-05, "state_tax_before_refundable_credits_change_b": 38.208239930026735, + "state_use_tax_change_b": 0.0009049262934057918, + "mental_health_services_tax_change_b": 4.806032712919103, "state_refundable_credits_change_b": 2.1276283674584833, "state_benefits_change_b": 0.9144704531973168, "state_net_change_b": 35.16614110937093, "sdi_change_b": -11.260789618247376, - "state_net_including_sdi_change_b": 23.905351491123557 + "state_health_change_b": 16.44692199580761, + "state_health_split_change_b": 16.155222511271447, + "state_only_coverage_change_b": 0.18880121151452806, + "state_health_programs_change_b": 0.10289827302163111, + "tanf_change_b": 1.5295378666077857, + "state_net_with_health_tanf_change_b": 17.189681246955544, + "state_payroll_tax_change_b": -11.404116982476815, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 23.905351491123557, + "state_net_with_health_tanf_including_sdi_change_b": 5.928891628708168 }, "california_by_group": [ { @@ -1117,11 +1848,22 @@ "capital_income_change_b": 32.82733090555146, "market_income_change_b": -98.231075273647, "state_tax_before_refundable_credits_change_b": -0.7362924841283481, + "state_use_tax_change_b": -0.00817850479070633, + "mental_health_services_tax_change_b": 0.0, "state_refundable_credits_change_b": 1.85971916722936, "state_benefits_change_b": 0.6951572944972174, "state_net_change_b": -3.2911689458549263, "sdi_change_b": -1.5153971262117865, - "state_net_including_sdi_change_b": -4.806566072066713 + "state_health_change_b": 9.195398888841968, + "state_health_split_change_b": 9.096276107365783, + "state_only_coverage_change_b": 0.08705528962717433, + "state_health_programs_change_b": 0.012067491849009498, + "tanf_change_b": 1.3552024823009072, + "state_net_with_health_tanf_change_b": -13.8417703169978, + "state_payroll_tax_change_b": -1.5964346702850116, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -4.806566072066713, + "state_net_with_health_tanf_including_sdi_change_b": -15.357167443209587 }, { "group": "50th to 90th percentile", @@ -1129,11 +1871,22 @@ "capital_income_change_b": 142.95992210076025, "market_income_change_b": -290.6707044219503, "state_tax_before_refundable_credits_change_b": -17.169114071139298, + "state_use_tax_change_b": -0.029864916481406652, + "mental_health_services_tax_change_b": 1.4659187171473128e-05, "state_refundable_credits_change_b": 0.2629033952059039, "state_benefits_change_b": 0.21931315819159833, "state_net_change_b": -17.6513306245368, "sdi_change_b": -5.44239753615176, - "state_net_including_sdi_change_b": -23.09372816068856 + "state_health_change_b": 6.6948735071018275, + "state_health_split_change_b": 6.564524256539011, + "state_only_coverage_change_b": 0.058192137999507725, + "state_health_programs_change_b": 0.07215711256331003, + "tanf_change_b": 0.17433538430687856, + "state_net_with_health_tanf_change_b": -24.520539515945508, + "state_payroll_tax_change_b": -5.496413028258118, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": -23.09372816068856, + "state_net_with_health_tanf_including_sdi_change_b": -29.962937052097267 }, { "group": "90th to 99th percentile", @@ -1141,11 +1894,22 @@ "capital_income_change_b": 370.5395538036873, "market_income_change_b": 92.54853935680102, "state_tax_before_refundable_credits_change_b": 15.867143110989112, + "state_use_tax_change_b": 0.00930671617858399, + "mental_health_services_tax_change_b": 1.7353450980768688, "state_refundable_credits_change_b": 0.005097588499603407, "state_benefits_change_b": 5.085010380302037e-10, "state_net_change_b": 15.862045521981006, "sdi_change_b": -3.5614344420745723, - "state_net_including_sdi_change_b": 12.300611079906433 + "state_health_change_b": 0.5107619069587235, + "state_health_split_change_b": 0.48945045188042874, + "state_only_coverage_change_b": 0.0, + "state_health_programs_change_b": 0.021311455078294753, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 15.351283615022282, + "state_payroll_tax_change_b": -3.5692402140747936, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 12.300611079906433, + "state_net_with_health_tanf_including_sdi_change_b": 11.78984917294771 }, { "group": "Top 1%", @@ -1153,11 +1917,22 @@ "capital_income_change_b": 353.39635137314616, "market_income_change_b": 296.35325252037507, "state_tax_before_refundable_credits_change_b": 40.24650337430528, + "state_use_tax_change_b": 0.02964163138693478, + "mental_health_services_tax_change_b": 3.070672955655064, "state_refundable_credits_change_b": -9.178347638389696e-05, "state_benefits_change_b": 0.0, "state_net_change_b": 40.24659515778166, "sdi_change_b": -0.7415605138092564, - "state_net_including_sdi_change_b": 39.50503464397241 + "state_health_change_b": 0.04588769290508968, + "state_health_split_change_b": 0.0049716954862268565, + "state_only_coverage_change_b": 0.04355378388784599, + "state_health_programs_change_b": -0.0026377864689831735, + "tanf_change_b": 0.0, + "state_net_with_health_tanf_change_b": 40.20070746487657, + "state_payroll_tax_change_b": -0.7420290698588924, + "local_tax_change_b": 0.0, + "state_net_including_sdi_change_b": 39.50503464397241, + "state_net_with_health_tanf_including_sdi_change_b": 39.45914695106732 } ], "labor_income_moved_by_state_b": { diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 4d5af00..93cc931 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -1264,7 +1264,9 @@ def shifted(name, price_at_baseline): return branch # policyengine-us stores float32 values, so totals agree to about 1e-7. + assert (base_cost[:2] > 0).all() alone = np.asarray(shifted("alone", False).calculate("medicaid_cost", 2026), dtype=float) + assert (alone[2:] > 0).all() assert (alone * weights).sum() == pytest.approx((base_cost * weights).sum(), rel=1e-6) branch = shifted("priced", True) @@ -1309,3 +1311,38 @@ def test_household_frame_drops_healthier_oregon_for_medicaid_enrollees(): ) assert frame["state_health_programs"].tolist() == pytest.approx((programs - oregon).tolist()) assert frame["state_health"][0] > 0 and frame["tanf_in_state_benefits"].tolist() == [0.0] + + +def test_household_frame_carries_the_unlisted_state_subsidies(): + # ConnectorCare sits in STATE_HEALTH_UNLISTED. A Massachusetts adult at + # $30,000 with ACA take-up draws it, and the frame's state spending must + # hold it, or those dollars would leave every state's line. + pytest.importorskip("policyengine_us") + pd = pytest.importorskip("pandas") + from policyengine_us import Microsimulation + + from analysis.california_shift import _household_frame + + dataset = pd.DataFrame( + { + "person_id__2026": [0], + "person_household_id__2026": [0], + "person_tax_unit_id__2026": [0], + "person_spm_unit_id__2026": [0], + "person_family_id__2026": [0], + "person_marital_unit_id__2026": [0], + "age__2026": [45], + "state_fips__2026": [25], + "household_weight__2026": [1_000.0], + "employment_income__2026": [30_000.0], + "takes_up_aca_if_eligible__2026": [True], + "is_tax_unit_head__2026": [True], + "is_tax_unit_dependent__2026": [False], + } + ) + sim = Microsimulation(dataset=dataset) + subsidy = np.asarray(sim.calculate("ma_connector_care", 2026, map_to="household"), dtype=float) + assert subsidy[0] > 0, "policyengine-us no longer pays this adult a ConnectorCare subsidy" + frame = _household_frame(sim) + assert frame["state_health_programs"].tolist() == pytest.approx(subsidy.tolist()) + assert frame["state_health"][0] >= subsidy[0] From f01e9377edd751e628994a582eb69364f582715b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 9 Nov 2026 03:59:14 -0500 Subject: [PATCH 16/17] Describe the TANF limit precisely and fix chart labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Those states' TANF responds to the added capital income and misses only the wage cut, and the study measure's federal lines miss it too. The run description in the output changes to match; no number moves. The chart prints negatives as −$243B and moves the legend clear of the lines when the values run further below zero than above. The state-only Medi-Cal docstring says the line doesn't estimate California's actual cost. Co-Authored-By: Claude Opus 5.5 --- analysis/california_chart.py | 15 +++++++++-- analysis/california_shift.py | 24 ++++++++++++------ analysis/california_summary.py | 6 +++-- .../outputs/california_shift_pe6.16.0.json | 2 +- analysis/tests/test_california_shift.py | 25 ++++++++++++++++++- 5 files changed, 59 insertions(+), 13 deletions(-) diff --git a/analysis/california_chart.py b/analysis/california_chart.py index a469b88..724056f 100644 --- a/analysis/california_chart.py +++ b/analysis/california_chart.py @@ -108,7 +108,18 @@ def dollar_format(values): """Format for $B labels: one decimal when the values span less than $5 billion.""" span = max(values) - min(values) if values else 0.0 decimals = 1 if span < 5 else 0 - return lambda v: f"${v:,.{decimals}f}B" + + def label(v): + rounded = round(v, decimals) + sign = "−" if rounded < 0 else "" + return f"{sign}${abs(rounded):,.{decimals}f}B" + + return label + + +def legend_location(values): + """Where the legend clears the lines, which all start at zero on the left.""" + return "lower left" if -min(values) > max(values) else "upper left" def series_from_sweep(path=SWEEP_PATH): @@ -192,7 +203,7 @@ def draw(xs, ca, ca_sdi, other, out, source_note, measure_note=None, ax.tick_params(colors=TEXT_SECONDARY, length=0) for spine in ax.spines.values(): spine.set_visible(False) - ax.legend(frameon=False, loc="upper left", fontsize=9.5, labelcolor=TEXT_PRIMARY) + ax.legend(frameon=False, loc=legend_location(drawn), fontsize=9.5, labelcolor=TEXT_PRIMARY) fig.text(0.06, 0.955, title_for(ca), fontsize=14, fontweight="bold", color=TEXT_PRIMARY, ha="left") diff --git a/analysis/california_shift.py b/analysis/california_shift.py index c91d294..92a8b16 100644 --- a/analysis/california_shift.py +++ b/analysis/california_shift.py @@ -53,11 +53,12 @@ branch, so anything that reads their ``*_before_lsr`` inputs sees pre-shift earnings. policyengine-us's default TANF earned-income list reads those inputs, as do Massachusetts's, Montana's and South Carolina's lists, so those states' -TANF benefits don't respond and the TANF, other-states and all-government lines -miss that response. SNAP's gross self-employment income reads them too. -California's CalWORKs list reads the shifted variables, so California's line -holds the response. The published sweep's shift (``_apply_shift``) has the same -limit. +TANF benefits respond to the added capital income and not to the wage cut, and +can fall. The TANF, other-states and all-government lines and the study +measure's federal lines miss the response to the wage cut. SNAP's gross +self-employment income reads those inputs too. California's CalWORKs list reads +the shifted variables, so California's line holds the response. The published +sweep's shift (``_apply_shift``) has the same limit. Usage: python -m analysis.california_shift [--levels 0.1,0.2,0.5,1.0] @@ -202,8 +203,10 @@ "branch, so anything that reads their *_before_lsr inputs sees pre-shift " "earnings: the default TANF earned-income list and Massachusetts's, " "Montana's and South Carolina's lists, so those states' TANF benefits " - "don't respond and the TANF, other-states and all-government lines miss " - "that response (SNAP's gross self-employment income reads them too). " + "respond to the added capital income and not to the wage cut, and can " + "fall. The TANF, other-states and all-government lines and the study " + "measure's federal lines miss the response to the wage cut (SNAP's gross " + "self-employment income reads those inputs too). " "California's CalWORKs list reads the shifted variables. " "'levels' allocate the new capital income across the country, as the " "published sweep does; 'within_state_levels' give each state's freed " @@ -339,6 +342,13 @@ def _state_only_coverage(sim): overstates the state's cost by the federal share of their emergency care, an amount this accounting can't measure. CHIP's unborn-child coverage keeps its federal match (``_state_only_federal_share``). + + This line depends on the immigration status the data assigns, and it doesn't + estimate California's actual state-only Medi-Cal cost. Microcosm treats + anyone who reports Medicaid in the CPS as documented, and from 2026 + California's enrollment freeze keeps undocumented adults without existing + coverage out at every income, so the line stays small and only the groups + the freeze exempts can enter it. """ p = _parameters(sim).gov.hhs.medicaid.eligibility status = sim.calculate("immigration_status", period=YEAR, decode_enums=True) diff --git a/analysis/california_summary.py b/analysis/california_summary.py index 8a78795..75cdf73 100644 --- a/analysis/california_summary.py +++ b/analysis/california_summary.py @@ -62,8 +62,10 @@ "Note: the shift sets employment_income and self_employment_income on each branch, so " "programs that read their *_before_lsr inputs see pre-shift earnings. The default TANF " "earned-income list and Massachusetts's, Montana's and South Carolina's lists read them, " - "so those states' TANF response is missing from the TANF, other-states and all-government " - "lines. California's CalWORKs list reads the shifted variables." + "so those states' TANF responds to the added capital income and not to the wage cut, and " + "can fall. The TANF, other-states and all-government lines and the study measure's federal " + "lines miss the response to the wage cut. California's CalWORKs list reads the shifted " + "variables." ) diff --git a/analysis/outputs/california_shift_pe6.16.0.json b/analysis/outputs/california_shift_pe6.16.0.json index 75c9445..0c1cc3c 100644 --- a/analysis/outputs/california_shift_pe6.16.0.json +++ b/analysis/outputs/california_shift_pe6.16.0.json @@ -1,7 +1,7 @@ { "year": 2026, "state": "CA", - "description": "Positive employment and self-employment income fall by the shift share; the same weighted total is added to positive capital income (gains, interest, dividends and rent) in proportion to the capital income each person already receives. Static, current law, no behavioral response. State net revenue is state income tax plus use tax, less refundable state credits and the state benefits in gov.household.household_state_benefits (for California: SSP, CAPI, child care subsidies, CVRP, CARE, FERA and three Los Angeles programs; no Medi-Cal or CalWORKs); California's employee SDI contribution is reported separately. Federal net revenue is the all-government change less every state's net change and less state payroll taxes (employee and employer) and local taxes, which the all-government total counts but no state's line holds; federal_net_published_definition_change_b keeps them, as the published site does. Each shifted branch prices Medicaid enrollees at the baseline's cost per enrollee (the baseline's medicaid_slcsp_state_denominator), so state Medicaid totals move with enrollment. The *_with_health_tanf fields also charge each state its share of Medicaid, CHIP and Medicare Savings Program costs under federal matching rules (state_benefit_cost), the whole cost of coverage it funds alone for people whose immigration status rules out federal matching (CHIP coverage through the unborn-child option keeps its federal match), its own health subsidies (for California, the Covered California premium subsidy; Healthier Oregon only for people not also enrolled in Medicaid) and the TANF change its line doesn't already hold (CalWORKs for California; Massachusetts's TAFDC already sits in its state benefits), since the federal TANF block grant is fixed; the all-government total adds every program in healthcare_benefit_value and counts TANF once. The shift sets employment_income and self_employment_income on each branch, so anything that reads their *_before_lsr inputs sees pre-shift earnings: the default TANF earned-income list and Massachusetts's, Montana's and South Carolina's lists, so those states' TANF benefits don't respond and the TANF, other-states and all-government lines miss that response (SNAP's gross self-employment income reads them too). California's CalWORKs list reads the shifted variables. 'levels' allocate the new capital income across the country, as the published sweep does; 'within_state_levels' give each state's freed labor income to that state's own capital holders.", + "description": "Positive employment and self-employment income fall by the shift share; the same weighted total is added to positive capital income (gains, interest, dividends and rent) in proportion to the capital income each person already receives. Static, current law, no behavioral response. State net revenue is state income tax plus use tax, less refundable state credits and the state benefits in gov.household.household_state_benefits (for California: SSP, CAPI, child care subsidies, CVRP, CARE, FERA and three Los Angeles programs; no Medi-Cal or CalWORKs); California's employee SDI contribution is reported separately. Federal net revenue is the all-government change less every state's net change and less state payroll taxes (employee and employer) and local taxes, which the all-government total counts but no state's line holds; federal_net_published_definition_change_b keeps them, as the published site does. Each shifted branch prices Medicaid enrollees at the baseline's cost per enrollee (the baseline's medicaid_slcsp_state_denominator), so state Medicaid totals move with enrollment. The *_with_health_tanf fields also charge each state its share of Medicaid, CHIP and Medicare Savings Program costs under federal matching rules (state_benefit_cost), the whole cost of coverage it funds alone for people whose immigration status rules out federal matching (CHIP coverage through the unborn-child option keeps its federal match), its own health subsidies (for California, the Covered California premium subsidy; Healthier Oregon only for people not also enrolled in Medicaid) and the TANF change its line doesn't already hold (CalWORKs for California; Massachusetts's TAFDC already sits in its state benefits), since the federal TANF block grant is fixed; the all-government total adds every program in healthcare_benefit_value and counts TANF once. The shift sets employment_income and self_employment_income on each branch, so anything that reads their *_before_lsr inputs sees pre-shift earnings: the default TANF earned-income list and Massachusetts's, Montana's and South Carolina's lists, so those states' TANF benefits respond to the added capital income and not to the wage cut, and can fall. The TANF, other-states and all-government lines and the study measure's federal lines miss the response to the wage cut (SNAP's gross self-employment income reads those inputs too). California's CalWORKs list reads the shifted variables. 'levels' allocate the new capital income across the country, as the published sweep does; 'within_state_levels' give each state's freed labor income to that state's own capital holders.", "metadata": { "policyengine_version": "6.16.0", "policyengine_us_version": "2.2.1", diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 93cc931..1108b3d 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -299,7 +299,12 @@ def test_chart_draws_california_only(tmp_path): def test_chart_titles_subtitles_and_formats(): pytest.importorskip("matplotlib") - from analysis.california_chart import dollar_format, subtitle_for, title_for + from analysis.california_chart import ( + dollar_format, + legend_location, + subtitle_for, + title_for, + ) assert title_for([0.0, 1.0, 5.0]) == "California gains as labor income shifts to capital" assert title_for([0.0, -1.0, -5.0]) == "California loses as labor income shifts to capital" @@ -309,6 +314,24 @@ def test_chart_titles_subtitles_and_formats(): assert subtitle_for("study", False) == subtitle_for("study", True) assert dollar_format([0.0, 1.2, 3.4])(1.25) == "$1.2B" assert dollar_format([0.0, 12.0, 40.0])(12.4) == "$12B" + assert dollar_format([0.0, -243.2, 47.2])(-243.2) == "−$243B" + assert dollar_format([-1.0, 1.0])(-0.04) == "$0.0B" + assert legend_location([0.0, -243.2, 47.2]) == "lower left" + assert legend_location([0.0, -0.15, 47.2]) == "upper left" + + +@given( + st.lists(st.floats(-1e4, 1e4), min_size=1, max_size=8), + st.floats(-1e4, 1e4), +) +def test_dollar_format_signs_only_a_nonzero_amount_and_puts_the_sign_first(values, value): + pytest.importorskip("matplotlib") + from analysis.california_chart import dollar_format + + text = dollar_format(values)(value) + amount = float(text.removeprefix("−").removeprefix("$").removesuffix("B").replace(",", "")) + assert "-" not in text + assert text.startswith("−") == (value < 0 and amount > 0) COMPONENT_KEYS = ( From 83c8387266aa4ce6d4f31c82d1ebcdbabd28376d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 9 Nov 2026 04:24:19 -0500 Subject: [PATCH 17/17] Stabilize the dollar format test and cover the longer health list The new Hypothesis test sets deadline=None like every other one in the file, because its first example imports matplotlib when it runs alone. The health list check now has tests for a list that also holds the five state subsidies: a new program and a dropped program both refuse. Co-Authored-By: Claude Opus 5.5 --- analysis/tests/test_california_shift.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/analysis/tests/test_california_shift.py b/analysis/tests/test_california_shift.py index 1108b3d..92bd9cf 100644 --- a/analysis/tests/test_california_shift.py +++ b/analysis/tests/test_california_shift.py @@ -320,6 +320,7 @@ def test_chart_titles_subtitles_and_formats(): assert legend_location([0.0, -0.15, 47.2]) == "upper left" +@settings(max_examples=200, deadline=None) @given( st.lists(st.floats(-1e4, 1e4), min_size=1, max_size=8), st.floats(-1e4, 1e4), @@ -1057,6 +1058,14 @@ def test_check_health_lists(): # Releases after policyengine-us 2.2.1 list the five state subsidies in # household_health_benefits too; the classification is the same. _check_health_lists(_health_parameters(health=EVERY_HEALTH), EVERY_HEALTH) + # That longer list gets the same checks. + with pytest.raises(ValueError, match="new_program"): + _check_health_lists(_health_parameters(health=EVERY_HEALTH + ["new_program"]), EVERY_HEALTH) + dropped = LISTED_HEALTH[0] + with pytest.raises(ValueError, match=f"missing.*{dropped}"): + _check_health_lists( + _health_parameters(health=[p for p in EVERY_HEALTH if p != dropped]), EVERY_HEALTH + ) with pytest.raises(ValueError, match="twice"): _check_health_lists(_health_parameters(flag=True), EVERY_HEALTH) with pytest.raises(ValueError, match="new_program"):