# explain - see what the reading decided Source: https://truecopy.dev/docs/explain/ Module: truecopy/explain The feature the popular extractors have and the careful ones forget. Text, not an image - it goes into a terminal, a CI log, a bug report and a test. The first thing you need when a reading comes out wrong is not a better parser. It is knowing **which of three things** broke: - the document is odd, - the cut into columns is wrong, - or your rule is wrong. Each calls for a different fix, and a library that only returns records leaves you guessing between them. ```ts import { explainDocument } from 'truecopy/explain'; console.log(explainDocument(document, { signature, roles })); ``` ``` truecopy - statement-2026-05.pdf pdf, 1 page(s), 6 row(s) page 1 - cut at 100, 290 3 column(s), 6 row(s) col kind filled role 0 date 83% when 1 text 100% - 2 amount 100% money 1 02/05/2026 | CARTE 28/04 AMAZON EU> | 12,40 2 03/05/2026 | VIR SEPA LOYER MAI | 750,00 3 05/05/2026 | CARTE 03/05 SNCF CONN> | 68,00 4 09/05/2026 | PRLV EDF | 91,32 5 12/05/2026 | CARTE 11/05 LE BOULAN> | 7,90 6 ! | TOTAL DES DEBITS | 929,62 <- column 0 (date) is empty ``` Everything printed was already computed and already public. What was missing was somewhere to read it **all at once, in the order the reading made its decisions**. ## The cut names its ruler A list of numbers with no unit is a riddle. `cut at 1, 2` on a CSV sent somebody looking for a defect that was not there: those were the indices of the fields, and they were right. ``` page 1 - cut at 100, 290 a PDF: points, and points go unlabelled page 1 - cut at characters 7, 22 a table pasted with spaces page 1 - cut on the delimiter a CSV: the columns ARE the fields page 1 - no boundary nothing lined up ``` Points stay unlabelled because they are what a page has always been measured in. On a delimited file the numbers say nothing a reader does not already know, so the line names what happened instead. ## Text, and not an image It goes into a terminal, a CI log, a bug report, a test's expected value. It needs no canvas, no headless browser and no dependency. An image needs a screen. ## The options | | | | -------------------------- | ----------------------------------------------------------------------------- | | `signature` | naming cell kinds turns on the kinds, and the rows that break the shape | | `roles` | what each column was taken to **be** | | `boundariesOf` | the cut to read the page by, when it is not the page's own | | `cellsOf` | your own cut, row by row (the one being debugged is yours, not the library's) | | `page` | only this page | | `maximumRows`, `cellWidth` | how much to print | Left without a `signature`, the cut and the fill rates are still shown, and those are what a wrong cut shows up in first. `boundariesOf` matters more than it looks: [`readTable`](https://truecopy.dev/docs/table/) hands back the cut it used, and without passing it the heading and the cells would tell two different stories. ```ts const { document, boundaries } = await readTable(file); explainDocument(document, { boundariesOf: (page) => boundaries[page.pageNumber - 1] }); ``` ## Also here - **`explainRows(rows, options)`**: the same, for a reader holding rows that never came from a page: a paste, a CSV, an OCR line. - **`describeAnomaly(anomaly)`**: one anomaly, in the words of somebody looking at the page: `column 0 (date) is empty`, `column 2 is not amount`. A truncated listing that looks complete is how a missing row gets read as a row that is not there, so what is not printed is **counted out loud**: `... 4 more row(s)`. ## Put it behind your refusals A refusal with no evidence is a dead end: nobody can believe it or contradict it. One of the applications built on this library attaches `explainRows` output to every refusal that says _"I did not read this correctly"_, folded under the message: invisible to whoever does not care, and decisive for whoever opens a ticket. Map of this site for a model: https://truecopy.dev/llms.txt Every page in one file: https://truecopy.dev/llms-full.txt