PolicyEngine / PolicyEngine/microcosm

US top-income tail is missing above $5M of AGI: Table 1.1 size-of-AGI facts never bind, and the pool's tail stratum carries capital gains only

Open
#958 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
0
Forks
4
Avg merge
1d 3h
Merged PRs (30d)
94

Description

Summary

The certified US dataset (populace_us_2024, release populace-us-2024-spm-20260915, parent build populace-us-2024-buildp-sparse-rmloss100-cae8640-20260728T011454Z) has almost no tax units above $5M of AGI and roughly 1.6x to 3.4x too many between $200k and $2M. National AGI calibrates within 0.3%, so the total is right and the shape is wrong. Top-bracket revenue estimates come in low as a result.

Everything below is either measured on artifacts or read in code at origin/main 16c8e78d2. Extrapolations are labeled.

Measured: certified file vs IRS SOI

policyengine 6.0.0, policyengine-us 2.2.1, 2026 uprated dataset, tax-unit level, weighted. SOI is TY2023 Publication 1304 Table 1.1 (23in11si.xls), read from the Chronicle feed, which reproduces the workbook values.

AGI band PE 2026 records PE 2026 returns PE 2026 AGI ($B) SOI TY2023 returns SOI TY2023 AGI ($B) returns ratio AGI ratio
$100k–200k 15,302 30.71M 4,393 27.60M 3,818 1.11 1.15
$200k–500k 9,202 17.95M 5,274 10.96M 3,154 1.64 1.67
$500k–1M 2,099 2.86M 1,950 1.78M 1,195 1.61 1.63
$1M–1.5M 1,035 1.21M 1,521 369k 444 3.29 3.43
$1.5M–2M 201 511k 863 147k 253 3.47 3.41
$2M–5M 182 302k 824 203k 603 1.49 1.37
$5M–10M 9 12.1k 74 49.3k 336 0.25 0.22
$10M+ 3 ~1 0.01 30.4k 908 0.00 0.00

The two columns are different years, so ratios near 1.15 are consistent with nominal growth. The distortion starts at $200k.

Consequence for revenue estimates. Ordinary taxable income above the 37% threshold ($640,600 single, $768,700 joint in 2026) is $905B in PE against $1,160B taxed at 37% in SOI TY2023 Table 3.4 (23in34tr.xls). PE has 1.87M returns in the bracket against SOI's 1.10M. A 37% to 39.6% reform scores +$23.5B in 2026, against $41.1B (PSL Tax-Calculator) and $29.8B FY (Yale TBL) in CRS R49052. The response is exactly linear in the measured base ($9.05B per point), so the calculation is sound and the base is short. Scaling the SOI base to 2026 by income-tax growth gives about $1.44T and about $37B for the same reform. That scaling is an extrapolation.

Measured: where the tail is lost

Proxy AGI is the tax-unit sum of AGI-entering income inputs. It is a locator, not engine AGI. Stage files are the checkpoints of a from-scratch base build of main's chain on 2026-09-12 (three pooled ASEC years, processed puf_2024.h5 donor).

Stage tax units ≥ $5M (unweighted) $5M–10M units / $B $10M+ units / $B
Processed 2024 PUF donor file the build consumes 19,034 40.0k / 275 25.4k / 797
Raw pooled ASEC (001_pre_clone_enrichment) 0 0 0
After cloning (002) 0 0 0
After PUF QRF imputation (004_qrf_finalization) 18 6.4k / 42 1.7k / 25
After capital-gains tail transfer (005) 9,891 18.8k / 128 10.8k / 355
Final base export, pre-selection (023) 9,890 18.8k / 128 10.8k / 355
Certified file (2024 base year) 11 12.0k / 72 ~1 / 0.0
SOI TY2023 Table 1.1 n/a 49.3k / 336 30.4k / 908

Findings:

  1. The donor has the tail. The processed PUF donor sits close to SOI in both top bands.
  2. The survey spine cannot carry it. In the raw pooled ASEC, no person has total income above $3.15M, wages top out at $2.10M, and interest, dividends, capital gains and rents each top out at or below $1.0M.
  3. QRF imputation does not recreate it. The PUF enters as conditional draws onto cloned ASEC tax units. The eight predictors are filing status, person count and the recipient's own six ASEC income components (puf_support.py:199-208). Recipients with top-coded predictors rarely draw top donors: 18 of 231,007 cloned tax units land at or above $5M.
  4. The certified build predates the tail stage. puf_capital_gains_tail.py first landed on 2026-07-28 (66609b086, #568). Build P's commit cae8640 does not contain it, and the certified file has no clone-index-2 rows.
  5. The tail stage on main restores capital gains only. It transfers the donor's five capital-gains fields and leaves every other income field as the recipient clone's (puf_capital_gains_tail.py:94-104, :1735-1746). Donors are selected by short-term plus long-term gains above the weighted q99.5 (:334-376), so high-AGI donors with modest gains never enter. Measured composition of AGI above $10M: the raw 2015 PUF is 47% capital gains, 18% wages and 18% partnership and S-corporation income. The pool's tail stratum is 99% capital gains and 1% wages. Anchoring AGI alone would therefore not restore the ordinary-income base that the 37% rate applies to.
  6. Export, re-certification and uprating do not lose the tail. The 2024 certified file already has only 11 such units before any uprating.

Read: why calibration does not anchor the shape

  • fiscal_targets.py:3154-3168 refuses any SOI fact with a bounded AGI range whose tax year differs from the build period ("Refuse stale nominal SOI AGI bins as target-period hard targets"). The refusal is applied at :2411-2416.
  • The only exemptions are EITC decompositions and Historic Table 2 taxable interest (:330-334, :2535-2558), which are rebased as shares of an active national total.
  • Table 1.1 publishes one tax year behind the build period, so all of its size-of-AGI rows are dropped. The Chronicle feed already carries them, including 1m_to_1_5m, 1_5m_to_2m, 2m_to_5m, 5m_to_10m and 10m_plus, with return counts and AGI that match the workbook.
  • Verified on Build P's calibration_diagnostics.json: Table 1.1 compiles exactly two rows, the all-returns return count and AGI. Compiling main against the 2026-09-18 feed gives the same two rows.
  • The only national targets above $500k with a nonzero value are taxable interest for 500k_to_1m and 1m_plus. The filing-season EITC rows for those bands carry zero targets. In Build P the interest rows finish at +159% on amount for 500k_to_1m, and at +65% on returns and −39% on amount for 1m_plus.
  • The solver hard-clamps weights at 5x their initial value (calibration.yaml max_weight_ratio: 5.0; solve.py:922-939). With 12 records above $5M, no reweighting of the certified file can reach SOI's $1.24T there.

Proposed fix, in two increments

  1. Bind the Table 1.1 size-of-AGI distribution (return counts and AGI, national) through the same rebase-as-shares path taxable interest uses, for size classes from $100k up. Below that, the materializer counts every tax unit in the band with no filer filter (build_us_fiscal_refresh_release.py:4873-4875, :4907-4908), so SOI return counts are not the same concept. No Chronicle change is needed.
  2. Give the pool a full-vector, AGI-selected own-tail stratum. Carry PUF donors above the ASEC-supportable AGI ceiling into the pool with their own normalized weights and their whole income vector, generalizing the capital-gains tail stage. This is what restores wages and pass-through income at the top.

A third item needs Chronicle first: SOI Table 3.4 income taxed at 37%. The feed has no Table 3.4 facts, and the compiler has no reader for a rate-bracket universe.

On support: on main's pool, the tail stratum at base weights holds about 12k units in $5M–10M and 9k above $10M, a 3x to 4x stretch to SOI. docs/us-fact-to-target.md treats a ~3x stretch as bindable with selection-mass protection. On the certified file the same targets would need a hundreds-fold stretch, so increment 1 only bites in a new build that also carries a tail stratum.

Related: #940 (state top-tail mass uncalibrated), #943 (wage bands), #481 (QRF tail draws are a seed lottery), #578.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with fiscal_targets.py and the target compilation paths around the cited lines, then read puf_capital_gains_tail.py and docs/us-fact-to-target.md. Reproduce the current calibration diagnostics and pool-stage counts before changing the two proposed increments. Done means Table 1.1 size-of-AGI facts bind appropriately and the tail preserves the donor's full income vector, with diagnostics and tests showing the expected top-income distribution.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data-engineering
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.