The plausible wrong number
Run it. Same three rows, same innocent phrase — "average conversion rate" — and the two defensible readings land at 31.0% and 15.1%. That's not a rounding quibble; that's a 2x gap in a headline metric, produced by an aggregation choice. Average-of-rates hands tiny partner (100 visits, 55% conversion) the same voting weight as organic's 1,200 visits. Pooled weights every visit equally. Which one is "right" depends on the question the stakeholder actually asked — and the drafter doesn't know the stakeholder. It picks one, formats it beautifully, and moves on.
This is the documented failure family for LLM-drafted analysis, and it's worth naming its three members precisely, because you'll build a named check for each:
- Plausible-but-wrong aggregations. The code runs, the number is in a believable range, the aggregation answers a slightly different question than the one you asked. Nothing errors, ever. You catch it only by computing the answer a second way — on a sample small enough to do by hand.
- Silent null and filter handling. Lesson 1's territory: the choice got made, just not by you, and not on the record.
- Join fan-out. A join key that isn't unique on one side quietly multiplies rows, and every downstream
sum()inherits the multiplication. Your revenue goes up, which is exactly why nobody questions it until finance does.
Notice what all three have in common: plausibility is the camouflage. A crash you'd catch. A number 40x too big you'd catch. A conversion rate of 31% instead of 15% — in a week where marketing ran a campaign and everyone wants conversion to be up? That ships. And once it's in the exec deck it stops being the AI's number. It's yours; your name is in the doc header.
The defense this lesson builds has two halves. First, sample-first verification: no AI-drafted aggregation runs on the full data until it has run on a handful of rows whose answer you computed on paper beforehand. Second, the address system: every number that survives verification gets a ref — a name like q2_pooled_rate pointing at the result that produced it — and the memo cites refs, so "where does 15.1% come from" is a lookup, not an excavation. By the checkpoint, both are functions you've written and run.