Every US insurer has to file its products and prices with state regulators before it can sell or reprice. Those filings are public, but they live as multi-document PDF packets on SERFF — the System for Electronic Rate and Form Filing, a national portal that is a workflow, not a database. I built a production competitive-intelligence platform that collects those packets, extracts a product, rate, and provision ontology with page-level citations, and serves a fifty-state picture of who is selling what, where, at what price, and on which terms. Insurers have used it to go to market and for pricing intelligence. A few million dollars of projects have run on it. No one assigned this. I found the problem and built it.
Insurers cannot see who is selling what, where, at what price, and with what provisions, across fifty states. Before a carrier launches a group dental PPO in Ohio or reprices a vision rider in Texas, product and pricing teams need the competitive set: which carriers are approved, which forms they filed, what the rate pages actually say, and which waiting periods, age bands, participation rules, and commission schedules they locked in. The answers exist. Every licensed insurer files them with state departments of insurance. Most of that record sits on SERFF.
The portal is a workflow. Search is a multi-step form that expires sessions, paginates unpredictably, and rejects naive crawlers. A filing is not one PDF. It is a packet: policy forms, rate manuals, actuarial memoranda, redlines of prior versions, correspondence, and supporting exhibits. Product names do not travel. “PPO Dental — Large Group” in Michigan is “Employer Dental Plan B” in Florida, filed by a legal entity that does not match the brand the sales team knows. Rate factors hide in scanned tables. The provisions that move price sit in different exhibits depending on the state and the year.
Pricing and product teams still do this by hand. An analyst pulls a handful of packets, reads them, and pastes fragments into a workbook. Coverage is late and incomplete. Go-to-market decisions land after a competitor has already filed. Competitive pricing is a sample, not a market.
I treated this as a production data platform, not a scrape plus a chat window. Collection is headed Playwright against SERFF and a few state portals that never joined it, because those sites reject naive crawlers. The worker keeps a real browser session, walks the multi-step search, retries on timeout, and checkpoints after every filing so a job can resume without re-walking the queue. Temporal owns the orchestration: collect, parse, extract, resolve, publish. Raw packets land in S3. Filing metadata — carrier, state, type of insurance, tracking number, status, effective date — lives in Postgres.
The document pipeline splits each packet, classifies form types, OCRs the scans, and runs a layout-aware parse so tables and exhibits keep their structure. Extraction is schema-constrained. Pydantic models define a product, rate, and provision ontology; an instructor-style call has to fill those models or fail closed. Agents on the OpenAI Agents SDK run extract, then normalize, then enrich, under guardrails that refuse any field that cannot point at a page and a snippet. LiteLLM routes the model per stage — cheaper for classification, stronger for actuarial memos.
Entity resolution is a second pass. Embeddings propose that two carrier names or product labels are the same thing; deterministic rules and a small graph of known aliases decide. Twenty states become one dataset instead of twenty spelling contests. Serving is a warehouse, an API, a comparison workbook, and a dashboard. Go-to-market and pricing teams filter by state, line, carrier, and effective date, then open a cited field rather than a chat transcript.
Insurers have used the platform to go to market and to price against what competitors have actually filed, not against last year’s rumor. A product team can see, state by state, which forms are approved, which rate structures are in force, and which provisions a peer locked in before they write their own packet. A pricing desk can compare factors and waiting periods with a citation behind every cell.
A few million dollars of projects have run on it — competitive scans ahead of a launch, rate reviews when a peer files a change, and form-by-form comparisons when a state objects to language someone else already got approved. I found the gap — a public record nobody could query as a market — and I built the system that turns it into one.
Every extracted field carries a citation: document, page, and verbatim snippet. If the source is silent, the field is empty. Nothing is invented to fill a schema. The run cannot publish a rate factor that is not in the packet.
I keep a labeled gold set of filings and score field-level precision and recall on every extract and normalize change. Phoenix traces each run. Promptfoo regressions sit in CI so a prompt edit cannot silently drop waiting periods or invent a rate factor. Failures show up as missing fields with a reason, not as plausible numbers.
Python, Playwright, Temporal, AWS (S3, EC2), Postgres, Pydantic, OpenAI Agents SDK, LiteLLM, Phoenix, Promptfoo, pdfplumber, Tesseract, pytest.