Skip to main content
Every company that files an annual report with the SEC describes its own business in it. In this cookbook we classify those descriptions under the Standard Industrial Classification: 75 industry groups, one Choice per document. Most filings are easy: a regional bank is a regional bank. Some genuinely are not. A company that just sold one of its two segments, say, or a startup describing a business it plans to enter rather than one it runs. The model has to pick a group either way, and its answer looks the same either way. Telling the hard cases from the easy ones is normally where the cost goes: a second model, extra calls, human review. A Choice already tells you. Alongside the winning option it returns confidence, high when nearly all the probability landed on one option and low when it spread across several. That one number separates the answers you can trust from the ones you can’t. What to do with an untrusted answer depends on your labels. SIC labels form a hierarchy: industry groups roll up into broader divisions. That makes one response nearly free. When the model is unsure of the group, report the division it belongs to. The broad label follows from the narrow one, so there is no second call. Across 60 filings, a confidence cutoff of 0.9 splits them in half. The confident half is right 90% of the time; the other half, 40%. Reported one level up, that 40% becomes 70%. We end with a classify() function that returns a label plus how specific it is, at one request per document.

Setup

then set TYPESAFE_API_KEY. Every API call is cached to json_cache.json, which ships with the cookbook, so re-rendering replays the published numbers without calling the API. Delete that file to re-run everything live. Numbers below came from jev-1.12 on 2026-08-12.

Build the two levels of the taxonomy

sic_codes.tsv is the industry list the SEC publishes for filers to pick their own code from, fetched 2026-08-10: 444 four-digit codes, each with an industry title. The digits are a hierarchy. The first two are the major group (75 of them here, from 01 agricultural production to 99 non-classifiable), and fixed ranges of major groups make up the ten divisions, the broadest split SIC has. Both levels come out of that one file with no model involved: group the codes by their first two digits, then map those digits to a division.
A Choice needs something to describe each option, and a group’s own name is not always there: 42 of the 75 carry an umbrella title in the SEC’s list, and the rest carry none. So each group is described by the industries inside it, which is what someone reading the filing would match against anyway.

The filings

filings.jsonl holds 60 annual reports (10-K), each trimmed to Item 1 “Business”, the section where a company describes what it does, which is the only part an industry code is about. They span 1993–2024 and run from 700 to 2,200 words. Each one carries the SIC code its filer chose, plus the accession number to look it up on EDGAR. Where that label comes from matters before any accuracy number. It is self-reported: whoever prepared the filing picked it once, and it goes stale when a company sells the business the code names and keeps the code. These 60 were filtered down to filings whose own text supports the code they carry, so the numbers here measure the recipe rather than the state of EDGAR’s metadata.

Ask one Choice, and read the confidence

One Choice whose options are the 75 groups. The whole taxonomy fits in one request: a Choice works reliably up to roughly 240 options, and 75 is well inside that. The answer comes back with choice, the winning group; probabilities, the weight on each of the 75; and confidence, which says how concentrated that spread was. The recipe reads confidence rather than the winner’s own probability. A winner at 0.45 with a runner-up at 0.44, and a winner at 0.45 with the rest of the weight scattered thinly, are different situations, and confidence is what separates them.

Return the group when sure, its division when not

The four lines below are the whole recipe. At 0.9 confidence or above, the answer is reported as an industry group; below that, the same answer is reported as the division that group sits in. Every filing still comes back with a usable label. One the model could not classify confidently comes back one level up instead of being dropped or sent on. If a division is too coarse for your application to act on, this branch is where you hand it to a person.
The confidences line up with how hard each filing is to classify. The three at 1.00 are a pharmaceutical maker, a life insurer and a utility; all three are holding companies on paper, but each has one dominant business the filing names outright. The three at the bottom are harder for reasons you can read in the text. Two are development-stage companies describing a business they intend to start (Nevaeh “intends to operate as a software developer”, Barricode was “organized to enter into the computer security software industry”), and the third had two segments and sold one of them weeks before filing. Those three come back as a division rather than a group. classify() is the whole recipe. Point ask() at your own documents and rewrite describe() for your own taxonomy, and the rest carries over.

What the broader answer buys

All 60 filings, scored against the code each filer chose, under both policies: name a group every time, or report the division whenever confidence lands under 0.9.
Where the model was sure, the group it named is right nine times in ten. Where it was not, naming a group was wrong more often than right, at 40%. Reporting those same answers as a division takes them to 70%. The chart puts the two policies side by side, split by whether the model was sure.
output

Open it in the playground

This share link holds one filing and the 75-option question, so you can see the distribution and the confidence it produces without writing any code.
Open the filing + question in the TypeSafe playground →