DEV Community

Cover image for "red label adha kg 2 box": an offline WhatsApp order reader for Indian shops that asks before it guesses
Bipul Chamoli
Bipul Chamoli

Posted on AI-assisted

"red label adha kg 2 box": an offline WhatsApp order reader for Indian shops that asks before it guesses

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

ParchiMatch review screen: WhatsApp order lines with green checked rows and amber questions

TL;DR: Small Indian shops get orders on WhatsApp in a mix of Hindi, English and shorthand. ParchiMatch Lite turns each line into a checked billing row, on the shop's own 8 GB laptop with the Wi-Fi off. When it isn't sure, it asks one short question instead of guessing. On 80 locked test orders it filled in 0 wrong rows, and on messages my friends typed by hand, also 0.

What I Built

This is built for small Indian stores. Their orders come in on WhatsApp, and one message can mix Hindi, English and abbreviations:

Namaste bhaiya
kit kat 10/- darjan 4
red label adha kg 2 box
12 pack lais peela 10 wala
teen petti marrie 10 rs
Kal subah tak bhej dena
Enter fullscreen mode Exit fullscreen mode

Someone has to retype every line into billing software like Tally, Busy or Marg. Petti is a carton, darjan a dozen, adha kg half a kilo, lais peela the yellow Lay's. And if a line is matched to the wrong product, that's expensive: the wrong stock gets packed, billed and put on the van.

So I tried to build something that needs minimal input from a person, but asks for their full attention exactly where a mistake would cost money. It reads each line, matches it to the shop's own catalogue, and sorts it into one of three colours:

🟩 Green Matched and checked: teen petti marrie 10 rs → 3 cartons of Marie ₹10 (180 packets)
🟧 Amber Not sure, so it asks one short question with its best guess filled in: marie 30 wala 20 jodi → What is "jodi"? packet or carton?
⬜ Grey Greetings and notes, ignored: Kal subah tak bhej dena

Then it exports a CSV for the billing software. It also learns the shop's own words: confirm "lais peela" once with Remember ticked, and next time it goes straight through.

The rule behind everything: a wrong row must never be filled in on its own. When unsure, ask. A shop owner forgives a question. They don't forgive a wrong delivery.

Who would pay: small FMCG wholesalers and kirana suppliers who get dozens of WhatsApp orders a day and pay someone to retype them. It runs on the laptop they already have, works without internet, and costs nothing per order.

Pricing (an untested guess): around ₹299 a month per shop. I haven't put this price in front of a real shop yet; that's the first thing I'll test.

Demo

