Prop 40, BSZ analysis: the materials

Every script, test, data file and source behind the explorer, the report and the slides

Published

October 17, 2026

This page is a guide to the Open Materials layer (the repository) of the BSZ analysis (Boll, Saez and Zucman, BSZ, plus the Galle, Gamage, Saez and Shanske (GGSS) expert report): what to download, what to run, and what the checks found. Everything lives in one repository, fhoces/opa-prop40, under bsz-analysis/. The tables on this page are built from the files themselves when the page is rendered, so they cannot fall out of date with the code without a re-render showing it.

Rebuild everything

git clone https://github.com/fhoces/opa-prop40 && cd opa-prop40/bsz-analysis
curl -L -o original-materials/BSZ_MainTablesFigures.xlsx \
     https://eml.berkeley.edu/~saez/BSZ_MainTablesFigures.xlsx   # check the SHA-256 below
Rscript -e 'targets::tar_make(names = !tidyselect::any_of(c("report_html","report_pdf")))'
Rscript tools/site-test-results.R        # the full test suite; writes site/data/test-results.csv
cd site && quarto render repro.qmd && quarto render materials.qmd
cd slides && Rscript -e 'rmarkdown::render("slides.Rmd")'

The pipeline is an R {targets} project (an R package that runs the pipeline’s steps in order and records which outputs are current). The default workbook vintage (the dated version of the workbook a number comes from) is the August 2026 revision with the data appendix of 2026-10-08 (SHA-256 below); BSZ_VINTAGE=september selects the data appendix of 2026-09-04 and BSZ_VINTAGE=may the May version. CAWTBSZ_MATERIALS points the pipeline at the sources if they are not in original-materials/.

Every computation also has a Python twin in py/, with the same function names as in R/. After pip install -r requirements.txt, the command python py/run_export.py (about 2 seconds) writes the same results to export/py/, and tests/testthat/test-py-parity.R checks each file against R’s. The report’s code folds show both languages.

Tests

file blocks passed failed skipped
test-ai-badges.R 2 15 0 0
test-compute.R 9 216 0 0
test-data_sheets.R 9 45 0 0
test-figures.R 22 76 0 0
test-ingest_excel.R 5 21 0 0
test-py-parity.R 2 56 0 0
test-site.R 10 100 0 0
test-snapshots.R 28 34 0 0
test-sql-steps.R 6 18 0 0
test-sql-workbook.R 3 41 0 0
test-sql.R 3 38 0 0
test-tables.R 14 127 0 0
test-verify.R 6 7 0 0
total 119 794 0 0

Last run: 2026-10-16, August vintage: 794 expectations passed, 0 failed, in 119 test blocks. Of these, 694 belong to the reproduction itself and 100 to this site: they check that the explorer reproduces Table 5 to 1e-12, that explorer/grid.js is the exporter’s own output, and that every number typed into a hand-written page still matches the pipeline. GitHub Actions rebuilds the pipeline and runs the suite on every push that touches bsz-analysis/ (.github/workflows/bsz-ci.yml); it does not render the Quarto pages or the slides.

Scripts

file lines what it does
R/code_listing.R 24 helper for code listings in the original report
R/compute_billionaires_ca_inctax.R 736 CA income tax of all billionaires (FTB + Pareto), and all taxes
R/compute_data_sec_agg.R 46 yearly totals from the billionaire list (drops rows repeated in the 2026-09-04 copy)
R/compute_pareto.R 163 Pareto extrapolation of missing billionaires; the Laffer curve
R/compute_shortrunseries.R 282 wealth growth panel and the 2025 snapshot
R/compute_tab5.R 144 Table 5: the estimating function the explorer also runs, and the four estimate rows of the one-time tax
R/compute_top4taxes.R 133 taxes of the top 4 by year, 2004-2025
R/data_sheets.R 193 14 extractors, one per workbook sheet the pipeline reads
R/excel_cells.R 22 single-cell and row/column readers shared by the compute files
R/export_contract.R 104 Export contract: the only files the comparison layer (comparison/) reads from
R/figures.R 725 the twelve ggplot figures
R/form4_basis.R 132 R twin of py/form4_basis.py: capital gains on Form 4 sales, the sequential
R/ingest_excel.R 83 reads the workbook: sheet reader, vintage switch, source directory
R/render.R 75 writes tables and figures to outputs/
R/run_sql.R 191 R twin of py/run_sql.py: run a shared .sql file against
R/site_exports.R 263 this site's data: the dials, the grid (computed by compute_tab5.R), input origins
R/tables.R 718 the six gt tables
R/verify.R 59 the test helper that compares R with the workbook's cached cells
R/vintage.R 206 everything that differs between the May and August workbooks, in one place
R/workbook_db.R 126 The public workbook's two large input sheets in SQLite, for the shared

