Skip to content

Extracting Expense Categories From a Schedule C

9 min read · updated August 11, 2026

Schedule C is a good document to extract, because the numbers on it have to agree with each other in four places. It is also a document whose line numbering has changed within the last few years, which decides how the output should be stored.

Five parts and where the numbers flow

The form reports the profit or loss of a sole proprietorship, and it is organised as five parts that feed each other rather than as a flat list.

  • Part I, income. Gross receipts, returns and allowances, cost of goods sold, gross profit, other income, gross income. Seven numbered lines, ending in the figure the expenses are deducted from.
  • Part II, expenses. The category lines — advertising, contract labour, depreciation, insurance, interest, legal and professional services, office expense, rent, repairs, supplies, taxes and licences, travel, meals, utilities, wages and a residual other-expenses line — running from line 8 to line 27 and totalling on line 28.
  • The profit calculation. Line 29 is the tentative profit, line 30 the deduction for business use of the home, and line 31 the net profit or loss that carries to the taxpayer’s Form 1040. Lines 32a and 32b are at-risk checkboxes that only apply when there is a loss.
  • Part III, cost of goods sold. An inventory calculation on its own numbered lines whose total feeds the cost of goods sold line back in Part I.
  • Part IV, vehicle information, supporting the car and truck expense line, and Part V, other expenses, which itemises whatever went into the residual line in Part II and totals on line 48.

The four flows between parts are what make this worth extracting carefully rather than reading off as twenty numbers.

The form checks itself

# Part III -> Part I
cost_of_goods_sold      == part_iii_total

# Part V -> Part II
other_expenses_line     == line_48

# within Part II
line_28  == sum(all Part II expense lines, 8 through 27)

# the profit chain
line_29  == gross_income - line_28
line_31  == line_29 - line_30

These are the reason a Schedule C extraction can be trusted or rejected without a human reading it. In particular the line 28 identity is the only thing that catches the most common failure on this form, which is a dropped expense line: twenty-odd rows in a single column, several of them blank on any given return, and an extractor that misses one still produces a plausible object with a plausible profit. The total is what exposes it, and the residual names the amount to look for — the same diagnostic logic as reading a bank statement residual.

Two cautions. Blank is not zero on a tax form; a line the taxpayer left empty and a line they entered zero on are different marks, and where a later assertion depends on the distinction you want to have kept it. And a negative on this form is printed in parentheses, so a parser that strips punctuation before converting turns a loss into a profit of the same magnitude — which the line 31 identity will catch only if you kept the sign on line 29 and line 30 correctly too.

The lines with an a and a b

Several Part II lines split into a pair. Interest divides into mortgage interest paid to financial institutions and other interest. Rent or lease divides into vehicles, machinery and equipment on one side and other business property on the other. Travel and meals occupy a pair. And the final expense line is itself split.

Each half is a separate amount on a separate print line with its own caption, but the caption of the pair is printed once, in bold, beside the first half. A table extractor working row by row therefore sees a row with a caption and an amount, then a row with a short caption and an amount, and frequently attributes both amounts to the parent caption, or attributes the b amount to the a concept.

There is no clever prompt for this. The reliable approach is to extract the line label as printed — including the letter — treat each lettered line as its own record, and let the line 28 total decide whether you got them all. A merge shows up as a total that is too low by exactly one of the halves.

Which lines move between tax years

This is the part that decides your storage design. The expense line numbers are not stable across revisions of the form, and neither are the captions.

The clearest example is the pair at the end of Part II. On the instructions for the current revision, line 27a is the energy efficient commercial buildings deduction and line 27b is the other-expenses total carried from line 48; on earlier revisions those two letters carried the opposite contents, and one of the surrounding lines has been marked reserved for future use at various times. The meals line has also changed caption, having previously covered entertainment before that deduction was removed from the tax code.

Line numbers, letters and captions on this form change between tax years. Verify against the IRS Instructions for Schedule C for the specific year you are processing rather than relying on any mapping here, including this one.

The consequence is blunt: a ledger keyed on “Schedule C line 27a” means different things in different years of its own history, and nothing in the data records that. Any longitudinal report built over it — this year’s advertising spend against last year’s — is comparing two concepts that share a label.

A schema that survives a revision

Store the line as it was printed, and map to a stable internal identifier through a table keyed on the tax year.

{
  "tax_year": 2025,
  "part": "II",
  "line": "20b",
  "caption_as_printed": "Other business property",
  "amount": 24000.00,
  "category_id": "rent_other_property"   # from the per-year map
}

Three properties follow from that shape. The category_id is stable across years, so reports compare like with like. The printed caption is retained, so when a mapping turns out to be wrong you can re-derive without touching a single scanned page. And the line number survives, so you can still answer “what was on line 20b of that return” when somebody with the paper in front of them asks.

That stable identifier is also the natural target for anything feeding expenses in from the other direction. The category set built in categorising receipt line items should map onto these ids rather than onto line numbers, and so should a chart of accounts; then the tax form is one projection of a stable internal category set rather than the schema everything else has to bend around. Income figures reported on a 1099 land on the Part I side of the same structure.

Nothing here is tax advice, and an extracted Schedule C is a record of what was filed rather than a judgement about whether it was filed correctly. The value of the identities above is that they tell you whether you read the document accurately, which is a question about your pipeline and not about the taxpayer.