跳到正文
DEV Community· Vaidehi Kakad·· 4 小時前AI 評分53

Udhaar 結合 Gemma 3 4B 與 TabPFN-3.5 管理街角商店賒帳

The world's corner shops still run on a credit notebook. I gave it Gemma, TabPFN and no internet.

AI 導讀

作者 Vaidehi Kakad 建置 Udhaar,這款街角商店賒帳管理工具以本地 Gemma 3 4B、faster-whisper 和 SQLite 處理記帳。

正文

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

The notebook behind the counter

In Mexico it's called fiado. In a Philippine sari-sari store it's utang, written on a listahan. In an Indonesian warung it's hutang or bon. In India it's udhaar, kept in a khata. Different words, same notebook: the corner shop lets the neighbourhood take goods now and pay later, and writes it down by hand.

Every kirana shop in India has one. It's a long, thin notebook, usually red, full of names like Patil Kaku and Sharma Ji and numbers that keep growing. That's the udhaar khata, the credit book. Half the neighbourhood buys on it and pays at the end of the month… or the month after… or the month after that.

At Narmada General Store in Aurangabad, I noticed how they carefully write down each and every order in a diary to keep track of daily transactions. Seeing them maintain these handwritten records made me realise how much shopkeepers rely on manual record-keeping and how easily managing so many entries can become time-consuming.

Every app that tries to fix this asks the shopkeeper to type during the evening rush with oil on their fingers. It also asks them to upload their customers' debts to someone else's server. So most of them still use the notebook.

What I Built

Udhaar is an AI back-office for a corner shop that runs on the shop's own laptop. It has one box. You can speak into it or type into it in Marathi, Hindi, English, Spanish, Filipino or Indonesian, or the mix people actually speak at a counter.

"Doña Rosa, dos kilos de azúcar y un aceite, apúntalo, 96 pesos"
"Aling Nena, isang kilong bigas at dalawang itlog, 85 utang"
"Pak Budi ngutang beras 2 kilo dan minyak goreng 1 liter, 48.000"

All three become the same kind of entry in the same ledger. I built it around a real shop in Aurangabad, India, but nothing in it is Indian-only.

Say something the way you'd write it in the notebook, and it gets written down:

"Patil kaku, don kilo sakhar ani ek litre tel, 210 udhaar" → ₹210 udhaar for Patil Kaku: 2 kg sugar, 1 l oil.

Ask a question, and it answers from the shop's own records:

"Bhosale dada kiti baki?" → Bhosale Dada owes ₹11,396. Last paid on 9 Sep. Chance of paying late: 99%.
"How much sugar next week?" → About 43 kg (usually 61). In the two weeks before Diwali: 154 kg, +27%.

And it answers the question every shopkeeper actually agonises over, which no notebook can:

"How much more udhaar can I give Shinde Mama?" → Safe to give up to ₹4,750 more. The most he has ever paid back in a month is ₹7,402.
"Bhosale dada ko aur udhaar de sakte?" → Collect first. He owes ₹11,396 and is 99% likely to pay late.
"What if Gaikwad Sir doesn't pay for 3 weeks?" → His chance of paying late goes from X% to Y%.

Every entry also gets a quiet price check. If the mic hears "920" for two kilos of sugar, Udhaar asks: "₹920 for 2 kg sugar? This shop usually charges about ₹92." One tap fixes it. In a credit book, a wrong extra zero is someone's neighbour being asked for ₹828 they never borrowed.

Every morning it hands the owner a three-line briefing:

  • who to collect from today, and why;
  • what to order before the festival, and how much;
  • how yesterday went compared with a usual day.

Each line comes with a one-tap action: Gemma drafts a polite WhatsApp reminder, or a wholesaler order list built from the forecast.

Every risk score comes with its reasons in plain words, such as "No payment in 34 days. Usually pays every 43." A shopkeeper won't trust a number they can't argue with.

Who pays, and why open models are what make it a business

Who pays: the owner of a corner shop with 50–300 customers on credit. I’d price it at ₹149/month, less than one customer’s monthly sugar. The sample shop has ₹37,000 out on udhaar. If the briefing gets one late account collected a week sooner, or stops one extra zero, the month is paid for.

That price only works if the AI costs me nothing per entry. A busy counter logs 150–300 lines a day, and every line goes through speech-to-text, a language model and a price check. So:

  • Cost. Whisper and Gemma 3 4B run on my everyday laptop: an i5 with an RTX 2050 and 4 GB of VRAM. An entry costs electricity. With a per-call API, the AI bill would grow with every customer, and a ₹149 subscription would be underwater at a busy shop.
  • Privacy. A khata is a list of which neighbours owe money. It shouldn't live on a server the shopkeeper has never heard of. The ledger is one SQLite file on the shop's laptop, and speech and parsing never leave it.
  • It works when the internet doesn't. The shop's connection drops every time it rains. Udhaar doesn't notice.