Also: _targets.R (the pipeline: which function builds which result), tools/site-test-results.R (runs the suite for this page), and the site sources site/repro.qmd, site/materials.qmd, site/slides/slides.Rmd, site/index.html and site/explorer/index.html.

Data files the site publishes

file rows columns contents
grid.csv 1920 16 every cell of the explorer: six inputs and seven outcomes
inputs.csv 20 5 every input of the one-time estimate, with value, origin label and source
tab5.csv 4 8 Table 5, the four estimate rows, as the pipeline computes them
tab5-vs-printed.csv 28 7 every Table 5 cell against the digit printed in the paper
leavers.csv 7 4 the seven named leavers of Table 5 rows 3 and 4
test-results.csv 119 7 one row per test block from the last full run

Every file except test-results.csv is written by a target in _targets.R (site_grid_csv, site_inputs_csv, site_tab5_csv, site_tab5_printed_csv, site_leavers_csv; the explorer’s grid.js by site_grid_js). None is edited by hand.

Sources

The authors’ workbook and paper are not redistributed here. The manifest records where each file came from, its SHA-256 and its terms:

file provider obtained SHA-256 (first 12) terms
BSZ_MainTablesFigures.xlsx Boll Saez Zucman (via Saez lab, UC Berkeley) public-download 2026-10-14 ea0c14107e10 do-not-redistribute
september-2026/BSZ_MainTablesFigures.xlsx Boll Saez Zucman (via Saez lab, UC Berkeley) public-download 2026-09-22 c236cc941337 do-not-redistribute
BSZ26CAbillionaires.pdf NBER (Boll Saez Zucman) public-download 2026-09-22 ee1f2ea5a06c do-not-redistribute
may-2026/BSZ_MainTablesFigures.xlsx Boll Saez Zucman (via Saez lab, UC Berkeley) public-download 2026-05-26 cffe04bd950f do-not-redistribute
may-2026/BSZ26CAbillionaires.pdf NBER (Boll Saez Zucman) public-download 2026-05-26 cd8588edf529 do-not-redistribute
additional-documentation/galle-gamage-saez-shanskeCAbillionairetaxJuly26.pdf Galle, Gamage, Saez, Shanske public-download 2026-09-22 91d124417e5d do-not-redistribute
additional-documentation/galle-gamage-saez-shanskeCBTAcontrollingshares.pdf Galle, Gamage, Saez, Shanske public-download 2026-09-22 22d7f330ed06 do-not-redistribute
additional-documentation/galle-gamage-shanskeCBTAlegal.pdf Galle, Gamage, Shanske public-download 2026-09-22 ac75de294f93 do-not-redistribute
additional-documentation/GGS26props41-42.pdf Galle, Gamage, Shanske public-download 2026-09-22 97364601fbc3 do-not-redistribute
additional-documentation/responsetorauh26.pdf Galle, Gamage, Saez, Shanske (response to Rauh et al.) public-download 2026-09-22 c58b25e3540d do-not-redistribute
additional-documentation/saez-tpe26-presentation.pdf Saez public-download 2026-09-22 38c5cfd22421 do-not-redistribute
author-shared/2026-09-23_bsz-replication-code-data/ Boll, Saez, Zucman (author-shared) author-shared 2026-09-23 15dc5f6c3db5 confidential: use for re-analysis permitted; no public sharing

Full rows, including what each source feeds: DATA-SOURCES.csv; the column definitions: DATA-SOURCES.md. The author-shared bundle is confidential. The SQL in sql/ rebuilds the workbook’s data sheets from it and checks them against the public workbook; only that comparison is reported, and none of the bundle’s contents is committed or shown.

What the checks found

The code reproduces the paper’s published results from the authors’ public workbook: Table 5, the other tables and figures it builds, every variation of Table 5 the paper prints, and the other numbers checked on the checks page. A figure counts as reproduced when it rounds to the printed digit, or when the paper prints it one unit lower in the last digit (note 1). A figure the text derives counts as reproduced when the inputs the text states give it.

