A Telegram-native B2B sales CRM built for Stylework, an Indian coworking-space aggregator. A rep forwards a WhatsApp chat, sends a voice note, or shares a screenshot. No forms. The model extracts contact, company, seat count, city, space type, budget, move-in date, stage, and next action.
This page is the build record for SitelineCRM. It covers the data-capture problem, the extraction pipeline, the eval harness that measures it, and the honest state of what shipped versus what didn’t.
SitelineCRM was built solo as a job-application artifact for an “AI Product Builder” role at Stylework, an Indian coworking-space aggregator. It is live. Landing at argaur.github.io/siteline-crm, dashboard at /dashboard, source at github.com/argaur/siteline-crm.
Gaurav Gupta. Solo build, first commit 2026-07-16 13:20, latest 2026-07-18 16:16.
A 50-seat inquiry and a 10,000-seat managed-office RFP both start the same way. An unstructured conversation happens: WhatsApp, a call, a walk-in.
A human then has to re-key it into a CRM, chase it, and remember to follow up. It’s a data-capture problem. Today it’s solved with headcount.
Every conversation a rep has must be manually retyped into a CRM before it’s usable pipeline data. That retyping is where the process breaks down first.
Seat count, budget, move-in date, next action: a rep already knows all of it after the call. The CRM asks them to say it twice.
Deals rarely die from a bad pitch. They go cold because the last conversation never made it into the system before the next one arrived.
No forms. A rep forwards a WhatsApp chat, sends a voice note, or shares a screenshot to a Telegram bot. The model extracts contact, company, seat count, city, space type, budget per seat, move-in date, stage, and next action from raw text, audio, or images.
Rep forwards the WhatsApp thread as-is. ai.extract_from_text() pulls
the structured fields, then a quality gate checks the extraction before it saves.
Whisper transcribes, then intent classification routes it: “capture” extracts a new update, “recall” generates a spoken-style brief on the deal instead. There’s no quality gate. Voice goes straight through.
gpt-4o-mini vision reads a shared screenshot, whether it’s a
proposal PDF page, a WhatsApp screenshot, or a form, the same way it would read
the text version.
7-stage pipeline: Inquiry → Qualified → Site Visit → Proposal → Negotiation → Closed-Won / Closed-Lost, self-updating from every capture.
Heat scoring is recomputed on every read, never stored: recency (60%), interaction count (15%), deal size (25%). A never-touched lead reads Cold by design. It cannot drift out of date, because it’s never written.
Stalled-deal nudges: the bot DMs the assigned rep when a lead goes quiet past a threshold.
Space matching: city and available seats are hard filters. The 0-100 score is budget fit (45) + space type (30) + capacity right-sizing (25). A 50-seat lead into a 60-seat space outscores the same lead into a 500-seat space.
Manager (“Team”) view: team funnel, rep leaderboard, stalled-deal list. It’s role-gated server-side. Reps hitting these endpoints get HTTP 403.
Dashboard home: pipeline funnel, heat strip, site-visit radar with weather, inventory pressure, a “Needs a Human Today” panel, live activity feed, and city footprint.
A visitor who owns no leads reads the seeded pipeline behind a sample-data banner. The product demonstrates itself.
It’s read-only: cross-user writes still return 403, even in demo mode.
bot/eval/score_extraction.py runs against
bot/eval/dataset.jsonl: 23 hand-written labelled transcripts, including
Hinglish and informal phrasing that matches real usage, not clean English.
Six fields per row are graded: stage (exact match against the 7-value
enum) plus presence checks for contact_name, company,
budget_signal, seat_count, and city.
Free-text fields, summary and next_action, are
deliberately excluded. Grading prose needs a human or an LLM-judge, and that
reintroduces the non-determinism the metric exists to avoid.
A --min-accuracy flag makes the harness usable as a CI gate or a
tuning-loop stop condition.
When tuning extraction, only the prompt in ai.py may be edited. Never
the dataset or the scorer. Editing either of those to make the number go up would be
reward-hacking the eval instead of improving extraction.
The model under-reads stage, defaulting to Inquiry where
Qualified or unknown is correct. This was found by the eval and
confirmed three independent ways.
All 9 misses are this one failure mode. It’s a prompt fix, deliberately not attempted under deadline.
| Layer | Technology | Details |
|---|---|---|
| Bot backend | Python · FastAPI · python-telegram-bot | Single asyncio process, deploys to Railway. FastAPI lifespan wires bot polling + nudge JobQueue + /api/* together. |
| Database | Neon Postgres · asyncpg | Fully async. Every db.* call is awaited. Five tables: users, companies, leads, interactions, spaces. |
| AI · extraction & vision | OpenAI gpt-4o-mini | Text extraction, vision (screenshots), and reasoning. One provider, one key. |
| AI · transcription | OpenAI Whisper | Voice note transcription ahead of intent classification. |
| Dashboard | Static HTML/JS · no framework, no bundler | Deploys to GitHub Pages via Actions. Authenticates against the bot’s /api/* endpoints via signed per-user HMAC tokens. No credentials shipped to the browser. |
This candour is deliberate. A build story that hides its limitations is less useful than one that names them.
The pipeline runs on seeded sample data, labelled in-product as such. It’s a working product, not a customer deployment. No revenue.
Signed HMAC tokens with server-verified roles, but no expiry or revocation. This is a deliberate one-day-demo simplification, not production-grade auth.
Hand-written rather than drawn from real bot traffic, so 93.5% is indicative of extraction quality, not field-proven against production usage.
Most CRMs fail because they impose enterprise process on a five-person sales team. SitelineCRM is designed to earn its complexity as the org around it grows. The rep’s own experience never gets heavier.
| Org shape | What unlocks |
|---|---|
| Solo rep | The bot, and nothing else: capture, pipeline, heat score, nudges. |
| Add reps | Assignment and a leaderboard. |
| Add a manager | Team funnel and stalled-deal escalation, already built. |
| Add a sales head over multiple managers | Region roll-ups, forecasting, quota tracking. |
| CEO level | The revenue view. |
Structure accretes above the rep, not on top of them. That’s the thesis this build is arguing for.