A Telegram bot that lets mom build a grocery list by voice, photo, or text — and ships a live packing checklist to the neighborhood shopkeeper with one-tap status buttons.
Built for the DEV.to Gemma 4 Challenge.
Mom currently writes the monthly pantry list by hand, photographs it, and sends the photo to the shopkeeper on WhatsApp. She forgets items, sends "addendums," and the back-and-forth takes 30+ minutes. MomCart removes the typing friction, remembers past orders, tracks out-of-stock items, and gives the shopkeeper a structured, tappable checklist.
Mom speaks / types / sends photo
↓
faster-whisper ←─ voice note transcribed locally
↓
Gemma 4 E4B (Ollama) ←─ parses Hinglish / Telugu / English grocery text
↓
Chroma pantry DB ←─ fuzzy-matches items to canonical catalog names
↓
Notion (via MCP) ←─ items written as Status='cart' rows
↓
Mom builds cart over multiple days, then says "bhej do"
↓
Status flipped to 'pending'; shopkeeper notified
↓
Shopkeeper taps ✅ / ⚠️ / ❌ per item → Notion updated live
↓
❌ Out triggers substitution prompt to mom + wishlist entry
↓
Mom checks /status, /last, /wishlist anytime
Mom can send a voice note (Hinglish / Telugu / English), a photo (OCR — bonus), or plain text. faster-whisper transcribes audio locally with automatic language detection; Gemma 4 E4B parses the result into structured items with quantity and unit.
Items are written to Notion immediately as Status='cart' — no confirmation step. Mom can add items across multiple sessions and days. The active cart is tracked in data/active_cart.json with a stable CartID.
/cart— view the current cart as a formatted table/undo— remove only the last batch of items added/remove <item>— remove a single item (fuzzy-canonicalized via Chroma)/clear— empty the entire cart/sendor "bhej do" — flip all cart rows topendingand notify shopkeeper
After /send, the shopkeeper receives a single Telegram message listing every item with three inline buttons per row:
| Button | Meaning | Notion status |
|---|---|---|
| ✅ | Packed | packed |
| Partial stock | partial |
|
| ❌ | Out of stock | out |
Tapping a button collapses that row to a single label and updates Notion live. Mom watches progress with /status.
When the shopkeeper taps ❌ Out on any item, the bot automatically:
- Looks up the item's category in the Chroma pantry catalog
- Finds the top 2 most similar items in the same category via vector search
- Sends mom a new message with two substitute options and a Skip button
❌ toor dal nahi mila / Out of stock.
Substitute lena hai?
1️⃣ moong dal
2️⃣ chana dal
❌ Skip
- Mom taps a choice → substitute is added to her active cart instantly
- Mom taps Skip → no substitute, item stays in wishlist
- If shopkeeper later corrects a mistaken ❌ tap → wishlist row is auto-deleted
Every item marked ❌ Out is automatically added to a persistent wishlist in Notion (Status='wishlist'). If the same item goes out twice, its quantity accumulates — no duplicate rows.
/wishlist— view all unmet items as a formatted table/wishlist remove <item>or/wishlist_remove <item>— remove one entry/wishlist clear— empty the whole wishlist- "add all from wishlist" / "wishlist se sab add karo" → bulk-add to active cart
Proactive nudge: when mom starts a fresh cart after a /send, the bot checks for any open wishlist items and sends a one-time prompt like:
"Wishlist mein 2 items hain pichli baar ke (toor dal, mustard oil). Add karu?"
Replying "haan" or "yes" adds them all to the new cart automatically.
Mom can say "last time jaisa" or "same order" and the bot fetches her last order from the Chroma past_orders collection, re-parses any additions, and builds the combined list — without going to Notion.
/last— shows the most recently sent order with live statuses (packed / partial / out / pending), excluding wishlist and cart-draft rows/status— shows a count summary for the current active order
| Layer | Tool | Why |
|---|---|---|
| Bot framework | python-telegram-bot 22.5 (async) |
Native async, inline keyboards, file download |
| Speech-to-text | faster-whisper large-v3 |
Local, fast, Hinglish-capable, VAD filter |
| LLM | Gemma 4 E4B via Ollama | Gemma 4 Challenge requirement; runs on-device |
| LLM client | langchain-ollama ChatOllama |
Structured JSON output mode, zero-temp parsing |
| Agent orchestration | LangGraph / langchain-core |
ReAct agent pattern, tool chaining |
| Vector DB | chromadb (local persistent) |
Pantry catalog + order history embeddings |
| Embeddings | all-MiniLM-L6-v2 (Chroma default) |
Fast, good enough for grocery canonicalization |
| Data store | Notion database | Human-readable, shareable, already used by family |
| Notion integration | @notionhq/notion-mcp-server (stdio MCP) |
Official Notion MCP; no custom API wrapper needed |
| MCP client | langchain-mcp-adapters MultiServerMCPClient |
Bridges MCP tools to LangChain tool interface |
| Settings | pydantic-settings BaseSettings |
Typed env loading, .env file support |
| Logging | loguru |
Structured, colored, zero-config |
| Data wrangling | pandas |
BigBasket CSV filtering at seed time |
| Runtime | Python 3.11, asyncio-first | Required by langchain-mcp-adapters |
Create a database called Grocery Orders with these exact columns:
| Column | Type | Options |
|---|---|---|
| Item | Title | — |
| Qty | Number | — |
| Unit | Select | kg, g, L, ml, pcs, packet |
| Status | Select | pending, packed, partial, out, cart, wishlist |
| OrderID | Rich text | — |
| CreatedAt | Created time | — |
- Python 3.11+
- Ollama running locally with
gemma4:e4bpulled (ollama pull gemma4:e4b) - Node.js 18+ (for the Notion MCP server — installed automatically via
npx) - A Telegram bot token from @BotFather
- A Notion internal integration token + the database above
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
pip install -e .cp .env.example .env
# fill in all values — see .env.example for descriptionsDownload BigBasket Products.csv from Kaggle and save it as data/bigbasket.csv, then:
python -m scripts.seed_pantryThis loads ~200 filtered grocery SKUs + 41 hand-curated fresh produce items into Chroma.
python -m src.bot| Command | What it does |
|---|---|
/start |
Show help |
/cart |
View current cart |
/send |
Send cart to shopkeeper |
/clear |
Empty the cart |
/remove <item> |
Remove one item from cart |
/undo |
Remove the last batch added |
/last |
Show last sent order with statuses |
/status |
Live packing progress for current order |
/wishlist |
View out-of-stock items from past orders |
/wishlist remove <item> |
Remove one item from wishlist |
/wishlist clear |
Empty the wishlist |
Natural language shortcuts (no slash needed):
- "bhej do" / "send to shop" / "shop ko bhej do" →
/send - "add all from wishlist" / "wishlist se sab add karo" → add wishlist to cart
- "last time jaisa" / "same order" → recall previous order
- "haan" / "yes" → confirm wishlist nudge prompt
Shopkeeper receives one Telegram message per order with inline ✅ /
momcart/
├── CLAUDE.md — project brief + architecture decisions
├── README.md
├── .env.example
├── pyproject.toml
├── src/
│ ├── config.py — env loading, typed settings (pydantic-settings)
│ ├── bot.py — all Telegram handlers, cart/wishlist/sub logic
│ ├── stt.py — faster-whisper singleton + VAD transcription
│ ├── agent.py — Gemma 4 JSON parsing, canonicalization, recall
│ ├── notion_tools.py — Notion MCP client: cart, wishlist, order ops
│ ├── memory.py — Chroma collections: pantry_items, past_orders
│ ├── pantry_seed.py — BigBasket CSV → Chroma loader + fresh produce
│ └── prompts.py — all system prompts in one place
├── scripts/
│ ├── seed_pantry.py — CLI to populate Chroma from CSV
│ └── test_voice.py — CLI to test a voice file end-to-end
└── data/
├── bigbasket.csv — pantry catalog (gitignored)
├── chroma/ — persistent vector store (gitignored)
├── active_cart.json — current cart state (auto-managed)
└── active_wishlist.json — muted wishlist items (future)
- Mom: "do kilo toor dal, ek litre mustard oil" (voice note)
- Bot: "Cart mein add ho gaya: — 2 kg toor dal — 1 L mustard oil"
- Mom adds more items over the next day
- Mom: "bhej do" → order dispatched to shopkeeper
- Shopkeeper taps ❌ on toor dal → substitution prompt sent to mom
- Mom taps "moong dal" → moong added to cart, toor dal in wishlist
- Shopkeeper taps ❌ on mustard oil, mom taps Skip → mustard oil in wishlist
- Next day mom adds potatoes → bot nudges: "Wishlist mein 2 items hain (toor dal, mustard oil). Add karu?"
- Mom: "haan" → both moved from wishlist to cart automatically
- Mom:
/status→ live count of packed / partial / out / pending
See .env.example for the full list. Key variables:
TELEGRAM_BOT_TOKEN= # from @BotFather
MOM_ID= # Telegram user ID of mom
SHOPKEEPER_ID= # Telegram user ID of shopkeeper
NOTION_API_TOKEN= # Notion internal integration secret
NOTION_DATABASE_ID= # Notion DB URL or bare UUID
OLLAMA_HOST=http://localhost:11434
GEMMA_MODEL=gemma4:e4b
WHISPER_MODEL=large-v3
WHISPER_DEVICE=cpu # or cuda
MomCart.mp4