Pressentable Alpha

No. 001 A journal of plain reporting

The blog

What an LLM gets wrong when it reads your spreadsheet.

The model read your export, answered in clean prose, and got one of the numbers wrong without saying so.

You export the month, paste the file into a chat, and ask what happened. It answers in seconds, in better prose than you would have written, with the figures worked into the sentences.

Most of the time those figures are right. When AI gets your spreadsheet numbers wrong, it does it in a handful of specific ways, and every one of them is quiet. The prose does not wobble. The sentence that contains a total computed from the wrong column reads exactly like the sentence that does not.

That would be a small problem if the number stayed in the chat window. It does not. It goes into the report you send to a client, under your name, as a fact.

Six ways a model misreads a table

It assumes a column has not moved. Ask for the total of the fourth column and you get one, whether or not the thing you wanted is still in the fourth column. Upstream, somebody inserted a field in March. The answer comes back with the same confidence either way.

It picks the plausible neighbour. You ask for spend and it totals impressions. Both are columns of numbers with similar headings, sitting next to each other, and one of them is several hundred times the other. The result is wrong to anyone who knows the account, which is why it survives until the client reads it.

One text cell poisons the column. A single N/A, a dash where a zero should be, a footnote marker left in a cell. The column stops being numeric, and what comes back is zero, or a total so inflated it has concatenated something.

Blanks disappear without a word. Empty cells get dropped rather than counted, so an average is computed over a denominator you did not choose and cannot see. The mean of the rows that happened to have data is not the mean you asked for.

Similar headings become a guess. Two columns called Amount and Net amount, or Cost and Total cost, and the selection is resolved by whichever one the model decided you meant. Nothing in the answer records which one it used.

On a long file, it reads the top and estimates the rest. Given a file past a few hundred rows, a model will read the opening rows and extrapolate the remainder. What comes back is a projection from a sample, written in the same declarative sentence a total would get, with no mention anywhere that most of the file was never read.

Why does AI misread my spreadsheet?

Because a spreadsheet does not arrive as a table. It arrives as text, and the table has to be inferred from it.

You open a CSV and see a grid, because your eye does the column detection for free. A model receives a stream of characters in which the grid is implied by commas, quoting, line endings and a header row that may or may not be the first line. Before it can answer anything it has to decide where the header is, which rows are data, what each column holds, and what the characters inside a cell mean.

The header comes first. Plenty of real exports do not begin with theirs. There is a title line, a date range, the name of the account, a blank row, and then the column names on line six. Every downstream answer depends on getting that right, and there is no universal rule for it. The model decides, silently, and if it decides line one was the header then your first data row has become a set of column names and every total after that is computed over a table that is one row short.

Every one of those decisions is a judgement, and the six failures above are all the same event: a judgement made where a lookup was needed.

The judgement leaves no trace. There is no line in the answer that says which column was summed, which rows were read, or what was done with the blanks. The model is not hiding it. It does not distinguish between a figure it computed over every row and a figure it estimated from forty of them, because both are the most likely next tokens given the text it saw.

So the signal you would normally use is the one signal that fails here. With a junior analyst, uncertainty shows: the hedge, the caveat, the question. Fluent prose from a model carries no information about whether the number inside it is a lookup or a guess.

The advice that does not work

Search any of these failures and the answer is always some version of the same three steps. Clean the file before you send it. Normalize your date and number formats. Check the output before you use it.

The first two move the work to the person who already produced a correct export. Their file was fine. The decimal comma in it is correct in the country it was written in, the accounting parentheses are how their finance team writes a negative, and the N/A means the campaign had not started. They are being asked to pre-digest a correct file so that something downstream does not misread it.

The third is the one that sounds responsible and is the least actionable. Verifying an answer means knowing which number to check, and none of the six failures tells you. The wrong-column total looks like a total. The sampled figure looks like a figure. To verify properly you would have to recompute every number yourself, which is the work you handed over in the first place.

A reader that does not interpret

There is an older and duller way to get a number out of a file: read it with code, and write the rules down where anyone can read them.

That is how a Pressentable report reads an export, and the rules are not clever. They are fixed.

Where the table starts is decided, then shown to you. The header is taken to be the first row that is at least sixty percent as wide as the widest row in the sheet and whose every filled cell is a string, which is what a row of labels looks like and what a row of data usually does not. Failing that, the first row wide enough to qualify. Everything non-blank above it is preamble.

That rule is a judgement like any other, and it will not be right on every file anyone can produce. The difference is what happens next: reading the file is a two stage job, and the first stage puts the detected header and the skipped preamble rows in front of you in a preview grid, where you set them yourself if the guess was wrong. The decision still gets made. It gets made where you can see it and change it, before a single number has been read.

Money is peeled before any digit is read. Any Unicode currency symbol, leading or trailing, including the two-letter prefixed forms like US$ and C$. Twenty seven currency codes on either side of the number. Percent on either side. Accounting parentheses, so (1,234.50) reads as negative one thousand two hundred thirty four point five. A leading or trailing minus, including the Unicode minus that is not a hyphen. Thousands separators written as a comma, a space, a non-breaking space, a narrow non-breaking space, a thin space, or the Swiss apostrophe in either of its two forms.

