Skip to content

Net the premium tax credit out of the self-employed health insurance deduction (26 U.S.C. 280C(g), Rev. Proc. 2014-41) #10124

Open

Description

@MaxGhenis

Problem

PolicyEngine deducts self-employed health insurance premiums in full and also pays a premium tax credit (PTC) on the modeled Marketplace plan. Nothing nets one against the other. The law allows no deduction for the part of the premiums the credit pays, and the deduction and credit depend on each other.

Example. A 45-year-old single filer in Miami-Dade, Florida, 2025, who enters the benchmark plan's full premium as health_insurance_premiums. The benchmark premium is $6,976.57. Run on main with PR #10122 applied:

Schedule C profit Deduction PTC Deduction + PTC − premiums
30,000 6,976.57 6,976.57 6,976.57
45,000 6,976.57 5,847.62 5,847.62
60,000 6,976.57 3,768.98 3,768.98

At $30,000 the credit pays the whole premium and the filer still deducts all of it.

Whether a given record double counts depends on what health_insurance_premiums holds:

  • Household users who enter a Marketplace plan's full premium get both the full deduction and the credit.
  • Microcosm data (populace_us_2024, build populace-us-2024-spm-20260909): the input is the person's reported out-of-pocket premium (CPS PHIP_VAL, copied by the attribute_self_employed_health_premiums stage). For Marketplace enrollees that amount is already net of the advance credit they received. In that build, 515 of the 1,379 self-employed premium records (1.57 million of 3.74 million weighted people) report Marketplace coverage.

So subtracting PE's modeled credit from that input would subtract it twice for the data while fixing the household case.

Law

  • 26 U.S.C. 280C(g) (govinfo, USCODE-2023): "No deduction shall be allowed for the portion of the premiums paid by the taxpayer for coverage of 1 or more individuals under a qualified health plan which is equal to the amount of the credit determined for the taxable year under section 36B(a) with respect to such premiums."
  • Rev. Proc. 2014-41:
    • Section 2.05 (PDF pp. 4–5) names the circular relationship.
    • Section 4 (p. 5) says to deduct premiums other than specified premiums first, like any other deduction.
    • Section 5.01 (pp. 6–7) gives the iterative calculation; section 5.02 (p. 8) gives the alternative, a fixed four steps:
      1. AGI with a deduction for the specified premiums, limited under 5.03.
      2. PTC on that AGI.
      3. Deduction = specified premiums − Step 2 PTC, limited under 5.03.
      4. Final PTC on the AGI with the Step 3 deduction.
    • Section 5.03 (p. 9) caps the deduction at the earned income limit and at the specified premiums not paid by advance credit payments plus the repayment limitation.
    • Section 6 (p. 9) prorates the Step 2 credit to the months with specified premiums.
  • Pub. 974, part "Self-Employed Health Insurance Deduction and PTC":
    • 2025, PDF pp. 47–63:
      • Worksheets P, W and X (pp. 48–52);
      • the Iterative Calculation Method (pp. 52–54) and the Simplified Calculation Method (pp. 55–56);
      • a worked 2025 example of the simplified method (pp. 60–63).
    • The p. 47 caution allows any method in which "the sum of the deduction claimed for the premiums and the PTC computed, taking the deduction into account, is less than or equal to the enrollment premiums."
    • The same part is in every edition from 2015 (pp. 50–64) through 2024 (pp. 47–62).
  • Form 7206 instructions (2025, p. 2) send Marketplace enrollees with advance payments to Pub. 974.

Why this is not a formula change

Netting the credit inside self_employed_health_insurance_premiums closes a cycle. A reform that sets the base to max_(health_insurance_premiums - aca_ptc, 0) raises CycleError:

self_employed_health_insurance_ald -> self_employed_health_insurance_ald_person
-> self_employed_health_insurance_premiums -> aca_ptc -> is_aca_ptc_eligible
-> aca_magi_fraction -> aca_magi -> medicaid_magi -> adjusted_gross_income
-> irs_gross_income -> ... -> taxable_social_security -> taxable_ss_magi
-> self_employed_health_insurance_ald

What it would take

  1. Split the premiums.
    • Specified premiums are the modeled Marketplace plan's premium for the self-employed person's coverage family: selected_marketplace_plan_premium_proxy, allocated to the months of self-employment and to the coverage family (Pub. 974 2025 p. 49).
    • Nonspecified premiums are everything else, the current health_insurance_premiums.
    • Microcosm would then put only the non-Marketplace part of the reported premium into health_insurance_premiums for self-employed Marketplace enrollees. other_health_insurance_premiums already subtracts marketplace_net_premium from PHIP_VAL.
  2. Two passes through a branch, not a cycle.
    • Follow tax_liability_if_itemizing, using policyengine_us/tools/period_branch.get_override_branch.
    • Step 2's credit is aca_ptc calculated in a branch whose credit offset is overridden to 0.
    • Step 3's deduction is min(specified premiums − offset, earned income limit left after the nonspecified deduction).
    • The main simulation's aca_ptc is then Step 4.
    • This is Rev. Proc. 2014-41 section 5.02. The iterative method has no fixed pass count and is not suited to a vectorized model.
  3. Cost.
    • The branch is created while AGI is still being calculated, so it recalculates AGI, Medicaid eligibility and ACA eligibility for every tax unit. That roughly doubles the cost of that chain.
    • Each marginal-tax-rate branch would create its own.
    • A scalar check on whether anyone has specified premiums can skip the branch when it isn't needed.
  4. Worksheet X. PE does not distinguish advance payments from the reconciled credit (used_aca_ptc stands in for both), so the 5.03 cap's advance-payment and repayment-limitation terms need a modeling choice.

