How I Built an AI Auditor for India's 12 Million Kirana Stores
The Problem No One Talks About
India has 12 million kirana stores — the corner shops that feed and supply nearly every neighbourhood in the country. Each week, a store owner receives 3 to 5 distributor invoices: WhatsApp photos of handwritten bills, printed GST receipts, Tally exports. Verifying them manually takes hours and most owners simply don't. Distributors know this.
The result is silent, systemic leakage: overcharged prices, short deliveries, duplicate line items, wrong GST rates. A single store might lose ₹3,000–₹8,000 per month without ever knowing where it went.
Kirana Detective is my attempt to fix this with a six-agent AI pipeline that audits an invoice in under 60 seconds — and tells the owner exactly how much money they lost and what to do about it.
What It Actually Does
Upload an invoice photo (or PDF or WhatsApp screenshot) and up to five delivery photos. In under a minute, the app returns:
| Finding | Example |
|---|---|
| Price overcharge | Surf Excel 1kg charged ₹255 — historical price ₹220 (+15.9%) |
| Delivery shortage | Invoice says 24 Coke bottles — delivery photo shows 20 |
| Duplicate charge | Parle-G 80g appears twice on the same invoice |
| GST mismatch | Aashirvaad Atta billed at 12% instead of the correct 5% |
Every finding is converted to a rupee leakage number with a specific action step: "Call HUL rep. Request credit note for ₹35 overcharge."
The Six-Agent Architecture
I deliberately kept this modular — each agent does one job and passes a clean data structure to the next.
Invoice image/PDF
↓
Agent 1 — Invoice Extractor (MiniCPM-V 4.6, fine-tuned)
↓ Structured JSON: line items, quantities, prices, GST
Agent 2 — Product Matcher (MiniCPM5-1B GGUF, fine-tuned)
↓ Canonical SKU names: "SURF XL 1K" → "Surf Excel 1kg"
Agent 3 — Pricing Agent (rule-based, SQLite history)
↓ Price flags, GST flags, duplicate flags
Agent 4 — Visual Counter (YOLO26n ONNX, fine-tuned)
↓ Per-product counts from delivery photos
Agent 5 — Reconciliation Agent (rule-based)
↓ Shortage flags with ₹ loss per item
Agent 6 — Savings Agent (MiniCPM5-1B GGUF, fine-tuned)
↓ Human-readable rupee savings report + action items
The whole pipeline runs on CPU. No GPU required at inference time.
Three Custom Models, One Weekend of Training
The hardest part was realising that general-purpose models don't work for this domain. "SURF XL 1K" on a smudged bill photographed at an angle needs a model that has seen Indian invoices. "Maggi NDL 70g" being normalised to nestle_maggi_noodles_70g needs a model trained on FMCG catalog data.
So I fine-tuned three models on Modal A10G GPUs.
Model 1: MiniCPM-V 4.6 — Invoice Extractor
The challenge: Indian distributor invoices come in dozens of formats — printed GST bills, handwritten chits, Tally exports, WhatsApp photos. OCR alone fails because the fields vary wildly.
What I did: Generated 500 synthetic invoice images covering all four formats, with structured JSON annotations. Fine-tuned MiniCPM-V 4.6 with QLoRA (rank 16, 9.5M trainable parameters — 0.72% of the model). Merged the LoRA into full bfloat16 weights for clean inference.
Result: Eval loss 0.212 after 87 steps in 51 minutes. The model now reliably extracts supplier name, invoice number, date, and all line items into structured JSON even from blurry WhatsApp photos.
Published: build-small-hackathon/minicpm-v-4-6-indian-invoice-extraction-merged
Model 2: MiniCPM5-1B — Product Normalizer & Report Generator
The challenge: Distributors abbreviate product names in dozens of ways. "MAGGI NDL 70", "Maggi noodle", "NESTL MAGGI 70G" — all the same SKU. A price comparison is useless without canonical names.
What I did: Generated 2,000 (raw_name → canonical_name) pairs covering 200 Indian FMCG SKUs. Fine-tuned MiniCPM5-1B with Unsloth (QLoRA rank 16), then exported to GGUF Q4_K_M format for llama-cpp-python inference.
This same model doubles as Agent 6 — the savings report generator. It takes all the flags and produces the final human-readable rupee savings summary.
Published: build-small-hackathon/minicpm5-1b-indian-fmcg-normalizer
Model 3: YOLO26n — Product Detector
The challenge: Counting bottles of Coke or packets of Maggi from a delivery photo taken on a Rs 6,000 Android phone, in bad lighting, with products stacked at odd angles.
What I did: Merged three Roboflow datasets totalling ~11,400 images and 1,831 product classes. Fine-tuned YOLO26n (the smallest YOLO26 variant, ~2.4M parameters) for 100 epochs on an A10G.
Result: mAP50 = 0.428, mAP50-95 = 0.302. Not perfect, but good enough to catch a 4-bottle shortage in a crate of 24.
Published: build-small-hackathon/yolo26n-indian-fmcg-detection
Total training cost: ~$5.80 on Modal A10G.
Why Local Inference?
Kirana owners in Tier 2 and Tier 3 cities often have patchy internet. A system that calls OpenAI every time an invoice arrives would be unusable during a power cut or on 2G. More importantly, invoice data is sensitive — prices, supplier relationships, business volumes. It should never leave the device.
The full pipeline runs on CPU:
- MiniCPM-V 4.6 via
transformers(merged bfloat16 weights, ~3 GB) - MiniCPM5-1B via
llama-cpp-python(GGUF Q4_K_M, ~700 MB) - YOLO26n via ONNX Runtime (~5 MB)
Total: ~2.38B parameters, well within the Tiny Titan ≤4B constraint.
The Hardest Technical Problem
The most frustrating bug took me four hours to track down. The vision extraction agent was returning empty results — price ₹0.00, zero line items — despite the model clearly producing output in logs.
The issue was two compounding prompt-format bugs:
The vision model was wrapping its JSON in markdown code fences (
json ...), and my parser was looking for a raw{. I had to strip the fences before parsing.The text model for product normalisation was returning
{"canonical": "..."}but my code expected the canonical name as a bare string. A one-character assumption that silently discarded every normalisation result.
Both bugs were invisible in unit tests because I had mocked the model outputs. The lesson: always test end-to-end with the real model output format before writing the parser.
The Stack
| Layer | Technology |
|---|---|
| UI | Gradio 6.16 with custom CSS — rupee savings cards, colour-coded anomaly flags |
| Orchestration | Custom AuditOrchestrator with generator-based streaming |
| Invoice OCR | MiniCPM-V 4.6 (transformers, merged bfloat16) |
| Product normalisation | MiniCPM5-1B (llama-cpp-python, GGUF Q4_K_M) |
| Object detection | YOLO26n (ONNX Runtime) |
| Price history | SQLite (local) |
| Training | Modal A10G · Unsloth · PEFT · bitsandbytes |
| Deployment | Hugging Face Spaces (CPU) |
What's Next
Kirana Detective is a proof of concept, but the demand is real. A few directions I'd explore with more time:
- WhatsApp integration: Most kirana owners already share invoices on WhatsApp. A bot that accepts forwarded images directly would remove all friction.
- Supplier-specific models: Each major distributor (HUL, ITC, Nestlé) has a consistent bill format. A router that selects the right extractor per supplier would improve accuracy significantly.
- Regional languages: Invoices in Tamil Nadu or Bengal often mix English product names with Tamil/Bengali supplier details. Multilingual fine-tuning would broaden reach.
- Price network: If 1,000 kirana stores share their invoice prices anonymously, every store gets a live market price reference. The overcharge detector becomes dramatically more accurate.
Try It
Live demo: huggingface.co/spaces/build-small-hackathon/kirana-detective
Code: github.com/naazimsnh02/kirana-detective
Models:
- Invoice Extractor (MiniCPM-V 4.6)
- Product Normalizer (MiniCPM5-1B GGUF)
- Product Detector (YOLO26n ONNX)
Built for the Hugging Face Build Small Hackathon 2026 · Track 1: Backyard AI.
