How the FCR Dashboard Works — Architecture Overview
Plain-English walkthrough of the FCR Dashboard platform: what it is, the pieces it's built from, how they fit together, and why it's fast. Pair this with
fcr-dashboard-architecture-deck.md(the slide storyboard) to build a presentation, andhubspot-data-architecture.mdfor the data-layer detail.Written 2026-05-20. Figures verified live.
1. What it is
The FCR Dashboard is an internal sales & account-management intelligence platform. It pulls FCR's data — CRM, billing, HubSpot deals, Google Business Profiles, ads, search, keyword intelligence, census/area data — into one place and puts an AI advisor on top of it, so a rep or AM can ask a plain-English question about any account or prospect and get a complete, sourced answer in seconds.
It shows up in three places a rep already works:
- a web dashboard (portfolio, accounts, maps, reports),
- the HubSpot deal sidebar (the Prospect Advisor card),
- Roam chat (the same advisor in the team's chat tool), and
- Claude Code skills (
/check-account,/prospect, … — for power users in Claude Code / Claude for Work).
All four are powered by the same brain.
2. The four surfaces (what the user touches)
| Surface | What it is | Runs on |
|---|---|---|
| Web UI | React dashboard — portfolio, account lookups, area maps, reports | Cloudflare Pages (fcr-dashboard-ui.pages.dev) |
| HubSpot card | "Prospect Advisor" chat in the deal sidebar | HubSpot's UI-extension sandbox |
| Roam advisor | Same advisor, in Roam chat | Roam, via webhook |
| Claude Code skills | Slash-command workflows (/check-account, /prospect, /deep-dive, /portfolio, …) |
Claude Code / Claude for Work (CLI / desktop agent) — not the web UI |
Every one of them is a client — they all call the same back-end Worker API.
Claude Code skills (a note on what these are)
The skills are an FCR-specific layer that runs only inside Claude Code /
Claude for Work — they are not part of the web app, the worker, or the
HubSpot card. Each is a SKILL.md (in .claude/skills/ and .agents/skills/)
that tells the agent how to orchestrate the Worker's endpoints for a task, e.g.:
- Account / portfolio:
/check-account,/portfolio,/deep-dive,/am-review,/deals,/tickets,/revenue-bridge - Prospecting & maps:
/prospect,/area-map,/insites,/proposal,/seo-gap-audit - Performance & data:
/ads-report,/ads-stats,/analytics,/search-console,/gbp-check,/gmb-report,/ahrefs,/keyword-gaps,/category-keywords,/merchant-centre,/call-tracking,/ads-aimax-audit - Ops:
/deploy-worker,/bq-query
They're how a power user drives the platform from the terminal/agent. Same data, same Worker — a different (and the most flexible) front door. They're versioned in the repo so the agent's behaviour is reproducible.
3. The brain: one Worker at the edge
All the logic lives in a single Cloudflare Worker called fcr-dashboard-api
(fcr-dashboard-api.fcrmedia.workers.dev).
A Cloudflare Worker is serverless edge compute: there's no server and no region. Cloudflare runs the code in a lightweight V8 isolate per request, in whichever of its ~330 global data centres is nearest the caller, then tears it down. The same code runs everywhere, on demand. That's why responses are fast no matter where the rep is.
It's a monolith: ~126 endpoint handlers (account lookup, enrichment, keyword intelligence, the AI advisor, the deal-brief system, etc.) are bundled into one Worker. It doesn't call other Workers — everything is in the one deployable unit.
The Worker also runs on a schedule (cron triggers) and as a queue consumer, not just on web requests — that's how background jobs like cache pre-warming and the deal-brief refresh happen.
4. The data layer (what the Worker reads)
The Worker holds bindings to Cloudflare data services and reaches out to external systems. It doesn't store data itself — it orchestrates.
BigQuery (the warehouse) — the system of record for analytical data:
hubspot_deals— currently-open deals (refreshed daily).hubspot_deals_backcatalogue_18m— 18 months of closed won/lost deals (~10,800), assembled from three rolling 6-month cohorts, plus a frozen year-on-year comparison pair.hubspot_engagements— every note / email / call / meeting (~230k).active_clients, keyword intelligence, GBP performance, census/area data, etc.
Vectorize (semantic search indexes) — for "find things like this":
fcr-deal-history— past deals, so the advisor can say "deals like this one won/lost because…".fcr-site-portfolio— FCR-built sites, for "similar clients in this category".fcr-company-knowledge— FCR's own knowledge base.
KV (instant key-value cache) — sub-30ms reads at the edge, e.g. the precomputed deal briefs.
External, live — BigQuery REST, the CRM API, Google (SerpAPI/Places), Pleper (GBP), Ahrefs, Meta. These are the slow sources, which is exactly why we pre-compute (next section).
5. The deal-brief system (why it's instant)
The problem: when a rep opened a deal, the advisor used to fan out 4–5 live calls (CRM, Google Business Profile, website crawl, similar deals, category intelligence) while the rep waited — 15–30+ seconds every time.
The fix: precompute a complete deal brief for every open deal, ahead of time, and cache it. When a rep opens the deal, the advisor reads one cached blob in ~30ms instead of making the rep wait for live lookups.
How it's kept fresh:
- A background job (cron, every 2 hours during the day) refreshes briefs in small batches via a queue, so the heavy lookups happen when nobody's waiting.
- Opening a deal self-heals it — if its brief is out of date, opening it triggers a background refresh so it's current next time.
- The brief carries everything the four canonical rep questions need: "What's this deal about?", "Run prospect intel on this category", "What was last discussed?", "Draft a proposal note" — all answered from the cached brief with no live wait.
A brief bundles: deal facts, CRM + billing, Google Business Profile (live for prospects we don't have on file), a website crawl, category keyword intelligence, the local area picture, similar paying clients, similar past deals, and the recent conversation history (real rep contact ranked above automated emails).
6. The AI advisor (the intelligence)
The advisor is Claude (Anthropic) running inside the Worker, given a toolbox of ~40 functions — look up an account, fetch a deal brief, run prospect intelligence, search similar deals, draft an email from approved templates, post a note back to the deal, and so on.
When a rep asks a question, Claude decides which tools to call, the Worker runs them (now mostly instant cache reads), and Claude writes the answer in FCR's voice. The same advisor serves the HubSpot card and Roam.
Guardrails are built into its instructions — e.g. it leads with the precomputed brief, never presents free directory listings as paying clients, and frames a brand-new invisible business as a greenfield opportunity rather than a data gap.
7. End-to-end: what happens when a rep opens a deal
1. Rep opens a deal in HubSpot → the Prospect Advisor card loads.
2. The card pre-warms: it asks the Worker to make sure a fresh brief exists.
3. Rep clicks "What's this deal about?"
4. Advisor (Claude, in the Worker) calls get_deal_brief → ~30ms cached read.
5. Claude writes the answer, streamed back into the card.
(No live CRM/Google/website calls in the rep's path — already precomputed.)
Behind the scenes, on a schedule, the Worker keeps every open deal's brief fresh by composing them in the background through a queue.
8. Where the code lives & how it ships
- Source of truth: the Git repo (
github.com/FCR4IE/DASHBOARD).- Worker:
worker/src/**(entryindex.js,router.js, 126handlers/). - Web UI:
src/**(React/Vite). - HubSpot card:
hubspot-extension/src/**.
- Worker:
- Deploy:
scripts/deploy-worker.shbundlesworker/src/**into one script and ships it to Cloudflare (it lint-gates, refuses a dirty tree, and tags every deploy for rollback). The UI builds with Vite and uploads to Pages. The card uploads to HubSpot. - Two Cloudflare accounts: FCR Media (production —
…fcrmedia.workers.dev) and a personal dev account (…cathaldempsey.workers.dev).
On Cloudflare the Worker is stored as one compiled bundle, not individual files — the individual files only exist in the repo. (This is why the CF dashboard shows "Workers: 0 bound" — that counts other Workers this one calls, of which there are none; it's self-contained.)
9. Runtime, bindings & secrets (reference)
Snapshot of the production Worker (fcr-dashboard-api, FCR account) as
configured 2026-05-20.
Connected bindings (10) — resources the Worker talks to
| Type | Binding name | Resource | What it's for |
|---|---|---|---|
| R2 bucket | ADVISOR_CSV |
fcr-advisor-csv |
CSV attachments the advisor generates |
| Queue | BRIEF_QUEUE |
fcr-deal-brief-queue |
deal-brief refresh jobs (producer + consumer) |
| Browser | BROWSER |
— | headless Chrome (SERP screenshots / renders) |
| KV | CACHE |
CACHE |
general cache + the deal-brief:<id> blobs |
| Vectorize | COMPANY_KNOWLEDGE_VECTORIZE |
fcr-company-knowledge |
company knowledge-base search |
| Vectorize | DEAL_VECTORIZE |
fcr-deal-history |
similar past deals |
| Durable Object | PROGRESS_DO |
fcr-dashboard-api_ProgressDO |
live chat-progress + STT hand-off |
| KV | REPORTS |
fcr-REPORTS |
Discovery LRC reports |
| R2 bucket | SERP_SNAPSHOTS |
fcr-serp-snapshots |
durable SERP HTML/PNG snapshots |
| Vectorize | VECTORIZE |
fcr-site-portfolio |
similar FCR sites / clients |
The CF dashboard's "Workers: 0 bound" panel counts service bindings to other Workers (none — this is a self-contained monolith), NOT these resource bindings.
Secrets & variables
Plaintext variables (non-sensitive config): BQ_PROJECT_ID,
GOOGLE_ADS_CUSTOMER_ID, GOOGLE_ADS_LOGIN_CUSTOMER_ID, N8N_BASE,
SELF_BASE_URL.
Secrets (encrypted; values never in the repo — set via wrangler secret put).
What each unlocks:
| Secret | Unlocks |
|---|---|
API_KEY |
the x-api-key all UI/advisor calls authenticate with |
ADMIN_API_KEY |
extra gate for /dashboard-bq-admin (DDL/writes) |
ANTHROPIC_API_KEY |
Claude — the advisor |
OPENAI_API_KEY |
embeddings (vector search) + Whisper STT |
BQ_SERVICE_ACCOUNT_JSON |
BigQuery auth |
HUBSPOT_ACCESS_TOKEN |
live HubSpot deal/engagement reads |
SERPAPI_KEY |
Google Maps / SERP |
PLEPER_API_KEY, PLEPER_API_SIG |
live Google Business Profile data |
AHREFS_API_KEY |
Ahrefs SEO data |
GOOGLE_ADS_CLIENT_ID/SECRET/DEVELOPER_TOKEN/REFRESH_TOKEN |
Google Ads API |
META_ACCESS_TOKEN, META_AD_ACCOUNT_ID |
Meta reach estimates |
INSITES_API_KEY_DF, INSITES_API_KEY_LRC, INSITES_PUBLIC_KEY |
InSites audits |
N8N_API_KEY |
n8n webhooks |
ROAM_BEARER, ROAM_DEV_CHAT_ID, ROAM_DEV_GROUP_ID |
Roam advisor posting |
TEAMWORK_DESK_API_KEY, TEAMWORK_DESK_WEBHOOK_SECRET |
Teamwork tickets |
Trigger events
| Type | Schedule / source | Job |
|---|---|---|
| Cron | * * * * * (every minute) |
Pleper + SerpAPI enrich + HubSpot engagements drain |
| Cron | 30 7 * * * |
pre-warm AM portfolio caches |
| Cron | 45 7 * * * |
pre-warm check-account for open-deal subscribers |
| Cron | 0 8-20/2 * * * |
chunked deal-brief refresh (08:00–20:00 every 2h) |
| Queue | fcr-deal-brief-queue |
queue() consumer composes briefs |
Runtime settings
- Compatibility date: 2024-09-23 · flags:
nodejs_compat - Placement: default (CF picks the edge location per request)
- CPU time limit: default (shown as "—"). On the Paid plan that's 30s of
CPU per invocation, raisable to 300s via
[limits] cpu_ms. We use the default because handlers are I/O-bound — a compose is 14–46s of wall time but only a few hundred ms of CPU (waiting on BQ/SerpAPI/Pleper/KV doesn't count as CPU). So the ceiling is never approached. - Observability: Logs disabled, Traces disabled. Recommendation: enable
Workers Logs — debugging the queue backoff was blind without it (
wrangler tailreturns nothing when logs are off). - Two accounts: production = FCR Media (
…fcrmedia.workers.dev), dev = personal (…cathaldempsey.workers.dev). Secrets/bindings exist in both.
10. Glossary
| Term | Plain meaning |
|---|---|
| Worker | Serverless code that runs at Cloudflare's edge, per request |
| Pages | Cloudflare's static-site hosting (the web UI) |
| Edge | Cloudflare's ~330 global data centres; code runs at the nearest one |
| Binding | A connection from the Worker to a data service (KV, Vectorize, Queue…) |
| BigQuery (BQ) | Google's data warehouse — FCR's analytical system of record |
| Vectorize | Cloudflare's semantic-search index ("find things like this") |
| KV | Cloudflare's fast key-value cache |
| Queue | Background job pipeline — spreads slow work across many runs |
| Cron trigger | A schedule that runs the Worker automatically |
| Deal brief | A precomputed bundle of everything known about a deal |
| The advisor | Claude + a toolbox, answering rep questions in FCR's voice |
FCR Dashboard documentation · generated from docs/ · keep counts verified, not guessed.