# Crumb AI: A Sales Forecaster for Anton's Café

> Source: <https://dev.to/linuka_ar/crumb-ai-a-sales-forecaster-for-antons-cafe-3cji>
> Published: 2026-10-04 16:37:29+00:00

*This is a submission for the [Hacktoberfest Weekend Challenge: Build for a Friend](https://dev.to/challenges/hacktoberfest-weekend-2026-10-01).*

Every evening, my friend Anton has to guess how many croissants to bake for tomorrow. Too few and the shelf is empty by mid-morning. Too many and they go to waste. Anton helps run a small cafe and has months of sales history sitting in a spreadsheet, but no time to turn it into a forecast.

So I built **Crumb AI**, a local-first sales forecasting assistant. Anton uploads a sales CSV and asks practical questions in plain language:

Crumb AI answers with a forecast and uncertainty range, while the dashboard reports backtest error when available so Anton can see how much to trust the number. It also flags unusual days: possible closures, spikes, and slow periods.

The sales data and the AI inference never leave Anton's computer.

Crumb AI runs locally at `http://127.0.0.1:8000`.

```
ollama pull gemma3:4b
pip install -r requirements.txt
uvicorn crumb.app:app --host 127.0.0.1 --port 8000
```

Then upload `data/sample_bakery_sales.csv` (synthetic) and explore the forecast chart, anomaly markers, and chat panel.

**Demo video:** [Watch on Youtube](https://youtu.be/oKSu4OnTYBQ)

**Repository:** [Crumb AI on GitHub](https://github.com/LinukaAr/crumb-ai)

Built with Python, FastAPI, pandas, TabPFN, Ollama, Gemma, vanilla JavaScript, and a locally vendored copy of Chart.js.

Crumb AI has two open-source AI components with clearly separated jobs:

The key design decision was giving the LLM less authority, not more. Gemma never computes or invents a sales number. Python tools (pandas, baselines, anomaly detection, TabPFN) produce every figure, and then Crumb AI checks each number in Gemma's answer against the tool output. If a number doesn't match, it regenerates once and then falls back to a deterministic template.

``` php
CSV upload
    -> validation and date cleaning
    -> deterministic sales tools
    -> local TabPFN forecast (or labelled baseline fallback)
    -> Gemma routes the question and explains the result
    -> numeric post-check
```

**The check in action:** During development, I tested the safety check with a deliberately fabricated response: "You have 99999 items available." The tool result did not contain that number, so Crumb rejected the response and used its deterministic template instead. This behavior is covered by a unit test, so the language model cannot quietly introduce an unsupported number into the answer.

Items with fewer than 60 usable daily rows use a clearly labelled moving-average fallback instead of pretending to have a model.

I backtested the synthetic sample on its final 28 days, forecasting 7 days at a time across four rolling folds. The comparison uses TabPFN, a same-weekday-last-week baseline, and a four-week same-weekday moving average.

| Item | Rows | TabPFN MAE | Naive MAE | Moving-average MAE | TabPFN WAPE | 
|---|---|---|---|---|---|
| Cinnamon Roll | 273 | 1.4 | 1.9 | 1.7 | 5.1% | 
| Croissant | 273 | 2.3 | 3.0 | 2.8 | 4.6% | 
| Espresso | 273 | 2.8 | 4.1 | 3.2 | 4.1% | 
| Pain au chocolat | 273 | 1.6 | 2.0 | 2.0 | 4.9% | 
| Sourdough Loaf | 273 | 0.4 | 0.5 | 0.6 | 3.0% | 
| **Overall** | **1365** | **1.7** | **2.3** | **2.1** | **4.3%** | 

**Data used:** This table comes from the included synthetic sample, not Anton's private sales data. It contains 1,365 rows covering five items from January 1 through September 30, 2026.

**What I found:** TabPFN had the lowest MAE for every item in this sample, with an overall MAE of 1.7 units compared with 2.3 for the naive baseline and 2.1 for the moving average. This is an encouraging result, but it is not evidence that TabPFN will win on every real shop's data.

To reproduce these numbers, run `python scripts/run_backtest.py data/sample_bakery_sales.csv` from the repository root.

Anton's sales figures are exactly the kind of data a small business may not want on someone else's server: daily revenue, product mix, and slow and busy patterns. For this workflow, keeping the CSV and the inference on the shop computer is a better fit than uploading it to a hosted assistant.

Running open models locally made three things possible that a closed API wouldn't:

Open source also made honesty easier. Crumb AI can show uncertainty, backtest error, and the difference between a TabPFN forecast and a simple fallback, instead of hiding them behind one polished number.

Separating routing, computation, phrasing, and verification made the assistant far easier to reason about than letting one model do everything. The LLM is good at understanding a question and explaining an answer, and it should not be trusted with inventory math.

Local AI also has practical setup costs. The first run needs model downloads and local authentication for gated TabPFN weights. After that, the whole workflow stays on the machine. I also learned that local models need deliberate caching: Crumb keeps fitted forecasts, prefetches item forecasts after upload, keeps Ollama warm between requests, and runs expensive work in the background so the dashboard stays responsive.

This was built for **Anton**. After trying it, he said:

"I like that I can upload our sales file and quickly see how many croissants to prepare without sending our business data anywhere. The forecast range is helpful because it shows me how much extra stock to keep ready."
