Skip to content

Regenerate the uprating table from policyengine-us and count the site's uprating cards from it - #54

Draft
MaxGhenis wants to merge 10 commits into
fix-broken-linksfrom
regenerate-uprating-table
Draft

MaxGhenis wants to merge 10 commits into
fix-broken-linksfrom
regenerate-uprating-table

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

This PR builds on #52 and targets its branch, fix-broken-links. After #52 merges into main, I'll rebase this branch onto main and point the PR there.

What changed

scripts/generate_uprating_table.py writes public/data/uprating_analysis.csv from the tax-benefit system that policyengine-us 2.116.0 builds (commit 44f9211, which src/data/policyengineUsSnapshot.json pins). It installs the release with uv, resolving each dependency as PyPI served it a minute after the release's last upload, so a rerun at the same pin writes the same files. It then checks every installed file under parameters/ and variables/ against the pinned commit, since each row links to its file there and each formula link to its lines.

The script writes a row for a parameter file when the build grows at least one leaf parameter in it. The build grows a leaf when it sets a value after the last date the file sets, and that value differs from the one in force on that date. policyengine-core grows 8,512 leaf parameters by the series their metadata names, and policyengine-us grows 66 in 28 files with formulas in parameters/uprating_extensions.py. The script writes each of those 28 files' uprating as <index> via <function>, and writes the lines of each formula's call, with the comment above it, to src/data/upratingFormulaLines.json.

policyengine-us extends 28 index series, such as CPI-U, past their last projection. Of those, 24 grow other values and get no row. Three variables' formulas read CMS's projection of Medicare spending per enrollee directly (medicare_cost, medicare_gross_cost and qmb_cost_sharing), so that series gets a row: calibration.gov.hhs.medicare.per_capita_cost via extend_parameter_values. No value grows by the other three, CBO's projections of above-the-line deductions, IRS other income and taxable Social Security, and no variable names them. The script lists these three and the Medicare series, and stops on any other extended series that no value grows by.

A variable gets a row when the built system sets its uprating. Of the 542, 81 grow by the series their class names. policyengine-us grows the 382 that name none with CBO's adjusted gross income per person, and the 79 that name a national total with that total per person.

The script stops and names the files involved when it finds:

  • a parameter file that YAML can't parse or that the system didn't load;
  • an uprated value it can't place in a file, or a row whose series its file doesn't declare;
  • a value that outruns its file by no mechanism the table knows;
  • an extended index series that no value grows by and that the script doesn't list;
  • a row that code grows by no single call in uprating_extensions.py;
  • a variable defined outside policyengine_us/variables;
  • a dataset column that the default microsimulation grows by another series than its row shows;
  • an installed file that differs from the pinned commit.

To move the pin, edit the snapshot JSON and run:

uv run scripts/generate_uprating_table.py
GITHUB_TOKEN=$(gh auth token) bun scripts/snapshot-policyengine-us-files.mjs

The second command rewrites tests/fixtures/policyengine-us-files.txt, the file list that #52's link tests check every row against. uv run scripts/generate_uprating_table.py --check exits 1 when the CSV or the line ranges differ from what the pinned release builds. CI doesn't run it, because it installs policyengine-us and builds the tax-benefit system.

On /research

  • StochasticForecasting counts the parameter files, the variables and each card's rows and share from the CSV with summarizeUprating (src/utils/upratingTable.js), in place of hard-coded numbers. UPRATING_CARDS groups the generator's 23 categories into seven cards by index family. A category that no card claims falls to an "Other indexes" card, which lists it by name, so a later release's new categories still show.
  • The section fetches the CSV once and hands it to UpratingViewer as its table prop, so opening the explorer fetches nothing more. On its own, the viewer fetches the table itself.
  • UpratingViewer takes its categories from the table, links each row to its file at the pinned commit, and links each formula row to its lines of uprating_extensions.py, such as #L1205-L1212 for the payroll tax cap.
  • The section intro, the card descriptions and the explorer's intro, callout and "How PolicyEngine-US grows these values" card describe what 2.116.0 does. The counts say "parameter files", since each row stands for a file.
  • The CPI-W card names the Commissioner of Social Security, who raises benefits each year under 42 U.S.C. 415(i)(2)(A). Under 42 U.S.C. 1382f(a), SSI's dollar amounts rise by the same percentage.

