Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/liheap-shared-inputs.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a shared person-level self-employment expense input and derive gross self-employment income from existing net income and reported business expenses other than cost of goods sold.
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
- name: Case 1, reconstruct business income without including wages or changing net profit.
period: 2026
input:
employment_income: 36_000
self_employment_income: 18_000
self_employment_expense: 6_000
output:
self_employment_gross_income: 24_000
self_employment_income: 18_000

- name: Case 2, business expenses cover regular, SSTB, and farm self-employment together.
period: 2026
input:
self_employment_income: 6_000
sstb_self_employment_income: 3_000
farm_operations_income: 5_000
self_employment_expense: 4_000
output:
self_employment_gross_income: 18_000

- name: Case 3, preserve a business loss when adding back its expenses.
period: 2026
input:
self_employment_income: -3_000
self_employment_expense: 12_000
output:
self_employment_gross_income: 9_000
self_employment_income: -3_000

- name: Case 4, omitted expenses use the ordinary zero default.
period: 2026
input:
self_employment_income: 12_000
output:
self_employment_gross_income: 12_000
self_employment_expense: 0

- name: Case 5, a loss without reported expenses cannot create negative gross income.
period: 2026
input:
self_employment_income: -3_000
output:
self_employment_gross_income: 0

- name: Case 6, a farm loss does not offset non-farm profit when expenses are omitted.
period: 2026
input:
self_employment_income: 6_000
farm_operations_income: -10_000
output:
# Non-farm floor 6,000 + farm floor 0; the reconstruction is -4,000.
self_employment_gross_income: 6_000
farm_operations_income: -10_000

- name: Case 7, employee work expenses do not belong in this business-cost input, which is added back unconditionally.
period: 2026
input:
employment_income: 36_000
self_employment_expense: 6_000
output:
self_employment_gross_income: 6_000

- name: Case 8, cost of goods sold stays out of the add-back, so the result is gross income after it.
period: 2026
input:
# Receipts 100,000 - cost of goods sold 60,000 = gross income 40,000.
# Gross income 40,000 - other expenses 25,000 = net profit 15,000.
self_employment_income: 15_000
self_employment_expense: 25_000
output:
self_employment_gross_income: 40_000

- name: Case 9, fully reported expenses reconstruct a farm loss alongside non-farm profit.
period: 2026
input:
self_employment_income: 6_000
farm_operations_income: -10_000
self_employment_expense: 12_000
output:
# 6,000 - 10,000 + 12,000 = 8,000, above the 6,000 per-source floor.
self_employment_gross_income: 8_000

- name: Case 10, partially reported expenses fall back to the per-source floor.
period: 2026
input:
self_employment_income: 6_000
farm_operations_income: -10_000
self_employment_expense: 3_000
output:
# The reconstruction is -1,000, below the 6,000 per-source floor.
self_employment_gross_income: 6_000

- name: Case 11, a regular business loss does not offset SSTB profit when expenses are omitted.
period: 2026
input:
self_employment_income: -4_000
sstb_self_employment_income: 9_000
output:
# Regular floor 0 + SSTB floor 9,000; the reconstruction is 5,000.
self_employment_gross_income: 9_000

- name: Case 12, reconstruct gross from the existing net-income chain without changing wages or the SE tax base.
period: 2026
absolute_error_margin: 0.01
input:
employment_income: 36_000
self_employment_income_before_lsr: 15_000
self_employment_expense: 25_000
output:
self_employment_gross_income: 40_000
self_employment_income: 15_000
employment_income: 36_000
# The tax base still uses net profit: 15,000 * 0.9235.
taxable_self_employment_income: 13_852.50

- name: Case 13, directly supplied gross income does not infer net profit or expenses.
period: 2026
input:
self_employment_gross_income: 40_000
output:
self_employment_gross_income: 40_000
self_employment_income: 0
sstb_self_employment_income: 0
farm_operations_income: 0
self_employment_expense: 0
taxable_self_employment_income: 0

