One merged cell, one wrong number
Table extraction gets described as a detection problem. Assignment is the harder half. Every number has to be attached to the right row label and the right column header, and a spanned cell is a genuine ambiguity, and we keep it that way.
28 July 2026
Ask a system what the Q3 figure for the northern region was. Finding the table is the easy half. The work is establishing that this particular number, at these coordinates, belongs to that row and that column, and that the column header two rows up, spanning four columns, applies to it too.
Get the assignment wrong and the failure has a specific character: the answer is a real number, from the right document, on the right page, with a citation that points at a real cell. It is simply the wrong cell. Nothing about it looks like an error.
The grid is not in the file
An HTML table has rows and cells declared. A PDF table has, at best, some lines drawn, and often not even that; many are held together by whitespace alone. So the grid gets reconstructed from geometry: cluster text by vertical position into rows, by horizontal position into columns, then decide which apparent gaps are column boundaries and which are just a short value.
Every step there has a failure mode, and they compose. A single right-aligned numeric column next to a left-aligned one produces a wide whitespace channel in the middle that looks exactly like a column boundary, and a long description that runs into the next column looks exactly like the absence of one.
Spans are ambiguity, not damage
A cell spanning three rows is the table saying something the grid model cannot represent: this value applies to a group. Two readings follow. The value belongs to all three rows, or it belongs to the group as an aggregate, and which one is right depends on what the column means. A quantity spanning three rows is usually per-group. A currency code spanning three rows is per-row.
The parser's job with a spanned cell is to record the ambiguity and carry it into the answer, not to resolve it.
We store spans explicitly. A cell knows its row range and its column range, and a value inherited from a spanned cell is tagged as inherited. When an answer depends on an inherited value, the citation says so, in one line in the source panel next to the highlighted region.
Headers are a path, not a string
Real tables nest headers two or three deep: a top row of years, under it a row of quarters, under that a row of units. The meaning of a cell is the whole path, not the nearest label. So we serialise every cell as a record carrying its full header path and its row label chain, and index that record as its own retrievable unit.
This makes the retrievable text for a single cell look like Northern region > 2026 > Q3 > EUR thousands: 1,284, which is verbose, ugly, and matches how people ask questions almost exactly. Chunking tables by row-group instead of token window came out of the same observation, and is its own post.
What we measure
| Metric | What it catches | Current |
|---|---|---|
| Cell assignment accuracy | A value attached to the wrong row or column | 96.1% |
| Header path completeness | A nested header level dropped, so units or period are lost | 93.4% |
| Span detection recall | A merged cell read as an empty cell plus a value | 97.8% |
| Continuation stitching | A table split across pages indexed as two unrelated tables | 91.2% |
| Footnote attachment | A marker glued to the number, so 1,2842 is read as 12,842 | 99.4% |
That last row deserves a note, because it is the most embarrassing bug we have shipped. A superscript footnote marker sits at the same baseline cluster as the digits next to it. For four months, some tables produced numbers an order of magnitude too large, in a way that looked entirely plausible in isolation. It was found by a user whose reconciliation totals stopped matching, not by us.
Continuation is the underrated one
91.2% on continuation stitching is our weakest table number and the one we are actively working on. A table that continues on the next page, with or without a repeated header, is extremely common in financial appendices and extremely easy to get wrong. When it goes wrong the second half is not lost, just orphaned: retrievable, with no header path and no row context. Orphaned rows produce confidently wrong answers about the wrong dimension.
The current approach is unglamorous: compare column boundaries and column types across a page break, and if they match within tolerance and the first row of page two is not a header, stitch. It fails on tables that change column widths mid-flow, and on documents where an unrelated table starts immediately at the top of the following page. Both are in the regression corpus. Neither is solved.
Table reconstruction is in quire/ingest/table. If you have a table it gets wrong, the most useful bug report is the page as a file plus the cell you expected. The coordinates in the citation tell us the rest.
Chunking is a layout problem, not a token problem
Fixed windows cut tables in half. What we do instead, and what it cost.
The Quire Note
One email a month, engineering-first.