Table changes

main (1.413.0) This PR (2.116.0)
Rows 317 930
Parameter files 294 388
Variables 23 542

The regenerated table adds 638 rows: 104 parameter files and 534 variables. Of those, 80 files and 315 variables sit at paths that 1.413.0 didn't have. The table on main had no row for the other 24 files and 219 variables, and 130 of those variables grow with CBO's adjusted gross income per person.

Of the 292 rows in both tables, 49 show a different uprating:

  • 39 name the same series. In 35 the row now adds the rounding or timing that the values' own specs set, where main's row showed the series alone, and in 4 the rounding differs. For example, gov.irs.deductions.standard.amount reads {'parameter': 'gov.irs.uprating', 'rounding': {'type': 'downwards', 'interval': 50}} where main read gov.irs.uprating.
  • 6 grow by a formula in uprating_extensions.py: the Social Security payroll tax cap, the blind and non-blind substantial gainful activity amounts, the dependent standard deduction, Louisiana's standard deduction and the start of Oregon's CTC reduction.
  • 4 grow by another series. The IRS's Social Security and unemployment compensation totals follow CBO's projection of each (unemployment compensation from 2022), where main showed CBO's taxable Social Security series for both. The federal poverty guidelines follow gov.hhs.fpg_uprating, and state median income follows CBO's adjusted gross income per person from 2027.

25 rows drop out:

Rows Why
gov.contrib.biden.budget_2025.capital_gains.income_threshold, gov.irs.income.exemption.phase_out.start, gov.states.co.tax.income.subtractions.collegeinvest_contribution.max_amount Each file sets uprating on a parent with a filing-status breakdown, and the built system uprates none of its values. Two say in a comment that the parent uprating stays inactive pending PolicyEngine/policyengine-core#537.
gov.contrib.congress.romney.family_security_act_2024.pregnant_mothers_credit.income_phase_in_end, gov.contrib.congress.romney.family_security_act_2_0.ctc.phase_in.income_phase_in_end Each file holds only 0, so its uprated values hold at 0.
gov.irs.credits.retirement_saving.rate.joint, Minnesota's social_security subtraction alternative_amount and income_amount, gov.usda.snap.asset_test.limit Their 2.116.0 files declare no uprating.
gov.irs.deductions.itemized.reduction.agi_threshold 2.116.0 has no such file.
spm_unit_broadband_subsidy_reported, spm_unit_capped_housing_subsidy_reported, spm_unit_energy_subsidy_reported, spm_unit_state_tax_reported, tanf_reported 2.116.0 has no such variables.
social_security_retirement, _disability, _survivors, _dependents Formulas compute them in 2.116.0. Their _reported inputs grow with the IRS's Social Security total per person and have rows.
person_weight, tax_unit_weight, spm_unit_weight, rent, spm_unit_capped_work_childcare_expenses, spm_unit_spm_threshold Formulas compute them in 2.116.0, and they declare no uprating.

Dataset columns

Microsimulation grows the columns of its default dataset by the series that data/economic_assumptions.py assigns them, and by each column's own uprating otherwise. The script checks the 11 assigned columns that have rows against their rows' series and stops on any difference. The module also assigns series to 23 columns whose variables the built system computes with a formula or a sum and gives no uprating, so they get no row. None of the 23 appears among the 377 columns of the default dataset, populace_us_2024.h5:

  • population: person_weight, tax_unit_weight, spm_unit_weight;
  • CPI-U: rent;
  • the IRS's total per person: social_security, employment_income_last_year, partnership_s_corp_income, taxable_pension_income, total_self_employment_income;
  • CBO's adjusted gross income per person: american_opportunity_credit, cdcc_relevant_expenses, energy_efficient_home_improvement_credit, foreign_earned_income_exclusion, foreign_tax_credit, interest_deduction, misc_deduction, pre_tax_contributions, savers_credit, self_employed_health_insurance_ald, self_employed_pension_contribution_ald, state_and_local_sales_or_income_tax, tax_exempt_pension_income, taxable_unemployment_compensation.

Invariants

