Job-application artifact · Built for Stylework · July 2026 · Shipped in ~51 hours

SitelineCRM: capture sales data
where the conversation already is.

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.

B2B SaaS AI / Extraction Telegram Bot Coworking / Real Estate Solo Build
Documentation
GitHub → Live Product →
THREE CAPTURE MODES → ONE SAVE PATH WhatsApp Forward Raw text Voice Note Whisper transcription Screenshot gpt‑4o‑mini vision Telegram Bot flows.py handlers AI Extraction OpenAI gpt-4o-mini Quality Gate Text captures only _save_capture() Find/create + log Confirmation Card “Looks good” / “Edit stage” or: intent = “recall” → brief instead of capture Save path never regresses a lead’s stage backwards from a vague recapture
SYSTEM ARCHITECTURE: BOT + API + DASHBOARD TELEGRAM BOT / RAILWAY AI NEON POSTGRES DASHBOARD Rep’s Chat Forward/voice/image FastAPI + PTB Single asyncio process gpt-4o-mini + Whisper (voice) asyncpg Fully async db.py /api/* Endpoints HMAC-signed tokens Dashboard SPA Static JS, GitHub Pages Space Matching City/seats hard filter Single OpenAI key for extraction, vision, and transcription. No separate provider accounts to manage

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.

Save as PDF →

Project overview

Overview

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.

~51 hrs First commit to latest. Solo, no engineering team
32 Commits
93.5% Field-level extraction accuracy (129/138), measured by an eval harness
3 Capture modes: text forward, voice note, screenshot

Built by

Gaurav Gupta. Solo build, first commit 2026-07-16 13:20, latest 2026-07-18 16:16.

Deals don’t die from bad selling.
They go cold.

01 · Problem

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.

60-70% CRM abandonment rate. Re-keying data is friction nobody sustains

Where deals actually die

Re-keying is the tax

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.

The conversation is already structured, in someone’s head

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.

Nobody retyped it in time

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.

Capture stays where the
conversation already is.

02 · Solution

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.

Forwarded text

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.

Voice note

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.

Screenshot

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.

What’s actually built
and working.

03 · Capabilities

Pipeline & heat

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.

Nudges & matching

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 & dashboard

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.

Demo mode

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.

The extraction isn’t just demoed.
It’s measured.

04 · Eval Harness

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.

93.5% Field-level accuracy: 129/138
6 × 23 = 138 scored fields (6 fields per transcript)
9 Misses, all one failure mode

What’s scored, and what’s deliberately not

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.

A documented trust boundary

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 known weakness

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.

No framework, no bundler,
one provider.

05 · Tech
LayerTechnologyDetails
Bot backendPython · FastAPI · python-telegram-botSingle asyncio process, deploys to Railway. FastAPI lifespan wires bot polling + nudge JobQueue + /api/* together.
DatabaseNeon Postgres · asyncpgFully async. Every db.* call is awaited. Five tables: users, companies, leads, interactions, spaces.
AI · extraction & visionOpenAI gpt-4o-miniText extraction, vision (screenshots), and reasoning. One provider, one key.
AI · transcriptionOpenAI WhisperVoice note transcription ahead of intent classification.
DashboardStatic HTML/JS · no framework, no bundlerDeploys to GitHub Pages via Actions. Authenticates against the bot’s /api/* endpoints via signed per-user HMAC tokens. No credentials shipped to the browser.

What this is,
and what it isn’t.

06 · Limitations

This candour is deliberate. A build story that hides its limitations is less useful than one that names them.

Runs on seeded data

The pipeline runs on seeded sample data, labelled in-product as such. It’s a working product, not a customer deployment. No revenue.

Auth is demo-grade

Signed HMAC tokens with server-verified roles, but no expiry or revocation. This is a deliberate one-day-demo simplification, not production-grade auth.

The eval dataset is synthetic

Hand-written rather than drawn from real bot traffic, so 93.5% is indicative of extraction quality, not field-proven against production usage.

Complexity the product earns,
not imposes.

07 · Where This Goes

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 shapeWhat unlocks
Solo repThe bot, and nothing else: capture, pipeline, heat score, nudges.
Add repsAssignment and a leaderboard.
Add a managerTeam funnel and stalled-deal escalation, already built.
Add a sales head over multiple managersRegion roll-ups, forecasting, quota tracking.
CEO levelThe revenue view.

Structure accretes above the rep, not on top of them. That’s the thesis this build is arguing for.