Account Panels, Evaluation Scoring & Reconciliation

How the Account view is assembled (every panel + its data source), the scoring that evaluates an account / an AM portfolio / what's at risk, the manager settings that tune that scoring, and the Google Ads spend↔contract reconciliation. Several pieces are flagged (in development). Verified against the codebase 2026-05-20.


1. Account-level panels & their source

The Account view (src/screens/AccountView.jsx) renders a drag-orderable grid of cards. One hook — src/hooks/useDashboardData.js — fans out the fetches; route constants live in src/apiService.js. ${D} = the Worker (fcr-dashboard-api), ${N8N_BASE} = n8n (fcrmedia.app.n8n.cloud/webhook).

Panel (card) Component Fetched from Route Underlying data
Revenue Analysis RevenuePanel client-side revenueAnalysis.js over the CRM account CRM RIs (recurring / setup / one-time)
CRM / Dynamics CrmPanel n8n dashboard-account Dynamics CRM + BQ GP listings
HubSpot HubSpotPanel Worker dashboard-hubspot BQ hubspot_deals
Teamwork Tickets TicketsPanel Worker dashboard-tickets BQ teamwork_open_tickets
Teamwork Projects ProjectsPanel Worker dashboard-teamwork-projects BQ project tasks/milestones
InSites Audit InsitesPanel Worker dashboard-insites-bq-lookup + dashboard-insites-poll BQ InSites inventory + live poll (KV-cached)
GMB Activity Report GbpActivityPanel Worker copysheet-dashboard-gbp (.activity) BQ GMB performance
Google My Business GbpPanel Worker → n8n → n8n copysheet-dashboard-gbpdashboard-serp-gbp-readDashboard-listing-detail BQ GMB (managed) → stored SERP snapshot → live SERP fallback
GBP Services Gap GbpServicesPanel Worker dashboard-gbp-services BQ
Category & Service Gaps CategoryGapsPanel Worker dashboard-category-gaps BQ CATEGORY_* benchmarks
Google Ads GoogleAdsPanel Worker copysheet-ads-stats BQ Google Ads transfer (retail spend)
SitePro Sites SiteProPanel Worker dashboard-sitepro BQ MegaDoc cache
Google Analytics GA4Panel Worker copysheet-ga4-stats + copysheet-ga4-traffic-sources BQ GA4
Search Console GSCPanel Worker copysheet-gsc-stats BQ GSC
Ahrefs SEO AhrefsPanel Worker dashboard-ahrefs-seo Ahrefs API v3 (only if account holds an SEO product)
Golden Pages Listings GpListingsPanel n8n (account envelope) dashboard-account BQ GP listings
Category Keywords CategoryKeywordsPanel Worker dashboard-category-keywords BQ KEYWORD_INTELLIGENCE
Keyword Gap Analysis KeywordGapPanel Worker dashboard-keyword-gap BQ
Call Tracking CallTrackingPanel Worker dashboard-iovox BQ iovox
Yext Publisher Listings YextPanel Worker dashboard-yext-listings (+ live dashboard-yext-sync) BQ cache + Yext API (only if SayMore/Yext products)
Google Merchant Centre GmcPanel Worker dashboard-gmc-stats BQ GOOGLE_MC_AUTO

Header / Health enrichments (not cards): Built-website check (dashboard-built-websites), Reviews (dashboard-reviews), data-freshness (dashboard-data-freshness) — all Worker/BQ. Health Vitals (the strip under the header) and Opportunity Signals are computed client-side (see §2).

Pattern: the CRM account, the GBP SERP fallback, and the GP listings are the only account-panel reads still routed through n8n (dashboard-account, dashboard-serp-gbp-read, Dashboard-listing-detail). Everything else is Worker → BigQuery.


2. Account evaluation (scoring & "what's at risk")

Three independent, client-side evaluators. All read tunable parameters from the manager config (§3).

2a. Account health score — src/healthScore.js

computeHealthScore(account)0–100, label Strong/Moderate/Weak/Critical. Weighted factors (default weights, must sum to 100):