The seven notes below are kept for transparency. Each records a place where the paper’s text, its tables and the workbook describe a step differently. None is a failure to reproduce a printed result. They are of three kinds, and each note states its kind:

  • Rounding (note 1): the paper prints a value at a different rounding from its workbook.
  • Small numerical difference (notes 6 and 7): the text uses a first-order formula, and the workbook’s exact curve gives a slightly different figure. Each note gives the size.
  • Documentation (notes 2, 3, 4 and 5): the text, a table label and the workbook describe one calculation differently, or use different inputs for it. Each printed result reproduces from the inputs stated beside it.

An earlier version of these pages called the first note a pair of cells that “differ from the workbook”, and notes 5 to 7 numbers that “differ” or are “close”. They were reclassified on 2026-10-16 under the standard first applied to the JR analysis; feedback/007-2026-10-16-bsz-reproduction-standard/REPORT.md gives each item’s old and new label. The notes cover the current workbook only, so a note on four rows that the 2026-09-04 copy of the workbook listed twice was dropped on 2026-10-17: the 2026-10-08 update lists each row once.

Notes on the authors’ materials

  1. Rounding. Table 5 prints the income tax loss of rows 2 and 3 as $0.19B and $0.51B a year. The workbook, and this code, give $0.196B and $0.515B, which round to $0.20B and $0.52B. Each printed figure is one unit lower in the last digit than the workbook’s value rounded, and $0.006B and $0.005B a year below the workbook’s value itself. Row 4 of the same column is rounded, and the paper does not say why these two differ. Row 2’s printed figure is also what the May version printed, from $0.194B in the May workbook. No other printed number is built on these two cells. A test checks both.
  2. Documentation. Tab5!H6 types the share of income tax lost to leavers as a literal 0.05. The row label, “10% evasion/avoidance (half due to mobility)”, describes it as avoidance times the share due to mobility, 0.10 × 0.5, the same value. The explorer uses the product, so the share moves with those two dials. At the authors’ values the results are identical.
  3. Documentation. Table 5’s row 4 is described as rows 2 and 3 together, but it does not subtract the phase-in deduction that row 2 subtracts (Tab5!F9 against Tab5!F7). The printed $115 billion comes from the workbook as it is, and this reproduction and the explorer follow it. A consistent treatment would give $112.8 billion, $2.0 billion less.
  4. Documentation. Appendix Table A1’s annualized growth row (TabA1!B18:G18) spreads the growth from 1982 to 2026 over 43 years. Table 1 uses 44 (Tab1!C18:H18). The paper prints the workbook’s 43-year values, and so does this reproduction. The report states what 44 would give.
  5. Documentation. The text’s break-even (PDF p.22: 25 years if all billionaires left, 100 if one fourth did) uses the ratio of income tax to wealth the text states, about 0.2% a year (the abstract and Table 2’s recent years). Both figures reproduce from that ratio. Table 5 uses the 2019-2025 average tax against 1 July 2026 wealth, 0.13%. On Table 5’s inputs the break-even is about 38 years (151 if one fourth left).
  6. Small numerical difference. The text gives the revenue-maximising rate of a permanent tax with de-concentration as 1/(1 + d + e) = 1/26 = 3.8% (PDF p.30), a first-order formula. The exact peak of the workbook’s own Figure 9 curve, which this code reproduces, is 3.9%. In the same way, the text’s “about 15%” of the base lost at a 1% tax is d × 1%. The curve’s factor, 1 − 0.9915, gives 14.0%.
  7. Small numerical difference. The text says a 1% permanent tax keeps 88% of its mechanical revenue (PDF pp.29-30), adding up first-order effects (100% − 10% − 2%). The workbook’s curve gives 88.6%.

Errors in this OPA, found and fixed

  • A display error in this reproduction’s Tables 1 and A1: the display put Panel B’s values under Panel A’s headers and formats and kept the May workbook’s labels (GDP for AGI, 2025 for 2026); fixed in the display code on 2026-10-15.
  • Appendix Table A1 at first used 44 years, so three cells of the annualized row printed lower than the paper. It now uses 43, as the paper and the workbook do (note 4), and a test checks each cell of the row.
  • A malformed row in this OPA’s own source manifest: two provider fields contained an unquoted comma, which shifted every later column for any CSV reader; quoted on 2026-09-23.
  • Stale positions in this OPA’s code. When the authors updated the workbook, 184 of this reproduction’s tests failed on row positions and constants that this OPA’s code had fixed at the earlier layout. All were fixed (VERIFY-AUGUST.md).

Not reproduced

The paper’s Figure 8 and Appendix Figure A1 (wealth relative to AGI) have no builder yet. The raw data behind the workbook is taken as given.