Skip to content

Document and test the pbusinc self-employment tax difference from the bundled TAXSIM-35 build - #1421

Draft
MaxGhenis wants to merge 9 commits into
mainfrom
pbusinc-seca-treatment
Draft

MaxGhenis wants to merge 9 commits into
mainfrom
pbusinc-seca-treatment

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The emulator and the bundled TAXSIM-35 executable disagree on pbusinc/sbusinc (active qualified business income). The emulator applies self-employment tax to it. The bundled builds (cd2026081819 for macOS and Linux, cd2026081318 for Windows) do not, and apply the net investment income tax instead.

This PR does not change the mapping. It documents the difference accurately, pins both sides in tests, and links the open question. The question is already with TAXSIM's author on #1254, and neither NBER's documentation nor his reply there settles it (details below).

Stacked on #1419, which adds the two documents this PR edits. Until #1419 merges, the diff here includes its commits; only the last two commits are this PR.

What was checked

Engine output is treated as a hypothesis here, not as evidence of intent. The documentary sources are NBER's input pages and the author's public replies.

NBER's input page lists SECA, and now NIIT too; one of its notes points the other way

NBER, "Internet TAXSIM Test Version", https://taxsim.nber.org/taxsimtest/, section "Incomes", under "The following are for the TCJA Business Tax Deduction. but are ordinary income 1960-2017." Retrieved 2026-10-10 00:56 UTC; page footer "Date last modified: 28 February 2026"; HTTP Last-Modified: Fri, 04 Sep 2026 13:29:27 GMT:

  1. psemp and ssemp Self-employment income of Primary and secondary taxpayers not eligible as Qualified Business Income (QBI). Amount before SECA (FICA) but subject to SECA. See below for other QBI.
  1. scorp Passive business income not subject to FICA, SSTB, SECA or QBID phaseout but subject to the passive loss limitation.
  1. pbusinc and sbusinc Primary and secondary Taxpayer's active income eligible for the QBI deduction without phaseout and assuming sufficient wages paid or capital to be eligible for the full deduction. Subject to NIIT, SECA and Medicare additional Earnings Tax.

The Internet Archive's capture of the same URL dated 2026-04-17 (footer "Date last modified: 2 April 2025") has an earlier wording of the same items:

  1. scorp S-Corp and other passive business income not subject to SSTB, SECA or QBID phaseout.
  1. pbusinc and sbusinc Primary and secondary Taxpayer's active QBI subject to a preferential rate without phaseout and assuming sufficient wages paid or capital to be eligible for the full deduction. Subject to SECA and Medicare additional Earnings Tax.

So the page was revised: "NIIT" was added to item 26 and SECA was kept. The capture date does not date the revision (the footer and the HTTP header disagree with each other too).

The same page has a second footer, "Date last modified: August 23, 2024", after its "Random Notes" section, which says:

The FICA estimate includes both employee and employer tax. Half of FICA is paid by the employer, so the variable TFICA is supplied with the employee share. The values you supply for psemp and ssemp should be pre-FICA while the vaues you supply for the other QBI eligible amounts should be post-FICA, consistent with the information returns supplied to taxpayers.

That note reads toward no SECA on pbusinc. But it is in both Internet Archive captures (2025-08-21 and 2026-04-17), from when the bundled builds applied SECA to pbusinc, and it also covers pprofinc, which every bundled build taxes. So the page does not settle the question on its own.

NBER's production page, "Internet TAXSIM Version 35", https://taxsim.nber.org/taxsim35/, retrieved the same minute; footer "Date last modified: May 20, 2022"; HTTP Last-Modified: Thu, 03 Sep 2026 15:22:47 GMT:

  1. pbusinc Primary Taxpayer's Qualified Business Income (QBI) subject to a preferential rate without phaseout and assuming sufficient wages paid or capital to be eligible for the full deduction. Subject to SECA and Medicare additional Earnings Tax.

The three taxes item 26 now lists cannot all fall on active business income. 26 U.S.C. § 1411, subsection (c) "Net investment income" (LII, retrieved 2026-10-10). Paragraph (1)(A)(ii) counts:

other gross income derived from a trade or business described in paragraph (2)

and paragraph (2), "Trades and businesses to which tax applies":

A trade or business is described in this paragraph if such trade or business is— (A) a passive activity (within the meaning of section 469) with respect to the taxpayer, or (B) a trade or business of trading in financial instruments or commodities (as defined in section 475(e)(2)).

Paragraph (6), "Special rule":

Net investment income shall not include any item taken into account in determining self-employment income for such taxable year on which a tax is imposed by section 1401(b).

So NIIT on income labelled "active" conflicts with (c)(2) whether or not SECA applies, and SECA would rule it out again under (c)(6). The emulator matches the SECA and Additional Medicare half of item 26. The bundled builds match the NIIT half.

