How I Stopped an AI From Lying to Me About Spreadsheet Totals
A few months ago I asked an AI tool to total up a sales spreadsheet.
It gave me a clean, confident number.
It was wrong. Not "rounding error" wrong — it had quietly skipped
rows and produced a total that just looked plausible. Nothing
flagged it. Nothing hedged. It just said the number, like it was fact.
That's the actual problem with LLMs on tabular data: they don't
calculate, they estimate. Ask a model to sum a column and, under
the hood, it's doing next-token prediction over a serialized chunk
of your spreadsheet — not arithmetic. Sometimes it's right.
Sometimes it silently isn't. And it never tells you which.
So I built Sheet Analysis AI
specifically so it can't do that. Here's the actual mechanism —
not the marketing version.
Rule 1: the AI is never allowed to do arithmetic
When you ask a question like "which region grew fastest?", the
model doesn't see your data. It sees:
- your column names (
region,revenue,date) - their inferred types
- maybe 3-5 sample rows
From that, it writes a small piece of JavaScript — actual code, not
a natural-language answer. That code is then executed locally, in
your browser, against your full dataset (tested up to 100k rows).
The model decides the approach (group by region, sum revenue,
sort descending). Your machine does the calculating. This alone
kills the "confidently estimated" failure mode, because there's no
estimation step left — it's just code execution.
The deterministic dashboard (KPIs, Mann-Kendall trend detection,
ANOVA seasonality, Pareto/RFM segmentation, forecasting) doesn't
even involve the AI — it's plain statistical code that runs the
instant you upload a file, no API key required at all.
Rule 2: nothing gets displayed until it's re-verified
Even code-generated numbers can be wrong — bad logic, an edge case,
a misread column. So before anything renders, a separate
deterministic auditor — no AI involved — re-checks every figure
against the source rows. A concrete example:
Say your data is:
| Region | Product | Revenue |
|---|---|---|
| North | Phone | 200 |
| North | Laptop | 200 |
| South | Phone | 100 |
| South | Laptop | 500 |
Total revenue is $1,000. The auditor checks this a few different ways:
- Traceability — does "$1,000" actually equal the sum of real rows in your file, or did something get invented?
-
Percent math — if a report says "North is 40% of revenue,"
is that literally
400 / 1000, or a plausible-sounding guess? -
Cross-foot — the same total sliced two different ways must
agree. By region:
400 + 600 = 1000. By product:300 + 700 = 1000. If those don't match, something's broken upstream and the number is blocked, not shown. - Claim binding — if the summary says "Laptop is the top product," that claim is checked against the actually-computed numbers before it's allowed to print.
Any single failed check blocks that figure. It doesn't get
downgraded to "approximately" — it just doesn't render.
What's deterministic vs. what needs a key
To be upfront about scope:
- Free, no key, instant: dashboard, KPIs, trend stats, forecast, and a rule-based version of "talk to your data."
- Needs your own AI key (OpenAI / Gemini / DeepSeek / etc., bring-your-own-key): the conversational Q&A gets smarter, and "Deep Analysis" (full AI report + hidden-pattern detection) becomes available. Nothing routes through my infrastructure or gets billed to me — your key talks directly to your provider from your browser.
Stack
React 19 + TypeScript + Vite. No backend in this build — parsing,
analysis, and the reconciliation gate all run client-side.
Licensed AGPL-3.0.
Why open source it
I don't think this needs to be a product. I think the pattern —
AI proposes the method, deterministic code executes it, a separate
auditor verifies it before display — is generally useful for anyone
building "AI + your data" tools, and it's more useful to more people
as a reference than as a SaaS with a handful of users.
Repo: https://github.com/Durlabhkumarjha/sheet-analysis-ai
Genuinely curious if anyone's solved this "AI + real numbers" trust
problem differently — would love to compare notes in the comments.
Top comments (0)