A percentage keeps its magnitude. 80.00% reads as eighty, never as zero point eight, because whether to scale it is the column's decision and not the parser's.

A code is not a number. 007, 01-02, 03:15 and 3-2 stay text. A leading zero on a multi-digit integer is treated as a refusal rather than as a number, because an invoice number is not a quantity.

Dates are ISO or they are text. Only YYYY-MM-DD is parsed as a date. Everything else stays text, which means the question of whether 03/04 is March the fourth or the third of April never gets answered, because it never gets asked. There is no wrong-date failure mode available.

Encoding is tried in a fixed order. A byte order mark if one is present, then UTF-8 if the bytes are valid UTF-8, then Windows-1252. Nothing throws. A wrong guess shows up as mojibake on screen, which you can see, rather than as an exception you cannot.

A cell that cannot be read stays text. This is the answer to the poisoned column, and it is not a clever one. If something in a column does not parse under the rules above, it stays what it was, and a column holding text will not chart. You get a column that refuses rather than a column that quietly reports the wrong total, and if the refusal is wrong, the preview grid has a per column type override that takes one click.

The trade has a cost. You will occasionally meet a column that you know is numeric and the reader does not, and you will have to tell it. That is a minute of annoyance in exchange for never being handed a confident zero.

None of it is sophisticated. It is written down, it is the same on every file, and the same export read twice produces the same numbers both times. Preferring the dull deterministic path over the clever one is the whole reason this product exists. Where the text is ambiguous there is a stated decision rather than an inference, and you can go and read what the decision was.

This is the same reasoning that makes a report bind to a column by name instead of to a cell by coordinate, which is what keeps it working when next month's file arrives with a different shape.

Why a reader never samples

The sampling failure gets worse as you get more data.

A Pressentable report does not store which rows to use. It stores a rule, and there are three: every row, the first few, or the last few. The type that holds it carries the instruction in a comment, rule rather than coordinate, and there is no list of row numbers anywhere in a report's configuration.

That detail about storage is what makes the sampling failure impossible. A sample is a list of particular rows, and there is nowhere in the config to put one, so there is no step at which a subset could be chosen and then presented as the whole. "Every row" means whatever every row turns out to be this month.

It also means a report's size has nothing to do with how much data it covers. A ten block pack with bound prose comes to roughly two and a half kilobytes, and the type makes it structurally impossible for that to grow as rows are added. The same configuration describes a forty two row file and a file with thousands of rows, because it never enumerates rows at all. Next month's export can be twice as long and nothing in the report needs to know.

Let the model write the words

None of this is an argument against using AI on your reporting. A model is genuinely good at the part of a client report that is writing: the summary paragraph, the explanation of why a number moved, the tightening of a sentence you wrote badly at eleven at night. That work is hard, you do not enjoy it, and handing it over is reasonable.

The part to keep away from it is the figure.

In a Pressentable report, a paragraph is a plain string and a figure inside it is a slot. The slot holds an index into a list of bound figures, which means it is a pointer to a column and a rule, not a number that was typed. When the report is built, the figure resolves from the data. The same resolver serves the sentence and the KPI tile next to it, so the two cannot end up disagreeing about what a change was or how a negative gets written.

So a paragraph drafted elsewhere, by you or by a model, can go in. The product then strips the slots out of the paragraph and tests everything that remains for the presence of any digit at all. If it finds one, it marks the block and tells you why, in these words:

It states a figure that was typed, not read from the data.

Which is the condition exactly. A digit sitting in the prose rather than in a slot is a number that will not move when the data does. The inverse is written into the same file:

A paragraph whose every number is bound is STRUCTURALLY INCAPABLE of being stale.

To be precise about what this does and does not do: it detects and it names. It finds the typed figure, it points at the block, and it gives you the sentence. Acting on it is yours. But the gap between a wrong number and your noticing it stops being a matter of whether you happened to reread that paragraph, which is the gap numbers going quietly stale lives in.

Should you use AI for client reports?

A model predicts likely text. That is the whole mechanism, and it is why the prose is good.

A number in a client report is a different kind of object. It is a lookup: this column, these rows, this rule, resolved at the moment the document is built. Asking a system that predicts text to produce one is asking for a retrieval from something that does not retrieve, and the answer comes back indistinguishable from the real thing.

So use the model for the sentence. Let the figure come from the file, through a rule you can read, in a document that rebuilds itself when the data changes. That is what the client reports page is for, and you can read a finished one at Tidewater Goods, quarterly sales review.

The same rules on a live page

Nothing on this page was read once and retyped. Each number points at the column it came from, which is why the next export lands in the same document.

A quarter of sales where every figure resolves from a column rather than being repeated into the page

Read the live report

Where this fits

Typesetting is the second decision. The first is the shape of the report itself: what goes in it, in what order, and what the client is meant to do after reading it.

The guide it belongs to Client reports your clients actually read The five-part client report format that wins renewals, with a free CSV template and a report published in the open.

Keep reading


From CSV to this in minutes

Upload the file. Fill the blocks: text, the KPI tiles, the chart, the table, your logo at the head of the page. Export a typeset PDF for the inbox, or publish a live link.

Founding members publish live links and print without our mark. See the founding offer.