Cascadia Fee Examiner
Entity resolution across a bankruptcy research database, a public court docket, and two task-code vocabularies that share no identifier — a stated rule resolves what it can, and the rest is published, not dropped.
Legal Operations Analytics
Cascadia Fee Examiner
A frozen bankruptcy research database, a public court docket, and two professional task-code vocabularies — three sources, three owners, none sharing an identifier. A stated, published rule resolves 9 of 25 caption-to-database firm pairs; the other 16 are reported with the reason they didn’t match, not dropped. The acquisition itself is reported at its actual state, not its finished one: 48 of 216 eligible cases searched so far.
Three systems, one firm, no shared key
Overview
A fee examiner is the officer a bankruptcy court appoints to review what the professionals actually billed the estate — a cost record the examiner did not produce, checked against vocabularies the examiner did not write. This module takes that posture and builds the spine underneath it: a frozen copy of the Florida-UCLA-LoPucki Bankruptcy Research Database, fee applications and fee orders pulled from the public docket through CourtListener’s RECAP archive, and two professional task-code vocabularies — the ABA’s 1997 UTBMS code set and the U.S. Trustee’s Appendix B categories. Three sources, three owners, and none of them shares an identifier with another.
Every other module in this portfolio proves a spine can be built from one source, or from sources that already share a key. This is the first to resolve identity where no key exists at all — the same firm named one way in an academic database’s editorial record and another way in a court filing’s caption, with nothing but the text itself to join them on. The answer here is not a similarity score. It is a stated, auditable rule, written down before a single row was resolved, plus a policy of publishing what the rule cannot resolve rather than dropping it or guessing at it.
The population is 216 large, public-company Chapter 11 cases filed under the U.S. Trustee’s Appendix B fee guidelines. 48 have been searched against the docket archive so far; the rest are not “uncovered” — they are not yet searched, and the module keeps those two claims apart rather than letting one stand in for the other.
Why This Stack
Python’s standard library, plus two dependencies. csv, json, hashlib, re, pathlib and a handful of others do the conforming. The only third-party packages anywhere in the build are requests, to pull from the RECAP API under its own rate limit, and Playwright, to render the published charts through headless Chromium — the same renderer that produced the images the blind reading panel saw.
There is deliberately no database. The frozen spine is 1,218 cases; the acquisition has retrieved 63 documents so far. At that scale a warehouse is scaffolding around a problem that was never about row count — every artifact is a flat CSV or JSON file, hash-verified where it is frozen and rebuilt from scratch where it is live. The hard part was never fitting the data anywhere; it was that three sources describing the same events do not agree on what to call them.
Architecture
LoPucki Bankruptcy Research Database (frozen, hash-verified) → subject-matter exclusion → Appendix B-era filter → resumable RECAP acquisition → UTBMS/USTP crosswalk + entity resolution → fee-award reconciliation attempt → static ECharts, rendered by Playwright.
- Freeze. The BRD Cases table is pulled once and verified by SHA-256 against the frozen copy on every build. The as-of date, 2023-01-12, is the publisher’s own release date, not a retrieval date. 1,218 cases sit in the snapshot; 95 are excluded at an industry-code test before any measure runs, because no telecommunications debtor is analysed in this module; 216 fall inside the Appendix B era that makes fee data reachable at all.
- Acquire. A resumable pull against CourtListener’s RECAP archive, bounded by a 50-requests-per-hour limit on either credential tier. 48 of 216 cases have been searched; 168 have not — reported as not yet searched, a different claim from not covered, never conflated with it. 6 searched cases were truncated at a page cap and are excluded from the reach rates rather than counted as complete.
- Conform. Two independent resolution problems, run separately. The first crosswalks the ABA’s 1997 UTBMS task-code set against the U.S. Trustee’s Appendix B categories — 9 of 22 categories match exactly, 8 are renamed only, 2 are broader in Appendix B, and 3 have no UTBMS counterpart at all. The Trustee’s own adoption notice claims all but one of seven older categories carried forward; this build tested that claim instead of repeating it, and named the one: Reporting. The second resolves firm identity between the database’s editorial name and the docket caption’s filer string, by the rule below.
- Reconcile. Fee applications are checked against fee orders on the same docket. None have paired cleanly yet — every unpaired row states which of three reasons stopped it, down to confirming that a missing order is a coverage gap in the public archive, not a parsing failure.
- Publish. Three independent gates must pass before anything renders;
render_charts.pythen drives headless Chromium to produce the static charts.
Headline Skill: Resolving Firm Identity, and Publishing What It Can’t
The differentiator is not that two names got matched. It is that the rule was written down before a single row was resolved, and that a near-100% match rate is treated as a warning sign rather than a win.
The rule. A candidate pair matches when the database’s short, editorial firm name is a token subset of the docket caption’s longer, formal name — never a similarity score, and never a single shared token unless that token is the database name’s only token and it is unique across every candidate caption on that docket. Both sides are normalized first: lowercased, accents stripped, “&” expanded to “and,” entity suffixes dropped (LLP, LLC, PC, Ltd…), role words dropped (attorneys, counsel, debtors, committee…).
The result, and what happens to the rest. 9 of 25 caption-to-database pairs resolve. The other 16 are written to a named file with a stated reason, not discarded — most often that the database recorded only the lead firm on a case where the docket caption names a co-counsel, local, or conflicts firm the database never captured. They are one shared mechanism so far, not sixteen distinct diagnoses; telling them apart by actual cause is recorded as the resolver’s own next build. A hand-authored override file exists for pairs the rule itself cannot reach — built, and unused: 0 overrides in the current run.
Publishing the miss is the point. The module’s own governance record states the anti-goal directly: a match rate near 100% would be evidence the rule is too loose, not that the data is clean.
Validation
Three independent gates, run separately from the scripts they check, and all three currently pass. One re-asserts the snapshot’s hash, confirms every file the freeze depends on is actually tracked by git rather than silently ignored, checks the output schema for an added as well as a forbidden column, and re-runs the subject-matter exclusion against the real output instead of trusting the run that produced it. A second is the same freeze-integrity script every Cascadia module carries, byte-identical by design. A third re-derives every published cell down a path implemented separately from the build — indexed and aggregated in a different order, so one bug cannot reproduce itself in both places.
The reconciliation check found something more interesting than a clean pass. Crossfooting a document’s two exhibits — its timekeeper table against its category summary — should confirm they describe the same hours. Run against the documents where both exhibits parsed, it found zero comparable, zero tied, and every one not comparable, for a stated reason: a final application’s category summary runs cumulative across the whole case, while its timekeeper table covers one billing period, and forcing those two into agreement would have been the wrong answer, not a passing check.
Then a blind reading panel read the rendered charts. Four seats, seeing only the five published chart images — no build notes, no source data, no brief, and no access to each other. They filed 15 findings. Every one was dispositioned on its own evidence: 9 fixed in the chart source; 2 that the panel had accepted with a stated reason were overridden into a real revision after the underlying data was checked directly; 1 resolved by a reclassification check neither the panel nor the original build could run; 3 rejected as filed.
Aaron then read the shipped rendering directly, in two further rounds the panel had no part in. The first caught what a fixed disposition list cannot: one chart’s court codes were unreadable to a reader who does not already have bankruptcy court abbreviations memorized, and three separate charts had each scaled their axis to a full category or pair count rather than to the tallest bar, leaving most of the plotted area empty. Both were fixed. The second round simplified further, dropping the unreadable court code from its chart entirely rather than only relabeling it.
The Module
Five charts, read against a health surface generated by the same gates that must pass before anything publishes — nothing on the page is restated by hand.
- Coverage by court — a ranked bar chart, one bar per court searched so far, reach from 0% to 100%. It carries one claim only: of the cases searched, how many returned at least one retrievable document. It says nothing about the cases not yet searched, and does not let a reader confuse the two.
- Vocabulary crosswalk — UTBMS against Appendix B, across four categories: exact, renamed, broader, and no counterpart at all.
- Firm-identity resolution — the 9 resolved pairs against the 16 that are not, all sharing one stated mechanism rather than sixteen separate diagnoses.
- Staffing split — a single fee application’s own hours and fees, by role. A reading-panel question changed this chart: it reads as one document’s internal split, not a sampled pattern, because the other two in-scope filings contribute only a pooled paraprofessional line.
- Award reconciliation — zero of eleven applications paired against what the court actually allowed, broken out by which of three reasons stopped each one. A follow-up check, prompted by the same panel question, confirmed the largest reason is a coverage gap in the public archive rather than a parser miss: the classifier found dozens of order-shaped docket entries correctly identified across the underlying dockets, every one marked unavailable — nobody has purchased and uploaded the document.
What it refuses to publish, and why. Total fees per case — the archive holds a minority of applications for most cases, so a total would sum an unknown fraction and read as complete. Anything at per-timekeeper grain — no per-person row exists anywhere in the pipeline; aggregation happens before anything is written. Anything cut by debtor identity — no output carries a party column. Awarded amounts as an input to any other measure — they are the reconciliation’s answer key, and an answer key used as an input stops being a check.
Roadmap
- Continue the acquisition: 168 of 216 cases remain unsearched, bound by a 50-requests-per-hour limit. A quarterly coverage re-check is designed but not yet scheduled to run on its own.
- Classify the 16 unresolved firm pairs by actual cause — an abbreviation mismatch reads very differently from a firm the database never recorded at all — rather than the single shared mechanism they currently publish under.
- Extend entity resolution to debtor entities filed under one lead case, which this build does not attempt.
Tech Stack
Python requests Playwright Apache ECharts Flat CSV / JSON Static HTML / JS GitHub Pages Git
Disclosure
Built from three public sources: the Florida-UCLA-LoPucki Bankruptcy Research Database, used under its published attribution terms; fee applications and fee orders retrieved from the federal courts’ public dockets via CourtListener’s RECAP archive; and two public task-code vocabularies — the U.S. Trustee’s Appendix B categories and the American Bar Association’s UTBMS code set, reproduced by the LEDES Oversight Committee with the ABA’s permission.
No natural person’s name appears anywhere on the page or in the module’s data — not a timekeeper, not an attorney of record, not a judge — and no telecommunications debtor is analysed; that exclusion runs at the spine, before any measure, on an industry-code test rather than a name match. Nothing here is legal advice.
Independent portfolio project.
Links
- Three systems, one firm, no shared key — the live module: five charts, the health surface, and what it refuses to publish
- Build Repository — the source register, the entity-resolution rule, the reading-panel disposition, and the three validation gates
- Cascadia Architecture Overview