Skip to content

Add SSTB self-employment income variable and split QBID by category#7944

Open
PavelMakarchuk wants to merge 5 commits intomainfrom
add-sstb-self-employment-income
Open

Add SSTB self-employment income variable and split QBID by category#7944
PavelMakarchuk wants to merge 5 commits intomainfrom
add-sstb-self-employment-income

Conversation

@PavelMakarchuk
Copy link
Copy Markdown
Collaborator

Summary

Closes #7939.

Adds a new sstb_self_employment_income input variable (Person, USD) and refactors the qualified business income deduction so that the §199A(d)(3) phaseout above the §199A(e)(2) threshold reduces only the SSTB component, leaving the non-SSTB component subject to the W-2/UBIA cap as before. This fixes the mixed-filer case (e.g., a doctor who also owns a rental property) where the legacy business_is_sstb boolean forced an all-or-nothing treatment.

Changes

New variables

  • sstb_self_employment_income (Person, USD) — SE income from a specified service trade or business under IRC §199A(d)(2). Subject to SECA, treated separately for QBID.
  • sstb_self_employment_income_would_be_qualified (Person, bool, default True) — parallels the existing <source>_would_be_qualified flags.
  • sstb_qualified_business_income (Person, USD) — SSTB QBI before the §199A(d)(3) phaseout, with QBI deductions pro-rated by gross-income share.

Modified variables

  • qualified_business_income — now excludes SSTB SE income from gross QBI and pro-rates the existing QBI deductions by non-SSTB share.
  • qbid_amount — computes the §199A(b)(2) per-business limit separately for the non-SSTB and SSTB categories before summing them with the REIT/PTP component. The legacy business_is_sstb flag is preserved for backward compatibility by routing the legacy QBI through the SSTB component when set.
  • taxable_self_employment_income, earned_income, market_income, and the IRS gross_income/sources parameter — add sstb_self_employment_income so it flows through SECA, AGI, and downstream income concepts in parallel with self_employment_income.

Notes on the issue's example tests

The integration tests in #7939 use round numbers ($20,000 and $10,000) that assume QBI is not reduced by the SE-tax / health-insurance / pension ALDs. PolicyEngine's existing qualified_business_income formula does subtract those ALDs (per IRS Publication 535 Worksheet 12-A), so the new tests in this PR follow the same approach: I include the issue's two scenarios verbatim but zero-out the QBI ALD inputs so the expected values isolate the SSTB-vs-non-SSTB routing rather than the deduction pro-rating.

Test plan

  • policyengine_us/tests/policy/baseline/gov/irs — 725 tests pass
  • policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/qbid — 34 QBID tests pass (24 existing + 10 new)
  • policyengine_us/tests/policy/baseline/gov/irs/self_employment — 11 SECA tests pass (10 existing + 1 new)
  • policyengine_us/tests/policy/baseline/gov/states/ca — 520 California tests pass
  • policyengine_us/tests/policy/baseline/gov/usda — 384 USDA tests pass
  • policyengine_us/tests/policy/baseline/gov/hhs — 386 HHS tests pass
  • make format clean

🤖 Generated with Claude Code

…ation

Per IRC §199A(d), a specified service trade or business (SSTB) is treated
differently from non-SSTB qualified businesses for the §199A deduction:
above the §199A(e)(2) threshold, the SSTB component phases to zero while
the non-SSTB component still receives the W-2/UBIA capped deduction. The
existing PolicyEngine model used a single per-person `business_is_sstb`
flag, which forced an all-or-nothing treatment and could not represent
mixed filers (e.g., a doctor with rental property).

This change introduces a new `sstb_self_employment_income` input
variable, splits qualified business income into non-SSTB
(`qualified_business_income`) and SSTB (`sstb_qualified_business_income`)
components with QBI deductions pro-rated by gross-income share, and
refactors `qbid_amount` to compute the §199A(b)(2) per-business limit
separately for each category before summing. The legacy
`business_is_sstb` flag is preserved for backward compatibility by
routing the legacy QBI through the SSTB component when set.

`sstb_self_employment_income` is also added to SECA gross income, IRS
gross income, market income, and earned income so it flows through the
rest of the tax model parallel to `self_employment_income`.

Closes #7939

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.09%. Comparing base (7b50e5d) to head (44d3d9d).
⚠️ Report is 85 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##              main    #7944      +/-   ##
===========================================
- Coverage   100.00%   99.09%   -0.91%     
===========================================
  Files            2       14      +12     
  Lines           42      222     +180     
  Branches         2        3       +1     
===========================================
+ Hits            42      220     +178     
- Misses           0        2       +2     
Flag Coverage Δ
unittests 99.09% <100.00%> (-0.91%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@MaxGhenis MaxGhenis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for one blocking regression.

  • sstb_self_employment_income is added to person-level market_income, federal gross income, earned income, and SE tax, but not to gov.household.market_income_sources, which drives household_market_income and then household_net_income. Repro on this branch: a one-person situation with sstb_self_employment_income = 100000 returns market_income = 100000, household_market_income = 0, and household_net_income = -23082.287109375 because the income is omitted from household resources while tax liabilities are still included. Please add it to household market income sources, and audit the other parameterized income-source lists that currently count self_employment_income if SSTB income should remain equivalent outside the QBI calculation.

Verified locally: uv run python -m policyengine_core.scripts.policyengine_command test policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/qbid policyengine_us/tests/policy/baseline/gov/irs/self_employment/taxable_self_employment_income.yaml policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_business_income_deduction.yaml policyengine_us/tests/policy/contrib/taxsim/outputs/taxsim_outputs.yaml -c policyengine_us passed: 71 tests.

Copy link
Copy Markdown
Contributor

@MaxGhenis MaxGhenis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after pushing the household market-income fix. Local verification: the household market income fixture passes, and the prior reproduction now returns household_market_income = 100000.

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.

QBID: add SSTB income variable and per-category computation per IRC §199A

2 participants