Customer Cases
Document AI deployments in production. Customer names are withheld under NDA and replaced by industry and scale descriptors. Every number on this page was measured on a live index we operate — not estimated, not rounded up.
25 years of listed-company filings in one queryable index
Capital markets · equity research and investor-relations workflows · production deployment
Engagement at a glance
| Industry | Capital markets — equity research and investor relations |
|---|---|
| Coverage | 2 listed issuers · 25 reporting years (June 2001 – August 2026) |
| Documents | 859 — 632 issuer disclosures, 227 brokerage research notes |
| Formats | PDF 673 · HTML 112 · DOCX 74 |
| Index size | 36,395 chunks · ~35.2 million characters |
| Research coverage | 227 notes from 25 brokerages · 112 investor-relations transcripts |
| Structured output | 457 financial metrics normalised into a queryable store, each traceable to its source filing |
| Deployment | Fully on-premises · source corpus mounted read-only · Qwen3-Embedding-4B vectors, 158 MB on-disk cache |
The problem
Analysts needed answers that span a quarter century: a 2008 balance-sheet footnote read against a 2026 interim report, a brokerage view read against the issuer's own disclosure. Doing that by hand meant opening dozens of PDFs per question, the oldest filings frequently failed to parse at all, and numeric answers had to be retyped out of tables with no way to trace them back to source.
What we built
- Idempotent ingestion keyed on content hash — re-runs skip unchanged files, failed files retry without duplicating registry entries, and the source corpus is never modified.
- Layer routing that treats issuer disclosures, brokerage research and investor-relations records under separate rules instead of one flat document pile.
- Hybrid retrieval: BM25 plus dense vectors fused by reciprocal rank fusion, then cross-encoder reranking.
- A recency window with organisation anchoring and year-aware query parsing, so "what did this brokerage say" still reaches notes published outside the default window.
- Table extraction into a structured metric store, so revenue and net-profit questions are answered from normalised numbers rather than from a paragraph of prose.
- Adaptive generation: direct numeric lookups receive top-1 context, analytical questions receive top-3, and composite questions are split and merged.
Measured results
| Metric | Value |
|---|---|
| Ingestion success rate | 859 / 859 documents, 0 failures |
| Retrieval hit rate, 10-question smoke test (issuer A, 17,590 chunks) | 10 / 10 |
| Retrieval hit rate, 10-question smoke test (issuer B, 18,805 chunks) | 10 / 10 |
| Wall clock for a full 10-question smoke run (issuer B, keyword mode, including index load) | 4.6 s |
| Candidate set after time filtering (issuer A) | 829 of 17,590 chunks — 95.3% of the index excluded as out-of-window noise |
| Candidate set after time filtering (issuer B) | 827 of 18,805 chunks — 95.6% excluded |
| Legacy PDF recovery (2001–2010 filings) | ~5% parsed to 0 pages from trailing cross-reference damage; auto-repaired, no manual handling |
| Financial metrics extracted into the structured store | 457 |
How these were measured: document and chunk counts come from the ingestion registry; hit rate and candidate-set sizes come from the 10-question retrieval smoke test shipped with the deployment, run in keyword-only mode with vector retrieval switched off. Timing covers process start, index load and all 10 queries on a local workstation.
What broke, and what we fixed
Long-horizon corpora fail in ways a demo corpus never does. These five issues were found in production and each fix is covered by a regression test.
| Symptom | Root cause | Fix |
|---|---|---|
| Brokerage notes older than the recency window never surfaced | A 400-day default window anchored to the newest report in the library excluded older coverage | Organisation anchoring: naming a brokerage pulls all of its notes into the candidate set regardless of date |
| Investor-relations transcripts were never indexed | The filename classifier did not recognise the document category and skipped the files | Classifier extended to route investor-relations records into the disclosure layer, with a regression test |
| Colloquial questions returned irrelevant sections | Lexical gap between spoken wording ("how much did they earn", "dividends") and filing wording ("net profit", "profit distribution") | Synonym expansion appended to the query rather than substituted, so the original terms still match |
| Retrieval quality dropped with no error raised | Vector retrieval silently fell back to keyword-only when the embedding cache was incomplete or the flag was missing at restart | Cache completeness check at load, plus a model-consistency key that invalidates the cache when the embedding model changes |
| Balance-sheet figures arrived glued together | PDF table cells merged during extraction, producing strings such as 3,294,053.003,320,386.00 |
Number splitting and table-markup stripping applied at generation time; the retrieval layer and vector cache were left untouched so no re-embedding was needed |
Where else this pattern applies
The pipeline above is not industry-specific — it is what long-horizon, format-mixed, table-heavy corpora require
If your archive spans years, mixes scanned PDFs with office formats, and hides its most valuable numbers inside tables, the same ingestion, retrieval and extraction pattern applies. Sector pages describe the document types we handle in each industry.
Financial services · Legal · Healthcare · Manufacturing · Government · Academic & research · E-commerce & retail · General enterprise
Public benchmark reference
Numbers measured on our own pipeline — see the full method on the benchmarks page
| Metric | Value |
|---|---|
| 443-page OFD, end-to-end | 124 s |
| Three-layer parsing QC score | 0.924 |
| Table structure mis-detection | reduced by 64% |
| Image token cost saving (GPT-4o basis) | 85.9% |
Run a test on your own documents
Bring the batch that troubles you most and we will show you the results with real test data.
Request a trial run