- name: Case 14, partnership earnings remain separate from reconstructed sole-proprietor gross income.
period: 2026
absolute_error_margin: 0.01
input:
self_employment_income_before_lsr: 10_000
partnership_self_employment_net_earnings: 5_000
self_employment_expense: 3_000
output:
self_employment_gross_income: 13_000
partnership_self_employment_net_earnings: 5_000
# Partnership SE earnings remain in the tax base, not in the add-back.
taxable_self_employment_income: 13_852.50

- name: Case 15, expense reconstruction and loss floors operate independently for each person.
period: 2026
input:
people:
person1:
self_employment_income_before_lsr: 15_000
self_employment_expense: 25_000
person2:
sstb_self_employment_income_before_lsr: -3_000
self_employment_expense: 12_000
person3:
self_employment_income_before_lsr: 6_000
farm_operations_income: -10_000
self_employment_expense: 3_000
person4:
employment_income: 36_000
output:
self_employment_gross_income: [40_000, 9_000, 6_000, 0]
self_employment_income: [15_000, 0, 6_000, 0]
sstb_self_employment_income: [0, -3_000, 0, 0]
farm_operations_income: [0, 0, -10_000, 0]
employment_income: [0, 0, 0, 36_000]

- name: Case 16, annual net income and expenses convert consistently for monthly consumers.
period: 2026-07
input:
self_employment_income_before_lsr:
2026: 15_000
self_employment_expense:
2026: 21_000
output:
# Annual gross is 15,000 + 21,000 = 36,000; July receives one twelfth.
self_employment_gross_income: 3_000
self_employment_income: 1_250
self_employment_expense: 1_750
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from policyengine_us.model_api import *


class self_employment_expense(Variable):
value_type = float
entity = Person
definition_period = YEAR
unit = USD
label = "Self-employment business expenses"
documentation = (
"Business expenses already deducted from regular, SSTB, and farm net "
"self-employment income, excluding cost of goods sold: tax year 2025 "
"Schedule C lines 28 and 30, and Schedule F line 33. Excludes employee "
"work expenses and personal income and self-employment taxes. Defaults "
"to zero when not reported. Used to reconstruct gross income, not to "
"reduce reported net income again or determine program-allowable costs."
)
reference = (
"https://www.irs.gov/pub/irs-prior/f1040sc--2025.pdf#page=1",
"https://www.irs.gov/pub/irs-prior/f1040sf--2025.pdf#page=1",
"https://www.irs.gov/pub/irs-prior/f1040sf--2025.pdf#page=2",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from policyengine_us.model_api import *


class self_employment_gross_income(Variable):
value_type = float
entity = Person
definition_period = YEAR
unit = USD
label = "Gross self-employment income"
documentation = (
"Gross income after cost of goods sold and before other business "
"expenses (tax year 2025 Schedule C line 7 and Schedule F line 9), "
"reconstructed from regular, SSTB, and farm net income plus "
"self_employment_expense. Excludes separately reported partnership "
"earnings. The result is floored at the sum of the three net sources "
"individually floored at zero; this modeling fallback can differ from "
"tax-reported gross income and is not an IRS rule. Non-farm net income "
"includes labor-supply responses."
)
reference = (
"https://www.irs.gov/pub/irs-prior/f1040sc--2025.pdf#page=1",
"https://www.irs.gov/pub/irs-prior/f1040sf--2025.pdf#page=1",
"https://www.irs.gov/pub/irs-prior/f1040sf--2025.pdf#page=2",
)

def formula(person, period, parameters):
net_income_sources = [
"self_employment_income",
"sstb_self_employment_income",
"farm_operations_income",
]
# Add back the expenses deducted from net profit, preserving losses
# until after the add-back.
net_income = add(person, period, net_income_sources)
expenses = person("self_employment_expense", period)
reconstructed = net_income + expenses
# Apply the modeling fallback separately to each net-income source.
source_floor = 0
for source in net_income_sources:
source_floor = source_floor + max_(person(source, period), 0)
return max_(reconstructed, source_floor)
Loading