This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend
Her day, before Nivara
My friend runs a supplements shop on her own. No staff, no counter, no software. Just her phone.
Orders come in as WhatsApp messages and voice notes, mostly in Hindi or Hinglish: "Rahul bhai ko 2 MB biozyme whey bhej dena kal tak." She keeps the stock count in her head. Most weeks that works. Then a best-seller runs out, and she finds out when a customer asks for it.
When I asked what she actually wanted, it wasn't a chatbot. It was three answers every morning:
- What do I deliver today?
- What do I reorder before it runs out?
- Am I paying a supplier more than I need to?
So that's what I built.
What Nivara does
Nivara is a small web app she opens on her phone between chats.
Today answers the morning question first. The top of the screen is a loaded barbell: every job is a plate, heaviest first, coloured like competition bumper plates. Red is "do this now", yellow is "this week", green is "when you have a minute". The heaviest job gets the full width of the screen and one button.
Orders turns a voice note into a draft order. She can record one or upload a forwarded WhatsApp note. I uploaded a short Hinglish clip to the live site. It showed what it heard, "राहुल भाई को 2 MB Biozyme भेज देना कल तक", and drafted Rahul Verma, 2 × MB biozyme whey at ₹2,599, ₹5,198 in total, delivery on 6 Oct worked out from "कल तक". Nothing is saved until she taps "Confirm and save". Pasting a chat message works the same way.
Ask takes typed or spoken questions in English or Hinglish. I said "मुझे क्या restock करना चाहिए?" and got the same two products, with the same order quantities, that Today and Forecast show. Answers can be read aloud.
Stock, Forecast and Suppliers cover the rest: what's on the shelf, what sells in the next 7 days against how long a delivery takes, and which quotes are cheaper. A cheaper quote only counts if it saves at least ₹10 and 5% a unit, so she isn't nagged to switch suppliers over ₹3. A morning brief is ready at 8 every day.
Demo
Music: "Wallpaper" Kevin MacLeod (incompetech.com), CC BY 4.0
Live: https://nivara-x9iv.onrender.com (free Render plan, so the first open can take up to a minute).
The one rule: Gemma does language, code does facts
A shop helper that confidently gives the wrong stock number is worse than none. So I drew a hard line early.
Gemma reads and writes words. It turns her messages and voice-note transcripts into JSON, picks which lookup a question needs, and writes the one-line summary at the top of the morning brief.
Code owns every fact. Stock, held units, days of stock, risk, how many to order, supplier savings and dates are plain TypeScript over the database, with tests. "Kal", "कल" and "parson" become dates in code, not in the model. Gemma never writes to the database.
In practice:
- Gemma's order JSON is checked with zod. If it's broken, Gemma gets one retry with the error, then the request fails cleanly.
- Code matches the draft to real customers and products ("bhai" is ignored in the name), then shows it to her. Only the confirm button saves, and it validates again.
- Standard questions are answered from templates over the tool output. I first let a small Gemma write those answers, and it dropped list items, swapped supplier names and mixed up available and total stock. Templates fixed that.
Why an open model
Her customers' names, their chat orders and her margins are not data I want to send to a closed API by default.
On the live site Nivara uses Gemma 4 (gemma-4-26b-a4b-it) through Google AI Studio, because a free Render instance can't run a model. The same app runs against Gemma in Ollama with one environment variable, and I built most of it against gemma3:4b on my laptop. If she wants her data to stay on her own laptop, it can. It costs nothing per message and doesn't depend on a vendor's pricing.
A small open model also kept me honest. It can't be trusted with numbers, so the design never asks it for any.
The data, and what's not real
I don't have her sales history yet, and there is no open dataset of Indian supplement sales. So I built the most honest stand-in I could:
- Catalogue: 212 real products from Open Food Facts India (ODbL). Real brands, and real mess: a few Chyawanprash jars made it in.
- Prices: Open Prices had observed INR prices for only 4 of them. The rest are estimates from pack size, product type and brand tier. My first pass priced whey tubs as sachets (₹139 for Biozyme Performance Whey); after fixing the pack-size rules it's ₹2,749.
-
Demand: Google Trends search interest for India, scaled to what a solo shop might sell. It's a stand-in, tagged
proxyin the database, and the Forecast page says so in one line.
When she confirms and delivers orders, those real sales join the same history.
Making it hers
- Shop words, not database IDs: Order 1, 2, 3, product names, "Rahul Verma", India time.
- No partner names on her pages. Those live on one Health page for me.
- When something fails she sees a plain sentence and a Retry button, not a stack trace.
- On her phone the menu folds into one button, and every tap target is big enough for a thumb.
- The example questions only read data. Tapping one can never change an order or the stock.
Checking it the way she'd use it
Before handing it over I clicked every page and button on the live site, on 360 and 390 px phones and a laptop, and checked each change in the databases rather than trusting the screen. Saving an order had to create it in Atlas. Marking it delivered had to take two tubs off the shelf (21 to 19), record the sale, and add a row to the Tiger sales history. That pass found real bugs:
- Today said to reorder 11 of a product while Ask said 7. One path used the forecast, the other used last week's sales. Now every page uses the forecast.
- "Cheapest online" picked a ₹197 sachet for a ₹6,949 tub of whey. An online price now has to share a brand word and sit within half to double her price.
- Orders had numbers like "Order 27" made from database ids. They're now Order 1, 2, 3.
- Times showed "2:38 am" because the server runs on UTC. Everything is in India time now, and delivered sales land on the shop's day.
- Voice notes with background music came back with "[outro jingle]" in the transcript. Sound tags are off now, and a note that is only music counts as silence.
What each partner does for her
Tiger Data: her sales history and product search. Daily units per product live in a Timescale hypertable, sales_daily. Two continuous aggregates, sales_demand_7d and sales_demand_28d, keep rolling 7- and 28-day totals ready, and the brief and Ask read her realised sales from them while the forecast handles next week. When she marks an order delivered in Atlas, a change stream writes each line to an order_sales table keyed by order and product, so a repeated sync can't count a sale twice, and adds the units to sales_daily on her day in India time. Product search is hybrid: Postgres full-text search plus pgvector over 768-dimension gemini-embedding-001 embeddings, ranked by text rank plus twice the vector similarity. Hard filters like "under 3000" apply to both sides, so a close vector match can't slip past her price limit. "MuscleBlaze whey under 3000" returns matches cheapest first.
MongoDB Atlas: the source of truth. Every order she confirms, her stock, customers, saved rules and briefs. The only way to save an order is the confirm path, which checks every product and customer exists.
Gemma: reading her messages. Order extraction from text and voice transcripts, choosing the tool, the brief's summary line.
ElevenLabs: her voice notes. Scribe turns the voice note or spoken question into text, then it goes through the same Gemma and code checks as a typed message. Answers can be read aloud.
TabPFN: what sells next week. TabPFN forecasts 7 days per product from 60 days of history. Render has no Python, so I ran it on my laptop over all 212 products (317 seconds on CPU) and stored the predictions in Atlas. Days of stock, risk and how many to order all come from that one forecast, and Health shows the day it was worked out.
SerpApi: online prices and demand. Google Shopping prices for products about to run out, and the Google Trends data behind the demand estimate.
Render: where it lives. One free web service in Singapore, health-checked on /api/health. A GitHub Actions job pings it every 10 minutes until 15 Oct so you don't land on a cold start. With no background worker on the free plan, the app writes the 8 AM brief itself.
The stack also uses Backboard to keep her rules in her own words, Mastra for the agent's tool definitions, Sentry to trace each question with prompts and customer details redacted, and Temporal for the scheduled jobs when a worker runs (on Render they run inside the app). There are 137 tests plus 15 recorded Keploy API cases.
What I'd do next
- Read orders straight from WhatsApp Business instead of forwarding voice notes.
- One tap from "order 11 more" to a message for the supplier.
- Rerun TabPFN on her real sales once there are a few weeks of them.
Prize Categories
Best Use of Tiger Data, Best Use of SerpApi, Best Use of TabPFN, Best Use of ElevenLabs, Best Use of MongoDB Atlas, Best Use of Gemma, Best Use of Render
Links
Built by Priyanshu Jha (@CodewithJha) for a friend who deserves to stop counting tubs in her head.




Top comments (0)