<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Linuka Arambawela</title>
    <description>The latest articles on DEV Community by Linuka Arambawela (@linuka_ar).</description>
    <link>https://dev.to/linuka_ar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4156273%2Ffda53371-def3-4450-8654-8f70f4a66098.jpg</url>
      <title>DEV Community: Linuka Arambawela</title>
      <link>https://dev.to/linuka_ar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/linuka_ar"/>
    <language>en</language>
    <item>
      <title>Crumb AI: A Sales Forecaster for Anton's Café</title>
      <dc:creator>Linuka Arambawela</dc:creator>
      <pubDate>Sun, 04 Oct 2026 16:37:29 +0000</pubDate>
      <link>https://dev.to/linuka_ar/crumb-ai-a-sales-forecaster-for-antons-cafe-3cji</link>
      <guid>https://dev.to/linuka_ar/crumb-ai-a-sales-forecaster-for-antons-cafe-3cji</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Crumb AI&lt;/strong&gt;, a local-first sales forecasting assistant. Anton uploads a sales CSV and asks practical questions in plain language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How many croissants should I bake on Saturday?"&lt;/li&gt;
&lt;li&gt;"Which items sold best this month?"&lt;/li&gt;
&lt;li&gt;"Did anything unusual happen to sales in June?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The sales data and the AI inference never leave Anton's computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Crumb AI runs locally at &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma3:4b
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
uvicorn crumb.app:app &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then upload &lt;code&gt;data/sample_bakery_sales.csv&lt;/code&gt; (synthetic) and explore the forecast chart, anomaly markers, and chat panel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo video:&lt;/strong&gt; &lt;a href="https://youtu.be/oKSu4OnTYBQ" rel="noopener noreferrer"&gt;Watch on Youtube&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6yn4g37j1tdfnjorehj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6yn4g37j1tdfnjorehj3.png" alt="Crumb AI forecast chart for one item with the uncertainty band, anomaly markers, and chat panel" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/LinukaAr/crumb-ai" rel="noopener noreferrer"&gt;Crumb AI on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built with Python, FastAPI, pandas, TabPFN, Ollama, Gemma, vanilla JavaScript, and a locally vendored copy of Chart.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Crumb AI has two open-source AI components with clearly separated jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;TabPFN&lt;/strong&gt; runs locally as the forecasting model. It predicts item-level sales from calendar and history features and returns a median plus a lower and upper range.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma&lt;/strong&gt;, running locally through Ollama, is the conversational layer. It picks which tool should answer a question, then phrases the result in friendly language.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CSV upload
    -&amp;gt; validation and date cleaning
    -&amp;gt; deterministic sales tools
    -&amp;gt; local TabPFN forecast (or labelled baseline fallback)
    -&amp;gt; Gemma routes the question and explains the result
    -&amp;gt; numeric post-check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The check in action:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Items with fewer than 60 usable daily rows use a clearly labelled moving-average fallback instead of pretending to have a model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;TabPFN MAE&lt;/th&gt;
&lt;th&gt;Naive MAE&lt;/th&gt;
&lt;th&gt;Moving-average MAE&lt;/th&gt;
&lt;th&gt;TabPFN WAPE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cinnamon Roll&lt;/td&gt;
&lt;td&gt;273&lt;/td&gt;
&lt;td&gt;1.4&lt;/td&gt;
&lt;td&gt;1.9&lt;/td&gt;
&lt;td&gt;1.7&lt;/td&gt;
&lt;td&gt;5.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Croissant&lt;/td&gt;
&lt;td&gt;273&lt;/td&gt;
&lt;td&gt;2.3&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;2.8&lt;/td&gt;
&lt;td&gt;4.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Espresso&lt;/td&gt;
&lt;td&gt;273&lt;/td&gt;
&lt;td&gt;2.8&lt;/td&gt;
&lt;td&gt;4.1&lt;/td&gt;
&lt;td&gt;3.2&lt;/td&gt;
&lt;td&gt;4.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pain au chocolat&lt;/td&gt;
&lt;td&gt;273&lt;/td&gt;
&lt;td&gt;1.6&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;4.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sourdough Loaf&lt;/td&gt;
&lt;td&gt;273&lt;/td&gt;
&lt;td&gt;0.4&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;0.6&lt;/td&gt;
&lt;td&gt;3.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overall&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1365&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.3%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Data used:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I found:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;To reproduce these numbers, run &lt;code&gt;python scripts/run_backtest.py data/sample_bakery_sales.csv&lt;/code&gt; from the repository root.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Running open models locally made three things possible that a closed API wouldn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy:&lt;/strong&gt; After the one-time model downloads, TabPFN and Gemma work with no sales data sent to any hosted AI service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control:&lt;/strong&gt; The tool schemas, prompts, number verification, and fallback behavior are all visible and changeable. When an answer looked wrong, I could trace exactly why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost and flexibility:&lt;/strong&gt; There is no per-question bill, and swapping the chat model is a one-line config change to another Ollama model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The included CSV is synthetic. A real shop needs enough consistent history for useful forecasts.&lt;/li&gt;
&lt;li&gt;Items with fewer than 60 usable daily rows use a labelled baseline fallback.&lt;/li&gt;
&lt;li&gt;Forecasts are estimates, not guarantees, which is why Crumb AI always shows the range and backtest error.&lt;/li&gt;
&lt;li&gt;Gemma's wording can occasionally be clumsy, although its numbers are checked against tool output.&lt;/li&gt;
&lt;li&gt;The app holds one uploaded dataset in memory and is built for a single local user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of TabPFN:&lt;/strong&gt; TabPFN is the local forecasting engine behind Crumb AI's item-level predictions, uncertainty ranges, and backtests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma is the local natural-language interface that routes questions to deterministic tools and explains their verified results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Built For
&lt;/h2&gt;

&lt;p&gt;This was built for &lt;strong&gt;Anton&lt;/strong&gt;. After trying it, he said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"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."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
