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, and hubspot-data-architecture.md for 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_18m18 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/** (entry index.js, router.js, 126 handlers/).
    • Web UI: src/** (React/Vite).
    • HubSpot card: hubspot-extension/src/**.
  • Deploy: scripts/deploy-worker.sh bundles worker/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 tail returns 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.

Ask the docsRAG over this site
Ask anything about the FCR Dashboard platform — architecture, BigQuery, the worker routes, billing rules, the LRC stack, scoring… Answers are grounded in this documentation, with source links.
How does the deal-brief refresh work? Which routes are Worker vs n8n? How is account health scored?