Where open actually beat closed

The reason I didn't expect: because Gemma is free to run, I can run it twice. Every entry is written instantly by fast rules, then Gemma re-reads the same line in the background and quietly corrects it if it disagrees. With a paid API I'd never design that, because I'd be paying to re-check every entry. With a local model, a second opinion on every line costs nothing, and it's what makes the counter both fast and accurate.

The second: I could measure and swap. I ran the same eval on Gemma 3 1B and 4B on my own GPU, saw the 1B was worse than my regex rules, and made the trade-off myself (below). I also constrain Gemma's output with a JSON schema through Ollama, so it can never return anything but a valid entry.

Where closed would win, honestly: a frontier API would probably parse my held-out Spanish/Filipino/Indonesian lines better, and faster than my laptop does. For the predictions I do use Prior Labs' hosted TabPFN-3.5, because it's more accurate. It only ever sees anonymous numbers like due amount, days since last payment, day-of-week, days to festival: no names, no items. A shop that wants zero network can switch to TabPFN's open weights running locally with the same code.

How it works

 speak / type ─► faster-whisper ─► entry or question?
                                      │                         │
                        entry: rules now, Gemma re-reads    question: plan (rules, or Gemma
                        in the background (JSON schema)     for compound ones) → 1–3 tools
                                      │                         │
                        TabPFN price check ("₹920?")        tools: SQL ledger · TabPFN risk ·
                                      ▼                     credit limit · what-if · forecast
                                 SQLite khata ◄─────────────────┘   (every number comes from here)

        Gemma → WhatsApp reminder in the customer's language · morning briefing · wholesaler order

The model routes, the database answers. For questions, Gemma only decides what is being asked: an intent such as "how much does X owe" or "forecast for item Y", plus the customer, item and period. The number in the answer always comes from a SQL query or a TabPFN prediction. Gemma can misunderstand a question, but it cannot invent a balance. For a tool that handles people's debts, I think that's the only acceptable design.

Speech → entry. Gemma gets a short system prompt that explains how a kirana counter talks: udhaar/khata = credit, jama/dile = paid back, sakhar = sugar, tup = ghee. It also gets three examples. Ollama's format parameter constrains it to a JSON schema, so I never have to regex the model's output. A small rule-based parser sits behind it, so the counter keeps working even if the model is still loading.

I wrote 18 real-sounding counter lines, including Devanagari, half-payments and three items in one breath, and scored both parsers:

Parser Fully correct entries Median latency
Rules only 12 / 18 <1 ms
Gemma 3 4B (local) 15 / 18 ~6 s
Gemma 3 1B (local, fits the GPU) 9 / 18 ~3.5 s

Gemma got the type of entry, the customer and the amount right on all 18 lines; the rules managed 15, 14 and 16. Gemma's three misses were all in the item list, which is the least costly mistake: the money is still right and the shopkeeper can fix an item with one tap.

That left me with a real problem: my laptop's GPU has 4 GB, Gemma 3 4B needs about 3.5 GB plus room to think, so half of it spills onto the CPU and every entry takes ~6 seconds. Nobody waits 6 seconds per line in the evening rush. The 1B model fits the GPU completely, but it was worse than my regex rules.

Outside India. I wrote 15 counter lines each in the style of a Mexican tiendita, a sari-sari store and a warung, and tuned the rule parser on them until it got all 15. That number is meaningless on its own, so I then wrote 9 new lines I never tuned against:

Lines Rules Gemma 3 4B
India (Marathi / Hindi / English), 18 12 15
Spanish / Filipino / Indonesian, held out, 9 5 7

Getting the rules to work in four languages taught me how many collisions there are: don is "two" in Marathi but a title in Spanish ("Don Pepe"), and andi is "eggs" in Marathi but a common Indonesian name ("Mas Andi"). Thousands are written "48.000" in Indonesia and spoken as "50 ribu". Those are exactly the cases the held-out lines catch, and where a language model earns its place.

So Udhaar uses both. The rules write the entry instantly (under a millisecond), and the shopkeeper can move on to the next customer. Gemma re-reads the same line in the background and, if it understood it differently, quietly corrects the entry a few seconds later, with a small "Corrected by gemma3:4b" note. The counter gets the speed of the rules and the accuracy of the model. Questions work the same way: the rules answer the common phrasings instantly, and Gemma is only called for the long tail.

Who will pay late (TabPFN classifier). There's no labelled dataset for "my neighbours' repayment habits", and a corner shop will never have enough data to train a model. That's exactly where TabPFN shines: it makes predictions from a few hundred rows with no training step. I replay the khata and take a snapshot of every open account each week. Each snapshot gets labelled by whether that person repaid at least half their dues within 30 days. TabPFN learns from roughly 700 of these snapshots in seconds and scores today's accounts.