Factor Weight Logic
Product diversity 35 coverage across Ads / SEO / Web / Listings / Social
Revenue level 25 banded by total revenue (€500→€5k)
Payment status 15 active Direct Debit > Credit Card
Order recency 15 last order within 1/2/3 yrs
Active order count 10 1 / 2 / 3+ active orders

Used per-account in the AM portfolio view (AMView.jsx) and behind the Health Vitals strip.

2b. AM portfolio score — src/amScore.js

computeAMScore(am)0–100 across five dimensions (default weights): Portfolio Health 30 (product coverage − suspension ratio), Growth 25 (MRR YoY vs floor/ceiling), Retention 20 (1 − suspension rate), Revenue Density 15 (MRR/account vs ceiling), Engagement 10 (tickets, open deals, call-tracking & review coverage). amScoreBreakdown() drives the stacked bars in ManagersView.jsx; an AM scoring below atRiskThreshold (default 40) is flagged at risk.

2c. Opportunity signals / risk engine — src/opportunityEngine.js (in development — currently disabled)

analyzeOpportunities() cross-references every data source into a sorted list of 41 signals in three classes — upsell (17), retention (11, the "at risk" signals), action (13) — each with priority, talking point, € impact and an actionRoute (client_conversation / am_ticket / optimiser_ticket / proposal / benchmark). Examples: ad-spend declining 3 mo, contract expiring with no renewal deal, open Credit-Control ticket + suspension, budget-deployment gap (billed > retail spend), low GMB rating, traffic/clicks decline.

The on-screen Opportunity Signals card is temporarily disabled in AccountView.jsx ("disabled during n8n migration") — the engine + all thresholds/toggles still exist and feed the AI advisor/insights.


3. Manager settings — src/config/managerConfig.js + src/components/ConfigPanel.jsx (in development — hidden tab)

Everything in §2 is tunable. ConfigPanel (rendered inside the Managers tab, which is hidden: true in Dashboard.jsx) lets a manager edit:

  • Health weights (the 5 factors in §2a, must sum to 100)
  • AM score weights (the 5 dimensions in §2b, must sum to 100)
  • AM score paramsatRiskThreshold (40), densityCeiling (500), growthFloor (−10), growthCeiling (20)
  • Signal thresholds — ~25 knobs (ad CTR floor, utilisation min, bounce max, avg-position max, min reviews/rating, answer-rate bands, InSites score mins, HubSpot disengagement days, contract-expiry windows 30/60/90)
  • Signal toggles — on/off for each of the 41 signals

Storage: browser localStorage key dashboard-manager-config (per-user, not server-side), read via getConfig() / written via saveConfig().

Because config is local, settings don't yet sync across users/devices — promoting this to a shared store is the obvious next step.


4. Google Ads reconciliation — spend vs contract value (in development — hidden tab)

The Spend tab (src/screens/SpendView.jsx, hidden: true) compares CRM contracted order value (DMS - SEA) against actual Google Ads retail spend, per account and in aggregate, surfacing the exposure (contracted − actual) and a % under-deployment, plus DEA per-month allocation, opening balance, and month pacing.

  • Per account: ADS_SPEND_URLdashboard-ads-spend
  • All accounts list: ADS_SPEND_ALL_URLdashboard-ads-spend-all

Both Worker handlers are deliberate stubs that throw to trigger the n8n fallback — the live spend-reconciliation logic (date-aware MRR, DEA allocation, opening balance, cancelled lines) currently lives in n8n. There's a standing TODO to port that logic back into the Worker handlers (worker/src/handlers/ads-spend.js, ads-spend-all.js).

Related: cross-view MRR reconciliation

Separately, the Reconciliation tab (ReconciliationView.jsxdashboard-reconciliation, Worker/BQ) checks that the four billing views agree — AM Portfolio vs Solution Portfolio vs Revenue Bridge vs the active_clients benchmark — by product group, and (in mode=diff) lists accounts present in one view but not another, or with an MRR delta > €1. See billing-rules-comparison.md.


Sources: AccountView.jsx, useDashboardData.js, apiService.js, healthScore.js, amScore.js, opportunityEngine.js, config/managerConfig.js, ConfigPanel.jsx, SpendView.jsx, worker/src/handlers/{reconciliation,ads-spend,ads-spend-all}.js.

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?