Skip to main content

Signal — Pulse Summary

Written by Schae Lilley

Pulse Summary is an admin-only leadership dashboard in Signal (Power Digital's internal BI web app) at route /predictions/pulse-summary. For a selected week it rolls up the weekly client "Pulse" check-ins that Account Directors submit in Signal — client sentiment, pacing to goal, goals established, and business performance year-over-year — across the entire pulse-eligible active client book, and overlays two machine signals: the churn model's risk score and each client's service-expansion (SE) status. It answers the leadership question "How healthy is our whole client book this week, by division, and where do human check-ins disagree with the churn model?" Data origins: the pulse answers are Signal's own records (entered by users in Signal's Submit Pulse screen); the client universe comes from Nova platform client records plus Vena revenue data (eligibility); churn scores, divisions, and SE status come from the AI/ML team's model/feature pipeline; the Department filter is derived from Vena revenue data plus Signal's own department-mapping records.

Field

Value

Route

/predictions/pulse-summary

Access

Admin+ (AdminRoute — passes users with the admin flag or the Gandalf flag; the Sauron super-admin tier auto-sets both)

Nav section

"Client Pulse" accordion → "Pulse Summary" link. The Client Pulse nav section itself is Team-Lead-gated; the Pulse Summary link inside it is additionally admin-gated

Primary data sources

Signal's own records (pulse_weekly_submissions, entered on Submit Pulse); AI/ML model pipeline (churn scores, division, SE status); Nova platform client records (client universe); Vena revenue data (eligibility + Department filter)

Writes

None user-initiated (read-only view; CSV export is a download). Passive: each page view logs a usage-telemetry event to Signal's own records (signal_usage_events)

Key code refs

src/views/predictions/PulseSummaryView.tsx (view); src/App.tsx (route guard + nav); src/services/api.ts getPulseSummary(); server/index.ts GET /api/pulse-summary (~line 5331); server/activeBook.ts (client universe); server/pulseEligibility.ts (eligibility rule); server/predictions.ts normalizeDivision(); src/utils/multiDivision.ts (Department filter); src/utils/week.ts (week math)

Last verified

staging @ 98df0c4, verified 2026-07-07

Purpose

Every week, Account Directors submit a "Pulse" check-in for each client they own on Signal's Submit Pulse screen: how the client feels about the relationship (sentiment), whether the account is pacing to its goals, whether goals are even established, and how the client's own business is performing year-over-year. Those submissions are useful client-by-client, but leadership needs the aggregate. Pulse Summary is that aggregate: for one selected week it shows distributions of every pulse answer across the entire pulse-eligible client book — counting clients with no submission explicitly as "Not Submitted", so it doubles as a submission-compliance check — and puts the human judgments side by side with two machine signals (the churn model's risk category and each client's service-expansion pipeline status). A By Division percentage table lets leadership compare book health across divisions, and a CSV export produces the per-client rows for offline analysis. Pulse Summary answers: across our whole book, how healthy are client relationships this week, which divisions are struggling, are teams actually setting goals, what share of the book actually got a check-in, and where does human sentiment disagree with the churn model?

Access & visibility

  • Route guard: /predictions/pulse-summary is wrapped in AdminRoute in src/App.tsx. AdminRoute allows users whose isAdmin OR isGandalf flag is true and redirects everyone else to /summary. "Admin" is Signal's second tier; Gandalf is above it, and the Sauron super-admin tier auto-sets both flags, so all higher tiers pass. (Dedicated PredictionsRoute-style wrappers were removed; admin-tier prediction routes now use AdminRoute directly.)

  • How the flags are granted: role flags are read from Signal's own records — the Supabase app_users table columns is_team_lead, is_admin, is_gandalf, keyed by the user's Google login email (src/context/AuthContext.tsx). A short hardcoded super-admin ("Sauron") email list in the frontend code always gets full access regardless of the table. Flags are cached in the browser's sessionStorage to avoid a role-check flash on load. To grant someone access to Pulse Summary, set is_admin (or is_gandalf) to true on their app_users row.

  • Nav flag: the "Pulse Summary" link sits in the "Client Pulse" nav section with adminOnly: true (visible to admin or Gandalf). Note the quirk: the Client Pulse nav section is marked teamLeadOnly: true, so an admin who is not also a team lead does not see the Client Pulse section at all and gets no nav link — the view is still reachable for them by typing the URL /predictions/pulse-summary directly, because the route guard is AdminRoute, not team-lead-based.

  • Server side: the backing endpoint GET /api/pulse-summary (server/index.ts) requires only a valid authenticated Signal session (requireAuth — a verified Google-login token for a @powerdigitalmarketinginc.com account). It does not re-check the admin flag server-side; the admin restriction is enforced by the frontend route guard.

Data sources & lineage

Everything on the Pulse Summary screen except the Department filter comes from one backend call, GET /api/pulse-summary (frontend wrapper api.getPulseSummary({ weeks: [selectedWeek] }) in src/services/api.ts), which fans out in parallel to Signal's own records and to warehouse-replicated tables. Origin systems below are the core origin of each fact, not the transport layer.

On-screen element

Origin system

Code path

Refresh

Caveat

Sentiment, Pacing to Goal, Goals Established, Business Performance YoY per client (drives KPI cards, four of the six charts, By Division table, CSV)

Signal's own records — pulse_weekly_submissions table, written by Account Directors on the Submit Pulse screen

server/index.ts GET /api/pulse-summary → Supabase REST read (status=eq.active rows only; voided submissions excluded)

Live — reflects a submission the moment it is saved

Table is upserted per (client, week); a client with no active row for the selected week shows as "Not Submitted"

Client universe — which clients are counted at all ("Total Clients" denominator)

Nova platform client records (client id, name, churn date) filtered by a revenue/contract eligibility rule using Vena revenue data (current-month MGP, latest VABO value) and the AI/ML feature pipeline (weeks until contract end)

server/activeBook.ts fetchActiveBook() + server/pulseEligibility.ts isPulseEligible()

Server-cached 15 minutes (code constant as of Jul 2026, server/activeBook.ts)

Same universe as Submit Pulse and Portfolio Review, so counts reconcile across those views. Eligibility: client not already churned (churn date today or earlier always excludes, even with residual revenue), AND (current-month MGP > 0 OR latest 2-month VABO value > 0 OR weeks remaining on contract > 0)

Client name and Division (Division filter values, By Division table columns)

AI/ML feature pipeline (FINAL_FEATURES table: client name, division), falling back to the Nova-platform client name when a client is missing from the pipeline output

server/index.ts pulse-summary handler; division labels normalized by normalizeDivision() in server/predictions.ts

Depends on the AI/ML team's pipeline schedule — not defined in the app repo

Clients absent from the feature pipeline get a blank division, rendered as "—" in the By Division table

Churn Risk Score chart; "Churn Risk Score"/"Churn Risk Category" CSV columns

AI/ML churn prediction model (CHURN_SCORES table — same model behind Signal's Churn Risk view)

server/index.ts pulse-summary handler; bucketed server-side

Depends on the churn-model scoring pipeline schedule — not defined in the app repo

Buckets are code constants as of Jul 2026: score ≥ 0.6 Critical, ≥ 0.5 High, ≥ 0.3 Medium, else Low; no score = "Unknown"

Service Expansion Status chart; "Open SE" and "Recently Lost SE" KPI cards

AI/ML feature pipeline (FINAL_FEATURES fields OPEN_SE_CONTRACTS, WON_CONTRACTS_3M, FAILED_SE_IN_LAST_2_WEEKS); upstream origin of those contract facts is not defined in the app repo

server/index.ts pulse-summary handler

Depends on the AI/ML feature pipeline schedule — not defined in the app repo

Derived server-side, priority order: any open SE contract → "Open SE"; else a win in the last 3 months → "Recently Won SE"; else a failed SE in the last 2 weeks → "Recently Lost SE"; else "Unknown"

Week dropdown options

Signal's own records — distinct week_start values across active pulse submissions

Response field available_weeks; current calendar week always prepended client-side (mondayOf() in src/utils/week.ts)

Live

Weeks are Monday-anchored; the current calendar week is offered even before anyone submits

Department filter options and filtering

Vena revenue data (which departments a client has revenue in, latest loaded month) + Signal's own records (department_mapping table for canonical department identity)

No extra API call — computed in the browser from DataContext account slices via departmentsByClient / lookupClientDepartments (src/utils/multiDivision.ts)

Follows the app's central month data load

Revenue-based membership (positive revenue in that department in the latest month; VABO excluded), not an org-chart mapping. The Department filter does NOT apply to the By Division table

CSV export file (pulse-week-YYYY-MM-DD.csv)

Same origins as all rows above

Browser re-calls GET /api/pulse-summary for the selected week and builds the CSV client-side (buildCsv in PulseSummaryView.tsx)

On demand

One row per eligible client: id, name, division, week start, four pulse answers, churn score (3 decimals) and category, SE status

Page-view telemetry (invisible)

Write to Signal's own records (signal_usage_events)

usePageViewTracking in src/hooks/useUsageTracking.ts → logUsageEvent

Per visit

Passive logging of user email + module pulse-summary; fire-and-forget

Metrics & definitions

All percentages on Pulse Summary use the currently filtered eligible book as the denominator — including clients that did not submit a pulse. Non-submission therefore drags the "good" percentages down by design. "Eligible book" = clients from Nova platform records that pass the shared pulse-eligibility rule (not yet churned, and having current-month Vena MGP revenue, recent VABO revenue, or remaining contract weeks).

  • Total Clients — count of pulse-eligible clients in the current filter scope. Sub-line: "X submitted (Y%)" where X = clients with an active pulse submission for the selected week and Y = X / Total Clients. Origin: Nova client records + Vena revenue (universe); Signal's own records (submissions).

  • High Sentiment — % of the filtered book whose submitted sentiment is "Delighted / Advocate" or "Happy / Content". Origin: Signal's own records (Submit Pulse answers).

  • On Track or Ahead — % of the filtered book whose submitted pacing-to-goal answer is "On Track or Ahead". Origin: Signal's own records.

  • Goals Established — % of the filtered book where the submission answered "Yes" to goals established (boolean true). "No" = explicit false; no answer or no submission = "Not Set". Origin: Signal's own records.

  • Improving YoY — % of the filtered book whose submitted business-performance answer is "Improving YoY". Origin: Signal's own records.

  • Behind on Goals — % of the filtered book whose submitted pacing answer is "Behind" (the red-flag card). Origin: Signal's own records.

  • Open SE — count (not %) of filtered clients with at least one open service-expansion contract. Origin: AI/ML feature pipeline.

  • Recently Lost SE — count of filtered clients flagged as having a failed SE in the last 2 weeks (and no open SE and no win in the last 3 months, per the priority order). Origin: AI/ML feature pipeline.

  • Churn Risk Category (chart + CSV) — the churn model's score bucketed server-side: ≥ 0.6 "Critical", ≥ 0.5 "High", ≥ 0.3 "Medium", below 0.3 "Low", no score "Unknown". Thresholds are code constants as of Jul 2026 in server/index.ts (data-team-tunable only via a code change). Origin: AI/ML churn model.

  • Service Expansion Status (chart) — one of "Open SE", "Recently Won SE" (win in last 3 months), "Recently Lost SE" (failure in last 2 weeks), "Unknown", derived server-side in that priority order. Note the deliberate asymmetry: wins use a 3-month window, losses a 2-week window (source fields WON_CONTRACTS_3M vs FAILED_SE_IN_LAST_2_WEEKS; the rationale is not documented in the app repo). Origin: AI/ML feature pipeline.

  • Distribution charts (six horizontal bar charts: Sentiment, Pacing to Goal, Churn Risk Score, Goals Established, Business Performance YoY, Service Expansion Status) — each bar shows count and % of the filtered book. Categories with zero clients are hidden from the chart. Sentiment scale: Delighted / Advocate → Happy / Content → Neutral → Concerned / Showing Friction → Very Unhappy / At Risk of Churn, plus Not Submitted.

  • By Division table — a percentage matrix (one column per division, row groups for all six categories above) computed over the unfiltered week's book; each division column's percentages within a category group sum to ~100% of that division's clients. Only rendered when no Division filter is active; the Department filter does not affect it.

How it's used

  1. Weekly book-health review. After the weekly pulse submission window, an executive opens Pulse Summary with no filters and scans the KPI cards: submission rate, High Sentiment %, Behind on Goals %. Decision: is overall client health acceptable this week, and which headline numbers go to the leadership meeting?

  2. Chase submission compliance. Read "Total Clients — X submitted (Y%)" and the "Not Submitted" bars. If coverage is low, follow up with division leads (the companion Employee Pulse / Pulse Coverage view at /pulse-coverage shows exactly which people have not submitted). Decision: where to push for check-in discipline so the rest of the numbers are trustworthy.

  3. Find the struggling division. With no filters, compare red rows (Very Unhappy, Behind, Declining YoY, Critical churn) across division columns in the By Division table, then set the Division filter to drill into that division's distributions. Decision: which division leader gets a retention conversation and where to deploy resources.

  4. Cross-check human judgment against the churn model. Compare the Sentiment chart with the Churn Risk Score chart for the same slice. A book that account teams rate "Happy" while the model rates "Critical/High" (or vice versa) signals blind spots; drill into individual clients in the Churn Risk view (/predictions/churn). Decision: which accounts need a deeper churn review despite optimistic (or pessimistic) team reads.

  5. Assess expansion momentum. Use the Open SE and Recently Lost SE cards and the Service Expansion Status chart, optionally filtered by division or department. Decision: is the upsell pipeline healthy, and are recent SE losses clustered somewhere that needs sales support?

  6. Export for leadership decks and offline analysis. Pick the week, click "Export Selected Week CSV", and get one row per eligible client (pulse answers + churn score + SE status + division) for spreadsheets, QBR prep, or trend analysis outside Signal.

What users can change here

Nothing — Pulse Summary is read-only. There are no user-initiated write paths on this screen; the CSV export is a browser download, not a data change. Filters (Division, Department, Week) are local UI state only.

Two related facts a bot should know:

  • The pulse answers displayed here are written elsewhere: on Signal's Submit Pulse screen (/pulse/submit), which is the single validated write path into Signal's pulse_weekly_submissions records. The server enforces that only the client's assigned Account Director (or an admin escape hatch) can write, and authorship is stamped from the login.

  • Opening Pulse Summary automatically logs a passive page_view usage-telemetry event (user email, module pulse-summary) to Signal's own records (signal_usage_events table) via usePageViewTracking in src/hooks/useUsageTracking.ts. This is fire-and-forget analytics, not user-editable data.

Caveats & data quality

  • Denominator convention: every KPI percentage divides by the whole filtered eligible book, including non-submitters. A low submission week makes all "good" percentages look worse; this is intentional (coverage pressure), but compare weeks with similar submission rates.

  • Churn thresholds are code constants (0.6 / 0.5 / 0.3, server/index.ts, as of Jul 2026), not configurable settings; a code change is required to retune them.

  • Model-data freshness is unknown from the app repo. Churn scores, divisions, and SE flags come from the AI/ML team's pipeline tables (CHURN_SCORES, FINAL_FEATURES); their refresh cadence (daily? weekly?) is not defined in the app repo — confirm with the AI/ML team.

  • "Unknown" values: a client absent from the AI/ML feature pipeline shows blank division ("—" column) and "Unknown" SE status; a client with no churn score shows "Unknown" churn risk. Whether that is a data-quality gap or expected for brand-new clients is not documented in the app repo.

  • SE window asymmetry: "Recently Won SE" uses a 3-month window while "Recently Lost SE" uses a 2-week window (source field names encode the windows). Not explained in the app repo.

  • Department filter is revenue-derived (client has positive revenue in that department in the latest loaded month, VABO excluded) — not an org-chart mapping — and it silently does not apply to the By Division table.

  • Client universe is cached server-side for 15 minutes (code constant, server/activeBook.ts), so a client churned or added minutes ago can lag briefly.

  • Server-side gate is authentication-only: GET /api/pulse-summary checks for a valid Signal login but does not re-check the admin flag; the Admin+ restriction lives in the frontend route guard (AdminRoute in src/App.tsx).

  • Nav discoverability quirk: an admin who is not also a team lead sees no "Client Pulse" nav section (it is team-lead-gated) and must use the direct URL /predictions/pulse-summary.

  • Unused multi-week API modes: the endpoint supports weeks (comma list), n_weeks, and week=previous parameters plus a per-week weeks_data response breakdown; the current screen only ever requests a single week. A historical/trend view could appear without notice. The intended consumer of the multi-week mode is not defined in the app repo.

  • Category labels are hardcoded lists in the view mirroring the Submit Pulse form's options; if the form's enumerations change, this screen must change with it.

  • Quarterly QBP submissions (pulse_qbp_submissions, written from the Submit Pulse area) are NOT shown on Pulse Summary — weekly pulses only.

Related views

  • Submit Pulse (/pulse/submit, Team Lead+) — upstream feeder: every sentiment/pacing/goals/business-performance value shown on Pulse Summary was entered there into Signal's pulse_weekly_submissions records. Both screens use the same client-eligibility rule and universe (server/activeBook.ts + server/pulseEligibility.ts), so client counts reconcile.

  • My Submissions (/pulse/my-submissions, Team Lead+) — an Account Director's own submission history.

  • Employee Pulse / Pulse Coverage (/pulse-coverage, Admin+) — the accountability companion: Pulse Summary shows what was submitted in aggregate; Pulse Coverage shows who has and has not submitted.

  • Churn Risk (/predictions/churn) — same churn-model output; Pulse Summary shows the distribution, Churn Risk is where you drill into individual at-risk clients.

  • Service Expansion (/predictions/se) — the SE status on Pulse Summary comes from the same AI/ML feature-pipeline fields that power the SE views; drill into specific opportunities there.

  • Portfolio Review (/predictions/portfolio) — its per-client panels read the same pulse submission records, so a client's recent pulse there matches the week's roll-up on Pulse Summary.

  • Downstream: pulse submission records feed a reverse-ETL pipeline out of Signal to other company systems (noted in server code comments), so pulse data quality matters beyond this screen.

FAQ

Q: Who can see Pulse Summary? A: Admin tier and above. The route /predictions/pulse-summary is wrapped in AdminRoute, which passes users whose is_admin or is_gandalf flag is set in Signal's app_users records (the Sauron super-admin tier auto-passes). Team Leads and base members are redirected to the Summary page. To get access, ask a Signal admin to set your admin flag; for login/authentication problems, contact the IT team.

Q: I'm an admin but I don't see "Pulse Summary" in the left nav — why? A: The "Client Pulse" nav section is only rendered for users with the Team Lead flag; the Pulse Summary link lives inside it. An admin who is not also a team lead gets no nav link but can still open the view directly at /predictions/pulse-summary — the route guard is admin-based, not team-lead-based.

Q: Why does Pulse Summary show more clients than actually submitted a pulse? A: Deliberately. The view counts the entire pulse-eligible active client book (from Nova platform client records, filtered by revenue/contract eligibility), not just submitters. Clients without a submission for the selected week appear as "Not Submitted", so the screen doubles as a submission-compliance check. The "Total Clients" card shows both the book size and the submitted count/rate.

Q: Why is a percentage on Pulse Summary lower than what I'd compute from submitted pulses only? A: All KPI percentages (High Sentiment, On Track or Ahead, Goals Established, Improving YoY, Behind on Goals) divide by the whole eligible book including non-submitters, not by the number of submissions. A 60% submission week caps every "good" percentage near 60%. This is by design — it keeps pressure on coverage.

Q: Where do the sentiment and pacing answers come from? A: They are Signal's own records — Account Directors enter them weekly on Signal's Submit Pulse screen (/pulse/submit), stored in the pulse_weekly_submissions table. Pulse Summary displays them live; there is no external system involved for those answers.

Q: Where does the Churn Risk chart on Pulse Summary come from, and what do Critical/High/Medium/Low mean? A: From the AI/ML team's churn prediction model (the same model behind Signal's Churn Risk view). Each client's score is bucketed server-side: ≥ 0.6 Critical, ≥ 0.5 High, ≥ 0.3 Medium, otherwise Low; clients without a score show "Unknown". The thresholds are code constants as of Jul 2026. How often scores refresh is controlled by the AI/ML pipeline, not by Signal — confirm cadence with the AI/ML team.

Q: What do "Open SE", "Recently Won SE", and "Recently Lost SE" mean on Pulse Summary? A: Service-expansion (upsell) pipeline status per client, derived from the AI/ML feature pipeline in priority order: any open SE contract → "Open SE"; else an SE win in the last 3 months → "Recently Won SE"; else a failed SE in the last 2 weeks → "Recently Lost SE"; else "Unknown". Note the windows differ on purpose in the data (3 months for wins, 2 weeks for losses); the rationale is not documented in the app repo.

Q: Why doesn't the client count here match the Churn Risk or Service Expansion views? A: Pulse Summary uses the pulse-eligibility universe (shared with Submit Pulse and Portfolio Review): clients not yet churned that have current-month Vena MGP revenue, recent VABO revenue, or remaining contract weeks. The Churn Risk and Service Expansion views have their own scoping rules, so totals can differ. Pulse Summary's counts should reconcile exactly with Submit Pulse and Portfolio Review.

Q: A client shows "—" for division or "Unknown" everywhere — is that a bug? A: Not necessarily. It means the client exists in the Nova-platform client records (so it is in the eligible book) but is missing from the AI/ML feature-pipeline table that supplies division, churn score, and SE flags — common for brand-new clients, but the exact expectation is not documented in the app repo. The client's name still shows (from Nova records), and its pulse answers still count.

Q: Can I change anything from Pulse Summary, and can I get the underlying data out? A: The view is read-only — no edits, approvals, or overrides happen there. To change a pulse answer, the client's Account Director resubmits on the Submit Pulse screen for that week (submissions upsert per client per week). To get the data out, use the "Export Selected Week CSV" button: it downloads pulse-week-YYYY-MM-DD.csv with one row per eligible client (pulse answers, churn score and category, SE status, division).

Q: How far back can I look, and what week is shown by default? A: The Week dropdown defaults to the current calendar week (Monday-anchored, labeled "(Current)") and also offers every past week that has at least one pulse submission, shown as a date range. Selecting a week re-fetches that week's data. There is no multi-week trend display on this screen today, although the backing API supports multi-week queries.

Did this answer your question?