The executable changed between the May and August 2026 builds

Every macOS build this repository has bundled that still runs, plus the September build in #1206, on one record: single, age 40, California, 2023, $100,000 of pbusinc, idtl 2.

Build Bundled by fiitax fica v10 qbid
cdate-2025Jul5 d6c3c9b 9,226.50 14,129.55 92,935.23 15,817.05
cdate-2025Aug23 6f685aa 9,226.50 14,129.55 92,935.23 15,817.05
cdate-20260521 #928 9,226.50 14,129.55 92,935.23 15,817.05
cd2026081819 (bundled now) #1150 10,469.90 0.00 100,000.00 17,230.00
cd2026090910 #1206 (draft) 10,469.90 0.00 100,000.00 17,230.00

In the first three, the pbusinc row equals the psemp row in these columns (v30 differs). In the last two it equals the scorp row in every column. #1254 reports the same result for cd2026092410 and cd2026092717, which were not re-run here.

In cd2026081819 and cd2026090910 the result holds for 2021 through 2025, for $1,000 to $1 million, and for sbusinc on joint returns (70 records each, none with SECA; in cdate-20260521 all 70 bear it). Above the threshold the build charges NIIT and no Additional Medicare Tax (single, 2024, $300,000: niit 3,800.00, addmed 0.00, against 0.00 and 693.45 for psemp).

pbusinc is not scorp under another name, though. On 4,000 random records (2021 to 2025, all states, single and joint, with wages, interest and gains), replacing pbusinc + sbusinc with the same total in scorp left fica, tfica, addmed, AGI and the EITC unchanged on every record, but changed some other output on 294 of them: state tax alone on 190 (in 20 states), and federal amounts on 104, of which 90 are joint returns with positive pbusinc and negative sbusinc, or the reverse.

Reproduce:

printf "taxsimid year state mstat page psemp pbusinc scorp idtl\n1 2023 5 1 40 100000 0 0 2\n2 2023 5 1 40 0 100000 0 2\n3 2023 5 1 40 0 0 100000 2\n" | resources/taxsimtest/taxsimtest-osx.exe

The bundled Linux build has the same stamp as macOS (cd2026081819); the bundled Windows build is older (cd2026081318). Neither was run locally (the Docker VM was not running, and there is no Windows machine). CI checked both: on this PR's first head (63087ce), the test (ubuntu-latest, 3.10) and test (windows-latest, 3.10) jobs ran tests/test_pbusinc_self_employment_tax.py and passed, so all three bundled builds give the pinned figures to the cent.

The author's replies

  • FICA and QBI income #384, 2026-03-28: a proposed table with pbusinc as QBI, not passive, SECA/FICA "yes".

  • Map pass-through inputs to QBID-bearing PolicyEngine variables #1051 (merged 2026-07-05) records his June 2026 comparison, in which TAXSIM-35 gave pbusinc the psemp result, and mapped pbusinc accordingly.

  • Does taxsimtest skip SECA on pbusinc? #1254 (opened 2026-09-29) reports the missing SECA with NBER's sentence. His reply the same day:

    I wanted to have a discussion with you before starting to modify code. If businc is subject to FICA, and scorp is subject to EIC (and therefore subject to SECA) and semp is subject to SECA (as it has been since the start of Taxsim) then there is no passthru that is free of SECA, which then means that there is no place in taxsim to put pure profits, such as real estate, manufacturing, etc. This also affects eic, addmed and niit.

    Let's have a real discussion about the treatment of all relevant 7 variables on Tuesday rather than opening an issue for each one here. What variables does PE have? Does PE have some pass-thru variable not subject to SECA?

    Nobody has answered those two questions on the issue yet. PolicyEngine does have one: scorp maps to partnership_s_corp_income, which bears no SECA.

  • Should the emulator assume sufficient W-2 wages for the QBI deduction, as TAXSIM does? #1404, 2026-10-07: "rather than bringing up each of these provisions as a separate issue, we should discuss them all together".

Conclusion

The author's reply indicates the builds' treatment is intentional, and he has a reason for it. But it is not settled: he asked to decide all seven pass-through inputs together, NBER's item 26 still lists SECA, and NIIT on active income conflicts with § 1411(c)(2). So the emulator keeps item 26's SECA for now, and the difference stays listed as a known difference with a test on each side.

Users who want the bundled builds' result can enter the amount as scorp: for the pinned records the emulator's scorp row (passive, the default from policyengine-us 2.10.1) equals the builds' pbusinc row to the cent, and a test pins that. The design document says so.

No new issue was opened, because #1254 already carries the report and the author asked for no more per-variable issues.

