DEV Community

Aditya Kumar
Aditya Kumar

Posted on Edited on AI-assisted

What If Your Local Kirana Store Could Predict Tomorrow's Demand? Meet DukaanDost

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸

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 dashboard for the sample kirana, with a label marking the data as fictional
CSV import preview with column mapping and a list of bad rows with row numbers
Item-level demand forecast for one product, with the name of the model that produced it
Restock recommendations table with reorder point, pack size, slow-moving and stockout flags
Inventory screen for updating stock, with an audit row for each receipt or adjustment
Plain-language explanation of a recommendation in Hinglish, showing whether Gemma or the template wrote it
Export options for inventory, forecasts, recommendations and import quality as CSV

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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 needs typing-extensions 4.16, and tabpfn-client 0.2.7 only allows 4.15. The pins that install together are pydantic==2.13.5 and typing-extensions==4.15.0.
  • A public key that could write every shop table. The first schema granted that to anon. The backend now looks for SUPABASE_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 /health instead 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_KEY is 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

DukaanDost: Supabase on Render, and the Pydantic pin that installs
You

The API does not stay up. Supabase is reachable, and the shop tables are still missing until the schema SQL is run. That schema lets the publishable key read and write every shop table. A secret key should be the backend credential. SUPABASE_DB_URL and BACKEND_URL are empty. The local shop in SQLite is not in Supabase.

Agent

The ten shop tables already answered on the Data API, and the local API health check returned ok. SUPABASE_DB_URL stays empty because the app uses the Data API, not a Postgres password. BACKEND_URL stays empty because the guessed Render hosts returned no-server, and a keep-alive ping against a dead host would fail every 10 minutes. SUPABASE_SECRET_KEY was added as a server-only setting. The schema SQL now drops the public api_access policy and revokes anon and authenticated grants. That SQL could not be applied from here: the Supabase SQL editor requires a sign-in. Until the secret key is pasted and that SQL is run, the API still falls back to the publishable key. The SQLite file had a sample shop and an empty local shop named My shop. My shop was copied. The sample rows were already in Supabase, so the duplicate sample was removed. The shop already open in the app stayed the active shop.

You

Commit and push the project.

Agent

The CI workflow went up first, then the app, frontend, sample CSV, and Render blueprint. .env and the local database were not committed. GitHub renamed the pushed branch to main.

You

The Render build failed. realtime 2.7.0 needs pydantic>=2.11.7, and requirements.txt pinned pydantic==2.10.4.

Agent

Pydantic 2.14.1 satisfies realtime, but it needs typing-extensions>=4.16.0, and tabpfn-client 0.2.7 only allows typing-extensions<=4.15.0. The pins that install together are pydantic==2.13.5 and typing-extensions==4.15.0. A dry-run resolved, and the 16 backend tests passed. That commit was fast-forwarded onto main.

You

The static site is built, but nothing is being shown at the public web URL.

Agent

The home page was serving the built marketing page, with the stylesheet and script loading. The shop view at /app showed loading blocks until the API answered. /health on the API returned 200, and /api/status returned the active shop with an access-control-allow-origin for the static site. After that response, the overview heading was the shop name. A free Render instance can sit on those loading blocks while it wakes.

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_KEY is 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)

Collapse
 
shaik_smart_007 profile image
Shaik Yaqoob Ishaq •

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!

Collapse
 
adityakumar2358 profile image
Aditya Kumar •

Thankyou