diff --git a/changelog.d/fix-sehi-earned-income-limit.fixed.md b/changelog.d/fix-sehi-earned-income-limit.fixed.md new file mode 100644 index 00000000000..23e7a80bdb3 --- /dev/null +++ b/changelog.d/fix-sehi-earned-income-limit.fixed.md @@ -0,0 +1 @@ +Limit the self-employed health insurance deduction to net profit less the deductible part of self-employment tax and self-employed retirement plan deductions, as 26 U.S.C. 162(l)(2)(A) and Form 7206 require. diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald.yaml new file mode 100644 index 00000000000..2937a6441e7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald.yaml @@ -0,0 +1,102 @@ +# Form 7206 line 14 (the Form 1040 instructions worksheet line 3 before 2023): +# the smaller of the premiums and the earned income limit. Expected values are +# hand-computed from the form. + +- name: Case 1, premiums above the limit are capped at net profit less half of self-employment tax. + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + is_self_employed: true + self_employment_income: 2_000 + health_insurance_premiums: 6_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + # 2022 Form 1040 instructions worksheet: line 1 is 6,000; line 2 is + # 2,000 - 141.2955 (Schedule 1 line 15) = 1,858.7045. + self_employed_health_insurance_ald_person: [1_858.7045] + self_employed_health_insurance_ald: 1_858.7045 + # 2,000 - 141.2955 - 1,858.7045. + adjusted_gross_income: 0 + +- name: Case 2, premiums below the limit are deducted in full. + period: 2022 + absolute_error_margin: 0.01 + input: + is_self_employed: true + self_employment_income: 50_000 + health_insurance_premiums: 6_000 + output: + # Line 2: 50,000 - 3,532.3875 = 46,467.6125. + self_employed_health_insurance_earned_income_limit: 46_467.6125 + self_employed_health_insurance_ald_person: 6_000 + +- name: Case 3, the plan deduction and half of self-employment tax both reduce the cap. + period: 2023 + absolute_error_margin: 0.01 + input: + is_self_employed: true + self_employment_income: 20_000 + self_employed_pension_contributions_desired: 3_000 + health_insurance_premiums: 20_000 + output: + # Form 7206: line 3 is 20,000; line 13 is 20,000 - 1,412.955 - 3,000. + self_employed_health_insurance_ald_person: 15_587.045 + +- name: Case 4, a farm business takes its share of the self-employment tax deduction. + period: 2024 + absolute_error_margin: 0.01 + input: + is_self_employed: true + self_employment_income: 30_000 + farm_operations_income: 10_000 + health_insurance_premiums: 40_000 + output: + # Form 7206 line 13: 30,000 - 0.75 x 2,825.91 = 27,880.5675. + self_employed_health_insurance_ald_person: 27_880.5675 + +- name: Case 5, each spouse's deduction is capped by their own earned income. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + is_self_employed: true + self_employment_income: 3_000 + health_insurance_premiums: 8_000 + person2: + is_self_employed: true + self_employment_income: 40_000 + health_insurance_premiums: 2_000 + tax_units: + tax_unit: + members: [person1, person2] + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: TX + output: + # person1: 3,000 - 3,000 x 0.9235 x 0.153 / 2 = 3,000 - 211.94325. + # person2: premiums of 2,000 are under the limit. + self_employed_health_insurance_ald_person: [2_788.05675, 2_000] + self_employed_health_insurance_ald: 4_788.05675 + +- name: Case 6, no deduction with a Schedule C loss. + period: 2025 + absolute_error_margin: 0.01 + input: + is_self_employed: true + self_employment_income: -5_000 + health_insurance_premiums: 3_000 + output: + self_employed_health_insurance_ald_person: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_earned_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_earned_income_limit.yaml new file mode 100644 index 00000000000..4c9af5e3662 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_earned_income_limit.yaml @@ -0,0 +1,95 @@ +# Form 7206 lines 4-13 (Pub. 535 Worksheet 6-A before 2023). Expected values +# are hand-computed from the form: the deductible part of self-employment tax +# is net profit x 0.9235 x 15.3% / 2 below the Social Security wage base. + +- name: Case 1, one business, the limit is net profit less half of self-employment tax. + period: 2022 + absolute_error_margin: 0.01 + input: + self_employment_income: 2_000 + output: + # Line 15: 2,000 x 0.9235 x 0.153 / 2 = 141.2955. + self_employment_tax_ald_person: 141.2955 + # Line 13: 2,000 - 141.2955. + self_employed_health_insurance_earned_income_limit: 1_858.7045 + +- name: Case 2, the SEP, SIMPLE and qualified plan deduction also reduces the limit. + period: 2023 + absolute_error_margin: 0.01 + input: + self_employment_income: 20_000 + self_employed_pension_contributions_desired: 3_000 + output: + # Line 15: 20,000 x 0.9235 x 0.153 / 2 = 1,412.955. + self_employment_tax_ald_person: 1_412.955 + # Line 16 (within the $66,000 annual additions limit). + self_employed_pension_contribution_ald_person: 3_000 + # Lines 8 to 10: 20,000 - 1,412.955 - 3,000. + self_employed_health_insurance_earned_income_limit: 15_587.045 + +- name: Case 3, a farm business takes its share of the self-employment tax deduction. + period: 2024 + absolute_error_margin: 0.01 + input: + self_employment_income: 30_000 + farm_operations_income: 10_000 + output: + # Line 15: 40,000 x 0.9235 x 0.153 / 2 = 2,825.91. + self_employment_tax_ald_person: 2_825.91 + # Lines 5 to 8: 30,000 / 40,000 = 0.75; 30,000 - 0.75 x 2,825.91. + self_employed_health_insurance_earned_income_limit: 27_880.5675 + +- name: Case 4, a partnership's self-employment earnings take their share too. + period: 2024 + absolute_error_margin: 0.01 + input: + self_employment_income: 30_000 + partnership_self_employment_net_earnings: 30_000 + output: + # Line 15: 60,000 x 0.9235 x 0.153 / 2 = 4,238.865. + self_employment_tax_ald_person: 4_238.865 + # Lines 5 to 8: 30,000 / 60,000 = 0.5; 30,000 - 0.5 x 4,238.865. + self_employed_health_insurance_earned_income_limit: 27_880.5675 + +- name: Case 5, a farm loss is left out of line 5, so the plan business takes the whole deduction. + period: 2024 + absolute_error_margin: 0.01 + input: + self_employment_income: 30_000 + farm_operations_income: -10_000 + output: + # Line 15: (30,000 - 10,000) x 0.9235 x 0.153 / 2 = 1,412.955. + self_employment_tax_ald_person: 1_412.955 + # Lines 5 to 8: line 5 is 30,000, so line 6 is 1; 30,000 - 1,412.955. + self_employed_health_insurance_earned_income_limit: 28_587.045 + +- name: Case 6, wages above the Social Security wage base leave only the Medicare part. + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 200_000 + self_employment_income: 10_000 + output: + # Wages exceed the $176,100 wage base, so line 15 is + # 10,000 x 0.9235 x 0.029 / 2 = 133.9075. + self_employment_tax_ald_person: 133.9075 + self_employed_health_insurance_earned_income_limit: 9_866.0925 + +- name: Case 7, net earnings under $400 owe no self-employment tax, so the limit is net profit. + period: 2025 + absolute_error_margin: 0.01 + input: + self_employment_income: 400 + output: + # 400 x 0.9235 = 369.40, under the $400 threshold of section 1402(b)(2). + self_employment_tax_ald_person: 0 + self_employed_health_insurance_earned_income_limit: 400 + +- name: Case 8, a Schedule C loss leaves no limit. + period: 2025 + absolute_error_margin: 0.01 + input: + self_employment_income: -5_000 + partnership_self_employment_net_earnings: 20_000 + output: + self_employed_health_insurance_earned_income_limit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employment_income.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employment_income.yaml index 526b7ca01e0..11b233376f0 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employment_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employment_income.yaml @@ -8,11 +8,13 @@ - name: Self-employed health insurance ALD uses SSTB income period: 2024 + absolute_error_margin: 0.01 input: sstb_self_employment_income: 10_000 self_employed_health_insurance_premiums: 12_000 output: - self_employed_health_insurance_ald_person: 10_000 + # Form 7206 line 13: 10,000 - 10,000 x 0.9235 x 0.153 / 2. + self_employed_health_insurance_ald_person: 9_293.5225 - name: Self-employed pension ALD uses SSTB income period: 2024 diff --git a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/oh_medical_care_insurance_premiums.yaml b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/oh_medical_care_insurance_premiums.yaml index 36c1c46fd9c..77175d10352 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/oh_medical_care_insurance_premiums.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/oh_medical_care_insurance_premiums.yaml @@ -33,14 +33,16 @@ - name: Premiums deducted as self-employed health insurance are left out period: 2025 + absolute_error_margin: 0.01 input: state_code: OH is_self_employed: true self_employment_income: 3_000 health_insurance_premiums: 5_000 output: - # 5,000 - min(3,000, 5,000). - oh_medical_care_insurance_premiums: 2_000 + # The federal deduction is min(5,000, 3,000 - 3,000 x 0.9235 x 0.153 / 2) + # = 2,788.05675 (Form 7206 line 14), so 5,000 - 2,788.05675 remain. + oh_medical_care_insurance_premiums: 2_211.94325 - name: The self-employed deduction cannot make premiums negative period: 2025 diff --git a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/oh_uninsured_unreimbursed_medical_care_expenses.yaml b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/oh_uninsured_unreimbursed_medical_care_expenses.yaml index d9eac668dff..b6853f57ac3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/oh_uninsured_unreimbursed_medical_care_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/oh_uninsured_unreimbursed_medical_care_expenses.yaml @@ -179,6 +179,7 @@ # Worksheet line 1 note: "You must reduce the amount you enter on this line # by your federal self-employed health insurance deduction". period: 2025 + absolute_error_margin: 0.01 input: people: person1: @@ -194,7 +195,8 @@ members: [person1] state_code: OH output: - # min(3,000 of earnings, 5,000 of premiums). - self_employed_health_insurance_ald: 3_000 - # 5,000 - 3,000. - oh_uninsured_unreimbursed_medical_care_expenses: 2_000 + # Form 7206 line 14: min(5,000 of premiums, 3,000 of earnings less the + # 211.94325 deductible part of self-employment tax). + self_employed_health_insurance_ald: 2_788.05675 + # 5,000 - 2,788.05675. + oh_uninsured_unreimbursed_medical_care_expenses: 2_211.94325 diff --git a/policyengine_us/tests/unit/test_self_employed_health_insurance_properties.py b/policyengine_us/tests/unit/test_self_employed_health_insurance_properties.py new file mode 100644 index 00000000000..9d6cf8ffe5a --- /dev/null +++ b/policyengine_us/tests/unit/test_self_employed_health_insurance_properties.py @@ -0,0 +1,199 @@ +"""Properties of the self-employed health insurance deduction. + +26 U.S.C. 162(l)(2)(A) limits the deduction to the taxpayer's earned income, +within the meaning of section 401(c), from the business under which the plan +is established. Section 401(c)(2)(A)(v) and (vi) determine that earned income +with regard to the section 404 plan deduction and the section 164(f) deduction +for half of self-employment tax. Form 7206 (Pub. 535 Worksheet 6-A before +2023) computes it as the plan business's net profit (line 4), less the part of +Schedule 1 line 15 attributable to that business by its share of all net +profits subject to self-employment tax (lines 5 to 8), less the Schedule 1 line +16 plan deduction (lines 9 and 10); the deduction is the smaller of the +premiums and that amount (line 14). The model treats a person's Schedule C +income as the plan business. + +Hypothesis draws batches of self-employed single filers and runs each batch as +one vectorized baseline simulation. Each drawn filer appears twice, the second +time with higher premiums. For every filer and year: + +1. Reference: the limit equals a closed-form Form 7206 computation that takes + lines 15 and 16 from the model, and line 15 equals half of the + self-employment tax. +2. Bounds: 0 <= deduction <= premiums, and deduction <= Schedule C profit less + the attributable part of line 15 (earned income after the self-employment + tax deduction), which never exceeds Schedule C profit. +3. Monotone in premiums: raising premiums by d raises the deduction by at + least 0 and at most d, and leaves the limit unchanged. +4. The deduction never makes adjusted gross income negative for a filer whose + only income is Schedule C profit: such a filer with negative AGI (from the + plan deduction) has no health insurance deduction. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +TOLERANCE = 0.01 +YEARS = [2022, 2024, 2026] +PREMIUM_RAISE = 2_500 + + +def every_year(value): + return {year: value for year in YEARS} + + +def build_situation(filers): + """One single-filer household per drawn filer, then again with higher premiums.""" + people, tax_units, households = {}, {}, {} + for raised in (False, True): + for i, filer in enumerate(filers): + name = f"{'r' if raised else 'b'}{i}" + premiums = filer["premiums"] + (PREMIUM_RAISE if raised else 0) + people[name] = { + "age": every_year(45), + "is_self_employed": every_year(True), + "self_employment_income": every_year(filer["schedule_c"]), + "farm_operations_income": every_year(filer["farm"]), + "partnership_self_employment_net_earnings": every_year( + filer["partnership"] + ), + "employment_income": every_year(filer["wages"]), + "self_employed_pension_contributions_desired": every_year( + filer["plan"] + ), + "health_insurance_premiums": every_year(premiums), + } + tax_units[name] = {"members": [name]} + households[name] = {"members": [name], "state_code": every_year("TX")} + situation = {"people": people, "tax_units": tax_units, "households": households} + return situation, len(filers) + + +def form_7206_limit(schedule_c, farm, partnership, line_15, line_16): + """Form 7206 lines 4 to 10 and 13, with lines 15 and 16 as given amounts.""" + line_4 = np.maximum(schedule_c, 0) + line_5 = line_4 + np.maximum(farm, 0) + np.maximum(partnership, 0) + line_6 = np.where(line_5 > 0, line_4 / np.where(line_5 > 0, line_5, 1), 0) + line_8 = line_4 - line_15 * line_6 + return np.maximum(line_8 - line_16, 0), line_8 + + +def column(filers, key, n_copies=2): + return np.tile(np.array([float(f[key]) for f in filers]), n_copies) + + +def check_properties(sim, filers, n): + schedule_c = column(filers, "schedule_c") + farm = column(filers, "farm") + partnership = column(filers, "partnership") + wages = column(filers, "wages") + for year in YEARS: + premiums = sim.calculate("self_employed_health_insurance_premiums", year) + limit = sim.calculate( + "self_employed_health_insurance_earned_income_limit", year + ) + deduction = sim.calculate("self_employed_health_insurance_ald_person", year) + line_15 = sim.calculate("self_employment_tax_ald_person", year) + line_16 = sim.calculate("self_employed_pension_contribution_ald_person", year) + se_tax = sim.calculate("self_employment_tax", year) + + # 1. Reference. + np.testing.assert_allclose(line_15, se_tax / 2, atol=TOLERANCE) + expected_limit, after_se_tax = form_7206_limit( + schedule_c, farm, partnership, line_15, line_16 + ) + np.testing.assert_allclose(limit, expected_limit, atol=TOLERANCE) + np.testing.assert_allclose( + deduction, np.minimum(premiums, expected_limit), atol=TOLERANCE + ) + + # 2. Bounds. + assert np.all(deduction >= 0) + assert np.all(deduction <= premiums + TOLERANCE) + assert np.all(deduction <= np.maximum(after_se_tax, 0) + TOLERANCE) + assert np.all(after_se_tax <= np.maximum(schedule_c, 0) + TOLERANCE) + + # 3. Monotone and 1-Lipschitz in premiums; the limit ignores premiums. + base, raised = deduction[:n], deduction[n:] + assert np.all(raised >= base - TOLERANCE) + assert np.all(raised <= base + PREMIUM_RAISE + TOLERANCE) + np.testing.assert_allclose(limit[:n], limit[n:], atol=TOLERANCE) + + # 4. The deduction never takes Schedule C-only AGI below zero. + schedule_c_only = (farm == 0) & (partnership == 0) & (wages == 0) + agi = sim.calculate("adjusted_gross_income", year) + negative_agi = schedule_c_only & (agi < -TOLERANCE) + assert np.all(deduction[negative_agi] <= TOLERANCE) + + +money = st.integers(0, 120_000) +filer_strategy = st.fixed_dictionaries( + { + "schedule_c": st.one_of( + st.sampled_from([-5_000, 0, 400, 434, 2_000, 160_000, 250_000]), + st.integers(-20_000, 300_000), + ), + "farm": st.one_of(st.just(0), st.integers(-30_000, 100_000)), + "partnership": st.one_of(st.just(0), st.integers(-30_000, 100_000)), + "wages": st.one_of(st.just(0), st.integers(0, 250_000)), + "plan": st.one_of(st.just(0), money), + "premiums": st.one_of(st.sampled_from([0, 1_000, 6_000]), money), + } +) +SETTINGS = dict( + max_examples=12, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +@settings(**SETTINGS) +@given(st.lists(filer_strategy, min_size=4, max_size=20)) +def test_deduction_follows_form_7206(filers): + situation, n = build_situation(filers) + check_properties(Simulation(situation=situation), filers, n) + + +def test_reviewer_example_and_boundaries(): + """Fixed filers: the $2,000 example, the $400 threshold, and a wage-base case.""" + filers = [ + dict(schedule_c=2_000, farm=0, partnership=0, wages=0, plan=0, premiums=6_000), + dict(schedule_c=434, farm=0, partnership=0, wages=0, plan=0, premiums=6_000), + dict(schedule_c=433, farm=0, partnership=0, wages=0, plan=0, premiums=6_000), + dict( + schedule_c=10_000, + farm=0, + partnership=0, + wages=250_000, + plan=0, + premiums=20_000, + ), + dict( + schedule_c=30_000, + farm=10_000, + partnership=0, + wages=0, + plan=0, + premiums=40_000, + ), + dict( + schedule_c=20_000, + farm=0, + partnership=0, + wages=0, + plan=20_000, + premiums=5_000, + ), + ] + situation, n = build_situation(filers) + sim = Simulation(situation=situation) + check_properties(sim, filers, n) + deduction = sim.calculate("self_employed_health_insurance_ald_person", 2022) + # 2,000 - 2,000 x 0.9235 x 0.153 / 2. + assert abs(deduction[0] - 1_858.7045) < TOLERANCE + # 434 x 0.9235 = 400.80 owes self-employment tax; 433 x 0.9235 = 399.88 does not. + assert deduction[1] < 434 - TOLERANCE + assert abs(deduction[2] - 433) < TOLERANCE diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald_person.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald_person.py index 8ecda97461a..939b3ba1e96 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald_person.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_ald_person.py @@ -6,11 +6,23 @@ class self_employed_health_insurance_ald_person(Variable): entity = Person label = "Self-employed health insurance ALD for each person" unit = USD - documentation = "Personal above-the-line deduction for self-employed health insurance contributions." + documentation = ( + "Personal above-the-line deduction for self-employed health insurance " + "premiums: the smaller of the premiums and the earned income from the " + "business under which the plan is established (Form 7206 line 14). " + "The premiums exclude qualified long-term care insurance (line 2), " + "which the model does not include in self-employed premiums." + ) definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/162#l" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/162#l", + "https://www.irs.gov/pub/irs-prior/f7206--2023.pdf#page=1", + "https://www.irs.gov/pub/irs-prior/i1040gi--2022.pdf#page=89", + ) def formula(person, period, parameters): - earnings = max_(0, person("total_self_employment_income", period)) premiums = person("self_employed_health_insurance_premiums", period) - return min_(earnings, premiums) + earned_income_limit = person( + "self_employed_health_insurance_earned_income_limit", period + ) + return min_(premiums, earned_income_limit) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_earned_income_limit.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_earned_income_limit.py new file mode 100644 index 00000000000..91cb5423d44 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/self_employed_health_insurance_earned_income_limit.py @@ -0,0 +1,64 @@ +from policyengine_us.model_api import * + + +class self_employed_health_insurance_earned_income_limit(Variable): + value_type = float + entity = Person + label = "Self-employed health insurance deduction earned income limit" + unit = USD + documentation = ( + "Earned income, within the meaning of 26 U.S.C. 401(c), from the " + "business under which the health plan is established; the " + "self-employed health insurance deduction cannot exceed it (Form " + "7206 line 13). The model treats the person's Schedule C business as " + "that business. Its net profit is reduced by the part of the " + "deductible part of self-employment tax attributable to it (lines 5 " + "to 7) and by the self-employed SEP, SIMPLE and qualified plan " + "deduction (line 9), which the model also bases on Schedule C income. " + "S corporation wages (line 11) are not modeled, and self-employment " + "income inputs are already net of any section 911 exclusion (line 12)." + ) + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/162#l_2_A", + "https://www.law.cornell.edu/uscode/text/26/401#c_2_A", + "https://www.irs.gov/pub/irs-prior/f7206--2023.pdf#page=1", + # The worksheet Form 7206 replaced, used before 2023. + "https://www.irs.gov/pub/irs-prior/p535--2022.pdf#page=23", + # The single-business worksheet in the Form 1040 instructions. + "https://www.irs.gov/pub/irs-prior/i1040gi--2022.pdf#page=89", + ) + + def formula(person, period, parameters): + # Line 4: net profit from the business under which the plan is + # established. + plan_business_profit = max_(0, person("total_self_employment_income", period)) + # Line 5: net profits of every business whose earnings are subject to + # self-employment tax, leaving out net losses. + other_business_profits = max_( + 0, person("farm_operations_income", period) + ) + max_(0, person("partnership_self_employment_net_earnings", period)) + all_business_profits = plan_business_profit + other_business_profits + # Line 6: the plan business's share of those profits. + plan_business_share = np.divide( + plan_business_profit, + all_business_profits, + out=np.zeros_like(all_business_profits), + where=all_business_profits > 0, + ) + # Lines 7 and 8: the deductible part of self-employment tax + # (Schedule 1 line 15) attributable to the plan business. + self_employment_tax_deduction = ( + person("self_employment_tax_ald_person", period) * plan_business_share + ) + # Lines 9 and 10: the SEP, SIMPLE and qualified plan deduction + # (Schedule 1 line 16) for the plan business. + retirement_plan_deduction = person( + "self_employed_pension_contribution_ald_person", period + ) + return max_( + 0, + plan_business_profit + - self_employment_tax_deduction + - retirement_plan_deduction, + )