What to stock (TabPFN regressor). Kirana demand is all calendar: weekends, the first week of the month when salaries land, and the build-up to festivals. Each row is (item, day-of-week, salary week, days-to-festival, recent 7- and 28-day averages, horizon). I predict every item × every day until Diwali in one batched call instead of looping. With the API, refreshing every item’s forecast takes about 30 seconds, so it runs in the background and the counter never waits for it.

TabPFN inside a small prediction agent

Questions go through a tiny agent loop: plan, then call tools. The planner is the instant rules for one-line questions, and Gemma for compound or advice-style ones ("Shinde mama wants more for Diwali, should I? what does he owe?" → customer_due + credit_limit). Gemma only picks the tools; it never writes a number.
The tools are SQL and TabPFN:

  • credit_limit: the fitted risk model re-scores the customer on a ladder of "what if I give ₹X more", all rungs in one batched TabPFN call. The limit is the tighter of two things: the last rung before their late-payment risk crosses 35%, and the most they have ever repaid within 30 days. A small prior keeps the ladder honest: giving someone more credit never makes them safer, so the curve is not allowed to dip.
  • what_if: the same model on a shifted snapshot: 21 more days with no payment, or ₹2,000 more on udhaar.
  • price_check: a TabPFN regressor learns price-per-unit from the shop's own last 2,000 entries (item, quantity, unit) and flags an amount that's more than 2.2× or under 0.45× what it expects. This is TabPFN as an anomaly detector, on the anomaly that matters most at a counter.

Every answer has a “How it got this” trace under it, showing each step, which engine ran it and how long it took. A shopkeeper won’t trust a number they can’t argue with, and neither would I.

Does it actually beat a guess?

I hid the future and made each model predict it. For restocking, the "shopkeeper guess" is the usual 4-week average, which is how most owners order today. For late payments, the baseline is the rule every owner already uses: worry once someone hasn't paid for a month.

Window Usual average Gradient boosting TabPFN
Ordinary fortnight 26% 23% 24%
Ganesh Chaturthi build-up 27% 22% 20%
Navratri build-up 27% 22% 21%

Forecast error (WAPE), lower is better. TabPFN wins exactly where it matters: the festival build-ups, when guessing from the usual average is most wrong. In an ordinary fortnight it's a tie.

Late-payment model AUC Hit rate
"Not paid in a month" rule 0.67 40%
Gradient boosting 0.81 56%
TabPFN 0.82 60%

These numbers come from 13 months of synthetic shop history that I generated to mirror a real kirana: weekends, salary week, festival build-ups, and customers with different repayment habits. I say that plainly because a real shop's khata is exactly the data nobody should publish.

The result is a card that says "Diwali in 25 days" with ghee, maida, besan and sugar at the top. That’s the list any kirana owner will tell you runs out first.

My first user

I haven’t put it in a shopkeeper’s hands yet. At Narmada General Store I watched every order go into a diary during the evening rush, and I built Udhaar for exactly that moment. The first real test is this week, on his counter, and I’ll post what breaks.

Try it

Live demo: https://udhaar.vaidehi-k.tech. The home page explains it in one screen; Try the sample shop opens the app with typed entries. The microphone version runs on the shop's own laptop, by design.

git clone https://github.com/vaidehi-snk/udhaar && cd udhaar
pip install -r requirements.txt && ollama pull gemma3:4b
python scripts/make_demo_data.py && uvicorn app.main:app

scripts/serve_https.py puts it on the shop's Wi-Fi so the owner can use their phone as the mic.

What's next

  • Automatic reminders on the 1st and 5th of the month, when salaries land
  • A monthly bill per customer, sent on WhatsApp.
  • An Android version with a small on-device model, for shops without a laptop.
  • A one-click thermal receipt option for older aunties who still want a paper chit in hand.
  • Fine-tuning a small Whisper on real counter audio, because Marathi grocery words are where it still stumbles.

Built with

  • Gemma 3 4B (Ollama, local): entry parsing, the agent's planner, reminders in six languages
  • TabPFN-3.5 (Prior Labs): late-payment risk, credit limits, what-ifs, price checks, festival forecast
  • faster-whisper (local) · FastAPI · SQLite
  • Render hosts the live demo's agent front end, deployed from render.yaml, live on udhaar.vaidehi-k.tech, a .tech domain claimed through Hacktoberfest
  • GitHub Actions runs the tests and re-scores the parser on every push, posting the table to the run summary

Code: github.com/vaidehi-snk/udhaar (MIT licensed)

來源:DEV Community · dev.to