Learn

Recurring report recipes

An analysis recipe is an ordered list of questions saved as a small JSON file. It holds a schema version, a name, a report title, your context note and up to twelve question strings. It does not hold the filename it was built from, the rows, the answers, the formulas, the chart points or the evidence. Load next week's export, run the recipe, and every figure recalculates from the new data; any question whose columns have gone is skipped and named.

The problem with the weekly report

Most analysis is not a discovery. It is the same eight questions asked of a new export, every Monday, forever. Total revenue. Revenue by region. Top five products. The trend by month. How many rows arrived. Whether anything is missing. Whether the join duplicated anything. The questions never change; only the file does.

The usual ways of automating that all carry the data with them. A saved spreadsheet holds last week's numbers and a formula grid pointing at cells that may have moved. A notebook holds the rows in its output cells, which is why notebooks leak. A BI dashboard holds a live connection to a warehouse, which is fine until the person who needs the report cannot be given warehouse access. A saved chatbot thread holds the whole file in its context, and produces slightly different prose every time you re-run it.

A recipe carries none of that. It carries the questions. The data stays where it is, the answers are recomputed in your browser from the file you are holding today, and the file you share with a colleague reveals nothing except what you were curious about.

The contract

A recipe is a JSON object with exactly six fields. Here is one in full, which is also all the documentation the format needs:

{
  "schema": "exploremydata.ask.recipe/1",
  "name": "Monday sales review",
  "title": "Weekly sales review",
  "context": "For the Monday call. Figures cover the export as delivered, refunds included.",
  "questions": [
    "How many rows?",
    "What is the total amount?",
    "Total amount by region",
    "Top 5 channel by amount",
    "Amount month over month",
    "Show missing values",
    "Are there duplicate rows?"
  ],
  "created": "2026-09-03T09:14:22.118Z"
}

schema is the format version. A build reads exactly the versions it understands and refuses anything else by name, so a recipe written by a future version fails loudly instead of being half-read. name is what the recipe is called in your library. title is the heading the generated report gets. context is your own note, reproduced at the top of the report so the reader knows what they are looking at. questions is the ordered list, capped at twelve. created is an ISO timestamp.

That is the whole file. There is no filename, no row, no answer, no formula, no SQL, no chart point, no evidence table and no column value anywhere in it. Import validates the schema string, drops any key not on that list and tells you which keys it dropped, refuses a file over 64 KB, and refuses a question list longer than twelve. You do not have to take the claim on trust: open the file in a text editor and read it.

What happens when the schema changes

Exports change. A column gets renamed in an upstream system, someone drops a field nobody thought was used, a currency column is split in two. When that happens, a recipe does the only honest thing available to it: the questions that referred to the missing column are skipped, and each skipped question is listed by name, with the reason and the words that matched nothing.

It would be easy to do something cleverer. The parser already knows that revenue, sales and amount usually mean the same idea, so it could quietly re-point a question at whichever numeric column looks closest. It deliberately does not, when the match is not one it would have made from the words you actually wrote. A report that silently changed which column it totalled would look exactly like last week's report and mean something different, and nobody would find out until a decision had been made on it.

So the skip list is a feature. It is the fastest schema-drift alarm you will get for a file that arrives by email. If three of your seven questions are suddenly skipped, something upstream changed this week, and you have found out before you published rather than after.

Building and running one

On Ask CSV, load a file and ask the questions your report needs. Press Pin to brief on each answer worth keeping; the pinned findings collect on the right in the order you pinned them, and you can reorder them. Give the brief a title and a context note, then type a recipe name and press Save the pinned questions. The questions are saved. The answers are not.

Press Export to get the JSON file. Keep it wherever you keep the rest of the pipeline: a repository next to the job that produces the export is a good place, because then the questions are reviewed when the schema changes rather than after.

Next week, load the new file and press Run on this file. Every question is re-answered from the new rows, the brief is rebuilt in recipe order with your title and context restored, and the skip list appears above the answers if anything could not be run. Export the brief as standalone HTML or Markdown and the week is done.

Storage and portability

Saved recipes live in IndexedDB in your own browser, in a database called emd-ask. They are never sent anywhere, because there is nowhere to send them to: this site has no accounts and no server-side storage. Clearing site data removes them, a private window will not see them, and a different machine will not have them. That is the trade for storing nothing about you, and it is why every recipe can be exported as a file you own. In a browser where storage is unavailable the tool carries on working; only the saved library is missing.

Writing a recipe by hand is entirely reasonable. It is a small object with an obvious shape, and import validates it before anything runs. A team that keeps its standard weekly questions in version control gets code review on the questions, which is more than most reporting processes manage.

Why a recipe never calls a model

Recipes run against the deterministic parser only. The same recipe against the same file always produces the same figures, and every figure comes with the SQL that produced it. A recipe that called a language model would produce a recurring report whose numbers could move between runs for reasons nobody could audit, which is the opposite of what a recurring report is for.

The full app is where an open-ended question goes, with your own LLM key or a local model, and where the generated SQL is validated against your schema before it becomes a step. The two tiers are for different jobs. The recipe format belongs to the one that always gives the same answer.

Questions and answers

What is inside a recipe file?

Six fields and nothing else: schema, the format version string exploremydata.ask.recipe/1; name, what you called the recipe; title, the heading the generated report gets; context, your own note to the reader; questions, an ordered array of at most twelve strings; and created, an ISO timestamp. Importing a file with any other key drops that key and tells you it did. The file is capped at 64 KB, which is far more than twelve sentences need.

What is deliberately not in a recipe?

The filename it was built from. The rows. The answers. The formulas. The SQL. The chart points. The evidence. The column values. None of it is written and none of it is read back. That is what makes a recipe safe to commit to a shared repository, attach to a ticket or mail to somebody outside your company: it describes an analysis without carrying the data the analysis was run on.

What happens when a column disappears?

The questions that needed it are skipped, and each one is listed by name with the reason and the words that failed to match a column. The rest of the recipe still runs. Nothing is substituted, guessed or quietly dropped. A schema change is news: if last week's revenue column is now called net_revenue, you want to be told, not handed a report that looks identical and means something else.

Why twelve questions?

Because a recurring report that runs to more than a dozen figures is a dashboard, and a dashboard wants a different tool. Twelve is also small enough that the whole recipe fits on one screen, which matters when the point of the format is that you can read it and know exactly what it does.

Where are my recipes stored?

In IndexedDB in your own browser, under the database emd-ask. They are never sent anywhere. Clearing site data removes them, and a private window will not see them, which is why every recipe can be exported as a file you keep yourself. In a private window or with storage disabled the tool still works; only the saved library is unavailable.

Can I write a recipe by hand?

Yes. It is a small JSON object with an obvious shape, and importing it validates the schema string, the question count and the size before anything runs. Writing one by hand is a reasonable way to keep a team's standard weekly questions in version control next to the pipeline that produces the export.

Do recipes work with the LLM tier?

No, by design. A recipe re-runs against the deterministic parser only, so the same recipe against the same file always produces the same figures. A recipe that called a model would produce a report whose numbers could differ between runs for reasons nobody could audit, which defeats the purpose of having a recurring report at all.