Order Ledger: a WhatsApp-to-ledger side hustle that runs offline on open models A developer built Order Ledger, an offline, MIT-licensed tool that converts messy WhatsApp order chats in English, Hinglish and Kannada into structured order records using a local open-weight model. The app runs entirely on the seller's laptop via Ollama with qwen2.5:7b at temperature 0 and JSON-schema-constrained output, then validates phone numbers, units and dates server-side before a human review screen saves to orders.json. Because small models are unreliable at date arithmetic, relative dates like "kal" are resolved in code and annotated into the text before the model reads it. This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle https://dev.to/challenges/hacktoberfest-week2-2026-10-12 Small sellers run on WhatsApp. A home baker, a tiffin service or a tailor gets orders like this: C: bhaiya kal subah 20 samosa aur 1 dozen kachori chahiye S: ho jayega, 20 samosa 10 rs each = 200, kachori 12 x 15 = 180. total 380 C: thik hai, paise delivery pe dungi. Neha, 9000000002 Then they copy it into a notebook, and a week later nobody remembers who paid the advance. Order Ledger turns that chat into a structured order, shortened here: { "customer": "Neha", "phone": "9000000002", "items": { "name": "samosa", "qty": 20, "unit": "pcs" }, { "name": "kachori", "qty": 12, "unit": "pcs" } , "delivery date": "2026-10-14", "delivery time": "morning", "amount": 380, "paid": 0, "status": "confirmed" } The seller checks it on a review screen, saves it, and gets: Who would pay? Anyone who takes orders in chat and tracks them by hand: home bakers, cloud kitchens, tailors, small shops. The pain is the retyping and the money that slips through. Customer names, numbers and prices are also private, which is why the whole thing runs on the seller's own laptop. It handles English, Hinglish and Kannada in English letters, and it accepts exported WhatsApp chats as they are. The demo shows: pasting a WhatsApp-style chat, reading the order, fixing a field on the review screen, the schedule and pending-payments tabs, and then Wi-Fi switched off with one more order processed. Turn messy WhatsApp order chats into a clean order book. It runs entirely on your own computer with an open-weight model, so customer names, phone numbers and prices never leave your laptop. Built for the Hacktoberfest Open-Source AI Challenge, Week 2 Side Hustle . Home bakers, tiffin and cloud-kitchen sellers, tailors and small shops who take orders over WhatsApp and keep track in a notebook. Their chats are messy: English mixed with Hindi or Kannada, "kal subah", "advance 500 diya", orders that change halfway through. You paste the chat, and the app turns it into a structured order. C: for the customer and S: for you and extracts the customer, phone, items, quantities, delivery date and time, total, amount paid and status. MIT licensed. There is nothing to npm install : ollama pull qwen2.5:7b git clone https://github.com/Khushighosh/Order-Ledger.git cd Order-Ledger node server.mjs then open http://localhost:3000 The open pieces qwen2.5:7b 4.7 GB as the default and llama3.2:3b for comparison http and fetch , one HTML file for the UI, and a JSON file for storage pasted chat ──► convertChat WhatsApp export → "C:" / "S:" lines in the browser ──► annotateDates "kal" → "kal 2026-10-14 " ──► Ollama qwen2.5:7b, temperature 0, JSON-schema output ──► clean validate phone, units, dates server ──► review screen ──► orders.json ──► schedule / pending / CSV Ollama accepts a JSON schema as format , so the model's reply always parses and uses only allowed values: unit: { anyOf: { type: "string", enum: "kg", "pcs", "litre", "box" }, { type: "null" } }, status: { type: "string", enum: "confirmed", "tentative", "cancelled" }, The schema guarantees the shape of the answer, not that it's right . That gap is why there's a review screen and a test harness. Small models are unreliable at date arithmetic, so the date lookup happens in code and the model only reads the result: js export function annotateDates text, cal { const rules = /\bday after tomorrow\b|\bparso\b/gi, cal 2 .iso , /\b ?< after tomorrow\b|\bkal\b|\bnale\b/gi, cal 1 .iso , /\baaj\b|\btoday\b/gi, cal 0 .iso , ; let out = text; for const re, iso of rules out = out.replace re, m = ${m} ${iso} ; return out; } // "nale 2 litre dosa hittu" → "nale 2026-10-14 2 litre dosa hittu" Phone numbers are cleaned in code too, because the model kept returning +91 90000 00009 . I wrote 18 synthetic WhatsApp-style chats fake names and numbers and hand-labelled the expected JSON for each. evaluate.mjs scores 9 fields per chat and breaks results down per field and per kind of chat Hinglish, Kannada, same-day orders, "same as last time", and so on . With temperature: 0 , re-running an unchanged prompt gave identical results, so every prompt change could be A/B tested. | Test set | Model | Before the date fix | After the date fix | |---|---|---|---| | 12 chats I tuned on these | qwen2.5:7b | 11/12 orders correct · 99% of fields | 9/12 · 96% | | 12 chats | llama3.2:3b | 1/12 · 82% | 2/12 · 84% | | 6 fresh chats | qwen2.5:7b | 3/6 · 91% | 5/6 · 96% | | 6 fresh chats | llama3.2:3b | 3/6 · 89% | 3/6 · 91% | On a laptop the 7B model takes about 14 seconds per order once loaded about 50 seconds for the first order while the model loads . The 3B takes about 7. 2026-10-20 , and for "nale" Kannada for tomorrow it answered 2026-10-17 . The cause was that the date table in my prompt listed "Tuesday: 2026-10-20" next week , and the test day was a Tuesday. The model read "today is Tuesday" and looked it up. I fixed it by labelling that row "next Tuesday" and moving the relative-date lookup into code. The fresh set went from 3/6 to 5/6. An honest note on the numbers: I used both test sets to tune the prompt, and the date fix came after looking at the fresh set's failures. Treat these as indicative, not a benchmark. That's the reason the review screen exists. 127.0.0.1 , and orders live in a local file. MODEL=... . With a closed API I couldn't have inspected the setup this way, and an API-side model update could silently change the results. temperature: 0 gave identical results on re-runs, which made the debugging above possible. The trade-off, honestly: I didn't benchmark a closed API, so I'm not claiming open models are more accurate. A hosted frontier model is probably better at the tricky chats and faster per order. What open models gave me is privacy, a zero running cost and full control. For a seller who handles customers' private data on a laptop, that was the better deal. I built this in a long chat session with Claude as a pair programmer. It drafted the first versions of the prompt, server and UI, and I ran everything on my own laptop, pasted the failures back, and made the decisions: which idea to build, which model to keep, what to revert. The best moments came from the failures, like the 2026-10-20 date bug.