Acceptance tests

Pub. 974 (2025), pp. 60–63, Carla. A 2025 joint return with two dependent children. The family is enrolled in the applicable SLCSP at $13,000 a year, with $4,200 of APTC, and all premiums are specified premiums. Income and deductions:

  • Jim's wages $83,675, taxable interest $419, Carla's Schedule C profit $30,000; total income $114,094.
  • Deductible part of SE tax $2,119; Carla's qualified plan deduction $2,500.

Expected values:

  • Worksheet W: line 13 (earned income limit) $25,381.
  • Worksheet X: line 31 (Step 1 deduction) $12,050.
  • Step 1: AGI $97,425.
  • Step 2: PTC $6,862. That is 312% of the $31,200 poverty line, an applicable percentage of 6.30%: 13,000 − 0.063 × 97,425.
  • Step 3: deduction min(13,000 − 6,862, 12,050) = $6,138.
  • Step 4: AGI $103,337, Form 8962 line 26 $1,794, so the total PTC is $5,994.

Rev. Proc. 2014-41 section 7 (PDF pp. 9–15) adds four 2014 examples with the alternative calculation:

  • Example 2: deduction $6,322, credit $6,770.
  • Example 3: a $3,000 nonspecified premium first, then $5,797 and $7,005.
  • Example 4: section 6 proration, $2,483 and $4,406.

Their 2014 applicable percentages come before PE's 2018 start, so use them for structure only.

Found while fixing the employer-plan exclusion (PR #10122) and the earned income limit (PR #10109). The evidence script is in the first comment.

Activity

  1. MaxGhenis commented on Oct 11, 2026

    @MaxGhenis
    ContributorAuthor

    Evidence script for the table and the cycle above. Run it from a policyengine-us checkout with #10122 applied; output follows the script.

    """Evidence for the Pub. 974 / 280C(g) assessment (scratch, not committed)."""
    from policyengine_core.reforms import Reform
    from policyengine_us import Simulation
    from policyengine_us.model_api import *
    
    Y = 2025
    # A self-employed single adult in Florida buying the benchmark Marketplace plan,
    # who reports the plan's full (gross) premium as health insurance premiums.
    def situation(premiums, profit):
        return {
            "people": {"you": {
                "age": {Y: 45}, "is_self_employed": {Y: True},
                "self_employment_income": {Y: profit},
                "health_insurance_premiums": {Y: premiums},
            }},
            "tax_units": {"tu": {"members": ["you"]}},
            "households": {"hh": {"members": ["you"], "state_code": {Y: "FL"},
                                   "county_fips": {Y: "12086"}}},
        }
    
    for profit in [30_000, 45_000, 60_000]:
        base = Simulation(situation=situation(0, profit))
        slcsp = float(base.calculate("slcsp", Y)[0])
        sim = Simulation(situation=situation(slcsp, profit))
        d = float(sim.calculate("self_employed_health_insurance_ald", Y)[0])
        ptc = float(sim.calculate("aca_ptc", Y)[0])
        print(f"profit {profit:>7,}: benchmark premium {slcsp:,.2f}; deduction {d:,.2f}; PTC {ptc:,.2f}; "
              f"deduction + PTC - premiums = {d + ptc - slcsp:,.2f}")
    
    # Netting the credit from the premium base (280C(g)) closes a cycle.
    class self_employed_health_insurance_premiums(Variable):
        value_type = float
        entity = Person
        definition_period = YEAR
        defined_for = "is_self_employed"
        def formula(person, period, parameters):
            premiums = person("health_insurance_premiums", period)
            ptc = person.tax_unit("aca_ptc", period)
            return max_(premiums - ptc, 0)
    
    class net_ptc(Reform):
        def apply(self):
            self.update_variable(self_employed_health_insurance_premiums)
    
    sim = Simulation(situation=situation(9_000, 45_000), reform=net_ptc)
    try:
        sim.calculate("self_employed_health_insurance_ald", Y)
        print("no cycle")
    except Exception as e:
        print(type(e).__name__)
        print("\n".join(l for l in str(e).splitlines() if ">>" in l))

    Output:

    profit  30,000: benchmark premium 6,976.57; deduction 6,976.57; PTC 6,976.57; deduction + PTC - premiums = 6,976.57
    profit  45,000: benchmark premium 6,976.57; deduction 6,976.57; PTC 5,847.62; deduction + PTC - premiums = 5,847.62
    profit  60,000: benchmark premium 6,976.57; deduction 6,976.57; PTC 3,768.98; deduction + PTC - premiums = 3,768.98
    CycleError
      >> self_employed_health_insurance_ald@2025 (branch default)
      >> self_employed_health_insurance_ald_person@2025 (branch default)
      >> self_employed_health_insurance_premiums@2025 (branch default)
      >> aca_ptc@2025 (branch default)
      >> is_aca_ptc_eligible@2025 (branch default)
      >> aca_magi_fraction@2025 (branch default)
      >> aca_magi@2025 (branch default)
      >> medicaid_magi@2025 (branch default)
      >> adjusted_gross_income@2025 (branch default)
      >> irs_gross_income@2025 (branch default)
      >> taxable_unemployment_compensation@2025 (branch default)
      >> tax_unit_taxable_unemployment_compensation@2025 (branch default)
      >> taxable_uc_agi@2025 (branch default)
      >> taxable_social_security@2025 (branch default)
      >> tax_unit_taxable_social_security@2025 (branch default)
      >> tax_unit_combined_income_for_social_security_taxability@2025 (branch default)
      >> taxable_ss_magi@2025 (branch default)
      >> self_employed_health_insurance_ald@2025 (branch default)
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions