Skip to content
Uzair Azhar
Menu

AI CSV Analyst

Ask a table a question in plain English and see exactly what was run. It reads a real retailer's 1.07 million invoice lines or a CSV you upload, reports data-quality problems first, and answers with a chart, the SQL and the same query in pandas.

LiveAI and LLM appsReal data: UCI Online Retail II (CC BY 4.0)

1Problem

A manager has a spreadsheet export and a simple question: which countries grew last year, how many customers ordered in March. Getting the answer means waiting for an analyst, or writing formulas over data they have not checked.

Chat assistants that “talk to your data” make the first part easy and the second part worse. They write SQL or code nobody reads, answer confidently when the question does not match the data, and say nothing about duplicates, gaps or numbers stored as text that make the total wrong.

2Why it matters

A wrong number that looks right is worse than no number. For a tool like this to be trusted in a business, the reader has to be able to see what was computed, the system has to say when it does not understand, and it must be impossible for a question, or a model, to change or leak data.

Those are the properties this project is built around, and the ones its tests check.

3Solution

Before any question, the table is profiled: column types, missing values, duplicate rows, negative amounts, numbers stored as text and values that would run as spreadsheet formulas.

A question is turned into a query plan: which measures, grouped by what, filtered how, sorted and limited. A rule-based parser does this first, without any model. It knows the table's columns, their synonyms and their values, and it declines any question containing a word it does not understand. Only if the rules decline can a language model fill in the same plan through one function call.

Every plan, from either source, is validated against the table and compiled to parameterised SQL. The answer shows the plan as a sentence, the result as a chart and table, the SQL that ran, and the same query in pandas.

4Architecture

  1. 1Question

    Plain English, up to 300 characters

  2. 2Rule-based parser

    Fixed vocabulary from the table's columns and values; declines what it cannot read

  3. 3Query plan

    Data, not code: measures, groupings, filters, sort, limit

  4. 4Validator

    Columns exist, types fit, values are spelled as in the data

  5. 5Compiler

    Quoted identifiers and bound parameters, plus equivalent pandas

  6. 6DuckDB sandbox

    Read-only, no files or network, locked settings, 5 s timeout

Language model fallback (optional)

Only when the rules decline. One tool call fills in the plan; validation errors go back to the model for up to 2 retries. Uploads opt in, and send column names only.

Same plan, same checks

A model can only produce a plan. It never writes SQL, and its plan goes through the validator and compiler exactly like the rules' plan.

Tables. The retailer's invoices are built into a read-only DuckDB file when the image is built. Uploads become their own DuckDB file in memory-backed storage and are deleted after an hour.

Profiling. Every table is profiled before the first question: types, gaps, duplicates and values that would run as spreadsheet formulas.

Evaluation. Fixed question sets run every 6 hours against the live table; the latest scores are shown under Results.

A question goes to the rule-based parser, which produces a query plan. If the rules decline, an optional language model may produce a plan instead. Every plan is validated, compiled to parameterised SQL and run in a locked-down DuckDB sandbox.

5Technical implementation

Query engine
DuckDB 1.5, one read-only database file per table, columnar and in-process
Interpretation
A rule-based parser in plain Python, with an optional function-calling LLM
Plans
Pydantic models with a JSON Schema that doubles as the LLM's tool definition
API
FastAPI, typed responses, RFC 9457 errors, per-visitor quotas, multipart uploads
Interface
Next.js with types generated from the API's OpenAPI document; SVG charts
Operations
Runs in the site's API container; evaluation scheduled on the job queue

The plan. Up to 3 measures (count, distinct count, sum, average, minimum, maximum), up to 2 groupings including a time bucket (day to year), up to 6 filters, a sort and a limit of at most 100 rows. The same Pydantic model validates the rules' output and the model's tool call, and its JSON Schema is the tool definition.

The parser. Questions are split into the longest phrases it knows: column names and synonyms (“spent” means revenue, “orders” means invoices), values with their aliases (“UK”, “Ireland” as EIRE), dates and periods, numbers with currency signs and separators, and about 120 keyword phrases. Passes for filters, groupings and measures then build the plan. If any word is left over, it declines.

Dates. “In 2011” is the whole year, “before March 2011” means before 1 March, “after 2010” starts on 1 January 2011. Ranges are compiled as half-open intervals on whole days, so timestamps at 23:59 are never lost.

Checked equivalence. Tests run 24 plans through the SQL and through the displayed pandas code on the same data and require identical rows, including how missing values are treated.

6Live demonstration

Ask about the retailer's invoices, or upload a CSV of your own. Questions about totals, counts, averages, rankings and trends work best. Every answer shows how the question was read, so you can tell when it was read wrongly.

Table to analyse
Columns and data qualityLoading…

Profiling the table…

Portfolio demonstration. Each visitor can ask 60 questions and upload 10 files per hour.

7Results

Results are read from the running system and are not available right now. They return when the analyst service is reachable and has run its first evaluation.

8Failure handling

The question asks for something the table cannot answer
The rules decline instead of guessing: every word must be understood, so a question about reasons, forecasts or a column that does not exist gets an explanation and suggested questions, not a made-up number.
A model returns a bad plan
The plan is checked for unknown columns, type mismatches and misspelled values. The problems go back to the model as the tool result, with hints such as the real column names, for up to 2 more attempts. If it still fails, nothing runs and the page says so.
A model or a question tries to inject SQL
There is no path from text to SQL. A plan names columns, which must exist, and values, which are bound as parameters. Tests send injection strings through the parser, the validator, the compiler and the model fallback.
SQL reaches the database that should not
The connection is read-only, file and network access are switched off, extensions cannot load and the configuration is locked. Tests run statements such as COPY, ATTACH, INSTALL and read_csv('/etc/passwd') directly against the sandbox and check that each one fails.
A query is too slow or too big
Every query has a 5-second timeout that interrupts it, a 256 MB memory limit and one thread, and runs one at a time per API process. Results are capped at 100 rows.
A file is not what it claims
Uploads must be text: Excel, PDF, images, archives and executables are recognised by their first bytes and rejected, as are files with NUL bytes, over 2 MB, over 50,000 rows or over 50 columns. Windows-1252 files are converted to UTF-8.
The data contains spreadsheet formulas
Profiling flags values that start with =, +, - or @, and the Download CSV button prefixes them so Excel shows them as text instead of running them.
No model is available
The rules work on their own. Without a model the analyst answers what the rules can read and declines the rest; nothing is paid for and nothing breaks.

9Deployment

The analyst runs inside the site's API container on the same 2 vCPU, 8 GB server as the other projects. The retailer table is built into the image from the verified dataset download, so it is identical on every deploy. Uploads live in a memory-backed temporary folder inside the container and disappear on restart if not before.

The evaluation runs on the job queue every 6 hours and stores its scores, which the Results section reads. Releases go through the same pipeline as the rest of the site: tests, image build and scan, deployment with a health check and automatic rollback.

10Cost

$0 in API spend. The rules answer without any model. The optional fallback uses free-tier providers only (Groq, then Cerebras) through a token ledger that stops at a daily budget below the free limit and reserves a share for demonstrations. When neither is configured or both are exhausted, the analyst keeps working with the rules alone.

DuckDB answers most questions on the 1.07-million-row table in tens of milliseconds on one thread, so the analyst adds no noticeable load to the shared server.

11Limitations

  • Answers are aggregates. It does not list individual rows, join tables, compute shares or ratios (“what share of revenue comes from the UK?”), or filter on an aggregate (“countries with fewer than 100 customers”); it declines those.
  • The rules know a fixed vocabulary. Phrasings outside it are declined unless a model is available, and the held-out score above shows how often that happens.
  • It can still misread. On the held-out set, “Revenue from customers in EIRE per year” also counted the customers, because “customers” was read as a measure. The interpretation is always shown so such misreadings are visible.
  • Uploaded columns are typed by DuckDB's CSV reader. Numbers written with thousands separators are read as text (the profile says so) and cannot be summed.
  • The model fallback has not been scored, because this server runs without a model by default. When one is configured, its answers are labelled with the provider and model.

12Source code

The full source is on GitHub under the MIT licence, with the tests and the evaluation sets.

  • parser.pyRule-based question parser
  • plan.pyThe query plan and its JSON Schema
  • validate.pyChecks a plan against the table and normalises it
  • compile.pyPlan to parameterised SQL, pandas and a plain-English summary
  • sandbox.pyLocked-down DuckDB execution with timeouts
  • llm.pyFunction-calling fallback with validation retries
  • profile.pyProfiling and data-quality findings
  • evaluation.pyDevelopment and held-out question sets

Data: Chen, D. (2012). Online Retail II [Dataset]. UCI Machine Learning Repository, CC BY 4.0. Details on the data page.