Aditya Gaur
Work

Revenue dashboard

A CRM compiled to a single HTML file

A pipeline dashboard that started as one static file with the data baked in, and whose most useful view audits the pipeline's integrity instead of reporting its size.

Role
Solo — product, ETL, frontend
Timeframe
2026 — February to July
Status
shipped
Stack
Python·Vanilla JS·Zoho CRM API·Neon Postgres·Cloudflare Pages

Three questions between meetings

Leadership needs pipeline answers between meetings: what's closing this quarter, what changed since last week, which number do we say in the room. The CRM has all of it and answers none of it quickly: the data is spread across deals, timelines and owner views, behind a login, arranged for salespeople doing data entry rather than founders doing arithmetic.

The first version of the fix was almost aggressively primitive: a Python script pulls every deal and its full change history from the CRM, computes everything worth knowing, and bakes the result into a single self-contained HTML file, data inlined as a JSON literal, styles and script inlined beside it. No runtime backend, no database, no API call after load. Cloudflare Pages serves the file; Cloudflare Access decides who can see it, which bought an auth wall with zero auth code.

For a pipeline of a few hundred deals read by a handful of people, that model is close to unbeatable: zero infrastructure, instant loads, trivially auditable output, and it survives an outage of everything except the CDN. The interesting parts of this project are the two places the model earned an upgrade — and the view that has nothing to do with reporting at all.

1
HTML file, originally — data baked in
3.9 MB → 110 KB
page weight, after the split
20 min → 90 s
full refresh, after parallelizing one endpoint
1,675
commits

Founder Mode: an audit instrument, not a report

Every pipeline dashboard reports the total. The number is only as honest as the rows under it — and rows drift in specific, detectable ways. A deal quietly revised down late in the quarter. A close date that has slipped three times. A deal that moved backward a stage. A number that has sat untouched all the way into legal review.

Founder Mode is a separate view that walks every deal's change timeline and runs seven detectors over it. The design premise is an inversion worth stating: in a late-stage deal, an amount that has never been revised is more suspicious than one that has. Somebody negotiating validates the number; a number nobody touched is a placeholder wearing a stage label.

Detectors

The number moved — by more than the ₹1 epsilon — and someone should know when, and in which stage.

Pipeline inflation
52% of value is late-stage and never validated
Revised down, total
60 indexed — the at-risk number
  1. Client A — platformNegotiation · 140
    • Amount revisedamount down 200 → 140 (-30%)
  2. Client B — verifyInfosec/Legal · 95
  3. Client C — payroll dataCommercial Discussion · 60
  4. Client D — embeddedDemo Done · 75
  5. Client E — connectorsPOC ·
  6. Client F — platformProposal Sent · 180
    • Amount revisedamount up 150 → 180 (20%)
  7. Client G — verifyNegotiation · 220
  8. Client H — syncCommercial Discussion · 45

2 of 8 deals flagged by the active detectors.

Figure 1. Five of the seven detectors, over a synthetic pipeline. Switch them on one at a time — with everything off, this is an ordinary pipeline view, and it looks fine.

Detector logic follows the production payload builder: a ₹1 epsilon on amount changes, slippage from the second forward push, index-checked regressions. Deals are synthetic; amounts are indexed, not currency.

The details in the logic are where the timeline work pays off. Amount changes carry the stage the deal was in when the number moved, reconstructed by walking the timeline with a rolling stage variable, because a revision during negotiation is business and a revision the week before quarter-end is a finding. Slippage needs two forward pushes before it counts; one reschedule is life. And the roll-up number the detectors feed, the total revised downward across the book, is the at-risk figure founders actually ask for, sitting above whatever the headline total says.

Two per-deal detectors combine into a per-owner risk score with explicit weights (unvalidated late-stage counts triple, regressions and slippage double, zombies single), which is as close as the dashboard gets to scoring people, and it stays framed as whose pipeline needs review, not who's underperforming.

Two clicks from any number to its deals

The stated rule: leadership must be able to drill from any aggregate into the underlying deals in two clicks. The implementation is deliberately dumb, which is why it's fast: at build time, every drillable number pre-materializes its deal list into a flat array, and every clickable surface stores one integer index into it. Click one: the number opens a modal listing exactly the deals it counts, with the count in the footer so the drill visibly reconciles. Click two: any deal links out to its CRM record, the source of truth, one more click deep, exactly at the boundary where the dashboard's authority should end.

The v2 split, and what each stage refuses to do

The single-file model had one real weakness: the file was the database. Every refresh shipped 3.9 MB, history didn't exist, and one failing upstream API meant no new file at all. The v2 move was one sentence, split fetch-and-store from serve, and put the auth wall in front, and the shape of it is best described by what each stage declines to do.

The CRM, with one hot spot

Deals, timelines, leads, meetings, owners. The per-deal timeline endpoint is the critical path: sequential, a full run took twenty minutes; with twelve workers, ninety seconds. The review calls that thread pool the single highest-impact line in the project, and it is.

The fix
ThreadPoolExecutor(max_workers=12)
# ~20 min → 60–90 s

Figure 2. The pipeline as shipped, stage by stage — each with its refusal, which is where the reliability actually lives.

The frontend didn't change in the split: the same vanilla-JS app now boots from six fetches instead of an inlined literal — and the page went from 3.9 MB to 110 KB. The refresh schedule tells its own small story about docs versus reality: the documentation says hourly, a later doc says seven times a day, and the workflow actually runs three times a day, timed to Indian office hours. Each number was true when written. The code is the only one that's true now.

Success was defined, not measured

The written success criterion is one sentence: leadership doesn't open Zoho anymore for routine pipeline questions — the number on the dashboard is the number they cite in the room. I can't tell you whether that happened, and the repo is honest about why: there's no instrumentation, no view counts, no alert if the dashboard goes stale. The architecture review flagged observability as absent, and it still is. The dashboard that audits the pipeline's honesty cannot yet audit its own usage, which is the kind of sentence that belongs on the page rather than in a drawer.

What I'd do differently

Tests first, as the review said and I didn't do. The improvement list had a test harness at item eight of nine, with a note that it should really be item zero because every other change becomes safer once it exists. It was never built, and 1,675 commits landed on a system whose regressions were caught by looking. The one contract that was verified — the resilience of the snapshot layer — was verified manually, once.

Enforce Founder Mode at the API, not the keyboard. The view is gated by a keyboard shortcut, an interim measure with role-based access written up and scoped, including the sentence that matters: UI hiding alone is not security. The scoped design puts the check in the API. It's still a shortcut today, and anyone past the access wall can fetch the founder payload directly.

Delete what the docs say was deleted. The decommission list from the v2 design is only half-executed: the README still names an entry point that no longer exists, and credentials the review flagged as sprawl are still in the tree. A migration isn't done when the new path works; it's done when the old path is gone, and this repo is a working demonstration of the difference.