John Barrios · Yale School of Management Claude Code for Accounting Research

Module 3: EDGAR Text — Structured Data, and Your First Skill

Module 3 slides (PDF) · Lab

Accounting research meets AI most naturally in text. A 10-K’s Risk Factors section, its MD&A, its footnotes — this is disclosure an agent can fetch, parse, and count at a scale no RA could match by hand, and the raw material for the measures that show up in a paper’s main table as “tariff exposure” or “cyber risk disclosure” three sections later. That combination is also what makes this the most dangerous stretch of the course so far. A keyword count is not a construct. Getting from “the filing contains the word ‘tariff’ eleven times” to “this firm is meaningfully exposed to trade policy risk” is a measurement claim, and measurement claims are exactly where accounting expertise earns its keep — no amount of agent capability substitutes for the judgment of knowing what a disclosure measure is supposed to capture and where it can quietly fail to capture it.

This module has two acts built around one lesson. In the morning you build a real pipeline — CIK resolution, filing retrieval, section extraction, a DuckDB store, a keyword measure — and you build it the way a paper actually gets built: mostly automatable, with one stubbornly hard last mile that requires you, not the agent, to look at the failures by hand. In the afternoon, you take everything painful about that pipeline — the retries, the regex fixes, the caching logic you had to get right — and convert it into a SKILL.md, a packaged, reusable procedure that any future session, yours or a coauthor’s, can run without you re-explaining a thing. The two acts are the same lesson told twice: this module’s hard-won procedure is worth writing down once, precisely, so nobody — including future you — has to relearn it.

Pipeline diagram: CIK resolution, filing retrieval, section extraction, and a DuckDB store, with a quality report attached to each stage.

The EDGAR pipeline: CIK resolution, filing retrieval, section extraction, and a DuckDB store, with a quality report at every stage.
NoteLearning objectives

By the end of this module, you should be able to: front-load an EDGAR pipeline prompt with the CIK concept, the rate limit, and the User-Agent requirement, and explain why doing so up front saves real iteration time; describe why 10-K section extraction needs a multi-regex fallback rather than a single pattern, and read a quality report to diagnose the difference between a genuine edge case and a fixable regex gap; critique a keyword-count disclosure measure on construct-validity grounds and specify a stratified human-audit protocol you would run before publishing it; explain why a SKILL.md description has to be written as a trigger condition rather than a title, and demonstrate the difference on a skill you build yourself; and place this module’s habits inside the “Show Other People” framing — a skill converts something you do painfully once into something nobody on your team has to relearn.

EDGAR mechanics, and front-loading the facts that matter

