From 3f23308a7b64e2f9cea44ede465c03a9608f9a21 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 28 Sep 2026 16:46:02 -0400 Subject: [PATCH 1/8] Add shared work expense, gross self-employment, and dwelling inputs --- changelog.d/liheap-shared-inputs.added.md | 1 + .../expense/housing/dwelling_type.py | 21 +++++++++++++++++++ .../household/expense/person/work_expense.py | 9 ++++++++ .../self_employment_gross_income.py | 16 ++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 changelog.d/liheap-shared-inputs.added.md create mode 100644 policyengine_us/variables/household/expense/housing/dwelling_type.py create mode 100644 policyengine_us/variables/household/expense/person/work_expense.py create mode 100644 policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py diff --git a/changelog.d/liheap-shared-inputs.added.md b/changelog.d/liheap-shared-inputs.added.md new file mode 100644 index 00000000000..d5b2982c9ab --- /dev/null +++ b/changelog.d/liheap-shared-inputs.added.md @@ -0,0 +1 @@ +Add shared inputs for person-level work expenses and gross self-employment receipts, and household dwelling type. diff --git a/policyengine_us/variables/household/expense/housing/dwelling_type.py b/policyengine_us/variables/household/expense/housing/dwelling_type.py new file mode 100644 index 00000000000..d52faf3b21b --- /dev/null +++ b/policyengine_us/variables/household/expense/housing/dwelling_type.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class DwellingType(Enum): + HOUSE = "House, including modular homes" + DUPLEX = "Duplex" + APARTMENT = "Apartment" + MOBILE_HOME = "Mobile home" + TRAILER = "Trailer" + OTHER = "Other dwelling" + UNSPECIFIED = "Unspecified" + + +class dwelling_type(Variable): + value_type = Enum + entity = Household + possible_values = DwellingType + default_value = DwellingType.UNSPECIFIED + definition_period = YEAR + label = "Dwelling type" + reference = "https://data.census.gov/table/ACSDT1Y2024.B25024" diff --git a/policyengine_us/variables/household/expense/person/work_expense.py b/policyengine_us/variables/household/expense/person/work_expense.py new file mode 100644 index 00000000000..03a885e3201 --- /dev/null +++ b/policyengine_us/variables/household/expense/person/work_expense.py @@ -0,0 +1,9 @@ +from policyengine_us.model_api import * + + +class work_expense(Variable): + value_type = float + entity = Person + definition_period = YEAR + unit = USD + label = "Work expenses" diff --git a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py new file mode 100644 index 00000000000..aa7ef8359b8 --- /dev/null +++ b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class self_employment_gross_income(Variable): + value_type = float + entity = Person + definition_period = YEAR + unit = USD + # Receipts before business expenses, including farm and non-farm businesses. + # Missing receipts must be distinguishable from an explicitly reported zero. + default_value = -1 + label = "Gross self-employment receipts" + reference = ( + "https://www.irs.gov/instructions/i1040sc", + "https://www.irs.gov/instructions/i1040sf", + ) From 08b6a0b3482c80b9242257ec92e850a518e13ef5 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 28 Sep 2026 16:51:17 -0400 Subject: [PATCH 2/8] Use zero default for gross self-employment input --- .../person/self_employment/self_employment_gross_income.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py index aa7ef8359b8..83441f791a4 100644 --- a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py +++ b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py @@ -7,8 +7,6 @@ class self_employment_gross_income(Variable): definition_period = YEAR unit = USD # Receipts before business expenses, including farm and non-farm businesses. - # Missing receipts must be distinguishable from an explicitly reported zero. - default_value = -1 label = "Gross self-employment receipts" reference = ( "https://www.irs.gov/instructions/i1040sc", From 8a131c0a200241d8eba4e668411d61269c209d71 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 28 Sep 2026 19:09:57 -0400 Subject: [PATCH 3/8] Derive shared self-employment income and keep dwelling types state-specific --- changelog.d/liheap-shared-inputs.added.md | 2 +- .../self_employment_gross_income.yaml | 43 +++++++++++++++++++ .../expense/housing/dwelling_type.py | 21 --------- .../household/expense/person/work_expense.py | 3 ++ .../self_employment_gross_income.py | 18 +++++++- 5 files changed, 63 insertions(+), 24 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml delete mode 100644 policyengine_us/variables/household/expense/housing/dwelling_type.py diff --git a/changelog.d/liheap-shared-inputs.added.md b/changelog.d/liheap-shared-inputs.added.md index d5b2982c9ab..0699f4506f3 100644 --- a/changelog.d/liheap-shared-inputs.added.md +++ b/changelog.d/liheap-shared-inputs.added.md @@ -1 +1 @@ -Add shared inputs for person-level work expenses and gross self-employment receipts, and household dwelling type. +Add a shared person-level work expense input and derive gross self-employment income from existing net income and reported business expenses. diff --git a/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml b/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml new file mode 100644 index 00000000000..da15f4ebfae --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml @@ -0,0 +1,43 @@ +- 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 + work_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 + work_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 + work_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 + work_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 diff --git a/policyengine_us/variables/household/expense/housing/dwelling_type.py b/policyengine_us/variables/household/expense/housing/dwelling_type.py deleted file mode 100644 index d52faf3b21b..00000000000 --- a/policyengine_us/variables/household/expense/housing/dwelling_type.py +++ /dev/null @@ -1,21 +0,0 @@ -from policyengine_us.model_api import * - - -class DwellingType(Enum): - HOUSE = "House, including modular homes" - DUPLEX = "Duplex" - APARTMENT = "Apartment" - MOBILE_HOME = "Mobile home" - TRAILER = "Trailer" - OTHER = "Other dwelling" - UNSPECIFIED = "Unspecified" - - -class dwelling_type(Variable): - value_type = Enum - entity = Household - possible_values = DwellingType - default_value = DwellingType.UNSPECIFIED - definition_period = YEAR - label = "Dwelling type" - reference = "https://data.census.gov/table/ACSDT1Y2024.B25024" diff --git a/policyengine_us/variables/household/expense/person/work_expense.py b/policyengine_us/variables/household/expense/person/work_expense.py index 03a885e3201..3673b6efae1 100644 --- a/policyengine_us/variables/household/expense/person/work_expense.py +++ b/policyengine_us/variables/household/expense/person/work_expense.py @@ -7,3 +7,6 @@ class work_expense(Variable): definition_period = YEAR unit = USD label = "Work expenses" + # For self-employment, report the business expenses already deducted from + # the corresponding net income, across farm and non-farm businesses. + # Exclude employee expenses and personal taxes from this business amount. diff --git a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py index 83441f791a4..55c5160fa65 100644 --- a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py +++ b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py @@ -6,9 +6,23 @@ class self_employment_gross_income(Variable): entity = Person definition_period = YEAR unit = USD - # Receipts before business expenses, including farm and non-farm businesses. - label = "Gross self-employment receipts" + label = "Gross self-employment income" reference = ( "https://www.irs.gov/instructions/i1040sc", "https://www.irs.gov/instructions/i1040sf", ) + + def formula(person, period, parameters): + # Add back the same business expenses deducted from the reported net + # income. Preserve losses until after expenses have been added back. + # With no reported expenses, this assumes expenses are zero. + gross = add( + person, + period, + [ + "total_self_employment_income", + "farm_operations_income", + "work_expense", + ], + ) + return max_(gross, 0) From 504a7b2d4d8ba1729d50c9efcb04ee06cbf9f386 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 28 Sep 2026 21:20:16 -0400 Subject: [PATCH 4/8] Fix issues from review: document work expense and gross self-employment income, add boundary tests Move the work_expense definition into documentation metadata and label it as self-employment work expenses; state that the input includes cost of goods sold so self_employment_gross_income reconstructs gross receipts; document the covered sources, the combined zero floor, the SNAP input relationship and the under-reporting bias; add IRS form references; append three YAML cases (mixed-sign floor, expenses without a business, cost of goods sold). Co-Authored-By: Claude Fable 5.1 --- .../self_employment_gross_income.yaml | 25 +++++++++++++++++++ .../household/expense/person/work_expense.py | 16 +++++++++--- .../self_employment_gross_income.py | 19 ++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml b/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml index da15f4ebfae..9d18b1ddf64 100644 --- a/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml @@ -41,3 +41,28 @@ self_employment_income: -3_000 output: self_employment_gross_income: 0 + +- name: Case 6, a farm loss offsets non-farm profit under the combined zero floor. + period: 2026 + input: + self_employment_income: 6_000 + farm_operations_income: -10_000 + output: + self_employment_gross_income: 0 + 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 + work_expense: 6_000 + output: + self_employment_gross_income: 6_000 + +- name: Case 8, cost of goods sold is part of the add-back so the result is gross receipts. + period: 2026 + input: + self_employment_income: 15_000 + work_expense: 85_000 + output: + self_employment_gross_income: 100_000 diff --git a/policyengine_us/variables/household/expense/person/work_expense.py b/policyengine_us/variables/household/expense/person/work_expense.py index 3673b6efae1..0bda29ce11e 100644 --- a/policyengine_us/variables/household/expense/person/work_expense.py +++ b/policyengine_us/variables/household/expense/person/work_expense.py @@ -6,7 +6,15 @@ class work_expense(Variable): entity = Person definition_period = YEAR unit = USD - label = "Work expenses" - # For self-employment, report the business expenses already deducted from - # the corresponding net income, across farm and non-farm businesses. - # Exclude employee expenses and personal taxes from this business amount. + label = "Self-employment work expenses" + documentation = ( + "Self-employment business costs already deducted in arriving at the " + "reported net self-employment income, combined across all of the " + "person's farm and non-farm businesses. Include every cost deducted on " + "Schedule C (Part II expenses, the line 30 business use of home, and " + "Part III cost of goods sold) and on Schedule F (Part II expenses and " + "the line 1b cost or other basis of resale items), so that net income " + "plus this input equals gross receipts. Exclude employee work-related " + "expenses such as commuting, uniforms, and tools, and exclude personal " + "income and self-employment taxes. Treated as zero when not reported." + ) diff --git a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py index 55c5160fa65..6e001df6e99 100644 --- a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py +++ b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py @@ -7,9 +7,28 @@ class self_employment_gross_income(Variable): definition_period = YEAR unit = USD label = "Gross self-employment income" + documentation = ( + "Gross self-employment receipts, reconstructed as net Schedule C " + "(regular and SSTB) and Schedule F profit plus work_expense, the " + "business costs already deducted in arriving at that net profit. Covers " + "sole-proprietor Schedule C and Schedule F businesses only; " + "partnership_self_employment_net_earnings is excluded by design because " + "partnership expenses are deducted on Form 1065 and that variable is not " + "an additional gross source, so consumers needing partnership earnings " + "add it separately. The zero floor applies to the combined farm plus " + "non-farm sum. work_expense is the person-level counterpart of the " + "SPM-unit SNAP input snap_self_employment_income_expense and is not " + "derived from it. Omitted expenses are assumed to be zero, which biases " + "the result downward and zeroes business losses; with no data source for " + "work_expense, microsimulation yields max(net, 0). This variable reads " + "total_self_employment_income, so it is post-labor-supply-response, " + "unlike the pre-response snap_gross_self_employment_income_person." + ) reference = ( "https://www.irs.gov/instructions/i1040sc", "https://www.irs.gov/instructions/i1040sf", + "https://www.irs.gov/pub/irs-pdf/f1040sc.pdf", + "https://www.irs.gov/pub/irs-pdf/f1040sf.pdf", ) def formula(person, period, parameters): From afd9c94a7af6a5e9e1c04c135529fbaec92f4673 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 28 Sep 2026 23:59:00 -0400 Subject: [PATCH 5/8] Exclude cost of goods sold from self-employment expenses and floor gross income per source Rename work_expense to self_employment_expense so the input does not read as the employee work-expense deduction used across TANF programs. Define it as business expenses other than cost of goods sold, so net profit plus the input equals Schedule C line 7 and Schedule F line 9 gross income. Floor gross self-employment income at the sum of each source's net profit floored at zero, so a loss in one source does not offset another when expenses are omitted. Update the tests for both changes and add cases for complete expenses, partial expenses, and a regular loss alongside SSTB profit. Co-Authored-By: Claude Fable 5.1 --- changelog.d/liheap-shared-inputs.added.md | 2 +- .../self_employment_gross_income.yaml | 52 +++++++++++++---- .../expense/person/self_employment_expense.py | 27 +++++++++ .../household/expense/person/work_expense.py | 20 ------- .../self_employment_gross_income.py | 56 +++++++++---------- 5 files changed, 96 insertions(+), 61 deletions(-) create mode 100644 policyengine_us/variables/household/expense/person/self_employment_expense.py delete mode 100644 policyengine_us/variables/household/expense/person/work_expense.py diff --git a/changelog.d/liheap-shared-inputs.added.md b/changelog.d/liheap-shared-inputs.added.md index 0699f4506f3..8c905719109 100644 --- a/changelog.d/liheap-shared-inputs.added.md +++ b/changelog.d/liheap-shared-inputs.added.md @@ -1 +1 @@ -Add a shared person-level work expense input and derive gross self-employment income from existing net income and reported business expenses. +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. diff --git a/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml b/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml index 9d18b1ddf64..ba845cf97f6 100644 --- a/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml @@ -3,7 +3,7 @@ input: employment_income: 36_000 self_employment_income: 18_000 - work_expense: 6_000 + self_employment_expense: 6_000 output: self_employment_gross_income: 24_000 self_employment_income: 18_000 @@ -14,7 +14,7 @@ self_employment_income: 6_000 sstb_self_employment_income: 3_000 farm_operations_income: 5_000 - work_expense: 4_000 + self_employment_expense: 4_000 output: self_employment_gross_income: 18_000 @@ -22,7 +22,7 @@ period: 2026 input: self_employment_income: -3_000 - work_expense: 12_000 + self_employment_expense: 12_000 output: self_employment_gross_income: 9_000 self_employment_income: -3_000 @@ -33,7 +33,7 @@ self_employment_income: 12_000 output: self_employment_gross_income: 12_000 - work_expense: 0 + self_employment_expense: 0 - name: Case 5, a loss without reported expenses cannot create negative gross income. period: 2026 @@ -42,27 +42,59 @@ output: self_employment_gross_income: 0 -- name: Case 6, a farm loss offsets non-farm profit under the combined zero floor. +- 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: - self_employment_gross_income: 0 + # 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 - work_expense: 6_000 + self_employment_expense: 6_000 output: self_employment_gross_income: 6_000 -- name: Case 8, cost of goods sold is part of the add-back so the result is gross receipts. +- 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 - work_expense: 85_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: - self_employment_gross_income: 100_000 + # Regular floor 0 + SSTB floor 9,000; the reconstruction is 5,000. + self_employment_gross_income: 9_000 diff --git a/policyengine_us/variables/household/expense/person/self_employment_expense.py b/policyengine_us/variables/household/expense/person/self_employment_expense.py new file mode 100644 index 00000000000..fdc546d5135 --- /dev/null +++ b/policyengine_us/variables/household/expense/person/self_employment_expense.py @@ -0,0 +1,27 @@ +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, other than cost of goods sold, deducted in arriving " + "at the person's reported net self-employment income, combined across " + "the person's farm and non-farm sole proprietorships: Schedule C line " + "28 total expenses plus line 30 business use of the home, and Schedule " + "F line 33 total expenses. Exclude cost of goods sold (Schedule C line " + "4; Schedule F line 1b, or Part III line 49 for accrual-method farms), " + "so that net profit plus this input equals Schedule C line 7 and " + "Schedule F line 9 gross income. Exclude employee work-related " + "expenses and personal income and self-employment taxes. Treated as " + "zero when not reported." + ) + reference = ( + "https://www.irs.gov/instructions/i1040sc", + "https://www.irs.gov/instructions/i1040sf", + "https://www.irs.gov/pub/irs-pdf/f1040sc.pdf#page=1", + "https://www.irs.gov/pub/irs-pdf/f1040sf.pdf#page=1", + ) diff --git a/policyengine_us/variables/household/expense/person/work_expense.py b/policyengine_us/variables/household/expense/person/work_expense.py deleted file mode 100644 index 0bda29ce11e..00000000000 --- a/policyengine_us/variables/household/expense/person/work_expense.py +++ /dev/null @@ -1,20 +0,0 @@ -from policyengine_us.model_api import * - - -class work_expense(Variable): - value_type = float - entity = Person - definition_period = YEAR - unit = USD - label = "Self-employment work expenses" - documentation = ( - "Self-employment business costs already deducted in arriving at the " - "reported net self-employment income, combined across all of the " - "person's farm and non-farm businesses. Include every cost deducted on " - "Schedule C (Part II expenses, the line 30 business use of home, and " - "Part III cost of goods sold) and on Schedule F (Part II expenses and " - "the line 1b cost or other basis of resale items), so that net income " - "plus this input equals gross receipts. Exclude employee work-related " - "expenses such as commuting, uniforms, and tools, and exclude personal " - "income and self-employment taxes. Treated as zero when not reported." - ) diff --git a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py index 6e001df6e99..2b237c0c8d2 100644 --- a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py +++ b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py @@ -8,40 +8,36 @@ class self_employment_gross_income(Variable): unit = USD label = "Gross self-employment income" documentation = ( - "Gross self-employment receipts, reconstructed as net Schedule C " - "(regular and SSTB) and Schedule F profit plus work_expense, the " - "business costs already deducted in arriving at that net profit. Covers " - "sole-proprietor Schedule C and Schedule F businesses only; " - "partnership_self_employment_net_earnings is excluded by design because " - "partnership expenses are deducted on Form 1065 and that variable is not " - "an additional gross source, so consumers needing partnership earnings " - "add it separately. The zero floor applies to the combined farm plus " - "non-farm sum. work_expense is the person-level counterpart of the " - "SPM-unit SNAP input snap_self_employment_income_expense and is not " - "derived from it. Omitted expenses are assumed to be zero, which biases " - "the result downward and zeroes business losses; with no data source for " - "work_expense, microsimulation yields max(net, 0). This variable reads " - "total_self_employment_income, so it is post-labor-supply-response, " - "unlike the pre-response snap_gross_self_employment_income_person." + "Schedule C line 7 and Schedule F line 9 gross income (receipts after " + "cost of goods sold and before other business expenses), reconstructed " + "as net Schedule C (regular and SSTB) and Schedule F profit plus " + "self_employment_expense. Covers sole-proprietor Schedule C and " + "Schedule F businesses only; partnership_self_employment_net_earnings " + "is excluded because partnership expenses are deducted on Form 1065, " + "so consumers needing partnership earnings add it separately. Each " + "source's gross income is at least its net profit floored at zero, so " + "the result is never below the sum of those floors: a loss in one " + "source does not offset another when expenses are omitted. With " + "complete expenses the reconstruction is exact; with none, as in " + "microsimulation, the result is the sum of the per-source floors. " + "The non-farm sources are post-labor-supply-response." ) reference = ( "https://www.irs.gov/instructions/i1040sc", "https://www.irs.gov/instructions/i1040sf", - "https://www.irs.gov/pub/irs-pdf/f1040sc.pdf", - "https://www.irs.gov/pub/irs-pdf/f1040sf.pdf", + "https://www.irs.gov/pub/irs-pdf/f1040sc.pdf#page=1", + "https://www.irs.gov/pub/irs-pdf/f1040sf.pdf#page=1", ) def formula(person, period, parameters): - # Add back the same business expenses deducted from the reported net - # income. Preserve losses until after expenses have been added back. - # With no reported expenses, this assumes expenses are zero. - gross = add( - person, - period, - [ - "total_self_employment_income", - "farm_operations_income", - "work_expense", - ], - ) - return max_(gross, 0) + 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. + reconstructed = add(person, period, sources + ["self_employment_expense"]) + # Each source's gross income is at least its net profit floored at zero. + source_floor = sum(max_(person(source, period), 0) for source in sources) + return max_(reconstructed, source_floor) From 49ff1c8cc2681420747548ce471acc2c0f29561b Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 29 Sep 2026 12:22:20 -0400 Subject: [PATCH 6/8] Document self-employment income reconstruction and expand regression coverage --- changelog.d/liheap-shared-inputs.added.md | 2 +- docs/_toc.yml | 1 + docs/usage/self-employment-income.md | 85 +++++++++++++++++++ .../self_employment_gross_income.yaml | 75 ++++++++++++++++ .../expense/person/self_employment_expense.py | 13 ++- .../self_employment_gross_income.py | 36 +++++--- 6 files changed, 194 insertions(+), 18 deletions(-) create mode 100644 docs/usage/self-employment-income.md diff --git a/changelog.d/liheap-shared-inputs.added.md b/changelog.d/liheap-shared-inputs.added.md index 8c905719109..f3b88f10ffe 100644 --- a/changelog.d/liheap-shared-inputs.added.md +++ b/changelog.d/liheap-shared-inputs.added.md @@ -1 +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. +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. Document the one-way calculation and distinguish tax-expense add-backs from program-specific allowable deductions. diff --git a/docs/_toc.yml b/docs/_toc.yml index b6496e344f2..a9a5fab1ff3 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -65,6 +65,7 @@ parts: chapters: - file: usage/microsimulation - file: usage/parameter-discovery + - file: usage/self-employment-income - file: usage/speed - caption: Examples chapters: diff --git a/docs/usage/self-employment-income.md b/docs/usage/self-employment-income.md new file mode 100644 index 00000000000..65096511965 --- /dev/null +++ b/docs/usage/self-employment-income.md @@ -0,0 +1,85 @@ +# Self-employment income + +The shared variables reconstruct business gross income from existing net-income +amounts. Benefit programs apply their own deductions after that reconstruction. + +| Variable | Meaning | +| --- | --- | +| `employment_income` | Employee wages, separate from business income. | +| `self_employment_income` | Non-farm, non-SSTB net self-employment income. | +| `sstb_self_employment_income` | Net income from a specified service trade or business. | +| `farm_operations_income` | Net income from active farming operations. | +| `self_employment_expense` | Expenses deducted to obtain the three net amounts above, excluding cost of goods sold. | +| `self_employment_gross_income` | Their reconstructed gross income after cost of goods sold and before other business expenses, subject to the floor below. | + +These amounts are annual, person-level values. The two non-farm net-income +variables include labor-supply responses. + +## Formula direction + +The accounting reconstruction is: + +```text +net_income = self_employment_income + + sstb_self_employment_income + + farm_operations_income + +reconstructed_gross = net_income + self_employment_expense +``` + +The gross-income formula also enforces a floor equal to the sum of the three net +sources individually floored at zero. This is a fallback when expenses are +missing or incomplete. For example, $6,000 non-farm profit and a $10,000 farm loss +with no expenses supplied produce $6,000, rather than a negative gross amount. +This floor is a modeling assumption, not an IRS rule. Tax-reported gross income +can be negative, for example when cost of goods sold exceeds sales and other +income. Complete expense information therefore reproduces the tax-form gross +amount only when the floor does not bind and the reported net profit has not +already been adjusted by tax loss limitations. + +The dependency goes from net income and expenses to gross income. Do not add a +reverse formula deriving the existing net-income variables from gross income: +that would create a circular dependency. Supplying gross income directly does +not infer net income or expenses; callers still supply net amounts for tax +calculations. + +## Program deductions + +For a program using a standard percentage deduction, the consumer follows this +pattern; these names are placeholders for program-specific variables: + +```text +program_expense_deduction = applicable_gross_income * program_rate +program_countable_self_employment_income = + max(0, applicable_gross_income - program_expense_deduction) +``` + +For example, net profit of $15,000 plus $25,000 of expenses reconstructs $40,000 +of gross income. A 25% standard deduction leaves $30,000 of countable income. +The original $25,000 of expenses is not deducted a second time. The program's +rate belongs in its parameters. + +Programs permitting actual expenses must identify the costs their own rules +allow. `self_employment_expense` contains tax-return expense add-backs and is not +a universal benefit-program deduction. Any actual-cost election, loss treatment, +averaging period, and later earned-income disregard also belong in the program +calculation. Combine wages and self-employment income only after applying their +respective rules. + +## Coverage and missing information + +- The expense input defaults to zero. An omitted answer is not evidence of zero + actual expenses, so the derived amount should not be presented as verified + gross income without the necessary source information. +- The gross amount is after cost of goods sold. It does not reconstruct gross + receipts before that accounting step. +- The aggregate covers Schedule C and Schedule F income. It excludes + `partnership_self_employment_net_earnings` and does not preserve a gross-income + breakdown between farm and non-farm businesses. Consumers requiring those + distinctions need additional information. +- The floor applies to the three income categories, not to each individual + business within a category. It does not implement program-specific loss rules. + +The accounting definitions follow [Schedule C](https://www.irs.gov/pub/irs-pdf/f1040sc.pdf) +and [Schedule F](https://www.irs.gov/pub/irs-pdf/f1040sf.pdf). Their tax definitions +do not determine which income and expenses a benefit program must count. diff --git a/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml b/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml index ba845cf97f6..19519a178ce 100644 --- a/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/person/self_employment/self_employment_gross_income.yaml @@ -98,3 +98,78 @@ 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 diff --git a/policyengine_us/variables/household/expense/person/self_employment_expense.py b/policyengine_us/variables/household/expense/person/self_employment_expense.py index fdc546d5135..d8369408d29 100644 --- a/policyengine_us/variables/household/expense/person/self_employment_expense.py +++ b/policyengine_us/variables/household/expense/person/self_employment_expense.py @@ -14,10 +14,15 @@ class self_employment_expense(Variable): "28 total expenses plus line 30 business use of the home, and Schedule " "F line 33 total expenses. Exclude cost of goods sold (Schedule C line " "4; Schedule F line 1b, or Part III line 49 for accrual-method farms), " - "so that net profit plus this input equals Schedule C line 7 and " - "Schedule F line 9 gross income. Exclude employee work-related " - "expenses and personal income and self-employment taxes. Treated as " - "zero when not reported." + "so that matching net profit before tax loss limitations plus this " + "input reconstructs Schedule C line 7 and Schedule F line 9 gross " + "income before the model's gross-income floor. Exclude employee work-related " + "expenses and personal income and self-employment taxes. This input " + "adds back expenses already deducted from reported net profit; it " + "does not reduce that net profit again. Tax-return expenses are not " + "necessarily allowable expenses for a benefit program. Treated as " + "zero when not reported, which does not establish that actual " + "expenses were zero." ) reference = ( "https://www.irs.gov/instructions/i1040sc", diff --git a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py index 2b237c0c8d2..3eedc58b628 100644 --- a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py +++ b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py @@ -8,19 +8,25 @@ class self_employment_gross_income(Variable): unit = USD label = "Gross self-employment income" documentation = ( - "Schedule C line 7 and Schedule F line 9 gross income (receipts after " - "cost of goods sold and before other business expenses), reconstructed " + "Schedule C line 7 and Schedule F line 9 gross income (after cost of " + "goods sold and before other business expenses), reconstructed " "as net Schedule C (regular and SSTB) and Schedule F profit plus " "self_employment_expense. Covers sole-proprietor Schedule C and " "Schedule F businesses only; partnership_self_employment_net_earnings " "is excluded because partnership expenses are deducted on Form 1065, " - "so consumers needing partnership earnings add it separately. Each " - "source's gross income is at least its net profit floored at zero, so " - "the result is never below the sum of those floors: a loss in one " - "source does not offset another when expenses are omitted. With " - "complete expenses the reconstruction is exact; with none, as in " - "microsimulation, the result is the sum of the per-source floors. " - "The non-farm sources are post-labor-supply-response." + "so consumers needing partnership earnings handle it separately. " + "A modeling floor prevents the result from falling below the sum of " + "the three net sources individually floored at zero: a loss in one " + "source does not offset another when expenses are omitted. Complete " + "expenses matching the reported net amounts reconstruct tax-reported " + "gross income only when this floor does not bind. The floor is not " + "an IRS rule; tax-reported gross income can be negative. With no " + "expenses supplied, the result is the sum of the per-source floors. " + "The non-farm sources are post-labor-supply-response. This calculation " + "does not change net self-employment income or employment income. " + "Consumers apply their own expense deductions to the appropriate " + "gross-income base, then combine countable self-employment income " + "with separately treated wages." ) reference = ( "https://www.irs.gov/instructions/i1040sc", @@ -30,14 +36,18 @@ class self_employment_gross_income(Variable): ) def formula(person, period, parameters): - sources = [ + 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. - reconstructed = add(person, period, sources + ["self_employment_expense"]) - # Each source's gross income is at least its net profit floored at zero. - source_floor = sum(max_(person(source, period), 0) for source in sources) + 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 = sum( + max_(person(source, period), 0) for source in net_income_sources + ) return max_(reconstructed, source_floor) From fe9880849c44d2eb3c1bdc2181923a914a45f7a7 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 29 Sep 2026 13:18:57 -0400 Subject: [PATCH 7/8] Use explicit addition for self-employment income floor --- .../person/self_employment/self_employment_gross_income.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py index 3eedc58b628..1a7140acf25 100644 --- a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py +++ b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py @@ -47,7 +47,7 @@ def formula(person, period, parameters): expenses = person("self_employment_expense", period) reconstructed = net_income + expenses # Apply the modeling fallback separately to each net-income source. - source_floor = sum( - max_(person(source, period), 0) for source in net_income_sources - ) + source_floor = 0 + for source in net_income_sources: + source_floor = source_floor + max_(person(source, period), 0) return max_(reconstructed, source_floor) From 2d6a227c11b39b99e502c791e3b4ea5cf582a3ee Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 29 Sep 2026 14:29:49 -0400 Subject: [PATCH 8/8] Keep shared self-employment PR limited to variables --- changelog.d/liheap-shared-inputs.added.md | 2 +- docs/_toc.yml | 1 - docs/usage/self-employment-income.md | 85 ------------------- .../expense/person/self_employment_expense.py | 28 ++---- .../self_employment_gross_income.py | 34 +++----- 5 files changed, 21 insertions(+), 129 deletions(-) delete mode 100644 docs/usage/self-employment-income.md diff --git a/changelog.d/liheap-shared-inputs.added.md b/changelog.d/liheap-shared-inputs.added.md index f3b88f10ffe..8c905719109 100644 --- a/changelog.d/liheap-shared-inputs.added.md +++ b/changelog.d/liheap-shared-inputs.added.md @@ -1 +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. Document the one-way calculation and distinguish tax-expense add-backs from program-specific allowable deductions. +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. diff --git a/docs/_toc.yml b/docs/_toc.yml index a9a5fab1ff3..b6496e344f2 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -65,7 +65,6 @@ parts: chapters: - file: usage/microsimulation - file: usage/parameter-discovery - - file: usage/self-employment-income - file: usage/speed - caption: Examples chapters: diff --git a/docs/usage/self-employment-income.md b/docs/usage/self-employment-income.md deleted file mode 100644 index 65096511965..00000000000 --- a/docs/usage/self-employment-income.md +++ /dev/null @@ -1,85 +0,0 @@ -# Self-employment income - -The shared variables reconstruct business gross income from existing net-income -amounts. Benefit programs apply their own deductions after that reconstruction. - -| Variable | Meaning | -| --- | --- | -| `employment_income` | Employee wages, separate from business income. | -| `self_employment_income` | Non-farm, non-SSTB net self-employment income. | -| `sstb_self_employment_income` | Net income from a specified service trade or business. | -| `farm_operations_income` | Net income from active farming operations. | -| `self_employment_expense` | Expenses deducted to obtain the three net amounts above, excluding cost of goods sold. | -| `self_employment_gross_income` | Their reconstructed gross income after cost of goods sold and before other business expenses, subject to the floor below. | - -These amounts are annual, person-level values. The two non-farm net-income -variables include labor-supply responses. - -## Formula direction - -The accounting reconstruction is: - -```text -net_income = self_employment_income - + sstb_self_employment_income - + farm_operations_income - -reconstructed_gross = net_income + self_employment_expense -``` - -The gross-income formula also enforces a floor equal to the sum of the three net -sources individually floored at zero. This is a fallback when expenses are -missing or incomplete. For example, $6,000 non-farm profit and a $10,000 farm loss -with no expenses supplied produce $6,000, rather than a negative gross amount. -This floor is a modeling assumption, not an IRS rule. Tax-reported gross income -can be negative, for example when cost of goods sold exceeds sales and other -income. Complete expense information therefore reproduces the tax-form gross -amount only when the floor does not bind and the reported net profit has not -already been adjusted by tax loss limitations. - -The dependency goes from net income and expenses to gross income. Do not add a -reverse formula deriving the existing net-income variables from gross income: -that would create a circular dependency. Supplying gross income directly does -not infer net income or expenses; callers still supply net amounts for tax -calculations. - -## Program deductions - -For a program using a standard percentage deduction, the consumer follows this -pattern; these names are placeholders for program-specific variables: - -```text -program_expense_deduction = applicable_gross_income * program_rate -program_countable_self_employment_income = - max(0, applicable_gross_income - program_expense_deduction) -``` - -For example, net profit of $15,000 plus $25,000 of expenses reconstructs $40,000 -of gross income. A 25% standard deduction leaves $30,000 of countable income. -The original $25,000 of expenses is not deducted a second time. The program's -rate belongs in its parameters. - -Programs permitting actual expenses must identify the costs their own rules -allow. `self_employment_expense` contains tax-return expense add-backs and is not -a universal benefit-program deduction. Any actual-cost election, loss treatment, -averaging period, and later earned-income disregard also belong in the program -calculation. Combine wages and self-employment income only after applying their -respective rules. - -## Coverage and missing information - -- The expense input defaults to zero. An omitted answer is not evidence of zero - actual expenses, so the derived amount should not be presented as verified - gross income without the necessary source information. -- The gross amount is after cost of goods sold. It does not reconstruct gross - receipts before that accounting step. -- The aggregate covers Schedule C and Schedule F income. It excludes - `partnership_self_employment_net_earnings` and does not preserve a gross-income - breakdown between farm and non-farm businesses. Consumers requiring those - distinctions need additional information. -- The floor applies to the three income categories, not to each individual - business within a category. It does not implement program-specific loss rules. - -The accounting definitions follow [Schedule C](https://www.irs.gov/pub/irs-pdf/f1040sc.pdf) -and [Schedule F](https://www.irs.gov/pub/irs-pdf/f1040sf.pdf). Their tax definitions -do not determine which income and expenses a benefit program must count. diff --git a/policyengine_us/variables/household/expense/person/self_employment_expense.py b/policyengine_us/variables/household/expense/person/self_employment_expense.py index d8369408d29..aa118a52002 100644 --- a/policyengine_us/variables/household/expense/person/self_employment_expense.py +++ b/policyengine_us/variables/household/expense/person/self_employment_expense.py @@ -8,25 +8,15 @@ class self_employment_expense(Variable): unit = USD label = "Self-employment business expenses" documentation = ( - "Business expenses, other than cost of goods sold, deducted in arriving " - "at the person's reported net self-employment income, combined across " - "the person's farm and non-farm sole proprietorships: Schedule C line " - "28 total expenses plus line 30 business use of the home, and Schedule " - "F line 33 total expenses. Exclude cost of goods sold (Schedule C line " - "4; Schedule F line 1b, or Part III line 49 for accrual-method farms), " - "so that matching net profit before tax loss limitations plus this " - "input reconstructs Schedule C line 7 and Schedule F line 9 gross " - "income before the model's gross-income floor. Exclude employee work-related " - "expenses and personal income and self-employment taxes. This input " - "adds back expenses already deducted from reported net profit; it " - "does not reduce that net profit again. Tax-return expenses are not " - "necessarily allowable expenses for a benefit program. Treated as " - "zero when not reported, which does not establish that actual " - "expenses were zero." + "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/instructions/i1040sc", - "https://www.irs.gov/instructions/i1040sf", - "https://www.irs.gov/pub/irs-pdf/f1040sc.pdf#page=1", - "https://www.irs.gov/pub/irs-pdf/f1040sf.pdf#page=1", + "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", ) diff --git a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py index 1a7140acf25..904d2dbdd72 100644 --- a/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py +++ b/policyengine_us/variables/household/income/person/self_employment/self_employment_gross_income.py @@ -8,31 +8,19 @@ class self_employment_gross_income(Variable): unit = USD label = "Gross self-employment income" documentation = ( - "Schedule C line 7 and Schedule F line 9 gross income (after cost of " - "goods sold and before other business expenses), reconstructed " - "as net Schedule C (regular and SSTB) and Schedule F profit plus " - "self_employment_expense. Covers sole-proprietor Schedule C and " - "Schedule F businesses only; partnership_self_employment_net_earnings " - "is excluded because partnership expenses are deducted on Form 1065, " - "so consumers needing partnership earnings handle it separately. " - "A modeling floor prevents the result from falling below the sum of " - "the three net sources individually floored at zero: a loss in one " - "source does not offset another when expenses are omitted. Complete " - "expenses matching the reported net amounts reconstruct tax-reported " - "gross income only when this floor does not bind. The floor is not " - "an IRS rule; tax-reported gross income can be negative. With no " - "expenses supplied, the result is the sum of the per-source floors. " - "The non-farm sources are post-labor-supply-response. This calculation " - "does not change net self-employment income or employment income. " - "Consumers apply their own expense deductions to the appropriate " - "gross-income base, then combine countable self-employment income " - "with separately treated wages." + "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/instructions/i1040sc", - "https://www.irs.gov/instructions/i1040sf", - "https://www.irs.gov/pub/irs-pdf/f1040sc.pdf#page=1", - "https://www.irs.gov/pub/irs-pdf/f1040sf.pdf#page=1", + "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):