The generator's helpers hold these over Hypothesis-generated inputs (analysis/tests/test_uprating_table.py, which CI runs):

  • A leaf belongs to the file with the longest parameter name that prefixes the leaf's name up to a . or [, or to no file when none does.
  • A file's specs join as each distinct spec once, the most-used first.
  • The table round-trips through CSV in its canonical order.
  • Following an uprating chain ends on every finite graph, by naming a category or raising on a loop.
  • A leaf's value on a date matches its latest value on or before that date, and projection_differs and changes_after report a change when, and only when, a day-by-day reading finds one.
  • The series in use hold every row's own series and each link of its chain.
  • Each dataset override lands once, as agreeing with, differing from or missing from the row that holds its column.

Example tests in the same file cover the rest. call_spans maps each statement in the entry function to its lines and the comment above it, and formula_links gives each formula row its one call and stops on a row with none or several. The recorder credits each value to the call that set it. The growth check stops on a step it can't account for, a leaf that both metadata and a formula grow, a leaf that outruns what it held, and an uprating added after uprating. check_release_matches_commit stops on any installed file that differs from the commit, and --check fails when either output differs from what the release builds, and writes neither.

The same file checks the committed files for canonical order, one row per file or variable and a named category on every row. It also checks that formula rows match the generator's formula steps, and that every formula row has a line range at the pinned commit. The table must hold a row for each extended series that a variable's formula reads, and none for an unread series.

On the site, Vitest checks that:

  • the cards partition the table, on the committed table and on 300 tables that fast-check generates: each row counts in one card, the card counts sum to the total, the shares sum to 1, and each card shown holds a row;
  • the card counts match a tally made without summarizeUprating;
  • each category goes to one card, and a card shows only examples the table holds;
  • each formula row links to its line range at the pinned commit, and no other row gets a formula link;
  • the section shows the table's totals and each card's count and share, and fetches the table once with the explorer open;
  • the explorer links every item it shows to its file at the pinned commit and each formula row to its lines.

Checks

I ran these checks at 2590555. The next commit, 31d3a47, changes only the generator's docstring, two comments and three lines of the report it prints, and ruff passes on it.

  • --check in the pinned environment: both files match policyengine-us 2.116.0, and all 15,206 installed files under parameters/ and variables/ match the commit.
  • bun run lint -- --max-warnings=0 passes, and bunx vitest run --maxWorkers=4 passes 69 tests in 14 files.
  • pytest analysis/tests passes 255 tests on Python 3.11 and skips the 13 that need policyengine_us or matplotlib, which CI doesn't install either. ruff passes both Python files.
  • tests/e2e/images.spec.js passes its 3 tests against next dev at the default base path. A local Playwright config points it at the cached Chrome for Testing build.
  • NEXT_PUBLIC_BASE_PATH=/ai-inequality bun run build exits 0. In next dev and in the static export, /research shows 388 parameter files, 542 input variables and seven cards that sum to 930, and the export fetches the CSV once with the explorer open. The payroll tax cap row links to cap.yaml and to uprating_extensions.py#L1205-L1212 at 44f9211, and both links return 200.

axiom: n/a: site data and UI change

🤖 Generated with Claude Code

Add scripts/generate_uprating_table.py, which installs the pinned
policyengine-us release, checks its files against the pinned commit, builds
the tax-benefit system and writes one row per parameter file whose values
grow and one per variable whose uprating is set, defaults included.

Pin 2.116.0 (44f9211) and regenerate the file list. Work in progress: rows
for the parameters policyengine-us grows by formula in code come next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ai-inequality Ready Ready Preview Nov 10, 2026 8:10pm UTC

Request Review