EDGAR is public, and that changes the texture of this module relative to Module 2’s FSDS work in one specific way: there is a stable firm identifier — the CIK, a numeric code assigned once and never reused — that every downstream query keys off of, and there is a full-text search endpoint (efts.sec.gov) plus a per-filer submissions endpoint (data.sec.gov/submissions/CIK##########.json) that together get you from “I want Apple’s last five 10-Ks” to a list of accession numbers in one request. The SEC also enforces two operational facts that have nothing to do with judgment and everything to do with getting a script working on the first try: a rate limit of no more than 10 requests per second, and a mandatory User-Agent header that identifies you (a name and an email address, not a blank string or a generic library default) on every single request. Skip the header and the very first request 403s; ignore the rate limit on a 20-firm, multi-year pull and you risk a temporary block that costs far more time than the delay you were trying to save.

The single biggest time-saver in a module like this is not writing better code — it’s writing a better first prompt. Compare two ways of asking for the same pipeline. Vague: “get me the risk factors from some 10-Ks.” Front-loaded: “Fetch the Item 1A Risk Factors section from the 10-Ks filed by the CIKs in pinned_ciks.csv, for fiscal years 2021–2023, using the EDGAR full-text search and submissions endpoints. Respect a 10 request/second rate limit and set the User-Agent header to <name>@<institution>.edu on every request. Cache raw filing HTML to data/filings/ so a re-run never re-downloads a file that already succeeded.” The vague version reads as reasonable — it names the goal — but it leaves every operational fact for the agent to discover the hard way: it will hit the 403, recover, hit the rate limit, back off, and only then start actually parsing. The front-loaded version puts the same facts a domain expert already knows into the prompt before any code is written, and the difference shows up as fewer rounds of trial and error, not as a different final pipeline. This is worth noticing precisely because it cuts against the instinct that a shorter prompt is a more efficient one — for anything with known operational constraints, the efficient prompt is the one that states them.

10-K anatomy and the case for multi-regex fallback

Every 10-K nominally has an Item 1A (Risk Factors) and an Item 7 (MD&A), and in a perfect world extracting either section would be a single regular expression: find the heading, find the next heading, take everything in between. Filers do not cooperate. Across a real sample of even a few dozen firms you will find “ITEM 1A” in all caps with no punctuation, “Item 1A.” with a trailing period, the item number and the word “Risk Factors” split across a table-of-contents anchor tag and the actual section heading, and — the case that should make you nervous rather than confident — filings where the phrase “Item 1A” appears in cross-references elsewhere in the document, so a naive first match grabs the wrong location entirely. A single regex pattern that works beautifully on the first ten firms in your list will typically fail silently on the eleventh, and “fail silently” is the operative danger: a section extraction that returns three sentences of boilerplate that happened to sit between two matched headings does not look like an error, it looks like a very short Risk Factors section.

The fix is not a cleverer single pattern; it’s an ordered fallback chain — try the cleanest pattern first, and if it produces a suspiciously short match (say, under some threshold of expected section length), fall through to progressively looser patterns, logging which pattern actually fired for each filing. This is where the module’s central number lives. Building this lab’s pinned firm list, a first-pass extraction across the pinned CIKs and three fiscal years typically lands around 95 percent success on the first regex pass — a good number, and one that would look publication-ready if you stopped there. It is not publication-ready. Reading the quality report on the failures — not re-running the whole pipeline, just reading what it flagged — usually turns up a small number of genuine regex gaps (a filer’s unusual heading format the fallback chain hadn’t anticipated yet) that a second pattern fixes, pushing the number to around 99 percent. What is left after that fix is not a coding problem anymore: it is one or two filings with a structure so far outside the pattern space — a heavily reformatted amendment, a filer who nests Item 1A inside an exhibit — that a human has to open the filing and either hand-extract the section or make a documented decision to exclude it. That last mile is the whole lesson: automation plus iteration gets you from 0 to roughly 95, one targeted fix gets you close to 99, and a paper that actually gets published hand-checks every remaining failure rather than quietly dropping it from the denominator.

WarningCommon failure: treating the quality-report number as the deliverable

It’s tempting to report “99% extraction success” as if the number itself were the finding. It isn’t — the denominator matters as much as the numerator. A pipeline that silently drops the hardest 1% of filings from its sample has a selection problem, not a success rate, if those filings are systematically different (older, smaller filers, unusual corporate structures) from the ones that extracted cleanly. The one-line diagnosis for every failure, kept in the quality report rather than discarded, is what turns “99% extracted” into a defensible claim about your sample rather than a number that hides exactly which firms didn’t make it in.

Pipeline robustness: cache first, fix, and re-run touches only what’s missing

None of the iteration above is affordable if it means re-scraping the entire firm list every time you tweak a regex. The habit that makes it affordable is caching raw HTML to disk on first download — one file per filing, named predictably — before any extraction logic runs against it. Extraction then becomes a pure function over files already sitting locally: fix the regex, re-run the extraction step, and the pipeline should touch zero network requests, because every filing it needs is already on disk. This is not an optimization you bolt on later; treat it as part of the pipeline’s first draft, the same way you treated named scripts as non-negotiable in Module 2. Twenty firms times three years times a rate limit of 10 requests per second is a real amount of wall-clock time and a real amount of exposure to a temporary block if you re-trigger it every time you adjust one pattern.

The second habit that makes iteration tractable is a quality report that runs automatically after every extraction pass: a count of successes and failures, and — critically — a flag for extractions that succeeded technically but returned a suspiciously short section, which is usually a sign the regex matched the wrong heading rather than a sign the firm actually wrote a two-paragraph Risk Factors section. The single most useful prompt pattern for this stage of the module is blunt and works because it is blunt: ask Claude directly why N filings are missing or flagged, and let it read the cached HTML for those specific cases and report back what’s structurally different about them. This is autonomous debugging in the sense Module 2 introduced — the agent investigates and proposes; you decide whether the fix is worth making or whether the case belongs in the “flag for manual review” pile.

Mock extraction quality report listing successes, failures with one-line diagnoses, and flags for suspiciously short sections.

A quality report in practice: successes, failures, and the suspiciously-short flags that turn a silent extraction gap into a diagnosable one.

DuckDB as the research store

Once sections are extracted, they need somewhere to live that is queryable but does not require you to stand up a server. DuckDB is the right tool for exactly this reason: it is a single file, requires no configuration or running process, and reads and writes from Python, R, or the command line interchangeably. For this module’s pipeline, three tables are enough — filings (one row per CIK-fiscal year, with the cached file path and extraction status), sections (the extracted text itself, keyed to the filing), and keywords (the counts you compute from that text, keyed the same way). You do not need to be fluent in SQL to get value from this: describe the aggregate you want in plain English — “give me the total tariff-keyword count by firm, sorted highest to lowest” — and let Claude translate that into the query, then read the query it wrote before trusting the numbers. The selling point for an accounting researcher who has spent a career in Stata is not SQL fluency; it’s that a folder of forty CSVs — one per firm-year, easy to lose track of — becomes one file, edgar_panel.duckdb, that any script or any coauthor can open and query without asking you which version is current.

Measurement validity: what makes this an accounting course

This is the part of the module where domain expertise does work that no amount of agent capability replaces, and it deserves the most careful treatment of anything covered so far. Start from the plainest version of the problem. Suppose your keyword table reports that a firm’s 10-K mentions “tariff” fourteen times in its Risk Factors section this year, up from six last year. The temptation is to read that jump as evidence of rising trade-policy exposure. It might be. It might also be almost entirely mechanical: a law firm template got updated, the firm’s boilerplate risk-factor language was copied forward with one paragraph inserted, and the actual change in the firm’s economic exposure to tariffs is close to zero. A keyword count is a measure of word frequency in a document; “exposure to trade policy risk” is a construct about the firm’s economic situation. The gap between the two is construct validity, and it is not a technicality — it is the single most common way a disclosure-based measure ends up wrong in a way that survives peer review, because the measure looks reasonable, the coefficient is significant, and nobody checked whether the words being counted actually track the thing being claimed.

Boilerplate is the specific failure mode worth naming, because it is both common and detectable. Public filers reuse risk-factor language year over year far more than a first read of “disclosure” suggests — the same law firm drafts filings for dozens of clients, the same paragraph gets carried forward with minor edits, and a keyword count treats a copy-pasted paragraph exactly the same as a rewritten one. The detectable signal is year-over-year textual similarity: compute a similarity score (Jaccard similarity on shingled text, or cosine similarity on a bag-of-words or embedding representation) between a firm’s Risk Factors section this year and the same section last year. A high similarity score alongside a rising keyword count is close to a smoking gun for boilerplate inflation — the keyword count went up because the section got longer or the topic got mentioned once more, not because the underlying disclosure meaningfully changed. A low similarity score alongside a stable or rising keyword count is closer to what a real disclosure-change measure should look like: the firm rewrote the section, and the new language is where the keyword signal is coming from. Neither similarity score alone settles the question of whether a given measure is valid — they are diagnostics, not proof — but a paper that reports a text-based measure without having run this check at all has skipped a step referees at any serious accounting journal will ask about.

Diagram mapping measurement threats — boilerplate, mismeasured constructs, unaudited claims — to their defenses: similarity scoring, rubric coding, and a stratified human audit.

Measurement validity in practice: the threats — boilerplate, mismeasured constructs, unaudited claims — mapped against their defenses, from similarity scoring to the stratified human audit, converging on a defensible measure.

The check that actually settles the question, and the one that is not delegable, is a human audit sample. Before a text-based measure goes into a paper’s main table, someone who understands the construct has to hand-read a sample of the underlying filings and code, by hand, whether the measure means what the paper claims it means. The protocol that makes this defensible rather than ad hoc has three parts. First, the sample has to be stratified, not convenience-drawn: pull filings across the range of the measure (high, middle, and low keyword counts), across firm size, and across the industries in your panel, so the audit doesn’t end up checking only the easy cases. Fifty filings, stratified this way, is a reasonable target for a measure that is going into a working paper — enough to catch a systematic problem, small enough to actually finish. Second, the coding has to be done against a written rubric decided in advance — what counts as “genuine tariff exposure” language versus boilerplate mention, stated before you start reading, not adjusted filing by filing as you go. Third, if more than one person codes any part of the sample, report an inter-rater agreement statistic; if only one person codes it, say so plainly rather than implying a validation step that didn’t happen. None of this is optional cleanup work to be done if time allows — it is the step that turns “we counted a word” into “we have evidence this measure captures what we say it captures,” and it is squarely the researcher’s job, not the agent’s. Claude can build the pipeline that produces the keyword counts and the similarity scores in an afternoon; only a human who understands the accounting construct can read fifty filings and say whether the number means what the paper claims.

TipVerify this: the protocol is the deliverable, not the measure

This module’s lab does not ask you to prove your tariff-mention measure is valid — fifty filings is not enough to prove anything at the scale a real paper would need, and pretending otherwise would be its own validity failure. What it asks for is the protocol you would run before trusting it: which fifty filings, stratified how, coded against what rubric, by whom. Writing that protocol down, in three sentences, before you’ve convinced yourself the number is fine, is the one habit from this module most likely to save a published claim from a referee’s construct-validity objection.

Everything you just did painfully is an SOP

Step back and look at what the morning actually required: a front-loaded prompt naming operational facts you had to already know, a fallback chain of extraction patterns built by iterating on failures, a caching layer that made iteration affordable, a quality report you read by hand, and a validity check that only you could run. None of that was a one-time cost specific to this list of twenty firms — it is the standard operating procedure for turning any EDGAR text corpus into a measured panel, and you will want to run it again in three months on a different topic, a different firm list, or a revision requested by a referee. The natural instinct is to keep the procedure in your head, or scattered across old chat transcripts and half-remembered prompt phrasing. A skill is the alternative: a packaged, written-down instruction set that a fresh Claude Code session can read and execute correctly on the first try, without you re-explaining a single step.

The framing worth carrying forward is one used by researchers who build these procedures for a living: a skill exists to “Show Other People” — it takes something you currently do by re-explaining it in a new chat every time and turns it into something any collaborator, present or future, can run without your involvement. For a PhD student managing RAs, this framing should land immediately: a skill is the artifact you’d hand a first-year RA on day one, except it never forgets, never phrases the instructions slightly differently on a bad day, and never leaves when they graduate.

SKILL.md anatomy — and the error almost everyone makes first

A skill lives as a folder containing a SKILL.md file, with a YAML frontmatter block at the top (a name and a description) and a body that spells out the procedure in steps, optionally alongside supporting files (templates, reference scripts) the skill can point to. The frontmatter is short; the mistake that determines whether the skill ever runs lives entirely in one field, and it’s worth seeing it done wrong before seeing it done right.

Here is the wrong version, and it is wrong in a way that looks completely reasonable the first time you write it:

---
name: edgar-panel
description: EDGAR filing tool
---

This skill will never fire. Not because the procedure written below it is broken, but because description is not a label — it is the trigger condition an agent matches against to decide whether this skill applies to the current conversation. “EDGAR filing tool” describes what the skill is; it gives the matching logic nothing to recognize in an actual request. A student asks for “risk factor counts for these tickers,” and nothing in “EDGAR filing tool” connects to that sentence. The skill sits in the folder, correctly written, permanently unused.

Here is the same skill with the one field rewritten as a trigger:

---
name: edgar-panel
description: Use when the user asks to scrape 10-K filings, build an EDGAR text
  dataset, or names a set of tickers or CIKs alongside a form type (10-K, 10-Q)
  and a date range.
---

Nothing else about the skill changed. The difference is that this description names the situations a real request will actually look like — “scrape 10-Ks,” a ticker list plus a form type, a date range — so the matching logic has concrete phrases to recognize. This is, by a wide margin, the single most common design error in a first skill, and the reason it’s worth seeing demonstrated rather than just described: a title reads as correct, compiles as valid frontmatter, and simply never triggers, which means the failure is invisible until you go looking for it in a fresh session and the skill you were sure you’d built doesn’t fire.

Annotated SKILL.md file: YAML frontmatter with a name and a trigger-condition description, numbered procedure steps, and pointers to supporting files.

Anatomy of a SKILL.md: frontmatter with a trigger-condition description, a numbered procedure, and pointers to supporting files.

The five-step workflow for building your first skill

Converting a painful procedure into a working skill follows a short, repeatable sequence. First, notice the repetition — you’re writing essentially the same instructions into a new session for the third or fourth time, which is the actual signal that something should become a skill rather than a hunch about what future-you might need. Second, name the inputs precisely: for this module’s pipeline, that’s a CIK list, a form type, and a fiscal-year range — the exact parameters that vary from run to run while the procedure itself stays fixed. Third, design the output structure before writing a line of the skill body — a DuckDB file with the schema from earlier, a metadata.csv describing what’s in it, and a quality report — because this is where a first attempt most often goes wrong: it’s easy to describe the steps and skip specifying exactly what the skill should leave behind when it finishes. Fourth, let Claude draft the SKILL.md itself from a one-paragraph spec covering the four points above; a skill is code Claude is generally good at writing once you’ve done the harder work of deciding what it should do. Fifth, test-fire it — in a fresh session, not the one where you built it — on two firms you didn’t use while developing the skill, and confirm it produces the output structure you specified without you re-explaining anything.

Mock session where a natural request matches a skill's trigger description and the packaged procedure runs without re-explanation.

A skill, firing: a fresh session recognizes the trigger condition in the description and runs the packaged procedure without re-explanation.

Two notes on scope. A skill can live at the project level (.claude/skills/, checked into your project’s git repository, so a coauthor who clones the repo inherits it automatically) or globally (~/.claude/skills/, available across every project on your machine). The rule of thumb: project scope for anything tied to one paper’s pipeline, global scope for something you’ll reach for across many projects regardless of topic — a writing-style skill, say, rather than a firm-specific data pipeline. The cost of getting this wrong is not correctness but context: every globally installed skill’s description loads into every session you open, whether or not that session needs it, so a global folder cluttered with narrow, project-specific skills is a quiet tax on every unrelated conversation you have from then on.

The bridge to Module 4

Your text panel keys everything to CIK. Firm fundamentals — assets, leverage, R&D spending — live in Compustat, keyed to gvkey, and the two do not speak to each other without a crosswalk. That crosswalk exists on WRDS, and in the next module you’ll use it for real, from your own account, through an MCP server that lets Claude query Compustat without your password ever touching the conversation. This module’s pipeline has CIKs; Module 4’s lab is what happens once those CIKs can find their financial statements.

From here, head to this module’s lab to build the pipeline, run the validity check, and write your first skill. Two mantras carry over from Module 1 with new force in this module: trust, but verify now means reading fifty filings by hand before you trust a text measure, and files persist, context doesn’t is the entire argument for writing a SKILL.md instead of re-explaining the same procedure next month.