The whole video is recorded with Wi-Fi off, on a MacBook Air M2 with 8 GB of RAM. Even the project site in the last minute is served from the laptop. What you'll see:

  • A real-looking order, matched in about 8 seconds: 6 lines green, 5 amber, 2 notes.
  • The model's guess can be wrong, and that's why it asks: for "lais peela" (the yellow Lay's) it suggests Magic Masala, but yellow is Classic Salted. One tap to pick the right one, and tick Remember.
  • It learns: after export it says "Learned 3 new names", and the same order comes back with 8 green lines instead of 6, with the corrected products.
  • Four tricky lines, and what the app does with each:
I typed What happened Why
aashirvaad atta 10 kg 1,500 bag 🟩 1,500 bags An early version read this as 1 bag and accepted it (see What I got wrong)
maggi 1,5 pkt 🟧 What does "1,5" mean? An unclear number is never guessed
red label adha kg 2 box 🟧 Is it Brooke Bond Red Label (250 g)? The model guessed 250 g; adha kg means 500 g. The code saw two sizes fit and asked
पारले जी 5 वाला 2 पेटी 🟩 2 cartons Parle-G ₹5 (288 packets) Hindi in Devanagari works too

Project site: https://bipul724.github.io/parchimatch/

Code

ParchiMatch Lite

CI

Messy wholesale orders → clean billing rows, on the shop's own computer.

A small FMCG wholesaler gets orders like these on WhatsApp:

Namaste bhaiya
parle g 5/- 2 petti
tata namak 1 kg 5 ctn, lays hara 10 rs 3 box
पारले जी 5 वाला 2 पेटी
fortune oil 2
Kal subah tak bhej dena

Someone retypes every line into Tally, Busy or Marg. ParchiMatch Lite reads each line, matches it to the shop's own catalogue, and either fills in a checked row or asks one short question. It never guesses a number, and it never auto-accepts a row it can't justify. It runs fully offline with an open-weight model on an 8 GB laptop.

🟩 Green Matched and checked: parle g 5/- 2 petti → 2 cartons Parle-G ₹5 (288 packets)
🟧 Amber One question, best guess pre-filled: marie 30 wala 20 jodi → What is
…

Zero npm dependencies; Node 24 runs the TypeScript directly. To try it: npm run app -- --open.

How I Built It

The model only picks the product. My code reads every number.

Small models are good at "which product did they mean?" and unreliable at "is petti a packet or a carton?". So the work is split:

  1. Code reads every number: quantities, unit words (petti, ctn, darjan, bori…), prices (5/-, 10 rs), pack sizes (1 kg, 500ml), Hindi number words (teen, paanch).
  2. Fuzzy text matching handles most lines instantly, with no model at all.
  3. Only when fuzzy matching can't decide does the local model see the line plus the 8 closest products. It answers in strict JSON, at temperature 0, and can only pick one of those 8.
  4. 16 safety rules in code check the pick: does the price match? The pack size? Is the product sold in that unit? Any failed check turns the row amber, and the question is written by the code, not the model.

Choosing the model: I tested five

On my 8 GB MacBook I tried five open-weight models in that "decider" role, on 320 synthetic practice orders:

Model Auto-accepted Wrong rows No typing needed Right suggestion on amber rows Typical / slowest
Gemma 4 E2B (chosen) 88.7% 0 96.1% 65.2% 3.1 s / 9.2 s
Qwen3.5 2B 88.7% 0 94.6% 52.2% 2.5 s / 3.3 s
Gemma 3 4B 88.7% 0 95.6% 60.9% 5.3 s / 8.4 s
Qwen3.5 4B 88.2% 0 95.6% 62.5% 5.2 s / 8.4 s
Gemma 4 E4B – 0 – – 5.6 s / 37 s

I picked Gemma 4 E2B because when it isn't sure, its suggestion is right far more often, so the shopkeeper taps Confirm instead of typing.

Then I tested it once, on orders I had locked away

Before tuning anything, I set aside 80 test orders and locked them; CI fails if they change. Each method ran on them exactly once:

Method Handled with no review Wrong rows auto-accepted No typing needed
Exact match 54.9% 0 54.9%
Fuzzy match 84.3% 0 84.3%
Model on every line 84.3% 0 92.2%
Hybrid (the app) 88.2% 0 92.2%

The model was needed on only 31 of the 80 lines, about 1.5 s each.

Watching the model be wrong, with Sentry

I traced an evaluation run in Sentry, one trace per order line, with steps for fuzzy matching, the model call and the safety rules. My favourite trace is the Red Label line.

Adha kg means half a kilo, so the 500 g pack. The model answered the 250 g pack, with status "ok", confident and wrong:

Sentry: the model's JSON answer for

The safety rules didn't trust it. Both pack sizes fit the order's words equally well, so the line went to a human:

Sentry trace: the safety-rules step for the Red Label line returns status needs_review because the 500 g and 250 g packs fit the order equally well

The model suggests; the code decides. The trace also shows every model call going to http://127.0.0.1:11434: the laptop itself, not a cloud. Across all 320 practice orders: 113 model calls, 640 tool calls, 58K tokens, $0.

Sentry Agents view: 113 LLM calls, 640 tool calls, 58K tokens, total cost $0

Tracing lives only in the evaluation tool, runs only on synthetic data, and only when switched on. The shop app itself sends nothing anywhere.

CI keeps me honest

Every push runs 210 tests on GitHub Actions, checks that the locked test orders haven't changed by a single byte, and posts the no-model baselines to the run page.

GitHub Actions run: tests,

What I Got Wrong

  • My first design asked the model for everything: product, quantity and unit. During development it was the worst design I measured, and on the final test it only tied plain fuzzy matching. That's why the model now picks only the product.
  • "1,500 bags" became 1 bag, and went green. The comma split aashirvaad atta 10 kg 1,500 bag into two lines. A wrong row, filled in on its own: the one thing the app must never do. Now Indian and Western digit groups (1,500, 1,00,000) are read as one number, and anything unclear like 1,5 gets a question.
  • "Remember" could learn the wrong size. "tata tea premium" was amber only because the order didn't say which size. Learning that name would have auto-accepted one size forever. Now only names the catalogue doesn't know can be learned, and a learned name means the product, never one pack size.
  • The model won near-ties for a silly reason. lays 10 wala 2 ctn names no flavour, but the model's pick, Classic Salted, led by 0.074, only because its alias was short. Now a model pick is accepted only if the order's own words put it ahead by at least 0.08, the same margin fuzzy matching needs. That was the last wrong row in development.
  • I tried a bigger model. Gemma 4 E4B needs about 6 GB; on 8 GB, only 2.5 GB fit on the GPU and the slowest line took 37 seconds. On a shop laptop, RAM is the limit, not disk.
  • I nearly trusted my friends' first replies. They were written by ChatGPT, not typed by hand. If I hadn't noticed that all four had the same shape, my "real-world" test would have been one more synthetic test (the full story is under My First User).

Why Does Open Innovation Matter?

  • The orders are the business. Customer names, what they buy, at what price: a shop won't paste that into someone else's cloud. With an open-weight model, nothing leaves the laptop. The app only listens on 127.0.0.1.
  • It costs ₹0 per order. No API bill growing with every WhatsApp message. That's what makes small-shop pricing possible.
  • It works when the internet doesn't. Market internet is unreliable; this runs fully offline, as the video shows.
  • I could test and choose. Because the weights are open and run locally, I could put five models through the same orders and pick the one that fits an 8 GB laptop.

My First User

Honestly, I don't have a paying user yet. No shopkeeper has used ParchiMatch. So I did the next best thing: I asked friends to type a restock order the way they'd really send it to a supplier.

The first replies were suspiciously perfect. Four messages, each exactly 17 lines, every item in the same order as my list, one a light rewording of another. My friends had asked ChatGPT. 😄 I threw them away and asked again, with six items and "please type it yourself."

Three real, messy messages came back, like this one:

bhai kal subh tk ye dal dena parle g 5wla 2 petti tata nmk 1 bori fotune tel pouch 1l 3 petti amul btar 100g 20 pis dairy milk 20rs 2 dabba or lifeboy 10 sabun 1 ptti bil sth bhej dena
Enter fullscreen mode Exit fullscreen mode

I labelled the 11 order pieces, locked them, and scored them once:

  • 0 wrong rows filled in, and 80% needed no typing.
  • One strict miss: fortune oil 1 ltr ke 1 carton went through without a question, though I had labelled it as worth asking (pouch or bottle?). Its row matched my label exactly, but by my own strict rule it counts, so I report it.
  • The app asked instead of guessing on every hard piece. For items joined with aur, though, its suggestion names only one product, so a busy operator could miss the second.

It's 11 pieces from 3 people: a signal, not proof.

What It Can't Do (Yet)

  • Items joined by aur, or sent with no separator, aren't split. The app asks about them, but its suggestion names only one product.
  • "Pouch" isn't a unit word yet. And Tata namak fits Tata Tea about as well as Tata Salt, so it asks.
  • Tested on synthetic shop data plus 11 real pieces, not on a real shop's catalogue yet.
  • It exports a CSV; it doesn't write into Tally or Busy directly.
  • Tested only on a MacBook Air M2 with 8 GB. Windows and Linux are untested.

The next step is one real shop and one week of real orders.

Prize Categories

  • Best Use of Gemma: Gemma 4 E2B (gemma4:e2b-it-qat via Ollama) is the model in the app, chosen by testing five models and confirmed once on locked test orders.
  • Best Use of Sentry Agent Tracing: the evaluation tool sends invoke_agent, chat and execute_tool spans for every order line. The traces above show a confidently wrong model answer being caught by the safety rules.
  • Best Use of GitHub Copilot: entered through the GitHub Actions route. CI runs the tests, the locked-test guard and the baselines on every push.

If you run a shop, or know someone who does: clone it, load your own catalogue as a CSV, and paste in yesterday's WhatsApp orders. It never needs the internet, and it will ask before it guesses.

Top comments (10)

Collapse
 
vimalgupta06 profile image
Vimal Gupta •

nice idea. the model only picks the item and the code checks the numbers, that is why no wrong rows. one question, will it work on a normal windows laptop with 4gb ram?

Collapse
 
bipul_chamoli_09b5f0cc719 profile image
Bipul Chamoli •

Thanks! Yes, that split is the whole idea: the model only says which product, and code checks every quantity, unit and price before anything goes green.

On a Windows laptop with 4 GB RAM: honestly, I haven't tested Windows or 4 GB yet. I built and measured everything on an 8 GB MacBook Air.

What should still work:

  • Without any model, ParchiMatch runs in fuzzy-matching-only mode. It just needs Node.js 24. On my locked test set, fuzzy alone handled 84% of lines automatically, still with 0 wrong rows. It simply asks more questions.
  • With a model, 4 GB is tight: the model needs a few GB of memory on its own. The smallest one I tested, Qwen3.5 2B (about 1.6 GB in memory), is the best bet: run it with --model qwen3.5:2b-q4_K_M.

If you try it on Windows, I'd love to hear how it goes. That's exactly the kind of laptop a small shop might have.

Collapse
 
vimalgupta06 profile image
Vimal Gupta •

Got it. 84% without any models is pretty good. One last thing, does the shop owner have an easy way to enter their product list or does that require some assistance?

Thread Thread
 
bipul_chamoli_09b5f0cc719 profile image
Bipul Chamoli •

Thanks! An honest answer is that loading is simple; preparing it is the tricky step now.

  • Loading: within the app, Catalogue → "Replace catalogue" and select CSV. In case any issue in any particular row, it will be highlighted along with the row number and error reason and nothing gets changed until the file is fixed. The list is saved and reloaded even after restarting.

  • Preparing: the CSV requires one row per pack size (e.g. ₹5 and ₹10 Parle-G are two separate rows), its units of sale (e.g. packet, carton, dozen, etc.) and number per carton. Most of the stores will have their item list available with them through their Tally/Busy system and conversion of that file to CSV format is currently done manually through Excel sheet only. Any store owner who has just started his business, would definitely require help in doing it at least once.

  • Nicknames: there is no requirement to enter those details since they will be taught by the store while working: correcting one amber row and clicking on "Remember" option.

An import utility for exporting files from Tally/Busy will be a great addition here.

Collapse
 
gun_acd39d460030863f9c63e profile image
Gun •

the chatgpt friends part made me laugh 😂 good you caught it. nice work, all the best

Collapse
 
bipul_chamoli_09b5f0cc719 profile image
Bipul Chamoli •

Haha, thank you very much! 😂 Well, to be honest, it turned out to be the most productive error of the entire project. Initially, the data generated by AI seemed okay, but actual people produced more chaotic data, revealing gaps that my synthetic data had not revealed, such as words combined with "aur".

Collapse
 
utkarsh_rastogi_2e63a7b09 profile image
Utkarsh Rastogi •

Just wondering about what others do – when you run a small local LLM, do you allow it to generate numbers, or do you put all numbers in the code as I did? In my case, dividing it into “model selects the product; code checks everything else” made the crucial difference.

Collapse
 
bipul_chamoli_09b5f0cc719 profile image
Bipul Chamoli •

Thanks Utkarsh! Same here: in my case the model never outputs a number at all. Its JSON has only three fields: the SKU it picked, a short "evidence" quote from the order, and ok/needs_review. Quantity, unit, price and pack size are all parsed by code.

I learned that the hard way. My first version asked the model for everything, and it did things like turning "petti" (a carton) into a packet. During development that was the worst design I measured. Splitting it this way is what got wrong rows to zero.

I also don't fully trust the product pick: code checks that the price and pack size written in the order match the product the model chose, and if two products fit almost equally well, it asks the human instead of accepting the model's choice.

From what I've seen on DEV, a lot of the stronger local-LLM projects land on the same pattern: let the model read and choose, and let code do the arithmetic and the final check. Glad it made the difference for you too!

Collapse
 
utkarsh_rastogi_2e63a7b09 profile image
Utkarsh Rastogi •

Nice the "evidence" check is a good one. Does your code verify that the quote was in the original order text as well? That would be one simple way to spot the model fabricating facts.

Thread Thread
 
bipul_chamoli_09b5f0cc719 profile image
Bipul Chamoli •

Yes! That's one of the rules (S9 in the repo). When the model says "ok", every word of its evidence quote must appear in the order line (after normalising case and punctuation, Hindi included). If the quote is empty or has even one word that isn't in the order, the row goes amber and asks which product.

One honest limitation: it checks words, not the exact phrase, so real words rearranged would still pass. And a real quote doesn't prove it names the chosen product. That's why there are two more checks after it: the order's own words must score the model's pick clearly ahead of the other candidates (otherwise it's a near-tie and it asks), and any price or pack size written in the order has to match the product.

So the evidence quote catches made-up words, and the scoring catches real words pointing at the wrong product. Thanks for the idea of checking for fabrication; tightening it to an exact-phrase match would be an easy improvement.