Example and Hypothesis property tests for owning_file, the spec helpers,
category_for, the leaf-value readers and render_table, plus structural
checks on public/data/uprating_analysis.csv. They load the script by path
and need no policyengine-us.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add src/utils/upratingTable.js, which groups the generator's categories
into index families and totals them, and a hook that loads the CSV. The
forecasting section's counts and shares and the explorer's categories now
come from public/data/uprating_analysis.csv, and the explorer links each
formula-grown row to its formula.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vitest checks that the cards partition the table, that their counts and
shares match a count made apart from the summary (on the committed table
and on 300 seeded generated tables), that each curated example sits in its
card, and that formula-grown rows link to their formulas' module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/generate_uprating_table.py now records which step of
set_all_uprating_parameters writes each value. The table gains 23 files
that formulas in parameters/uprating_extensions.py grow (Medicaid
long-term care limits, NYC tax elimination thresholds, SSA wage-indexed
amounts, Louisiana and Oregon amounts, the Washington millionaires tax
deduction), and 6 rows whose metadata uprating a formula overrides now
name that formula. 932 rows: 390 parameter files and 542 variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits November 9, 2026 20:57
Microsimulation extends its default dataset with the series that
data/economic_assumptions.py overrides some columns with, then stores
aggregates on their pre-response inputs and Social Security components
on their reported twins. The generator now fails if a column it stores
on a table row grows by another series than the row shows, and reports
the columns that grow only there. At 2.116.0, 11 columns agree with
their rows and 23 (computed variables such as rent and person weights)
get no row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The card descriptions now name what each index grows in 2.116.0,
including the formula-grown Medicaid long-term care limits, SSA
wage-indexed amounts and veterans benefits, with examples drawn from
those rows. The section and explorer copy use active verbs and name the
actors (the IRS, the Social Security Administration, lawmakers), and
headings and labels use sentence case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In next dev with Turbopack, the forecasting intro rendered "542 input
variablesover time": the compiled text after the bold count lost its
leading space when it held &apos;. A plain apostrophe keeps the space, and
the page shows the same character.

The explorer read "Showing 1 items (1 parameters, 0 variables)" after a
search that found one path. counted() moves to upratingTable.js, and both
components use it; a test covers the singular and the plural.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Nov 10, 2026
On #54's branch, useUpratingTable fetches
withBasePath(UPRATING_TABLE_PATH), a constant that upratingTable.js
defines. The fetch check counted only a path written in the call, so it
failed on a tree that merges #54, though the fetch still asks for a file
public/ serves.

The check now also accepts a fetch that names a constant, and looks up the
value site code gives that constant. It fails when no site code defines
the name, or when site code gives it more than one value. A unit test
checks fetchedFile with a written path, a constant, a constant with two
values and an undefined name.

On a tree that merges #54, six breaks fail the check: a missing file, a
constant with two values, a remote address in a constant, an undefined
name, a template path and a fetch without the base path. Renaming the
constant passes, and so does defining it twice with one value.

The header-and-footer test and the BibTeX download test now get the
120-second limit the page tests have. Under load the header sweep took 6.5
seconds and failed Vitest's default 5-second limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Give a parameter file a row only when the build sets a value after
  the file's last date that differs from the one in force then. The
  two Romney files that hold only 0 and home_equity/limit.yaml drop out.
- Give CMS's Medicare spending per enrollee a row, since three
  variables' formulas read it, and list the three CBO series that no
  value grows by and no variable names.
- Write the lines of each formula's call, with the comment above it,
  to src/data/upratingFormulaLines.json, and link each formula row to
  them at the pinned commit.
- Fetch the CSV once on /research and hand it to the explorer.
- Describe the CPI-U, CPI-W and health cards, the explorer's intro,
  callout and How card from 2.116.0, and count parameter files.
- Test the recorder's stop paths, call_spans and formula_links, and
  check the cards' partition with fast-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-us 2.116.0 extends three CMS health spending series
(moop_per_capita, esi_premium_per_enrollee and
direct_purchase_premium_per_enrollee) on a path that glides from their
2033-2034 growth rate toward the Medicare Trustees' long-range rates
(parameters/uprating_extensions.py lines 1300-1340), so "own trend"
misdescribed them. The docstring, two comments and the printed report now
say only that policyengine-us extends the series, and the direct-series
comment says what the row shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Nov 10, 2026
#54 adds a comment that uses the word fetch, and
the check counted that word as a request with no served fetch to match it.
The check now blanks every comment that espree finds before it counts.
espree, the parser ESLint already runs on this code, leaves a URL inside a
string whole. A request word in a string or in page copy still counts, and a
file espree can't parse, such as a TypeScript file, keeps its comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — 31d3a47d Deployed Nov 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant