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
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:
- The donor has the tail. The processed PUF donor sits close to SOI in both top bands.
- 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.
- 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. - The certified build predates the tail stage.
puf_capital_gains_tail.pyfirst landed on 2026-07-28 (66609b086, #568). Build P's commitcae8640does not contain it, and the certified file has no clone-index-2 rows. - 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. - 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-3168refuses 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_10mand10m_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_1mand1m_plus. The filing-season EITC rows for those bands carry zero targets. In Build P the interest rows finish at +159% on amount for500k_to_1m, and at +65% on returns and −39% on amount for1m_plus. - The solver hard-clamps weights at 5x their initial value (
calibration.yamlmax_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
- 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. - 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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