This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
⭐ If this is useful to a shop near you, star the repo: DukaanDost. It helps the next counter find it.
Who I built it for
Sharma Kirana is the shop next door. The owner already had a billing export, and he still ordered by feel.
A quiet week might mean nobody wants the item. It might also mean the shelf was empty. A festival week can look like a spike that will not come back. Packs arrive in fixed sizes, and a bad buy ties up cash a small counter does not have.
I built DukaanDost for that counter. Kam andaaza. Sahi stock. Behtar dukaan. Less guessing. The right stock. A better shop.
He used it. He read the forecast against his sales and the festivals coming up, and he restocked candles and oils, because the prediction put those items in demand.
Other people building for the same counter ran into the same facts. A forecast that looks fine for a product family falls apart at the SKU, which is the only level a shopkeeper buys at (Why Your Demand Forecast Is Wrong at the SKU Level). A kirana that runs out of milk on Sunday is a stockout, not a demand of zero (My friend's kirana store runs out of milk every Sunday). And TabPFN plus Gemma can sit behind a small shop's own history, as in SECONDO, without the screen pretending a fallback was the model.
The problem it solves for him
His bills already knew what sold. They did not tell him what to buy next, in which pack size, or whether a quiet stretch was a stockout. DukaanDost turns that file into a quantity he can check before the next purchase.
What it does
DukaanDost takes a billing CSV and writes item-level demand, a reorder quantity, and a short explanation.
- Forecasts are built per product from lagged sales, rolling pace, and the calendar.
- Restock is lead-time demand plus safety stock, rounded to pack size.
- A stockout is not treated as proof that nobody wants the item.
- You can compare weeks. The app does not invent a festival uplift.
- Bad dates, blank products, and duplicate files are listed with row numbers.
- Explanations are English or Hinglish. If Gemma does not answer, a template says so.
- If TabPFN cannot run, the screen names the baseline. It does not call that baseline TabPFN.
Forecasts are estimates, not a promise of tomorrow's sales. The public sample file uses the same shop name and is marked fictional, so a visitor does not confuse that demo with his bills. The candle and oil restock came from his own sales.
What's in it
| Area | What you can do |
|---|---|
| Sample | Open a labelled sample kirana that stays separate from your own shop |
| Import | Preview a CSV, map columns, and see row errors before anything is saved |
| Inventory | Update stock, with an audit row for receipts and adjustments |
| Forecast | One series per product, or a clear note when the history is too thin |
| Restock | Reorder point, pack size, slow-moving flag, stockout flag |
| Words | English or Hinglish. Gemma through the Google endpoint when a key is set and the words still match the numbers |
| Export | Inventory, forecasts, recommendations, import quality, and evaluation as CSV |
| Privacy | SQLite when no Supabase key is set. The hosted demo stores shop rows in Supabase and says so |
Demo
The public site is https://dukaandost-web.onrender.com. The API is https://dukaandost-api.onrender.com.
A free Render service sleeps. The shop page can sit on a loading block until /health answers. Use the sample shop on that demo. Anything you import there is stored in the hosted Supabase project, not on your computer.
DukaanDost
Kam andaaza. Sahi stock. Behtar dukaan.
DukaanDost turns a small shop's billing CSV into stock advice the owner can check. TabPFN forecasts demand through the Prior Labs API when TABPFN_API_KEY is set. Gemma explains the numbers in plain language when LLM_API_KEY is set and the reply still matches the math. If either one is absent, the shop still gets a named baseline and a template that says so.
It is built for one real counter: Sharma Kirana, next door. He restocked candles and oils from the forecast.
Built by Aditya Kumar (@ADITYA-KUMAR-2358) for the DEV Hacktoberfest challenge, Side Hustle.
Code
https://github.com/ADITYA-KUMAR-2358/Dukaan-Dost
The application code is MIT. TabPFN is called through the Prior Labs API. This repository does not ship model weights.
How I Built It
TabPFN reads the billing table when an API key is set. Gemma writes the explanation when its own key is set. Neither one is allowed to wear the other's name. On the hosted check I ran, the forecast path used a named baseline, and the explanation came back as the template. The status screen can say Gemma is configured while a particular reply is still the template. The screen has to say which one it was.
TabPFN comes in through tabpfn-client. An empty TABPFN_API_KEY stays on a named baseline: seasonal naive, Croston, or a moving average. There is no weight download in this deploy.
Gemma is optional, through Google's OpenAI-compatible endpoint (gemma-4-26b-a4b-it by default). Leave LLM_API_KEY empty and the template is used. The reply is kept only when it still matches the calculated numbers.
The hosted shop data goes through the Supabase Data API. SUPABASE_DB_URL can stay empty. The backend credential is SUPABASE_SECRET_KEY. The publishable key is public, and the schema does not grant it access to the shop tables. Run backend/app/db/supabase_schema.sql in the Supabase SQL editor after the secret key is in the environment. With both the URL and the key empty, the API uses a local SQLite file.
Architecture
+--------------------------------------+
| BROWSER |
| React 19, Vite, TypeScript |
| Tailwind v4, GSAP |
| Render static site |
+--------------------------------------+
|
| CSV, then JSON
v
+--------------------------------------+
| FastAPI on Render |
| pandas |
| Supabase Data API, or SQLite |
+--------------------------------------+
| |
v v
Prior Labs Gemma via Google
TabPFN API or an English /
or a named Hinglish template
baseline
| Layer | Choice | Why |
|---|---|---|
| Frontend | React 19, Vite, TypeScript, Tailwind v4, GSAP | A shop page that stays readable, with a few deliberate motions |
| Backend | FastAPI, pandas, Supabase Data API or SQLite | Hosted shop rows in Supabase. A local file when those keys are empty |
| Forecast | TabPFN through the Prior Labs API, then a named baseline | Tabular history is what a billing CSV already is |
| Words | Gemma 4 through the Google endpoint | Optional. Empty API key keeps the template |
| Honesty | Model id stored with the run | A fallback is never labelled TabPFN |
From a billing file to an order
Owner exports a billing CSV
|
v
Preview, map columns, list bad rows
|
v
Features: lags, rolling pace, calendar
|
v
TabPFN API ---- no key or the call fails ----> seasonal naive,
| Croston, or
| moving average
v
Reorder quantity, rounded to pack size
|
v
Gemma explains the numbers
| no key, or the reply drifts
v
English or Hinglish template, and it says so
Things I had to get right
- Naming the model that actually ran. If TabPFN cannot run, the baseline keeps its own name.
- Keeping his shop separate from the sample. The demo file is fictional on purpose.
- Not inventing a festival uplift. He can compare weeks. The app does not add a festival percentage he cannot see.
- Not treating a stockout as "nobody wants this."
- Checking Gemma. If the sentence does not match the calculated numbers, the template is what he reads.
- Keeping the publishable Supabase key out of the write path. That key is meant to be public.
What was hard
- The Render install.
realtime, which ships with the Supabase client, needs Pydantic 2.11.7 or newer. Pydantic 2.14 needstyping-extensions4.16, andtabpfn-client0.2.7 only allows 4.15. The pins that install together arepydantic==2.13.5andtyping-extensions==4.15.0. - A public key that could write every shop table. The first schema granted that to
anon. The backend now looks forSUPABASE_SECRET_KEY, and the schema file revokes the public grants. The SQL still has to be run in the Supabase editor after the secret key is saved. - Thin history. Some items do not have enough days for a fit. Those rows need a clear note, not a confident number.
- A quiet Gemma. With no API key, or when a call does not come back clean, the template has to be good enough to order from, and it has to admit Gemma was not used.
- A sleeping host. The static site can be up while the free API is still waking. The shop page waits on
/healthinstead of showing an empty shop as if the data were gone.
What I'd do next
A printed order list he can take to the wholesaler, and Hindi on the rest of the screen so the whole shop, not only the explanation, can be read in the language he uses at the counter.
Why Does Open Innovation Matter?
Sharma Kirana's bills are a private record. What sold last Tuesday should not have to leave the shop before he can decide what to buy.
-
Control. With no Supabase key, the forecast and the SQLite file stay on the shop computer. On the hosted demo, shop rows live in Supabase. Gemma is off until
LLM_API_KEYis set, and then only the calculated facts are sent, not the raw CSV. TabPFN, when its key is set, sends the feature table to Prior Labs. - Swappability. TabPFN can be turned off. Gemma's model name is a setting. The baseline and the template still answer.
-
Cost. An empty key spends nothing. Gemma uses the limits in
.env(LLM_MAX_RPM,LLM_MAX_PER_DAY,SUBMIT_LIMIT). - Tunability. The rule that mattered most was faithfulness: show the number you calculated, and name the model that produced it.
A closed forecast that fails and still comes back wearing the vendor's name is worse than a baseline with its own name. The screen has to say which one ran.
My Agent Session
My First User
He is the owner of Sharma Kirana, the shop next door. He used DukaanDost on his own sales. The forecast, read with those sales and the festivals coming up, put candles and oils in demand, and he restocked them.
He has not paid. I built it for his counter. The result so far is stock on the shelf, not a guess from memory.
Prize Categories
-
Best Use of TabPFN: TabPFN forecasts item-level demand from a billing CSV when
TABPFN_API_KEYis set. If the call cannot run, the baseline keeps its own name. - Best Use of Gemma: Gemma explains those numbers in English or Hinglish, and only when the words still match the math. No API key, or a reply that does not match, means the template.
If you know a shop that still orders by feel, start with their last billing file. And if this is useful, please ⭐ star DukaanDost on GitHub. Thank you for reading. ❤️







Top comments (2)
This is such a brilliant use case for AI! Building something that solves a practical, everyday problem for a local shop owner is genuinely inspiring. Love the focus on local privacy. Great work on this!
Thankyou