Changes

  • tests/test_pbusinc_self_employment_tax.py (new):
    • the emulator gives pbusinc the result taxsimtest gives psemp (expected values from the bundled binary's psemp rows);
    • the emulator's scorp row equals taxsimtest's pbusinc row on the pinned records (a differential test against the live binary);
    • test_taxsimtest_* pin the bundled binary: SECA and Additional Medicare on psemp and pprofinc; none on pbusinc, with NIIT; the pbusinc row equals the scorp row on the pinned records; no SECA on pbusinc/sbusinc for 2021 to 2025 and $1,000 to $1 million. A refreshed build that changes this fails with a message naming what to revisit;
    • the design document quotes the pinned figures and the scorp alternative.
  • docs/design.md: the "Active business income" entry under "Known differences from TAXSIM-35" now says what each engine does, quotes item 26 and the post-FICA note, gives the scorp alternative, and links Does taxsimtest skip SECA on pbusinc? #1254.
  • docs/reference/input_variables.yaml and the regenerated docs/input-guide.md: the pbusinc entry, which now also says a pbusinc loss offsets psemp.
  • tests/test_passthrough_qbid.py: a docstring note that its TAXSIM-35 figures are from June 2026.

Invariants

  • For every year from 2021 to 2025, filing status, age, state and dependant count, and any whole-dollar amounts, moving pbusinc into psemp and sbusinc into ssemp leaves every input of the dataset the emulator builds unchanged (test_pbusinc_is_interchangeable_with_psemp, Hypothesis). The simulation-stage overrides in _run_chunk do not read pbusinc, so this covers the whole emulator path.
  • Differential: the per-record path (generate_household) and the Microsimulation runner both put psemp + pbusinc on the primary taxpayer and ssemp + sbusinc on the spouse (test_single_household_path_sums_them_like_the_runner, Hypothesis).

No counterexamples were found. The new module passes locally on Python 3.11 (policyengine-us 2.38.0) and 3.10 (policyengine-us 1.783.0).

Review

Independent review (Opus 5.5 on a Subfleet lane, as the Codex lanes were closed) on 63087ce: changes requested. Addressed in the second commit: the post-FICA note and § 1411(c)(1)-(2) quoted, the #1254 reply quoted in full, the source page and retrieval date named, "in these columns" where rows were not identical, the Windows docstring claim removed (CI showed all three builds agree), a pprofinc pin, the scorp differential test, years sampled in the invariant, and float32-aware tolerances.

Impact

No change to any emulator output.

axiom: n/a: emulator documentation and tests only.

🤖 Generated with Claude Code

MaxGhenis and others added 6 commits October 9, 2026 19:49
Three guides under docs/, asked for at the 2026-10-08 BLS Consumer Expenditure
seminar. Their reference tables are generated from the code by
scripts/generate_docs_reference.py, and tests fail when they drift.

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

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

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
policyengine-taxsim Ready Ready Preview Oct 10, 2026 1:50am UTC

Request Review

MaxGhenis and others added 3 commits October 9, 2026 21:23
- Temporary files: deletion is attempted, not guaranteed; test leftovers.
- Pre-2021 records: state checks do not apply, state -1 returns a wrong
  answer, idtl 5 does not work, marginal rates are 0.
- Maryland: the bundled TAXSIM build includes county tax.
- tfica includes employee state payroll taxes on PolicyEngine rows.
- Linux TAXSIM build needs glibc 2.34 (generated from the binary).
- Examples: do not round identifiers; Stata stores IDs as double and checks
  inputs before running.
- Say what the network test covers and what it cannot see.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The emulator applies self-employment tax to pbusinc and sbusinc, as
NBER's input documentation describes. The bundled taxsimtest build
(cd2026081819) does not, and applies the net investment income tax
instead. Taxsim #1254 asks TAXSIM's author which is intended.

- tests/test_pbusinc_self_employment_tax.py pins both treatments, with
  expected values from the bundled binary, and adds property tests that
  pbusinc is interchangeable with psemp in both input paths.
- docs/design.md and the pbusinc entry of the input reference say what
  each engine does and link the open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Quote the taxsimtest page's post-FICA note and 26 U.S.C. 1411(c)(1)-(2)
  next to item 26, name the page and retrieval date, and say the page
  does not settle the question on its own.
- Say the bundled Windows build (cd2026081318) agrees; CI ran the binary
  pins on all three platforms.
- Pin self-employment tax on pprofinc in the bundled builds, and add a
  differential test that the emulator's scorp row equals the builds'
  pbusinc row, which the design document now offers as the way to get
  their result.
- Sample years in the interchangeability property, and use tolerances
  that allow for the emulator's float32 outputs.

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

This branch was successfully deployed

1 active deployment
Preview — 4486fdea Deployed Oct 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