Skip to main content

Signal — Service Expansion

Written by Schae Lilley

Service Expansion is a module in Signal (Power Digital's internal BI web app) at route /predictions/se, visible to Team Leads and above (base-tier Members are redirected away). It ranks every active client by an ML-computed expansion "readiness score" refreshed weekly by the data-science pipeline, recommends the specific next services to pitch, overlays Salesforce deal activity and Gong-derived contact sentiment, and can draft an AI growth roadmap (Claude) that hands off to Iris (iris.novapower.io). It answers the question: "Of all my clients, who should I pitch more work to this week, what should I pitch, and is now a good time?" Top-level data origins: the weekly ML readiness model, Salesforce opportunity data, Gong call analysis, Vena finance figures, Nova client records, Claude-generated text, and Signal's own records (user-entered dismissals, notes, actions).

Field

Value

Route

/predictions/se (deep link per client: /predictions/se/:clientId)

Access

Team Lead and above — base Members are redirected to /summary by MemberAllowedRoute

Nav section

Client Health (alongside Portfolio Review, POC Management, Churn Risk, Division Forecast)

Primary data sources

Weekly ML readiness model (data-science pipeline); Salesforce opportunities; Gong call sentiment; Vena finance (MRR / monthly GP); Nova client attributes; Claude (AI Summary, Roadmap, Brand Intel); Signal's own records

Writes

Signal's own records: dismissals (se_client_feedback), change audit (change_history), action feedback/notes, manual follow-ups, client notes, favorites, AI text (client_summaries, macro_brand_insights), Asana links, usage events; plus real tasks created in Asana

Key code refs

src/views/SePredictionsView.tsx → src/views/predictions/{SePredictionsView,SeOverviewTab,SeServiceDetailTab,SeClientDetail,SeFeedbackModal,seRecommendedActions}.tsx; server/predictions.ts (fetchSeData); server/index.ts (/api/predictions/service-expansion, /api/predictions/se/*, /api/client-summaries/*, /api/macro-brand-insights/*, /api/asana/*); server/snapshotClientSummaries.ts; server/brandIntel.ts; src/App.tsx (route + nav)

Last verified

staging @ 98df0c4, verified 2026-07-07

Purpose

The Service Expansion module in Signal centralizes the judgment of which existing clients are ready to buy additional services (an "SE" — Service Expansion deal in Salesforce). Expanding an existing client is cheaper than winning a new one, but knowing which client is ready — and which pitch would backfire because the client is unhappy or a churn risk — historically lived in individual Account Directors' heads. A weekly data-science model scores every client's expansion readiness from contract history, revenue performance, sentiment (Gong call analysis + CSM pulse), invoice health, and past deal behavior. The module ranks clients by that score, flags who is "Ready", recommends the specific next services (based on what similar clients bought together, SE transition patterns, industry frequency, and department inefficiency), and can generate an evidence-grounded, phased growth roadmap draft to hand to Iris (iris.novapower.io) for enrichment into a client-facing deck. Signal's backend does not compute the model itself — it merges, labels, caches, and serves the pipeline's output.

Access & visibility

  • Route guard: /predictions/se and /predictions/se/:clientId are wrapped in MemberAllowedRoute in src/App.tsx. This guard redirects users whose only role is the base "Member" tier to /summary. Any elevated role passes through: Team Lead, Admin, Gandalf, Sauron. There is no Service-Expansion-specific permission — safe phrasing is "available to team leads and above; base Members don't see it."

  • How the tier is granted: roles are stored in Signal's own user records (the Supabase app_users table managed via the Access → General Access screen, Gandalf-only) and resolved by AuthContext at login. Login itself is Google OAuth restricted to @powerdigitalmarketinginc.com.

  • Nav flag: the "Service Expansion" link sits in the Client Health nav section in src/App.tsx with no per-link flag — it appears for every non-Member. Members' sidebars are additionally trimmed to exactly two destinations (Summary and Employees), so Members never see the link at all.

  • Feature flag inside the view: the Strategic Roadmap tab (and related deep-recommendation UI) has a kill switch: env var VITE_SHOW_SE_DEEP_RECS — default ON, set to the string false to hide (src/views/predictions/SeClientDetail.tsx, code constant as of Jul 2026). The Brand Intel tab is hidden only when the detail component is embedded elsewhere (e.g., the Portfolio Client 360 insight panel passes hideBrandIntel); on /predictions/se it always shows.

Data sources & lineage

Everything the Service Expansion screen ranks and scores originates in the weekly data-science (ML) pipeline; Signal's backend serves it through GET /api/predictions/service-expansion (server/predictions.ts → fetchSeData()) with a 15-minute server-side cache. Signal's own records (Supabase, entered by users in Signal) hold only human-generated state — never the scores.

On-screen element

Origin system

Code path

Refresh

Caveat

Readiness ranking table (client, tier, division, readiness score, Blueprints/Performance/Sentiment sub-scores, Is Ready, Days to End, Close Rate)

Weekly ML model (data-science pipeline); client attributes assembled from Nova client records

GET /api/predictions/service-expansion → fetchSeData() in server/predictions.ts (model scores table left-joined with the pipeline's client-features table)

Model runs weekly; API payload cached 15 min server-side

Displayed readiness score is overridden per client with the latest weekly historical snapshot because the primary model table can lag; frontend also dedupes multiple rows per client, keeping the highest score

"Ready" cut-off (Is Ready badge, mixed-signals ⚠)

ML model config table (score_threshold)

fetchSeData(); falls back to 0.7 if the config row is missing (code constant as of Jul 2026)

With the weekly model run

Threshold is data-team-tunable — quote "the model's threshold", not a fixed number

Recommended Services (Top Recs column + Service Details tab, with co-occurrence / SE-transition / industry / dept-inefficiency sub-scores and notes)

ML model (service recommendations table)

Same endpoint; sub-scores mapped explicitly in fetchSeData()

Weekly

Weighting described in code: Blueprint Co-occurrence 20% + SE Transition 30% + Industry Frequency Rank 20% + Department Inefficiency 30% (SeClientDetail.tsx roadmap prompt, as of Jul 2026); the actual math lives in the upstream pipeline, outside the app repo

Currently Running / Previously Ran / Never Ran service lists

ML pipeline service-history table (JSON arrays per client)

Same endpoint

Weekly

Departments backfilled from the recommendations table when missing from the JSON

SE Status badge (Open SE / Failed SE), Recent (3m) won/lost pills, close rate, last closed-won contract

Salesforce opportunity data (carried through the weekly pipeline as JSON columns, incl. verbatim lost-deal explanations)

Same endpoint; parsed by parseOpenOpps / deriveFeSeStatus in server/predictions.ts

With the weekly model run

"Recent Failed SE" = expansion opportunities lost in the last 2 weeks

Salesforce tab (open opportunities + full deal history with links)

Salesforce (pipeline's Salesforce history table)

GET /api/predictions/se/salesforce-history/:clientId in server/index.ts

With the pipeline refresh

All opportunities, newest first

POC Contacts accordion (per-contact engagement, satisfaction, mood, churn risk, call rates)

Gong call analysis (pipeline's POC sentiment table)

Same main endpoint; buildPocMap() in server/predictions.ts

Last-5-week rolling averages, weekly

"Decision maker" flag is hardcoded false pending an upstream column (code TODO as of Jul 2026)

Monthly GP / MRR figures in the client detail

Vena finance data (via the pipeline's Vena clients table)

Same endpoint (fetchVenaClients())

15-min-cached fetch shared with Churn and Portfolio

Readiness Score History chart (weekly trend)

ML model historical snapshots

GET /api/predictions/se/history/:clientId → fetchScoreHistory('SE_SCORES', …)

One point per week (latest run per week)

Date column is auto-discovered at runtime

Churn overlay in the detail drawer and roadmap prompt (risk band, drivers, Gong sentiment)

Same weekly ML pipeline (churn model)

GET /api/predictions/churn fetched lazily in the browser (getChurnClientMap() in SeClientDetail.tsx)

15-min server cache

Same data as the Churn Risk module

AI Summary tab

Computed by Claude (claude-sonnet-4-6, code constant in server/summaryPrompt.ts as of Jul 2026) from the SE model payload; persisted in Signal's own records (client_summaries, one row per client/week/type)

GET /api/client-summaries/:id?type=se; POST /api/client-summaries/:id/regenerate

Weekly in-process cron Mondays 09:15 server time (server/snapshotClientSummaries.ts) + lazy first-open generation + manual Regenerate

Text style and model version will evolve

Strategic Growth Roadmap tab

Computed by Claude (claude-opus-4-6, code constant in SeClientDetail.tsx as of Jul 2026); prompt built in the browser from SE + churn + POC + service-history data; persisted in client_summaries (type=se_roadmap)

POST /api/client-summaries/:id/regenerate with the pre-built prompt

On demand (Generate/Regenerate button); persisted draft rehydrates on reopen

Prompt explicitly forbids sources outside the model payload; "Copy for Iris" / "Continue on Iris ↗" hand off to iris.novapower.io

Brand Intel tab (macro brand briefing)

Computed by Claude (claude-sonnet-4-5, code constant in server/brandIntel.ts as of Jul 2026) with live web search; cached in Signal's own records (macro_brand_insights, one row per client per month)

POST /api/macro-brand-insights/:clientId/generate

At most monthly per client; per-user daily generation cap (default 50/day, env-tunable BRAND_INTEL_DAILY_CAP_PER_USER)

Attribution (who generated) is recorded

Recommended Actions list (auto)

Computed in the browser from the client's scores/flags (rule-based, seRecommendedActions.ts)

No backend call for generation; done/dismiss state and notes persist to Signal's own records

Recomputed on render

Unlike Churn's actions, SE auto-actions are not server-snapshotted weekly

Manual follow-ups & client notes

Signal's own records (entered by users in Signal)

src/services/manualActions.ts / clientNotes.ts → manual_recommended_actions, client_notes

Live

Asana Tasks card / "Create Asana task"

Asana API (live) with Signal↔Asana link records in Signal's own records (asana_links)

GET/POST /api/asana/* (server/asana.ts); inbound Asana webhook keeps links fresh

Live + 60s pull refresh

Requires server-side Asana configuration

Dismiss / feedback state (dismissed rows, reasons, auto-resurface)

Signal's own records (se_client_feedback)

Browser → Supabase REST (src/services/supabase.ts); each change also audit-logged to change_history

Live

Auto-resurfaces when the readiness score moves ≥ 0.15 from the dismissed score (default; per-row override possible)

★ My Accounts filter

Signal's own records (user_account_favorites, per user email)

Browser → Supabase REST

Live

Shared with other modules showing the star

Usage telemetry (page opens, detail expands, roadmap generation, Iris handoffs)

Signal's own records (signal_usage_events)

logUsageEvent() in src/services/supabase.ts

Live

Powers the team's adoption reporting

Metrics & definitions

All model scores on the Service Expansion view are produced by the weekly data-science pipeline; Signal labels and displays them. The mathematical weighting inside each score is not defined in the app repo.

  • Readiness score (0–1): the model's overall estimate that the client is ready for an expansion conversation. The table sorts by it descending. Displayed value is the latest weekly historical snapshot per client (falls back to the primary model table). Origin: ML model.

  • Is Ready (badge): the model's own boolean, not recomputed by Signal. A ⚠ "mixed signals" tooltip appears when the readiness score clears the model threshold but Is Ready is still No — typically because churn risk is present. Origin: ML model.

  • Ready threshold: read from the model config table; Signal falls back to 0.7 when the config row is missing (code constant in server/predictions.ts as of Jul 2026; data-team-tunable).

  • Blueprints score (a.k.a. Blueprint Model / existing model score): composite of contract-structure signals — expansion history, retention, contract recency, contract ending, business type, previous pillar sub-scores (each 0–1, shown in the detail drawer). UI bands: ≥0.7 Strong, ≥0.4 Moderate, else Low (code constants). Origin: ML model.

  • Performance score: composite of revenue trend/growth, marketing efficiency, goal pacing, and AR (accounts-receivable) sub-scores. Same Strong/Moderate/Low bands. Origin: ML model (financial inputs ultimately from Vena and invoice data; goal pacing from CSM pulse).

  • Sentiment score: composite of budget signal, performance satisfaction, relationship health, negative mood, and pulse sentiment sub-scores, plus latest NPS when present. Sub-score bands in the detail drawer: ≥0.6 High, ≥0.3 Moderate, else Low (code constants). Origin: ML model over Gong call analysis + CSM pulse submissions.

  • SE Status: "Currently open SE" when the client has an open expansion opportunity in Salesforce; "Failed SE recently" when an expansion was lost in the last 2 weeks; blank otherwise. Derived server-side from Salesforce JSON (deriveFeSeStatus). (Payload field name is feb_se_status — a legacy name; it is derived live, not a February snapshot.)

  • Recent (3m): counts of Salesforce opportunities won (+N, green) and lost (−N, red) in the last 90 days; hover names the opportunities; lost deals carry verbatim "why we lost it" explanations. Origin: Salesforce.

  • Close Rate (0–1, hidden by default): the client's historical Salesforce opportunity close rate. Key-signal bands: ≥0.6 "High historical close rate", ≥0.3 "Moderate" (code constants). Origin: Salesforce.

  • Days to End: days until the client's last non-SE blueprint (contract) ends. Key-signal windows: 1–30 days = "active expansion window" (renewal underway), 31–60 days = "ideal expansion window" (code constants in getSeFlags). Origin: ML pipeline contract features.

  • Recommended service score: per client-service pair; sum of blueprint co-occurrence, SE-transition, industry-frequency-rank, and department-inefficiency sub-scores, with a recommendation rank and note. Service Details tab shows the top 10. Origin: ML model.

  • POC contact metrics: per-contact 5-week rolling averages of satisfaction, engagement, churn risk, decision influence, mood, and positive/negative call rates. Origin: Gong call analysis via the pipeline.

  • Monthly GP / MRR / retainer: current-month managed gross profit and monthly recurring revenue from Vena finance data; total active retainer from contract JSON. Origin: Vena.

  • Key Signals (top 4 flags): rule-based plain-English flags (e.g., "Revenue growing — room to expand", "Contract up in 52d — ideal expansion window") computed in the browser from the scores above; priority-ordered, sliced to 4. Origin: computed in Signal from ML model outputs.

How it's used

  1. Weekly book review (Account/Group Director). Open Service Expansion, filter to your name (Account Director / Group Director filters) or check "★ My Accounts". The table is already sorted by readiness. Clients with a Ready badge, Strong Performance/Sentiment labels, and no Open SE badge are the week's pitch list. Decision: which 2–3 clients get an expansion conversation.

  2. Pick the pitch. For a ready client, open the Service Details tab (or the hidden-by-default Top Recs column) to see which services the model recommends and why — what similar clients added, what this client never ran, where their department mix is inefficient. Decision: what to actually propose.

  3. Time the ask. Check Days to End and Key Signals — a contract ending in 31–60 days is flagged the "ideal expansion window"; 30 days or less means renewal is already active. Cross-check Sentiment Signals and the churn overlay; the ⚠ mixed-signals icon warns when a high score coexists with churn risk (expansion could double as a retention play, but validate sentiment first). Decision: pitch now, wait, or fix the relationship first.

  4. Build the case. In the client drawer, generate the Strategic Growth Roadmap — a phased draft grounded strictly in model data, citing specific scores and POC signals — then "Copy for Iris" or "Continue on Iris ↗" to enrich it on iris.novapower.io with Gong quotes and KPI packs and turn it into a client-facing deck. Decision: the actual expansion proposal.

  5. Work the list down. Dismiss non-viable clients with a reason (Not Interested / Already Selling / Bad Timing / Relationship Issue / Other) so the active list stays honest; a dismissed client auto-resurfaces if its readiness score later moves ≥ 0.15. Create Asana tasks or manual follow-ups for committed next steps; mark auto-recommended actions done or dismissed. Decision: who owns which next step.

  6. Post-mortem lost expansions. Filter Recent (3m) to "Has losses" and read the verbatim Salesforce lost-deal explanations in Contract Activity to spot patterns (budget objections, timing, service gaps) before the next attempt. Decision: what to change in the next pitch.

What users can change here

All writes from the Service Expansion module land in Signal's own records (Supabase) or in Asana; nothing writes back to the ML pipeline, Salesforce, Vena, or Nova.

  • Dismiss / reactivate a client — status, reason, notes, score-at-dismissal, and resurface threshold saved to se_client_feedback; every dismiss/undo is also audit-logged to change_history. Side effect: the client leaves the default (Active) list; it auto-resurfaces if its readiness score moves ≥ 0.15 (default) from the dismissed score.

  • Regenerate AI Summary — triggers a server-side Claude call (claude-sonnet-4-6) and upserts client_summaries (type se), attributed to the user's email. First open of a client with no summary this week generates one lazily.

  • Generate / Regenerate Strategic Growth Roadmap — browser builds the prompt, server calls Claude (claude-opus-4-6) and persists to client_summaries (type se_roadmap). "Copy for Iris" copies a handoff prompt + draft to the clipboard (no server write besides a usage event).

  • Generate Brand Intel — server-side Claude with web search; cached in macro_brand_insights (one row per client per month, attributed). Side effect: counts against the per-user daily cap (default 50/day, env-tunable).

  • Recommended-action feedback — mark auto actions done or dismissed (recommended_action_feedback) and attach per-action notes (action_notes).

  • Manual follow-ups — create/edit/archive your own actions with severity (manual_recommended_actions).

  • Client notes — free-text notes on the client (client_notes, module-scoped to se).

  • Asana tasks — creates real tasks in the Asana workspace via the API; the Signal↔Asana link is stored in asana_links; task status syncs back via an Asana webhook plus a 60-second pull refresh.

  • ★ star a client — user_account_favorites (drives the "My Accounts" filter in every module that shows the star).

  • Column visibility and CSV export — per-user/browser conveniences (localStorage + client-side CSV of the current filtered view); no server write.

Caveats & data quality

  • "Ready Clients by Tier" donut is mislabeled: it counts all clients per tier, not ready ones (tierReadyData in SeOverviewTab.tsx does not filter on is_ready; confirmed still present as of staging 98df0c4, Jul 2026).

  • Readiness score source is subtle: the Service Expansion table shows the latest weekly historical snapshot per client, while Portfolio Review deliberately uses the raw primary-model score — small discrepancies between the two screens are by design, not bugs.

  • 15-minute server cache: the main payload is cached 15 minutes, so "refresh and the number changed" moments are possible; the underlying model only changes weekly.

  • Ready threshold is dynamic: read from the model config table with a 0.7 code fallback (as of Jul 2026, data-team-tunable). Don't quote 0.7 as a permanent fact.

  • Model internals are not in this repo: how the readiness score, the Blueprint/Performance/Sentiment composites, and the service-recommendation scores are computed lives in the upstream data-science pipeline. Exact model run day/time: not defined in the app repo (code comments only say "weekly").

  • POC "decision maker" flag is a placeholder — always false until the upstream pipeline ships the column (code TODO as of Jul 2026).

  • Auto-resurface threshold (0.15) is a hardcoded default in SePredictionsView.tsx (per-row overrides possible via resurface_threshold); may be tuned.

  • Key-signal windows and bands (30/60-day contract windows, 0.7/0.4 and 0.6/0.3 score bands, 0.6/0.3 close-rate bands) are frontend code constants as of Jul 2026, not model outputs.

  • feb_se_status field name is legacy; the value is derived live from open/failed SE data each run.

  • 'in_pipeline' feedback status exists in code comments but is not reachable in the UI — only Active / All / Dismissed are exposed.

  • AI panels name specific Claude models (claude-sonnet-4-6 summary, claude-opus-4-6 roadmap, claude-sonnet-4-5 brand intel, all code constants as of Jul 2026); versions will evolve — describe the capability, not the model string.

  • SE auto-recommended actions are browser-computed and not snapshotted weekly (the Monday 09:00 actions snapshot cron covers Churn only); the AI Summaries snapshot (Mondays 09:15 server time) does cover SE summaries.

  • Whether dismissed-client reasons feed model retraining cannot be determined from the app repo — they are stored in Signal's own records and audit-logged, but no pipeline consumer is visible in this codebase.

  • Duplicate model rows: the frontend dedupes multiple score rows per client (keeps the highest readiness score) as a defensive guard.

Related views

  • Churn Risk (/predictions/churn): sibling module from the same weekly ML pipeline. The Service Expansion detail drawer lazily pulls the churn feed for its retention overlay, and the roadmap prompt's retention-first rules key off churn risk. Server-side fetches of shared tables are deduplicated.

  • Portfolio Review (/predictions/portfolio): unions churn + Service Expansion clients into one grid, carrying readiness score, open-SE flag, SE count, and days-since-last-SE. Note: Portfolio uses the raw model readiness score, not the historical override used on the Service Expansion table.

  • POC Management (/predictions/poc-management): manages the same Gong-derived POC contact sentiment shown in the Service Expansion drawer's POC accordion.

  • Iris (external, iris.novapower.io): designated downstream for Strategic Growth Roadmaps — Signal drafts from model data; Iris adds qualitative evidence (Gong quotes, KPI packs) and produces the client deck.

  • Asana: execution layer for tasks committed from the Service Expansion drawer.

  • Analytics (/analytics, super-admin only): reads the usage events this module logs (signal_usage_events).

FAQ

Q: Who can see the Service Expansion page? A: Anyone with an elevated Signal role — Team Lead, Admin, Gandalf, or Sauron. Base-tier Members are redirected to the Summary page and don't see the link in the sidebar. Roles are managed by Signal admins in the Access → General Access screen; ask your manager or a Signal admin if you need access.

Q: What does the readiness score actually mean? A: It's a 0–1 score from a weekly data-science model estimating how ready the client is for an expansion (upsell/cross-sell) conversation, built from contract history, revenue performance, sentiment (Gong calls + CSM pulse), invoice health, and past deal behavior. It is produced by the ML pipeline, not computed by Signal itself, and refreshes weekly.

Q: Why does a client show a high readiness score but "Is Ready: No"? A: "Is Ready" is the model's own decision, and it can say No even when the score clears the threshold — usually because churn risk is present. Signal shows a ⚠ "mixed signals" tooltip in that case. Treat it as: the client looks expandable on paper, but validate relationship health before pitching.

Q: Why doesn't the readiness score here match Portfolio Review? A: By design. The Service Expansion table shows the latest weekly historical snapshot per client (the most current model run), while Portfolio Review shows the raw primary-model score, which can lag. Small differences between the two screens are expected, not a data error.

Q: How fresh is the data on this page? A: The underlying model refreshes weekly; the API response is additionally cached for 15 minutes on the server. Salesforce deal data (open SEs, wins/losses) rides along with the weekly model run. AI Summaries are regenerated weekly (Mondays, ~09:15 server time) and on demand. The exact day/time of the weekly model run is not defined in the Signal app repo.

Q: Where do the recommended services come from? A: From the ML pipeline's service-recommendation model, scored per client-service pair from four signals — what services co-occur in similar clients' blueprints (20%), what services clients typically transition into via SEs (30%), what's frequent in the client's industry (20%), and where the client's current department mix is inefficient (30%) (weights as described in code as of Jul 2026; the computation lives in the upstream pipeline).

Q: I dismissed a client — why did it come back? A: Dismissed clients auto-resurface when their readiness score moves at least 0.15 (default) from the score at dismissal, in either direction. The row is reactivated with a system note recording the score change. You can dismiss it again; dismissals and reactivations are audit-logged.

Q: Does dismissing a client or marking actions done change the model or Salesforce? A: No. All Service Expansion writes land in Signal's own records (dismissals, notes, action feedback, favorites, AI text) or create tasks in Asana. Nothing writes back to the ML pipeline, Salesforce, Vena, or Nova. Whether the data-science team consumes dismissal reasons for retraining is not determinable from the app code.

Q: What is the Strategic Growth Roadmap and can I trust its numbers? A: It's a Claude-generated (claude-opus-4-6 as of Jul 2026), phased expansion plan grounded strictly in the model data on the page — the prompt forbids outside sources and requires every recommendation to cite a specific datum. It's a draft: the intended workflow is "Copy for Iris" / "Continue on Iris" so Iris (iris.novapower.io) can corroborate claims with Gong transcripts and KPI packs before anything goes to a client. Roadmaps persist per client per week and reload when you reopen the client.

Q: Why don't the revenue figures here match Vena exactly? A: The Monthly GP and MRR figures do originate in Vena, but they arrive via the weekly data-science pipeline's finance table and a 15-minute server cache, so they can trail live Vena by up to a week. For client-performance specifics beyond what Signal shows, ask Iris (iris.novapower.io).

Did this answer your question?