Skip to main content

Signal — Submit Pulse Data

Written by Schae Lilley

Submit Pulse Data (route /pulse/submit) is the weekly client health check-in form in Signal's Client Pulse section, visible to Team Lead tier and above (typically Account Directors and Group Directors). Once a week, the client's assigned Account Director (AD), Senior AD, or Group Director (GD) records the client's sentiment, pacing to goal, performance narrative, and contract-term facts (rollover triggers, early-out dates); once a quarter they log the Quarterly Business Presentation (QBP) date plus narrative and deck links. It replaced the old Pulse Google Sheet with a governed, auditable store: submissions are Signal's own records (entered by users in Signal), ownership is enforced server-side against Signal's AD assignments and Nova's (Power Digital's internal platform) director data, and corrections are soft-voided (never deleted) for a full audit trail. The client list itself is assembled from Vena-derived revenue records and Nova client records; what gets typed here feeds Pulse Summary, Client 360, and downstream leadership reporting.

Field

Value

Route

/pulse/submit (supports ?clientId=<id> deep link that auto-expands that client's card)

Access

Team Lead and above (TeamLeadRoute guard; admins and Gandalf-tier implicitly qualify)

Nav section

"Client Pulse" (section itself is teamLeadOnly; siblings: My Submissions, and admin-only Pulse Summary)

Primary data sources

Signal's own records (pulse_weekly_submissions, pulse_qbp_submissions, client_account_directors, client_senior_account_directors); Nova (client records, user directory, group-director assignment history, executive-sponsor fallback); Vena-derived revenue records (account list, current-month MGP, latest VABO); churn-model feature data (weeks-until-end)

Writes

Upsert weekly pulse rows and QBP rows; soft-void (never hard-delete) either kind — all via the Signal backend, never directly from the browser

Key code refs

src/views/PulseSubmissionView.tsx; src/views/pulseSlotKey.ts; pulse functions in src/services/supabase.ts; POST /api/pulse/weekly, POST /api/pulse/qbp, void endpoints, assertPulseOwnership() in server/index.ts; src/utils/pulseEligibility.ts (mirrored in server/pulseEligibility.ts); src/utils/week.ts, src/utils/quarter.ts

Last verified

staging @ 98df0c4, verified 2026-07-07

Purpose

Submit Pulse Data (/pulse/submit) exists to give Power Digital a consistent, week-by-week read on every active client: how happy is the client, are we pacing to their goals, and what are the contract exposure dates (rollover triggers, early-outs) that determine churn risk and renewal timing. Historically this lived in a shared Pulse Google Sheet — hard to audit, easy to overwrite, impossible to join to revenue data. Submit Pulse Data moves that ritual into Signal: one structured submission per client per week (per division for multi-division clients), one QBP record per client per quarter, with server-enforced ownership (only the client's assigned AD, Senior AD, or GD can submit), a spoof-proof submitted_by stamp, and soft-void instead of delete. It is the input side of the Client Pulse system — Pulse Summary (/predictions/pulse-summary) and the Client 360 insight panel are the read side, and the pulse tables feed a downstream pipeline into the wider Nova reporting stack. Note this is Client Pulse; "Employee Pulse" (/pulse-coverage) is an unrelated internal staff-survey tracker that shares the word.

Access & visibility

  • Route guard: /pulse/submit is wrapped in TeamLeadRoute (defined in src/App.tsx). Users without the team-lead flag are redirected to /summary.

  • The flag: TeamLeadRoute checks isTeamLead from AuthContext (src/context/AuthContext.tsx). Effective team-lead status is is_team_lead OR is_admin OR is_gandalf, read from the app_users table in Signal's own records by email.

  • How the flag is granted: on sign-in the frontend calls POST /api/auth/ensure-provisioned (server/index.ts), which creates the app_users row if missing and auto-sets is_team_lead = true when the user's email appears as a client's Account Director (executive sponsor) or Group Director in Nova (Power Digital's internal platform). The flag is never auto-downgraded. It can also be set manually by a Signal admin.

  • Nav visibility: the "Client Pulse" nav section in src/App.tsx is teamLeadOnly: true; within it, "Submit Pulse Data" and "My Submissions" are visible to all team leads, while "Pulse Summary" is admin-only.

  • Viewing vs. submitting are different gates. Any team lead can open Submit Pulse Data and browse any AD's book (the AD dropdown is not restricted). Actually saving a submission is checked server-side per client: the caller must be that client's assigned AD or Senior AD (Signal's client_account_directors / client_senior_account_directors records, with Nova executive-sponsor fallback), the client's GD (most recent group_director in Nova's client assignment history), or a top-tier "Gandalf" admin. Anyone else gets a 403 with the reason.

  • Admin extras: admins see a "Compliance Dashboard →" header link — note it points to /pulse-coverage, the Employee Pulse tracker, not a client-pulse dashboard. Gandalf-tier admins can additionally submit or void on behalf of any client (server logs the bypass).

  • Nova identity dependency: the page resolves the logged-in user to a Nova user id via GET /api/pulse/whoami. If the login email is not in Nova's user directory, the AD picker cannot default to "you" and saves fail with "Cannot identify your user" — contact #signal-support.

Data sources & lineage

All numbers and pills on Submit Pulse Data trace to four origin systems: Signal's own records (entered by users in Signal), Nova (Power Digital's internal platform), Vena (Power Digital's financial planning system), and the churn-model feature pipeline.

On-screen element

Origin system

Code path

Refresh

Caveat

Client list (accordion cards), pass 1

Vena-derived account/contract records

accounts from DataContext (app-wide load) → merged by canonical client name in PulseSubmissionView.tsx (allClients)

Loaded with the app's account data

Covers clients active in Vena revenue data only

Client list, pass 2 + eligibility fields (churn date, current-month MGP, latest VABO, weeks-until-end)

Nova client records (id, name, churn date); Vena (MGP from contract revenue, VABO); churn-model feature data (weeks-until-end)

api.getClients() → GET /api/clients (server/index.ts; joins Nova clients/divisions/verticals/tiers with churn features and the active book in server/activeBook.ts)

Server cache 1 hour (clients_v4); active book 15 min

On a name collision, the Nova client id wins over the Vena-derived id so frontend writes agree with the server's ownership map

Which clients appear at all ("pulse-eligible")

Derived in the browser

isPulseEligible() in src/utils/pulseEligibility.ts (identical copy in server/pulseEligibility.ts; parity unit-tested)

Instant

Excluded if churn date is today or past (residual revenue does not keep a churned client eligible); otherwise needs current-month MGP > 0, or latest VABO > 0, or weeks-until-end > 0

Account Director dropdown (who owns which client, client counts)

Signal's own records (client_account_directors + client_senior_account_directors, maintained via Manage Leads) with Nova executive-sponsor fallback for clients with no Signal AD row

GET /api/client-account-directors/with-fallback → getClientAccountDirectorsWithFallbackRows() (server/index.ts)

Nova fallback cached 1 hour; Supabase rows read live

Pulse authorizes both the AD and the Senior AD; a Senior AD scoped to a division gets a separate per-division card

Group Director dropdown

Nova client assignment history (role = 'group_director', most recent per client, active users only)

GET /api/client-group-directors (server/index.ts)

Server cache 1 hour

GDs are not in the AD tables — selecting yourself as GD (with no AD chosen) shows your whole group for direct submission

"You" default in the AD picker + void-button gating

Nova user directory (login email → Nova user id)

GET /api/pulse/whoami → resolvePulseCallerEmpId() (server/index.ts)

Email→id map cached server-side 5 minutes (PULSE_OWNERSHIP_TTL_MS, as of Jul 2026)

Email not in Nova → no default selection and saves fail with "Cannot identify your user"

Submitted / Missing pills, "by ", "Last: ", progress bar

Signal's own records (pulse_weekly_submissions, active rows only)

Direct authenticated Supabase REST reads: getPulseWeeklyForClientsAtWeek(), getPulseWeeklyLastSeenByClients() (src/services/supabase.ts)

Live on every AD/GD selection change

Keyed per (client, division) slot; voided rows excluded, so a void flips the pill back to Missing immediately

Weekly form contents, week picker, prefill from prior week

Signal's own records (pulse_weekly_submissions)

getPulseWeeklyRows() (active + voided, for picker/timeline) and getPulseWeeklyPreviousBefore() (most recent active prior week, for prefill)

Live on card expand

Prefill is division-scoped and never seeds from a voided row

QBP form + quarter picker

Signal's own records (pulse_qbp_submissions)

getPulseQbpRows() (src/services/supabase.ts)

Live on card expand

One row per (client, quarter, division)

"Previously submitted" picker section

Signal's own records (caller's active pulse rows across both tables)

GET /api/pulse/my-submitted-client-ids (server/index.ts)

Live on page load

Lets an AD keep pulsing a client they no longer formally own; only shown when viewing your own book

Submitter display names ("by Jane Doe")

Nova user directory (roster)

employees from DataContext → GET /api/employees; the stored Nova user id is resolved to a name in the browser

Roster cached server-side 1 hour

Unresolvable ids display as the raw number

Submission history timeline (per client)

Signal's own records (both pulse tables, including voided rows)

Same getPulseWeeklyRows() / getPulseQbpRows() fetches, interleaved chronologically in SubmissionTimeline

Live on card expand

Read-only; voided rows shown struck-through with a "voided" pill

Metrics & definitions

Submit Pulse Data collects enumerated judgments and contract facts; the only computed numbers on screen are completion counts.

  • Week — every weekly pulse is anchored to a Monday-start week (mondayOf() in src/utils/week.ts; a database CHECK enforces Monday). Displayed as "Week of {Mon D}". The week picker offers the current week + 3 prior; any older week that already has a row stays selectable.

  • Progress bar ("X of Y submitted", missing count, %) — Y is the number of pulse-eligible (client, division) slots in the current AD/GD scope; X is how many of those have an active row for the current week. Percent is X/Y rounded. Computed in the browser from Signal's own submission records.

  • Sentiment — the submitter's weekly judgment of client mood. Fixed 5-point scale: "Delighted / Advocate", "Happy / Content", "Neutral", "Concerned / Showing Friction", "Very Unhappy / At Risk of Churn". Entered by the submitter in Signal.

  • Pacing to Goal — "On Track or Ahead", "Slightly Behind", "Behind", "Unknown". Entered by the submitter.

  • Business Performance YoY — "Improving YoY", "Flat", "Declining YoY", "Unknown". Entered by the submitter.

  • Do we have goals established? — Yes/No toggle. Entered by the submitter.

  • Missed Performance Goals? — Yes/No toggle; helper text defines it as "Client has missed their performance goals two months in a row." Who adjudicates a miss is not defined in the app repo.

  • Performance Goal and WoW — What Changed / What Are We Doing Next? — free-text areas with a 1,500-character soft warning counter (no hard cap; the counter turns red but saving still works).

  • Date of Last AD / Leadership Check-in — date input capped at today (past events only).

  • Term Type (required to save a weekly pulse) — "M2M (Month-to-Month)", "No Rollover", or "Auto-Renew".

  • Rollover Trigger Date — when the rollover notification fires; future dates allowed. Rollover Length (Months) — whole number ≥ 1.

  • Early Out Date — with an explicit "No early out (N/A)" checkbox (checking it clears and disables the date). Early Out Notice Date — date the early-out notice was or will be sent; future dates allowed.

  • Performance Tracker / Pacing Link — optional URL; must start with http:// or https://.

  • Quarter (QBP) — format YYYY-Qn (e.g. 2026-Q3), computed from the calendar quarter (computeQuarter() in src/utils/quarter.ts). Quarter picker: current + 1 prior, plus any historical quarter that already has a row.

  • QBP Date (required) — the Quarterly Business Presentation meeting date. May be scheduled up to 1 year in the future (qbpDateMaxIso(), src/utils/quarter.ts, as of Jul 2026 — the cap catches mistyped years, not planning). Dates must be real calendar dates between 2020 and 2100. QBP Narrative Link and QBP Presentation Link — both required URLs.

  • Submitted / Missing pill — Submitted means an active weekly row exists for that (client, division) slot for the current week, regardless of who submitted it. "Not in scope" (on "Other matches" search results) means status wasn't fetched, not that it's missing.

  • Status (active / voided) — voiding is a soft delete: the row's status flips to voided, deleted_by records who, deleted_at is set by a database trigger, and the row is retained for audit and downstream sync. Voided rows stop counting toward weekly status and reports.

  • Division slot — for multi-division clients (e.g. a "B2B" and a "Lifestyle" assignment), each division is an independent card with its own weekly/QBP records; uniqueness is per (client, week-or-quarter, division).

How it's used

  1. Monday weekly pulse run (AD): open Submit Pulse Data — the AD picker already shows you — set Status = "Missing" to see what's left, expand each card (the form pre-fills from your most recent prior submission), update only what changed, and hit Save Week. Feeds the week's client-risk picture: a sentiment drop to "Concerned / Showing Friction" or "Very Unhappy / At Risk of Churn" is the early-warning signal leadership and churn workflows key off.

  2. Log a contract exposure (AD): when renewal terms change, update Term Type, Rollover Trigger Date, Rollover Length, and the Early Out dates in the weekly form. These dates tell leadership and finance when revenue is at contractual risk.

  3. Quarterly QBP logging (AD): expand the client → QBP Submission form → enter the QBP date (a scheduled future preso up to a year out is fine) and paste the narrative + presentation links → Submit QBP. Proof the QBP ritual happened, with artifacts one click away.

  4. GD completion sweep (Group Director): pick yourself in the Group Director dropdown, then either "All ADs" (whole-group status view) or one AD, and chase the Missing pills. A GD can also submit directly on any client in their group without picking an AD first.

  5. Fix a mistake (any submitter): expand the wrong submission → "Void this submission" → confirm. The pill flips back to Missing, the voided row stays in the history timeline, and you re-submit on the correct client/week. Only the original submitter (or a Gandalf admin) sees the void link.

  6. Keep pulsing an inherited client: a client you pulsed before but no longer formally own appears in the "Previously submitted" section with a "No longer assigned" pill — you can keep submitting without waiting for a Manage Leads reassignment (note: your ability to save still depends on the server-side ownership check).

  7. Find any client fast: typing in "Search clients" also surfaces "Other matches" — eligible clients outside your current AD/GD scope, shown with a neutral "Not in scope" badge.

What users can change here

Submit Pulse Data is the only screen where Client Pulse rows are created or edited. All writes go through the Signal backend — the browser never writes the pulse tables directly (row-level security, migration 20260509b_pulse_rls_tightening.sql, blocks it). On every write the server re-checks ownership (assertPulseOwnership() in server/index.ts: assigned AD or Senior AD per Signal's records with Nova executive-sponsor fallback, or the client's GD per Nova assignment history, or a Gandalf admin) and stamps submitted_by with the caller's Nova user id from their login token — it cannot be spoofed.

  • Weekly pulse row → POST /api/pulse/weekly → upsert into pulse_weekly_submissions, one row per (client, week, division). Save is enabled only when something changed and the required Term Type is set. Same-week edits overwrite in place; editing a past week shows an explicit overwrite warning. Re-submitting over a previously voided week revives that row (status back to active, void fields cleared). GD Review Notes — a field on the same row authored in the Client 360 insight panel (WowChangeEditor), not here — are passed through untouched so a weekly re-save never wipes them.

  • QBP record → POST /api/pulse/qbp → upsert into pulse_qbp_submissions, one row per (client, quarter, division). QBP Date + both links are required; date allowed up to 1 year ahead.

  • Void → POST /api/pulse/weekly/void / POST /api/pulse/qbp/void → soft-delete (status = voided, deleted_by = your Nova user id, deleted_at set by a DB trigger). Allowed for the original submitter or a Gandalf admin; already-voided rows return a 409. Nothing is ever hard-deleted.

  • Reset button — reverts unsaved form edits to the loaded values (no server call).

Not changeable here: AD/Senior-AD assignments (Manage Leads, /leads), GD assignments (Nova), client eligibility (computed from churn/revenue data), and GD Review Notes (Client 360 panel).

Caveats & data quality

  • "Pulse" is two different systems. Submit Pulse Data is Client Pulse. The admin-only "Compliance Dashboard →" header link goes to /pulse-coverage, which tracks Nova's internal Employee Pulse survey — a different dataset, related in name only.

  • Ownership caches can lag. The server caches the email→Nova-id map and the AD/GD ownership maps for 5 minutes, and the Nova executive-sponsor fallback plus client/GD lists for 1 hour. A freshly assigned AD may briefly not see a new client or get a 403 on save. A Manage Leads reconcile explicitly invalidates the pulse AD cache, so that path propagates fast; other changes take up to the TTL.

  • Client eligibility is computed, not a hard list. Clients with a churn date of today or earlier disappear from the picker even if residual revenue is still flowing; clients with zero current-month MGP, zero VABO, and no weeks remaining also drop out. Screen contents legitimately differ week to week. Rule: isPulseEligible() in src/utils/pulseEligibility.ts, mirrored server-side with test-enforced parity.

  • Submitted status is per (client, division) slot. A multi-division client shows one card per division that submits and tracks independently — one division Submitted does not make the other Submitted. The ?clientId= deep link opens the whole-client (no-division) slot, which matches single-division clients.

  • The "Submitted" pill doesn't say the assigned AD submitted. It shows whoever submitted ("by ") — a GD or Gandalf admin submission satisfies the week the same way. Whether GDs are expected to submit weekly themselves or only backstop their ADs is a process norm, not enforced in code.

  • No void reason is captured. The void confirm modal collects no reason text and the API payload carries only the row id (who/when are recorded); code comments referencing a void "reason" are aspirational as of staging 98df0c4. The timeline shows voided rows struck-through but without a reason.

  • Two clients, one name = one card. The picker deduplicates by canonical (lower-cased) client name, preferring the Nova client id on collision so writes and ownership checks agree. Genuinely distinct clients that share an exact name would collapse to one entry — a production-data question, not defined in the app repo.

  • Text fields have no hard length cap. The 1,500-character counter on Performance Goal and WoW is a soft warning only.

  • Week/quarter picker depth is deliberately shallow (current week + 3 prior; current quarter + 1 prior) per code comments about the feature being recently launched — older periods only appear once they contain data, and the depth may change.

  • Drafts persist per session only. An expanded card keeps unsaved edits while you collapse/re-expand it within the page session, but nothing is saved until you click Save Week / Submit QBP; navigating away discards drafts.

  • Downstream sync cadence unknown. Both pulse tables feed a reverse-ETL pipeline into the wider Nova reporting stack (voided rows are retained specifically for that audit trail); the schedule and destination reports live outside this repo — not defined in the app repo.

Related views

  • My Submissions (/pulse/my-submissions, Team Lead+) — the logged-in user's own weekly + QBP rows across all clients, including voided ones; rows deep-link back to Submit Pulse Data via ?clientId=. Same underlying tables, read-only.

  • Pulse Summary (/predictions/pulse-summary, admin-only) — the portfolio-wide read side: latest pulse per client joined with churn-risk data. What ADs type on Submit Pulse Data is what leadership sees there.

  • Client 360 / churn insight panel — shows recent pulse history per client (GET /api/pulse/history → PulseHistorySection) and is where GD Review Notes are authored (WowChangeEditor writes the gd_review_notes column on the weekly row; Submit Pulse Data passes the value through untouched).

  • Manage Leads (/leads, Team Lead+) — the upstream write path for AD and Senior AD assignments; it decides who appears in the AD picker and who is allowed to submit (with the Nova executive-sponsor fallback filling gaps). A reconcile there immediately invalidates the pulse ownership cache.

  • Employee Pulse (/pulse-coverage, admin-only, under People)not related despite the shared word and despite the admin header link on this screen pointing there: it tracks Nova's internal employee survey completion.

FAQ

Q: Who is supposed to submit the weekly pulse, and how often? A: The client's assigned Account Director (or Senior Account Director) submits once per client per week, anchored to Monday-start weeks; the client's Group Director can also submit (or backstop). The progress bar at the top of Submit Pulse Data frames the task — "X of Y submitted" for the current week. Quarterly, the same screen carries the QBP form: one record per client per quarter with the QBP date, narrative link, and presentation deck link.

Q: Why can't I save — the Save Week button is greyed out? A: Save Week enables only when (a) something actually changed versus the loaded record, (b) the required Term Type is selected, (c) the Performance Tracker link (if any) starts with http:// or https://, and (d) Rollover Length (if any) is a whole number ≥ 1. For the QBP form, all three fields are required — QBP Date (a real date, at most 1 year in the future), Narrative Link, and Presentation Link.

Q: I got a 403 "You are not the assigned AD or GD for this client" — but it's my client. A: Submission rights are checked server-side against Signal's AD/Senior-AD records (maintained in Manage Leads, with a Nova executive-sponsor fallback) and Nova's group-director history. If your assignment just changed, caches (5 minutes for ownership, up to 1 hour for the Nova fallback) may not have caught up — a Manage Leads reconcile clears the pulse cache immediately. If it persists, fix the assignment in Manage Leads (/leads) or contact #signal-support.

Q: A client I manage isn't in my list at all. Why? A: Two common reasons. (1) Eligibility: clients whose churn date is today or past are always excluded, and a client needs current-month MGP > 0, VABO > 0, or weeks-until-end > 0 to appear. (2) Assignment: the list is built from your AD/Senior-AD assignments — if the client is assigned to someone else, use the "Search clients" box; it surfaces any eligible client under "Other matches" even outside your scope, or check "Previously submitted" if you pulsed it before.

Q: I submitted on the wrong client / wrong week. How do I fix it? A: Expand that submission and click "Void this submission" (visible only to you as the original submitter, or to a top-tier admin), then confirm. The row is soft-voided — it stops counting toward the week and downstream reports but stays in the database and in the card's Submission history timeline for audit. Then submit on the correct client/week. Re-submitting on a voided week cleanly revives that week's record.

Q: The form came pre-filled — did I already submit this week? A: Check the banner. "Submitted for by — edits below will overwrite this record" means a submission exists for the selected week. "Pre-filled from . Update what changed and save" means nothing exists yet for this week — the form is seeded from your most recent prior active submission (spreadsheet-style continuity) and nothing is saved until you click Save Week. "First submission for this client" means there is no history at all.

Q: Can I log a QBP that hasn't happened yet? A: Yes. QBP dates can be scheduled up to 1 year in the future (as of Jul 2026; earlier builds rejected forward dates). The cap exists to catch mistyped years. The narrative and presentation links are still required at submit time.

Q: Why does one client show two cards (e.g. "B2B" and "Lifestyle")? A: Multi-division clients are tracked per division: each division is its own slot with independent weekly submissions, QBP records, and Submitted/Missing status. If your assignment is scoped to one division, you only see that division's card.

Q: Who can see what I type here? A: Weekly and QBP submissions are Signal's own records and are visible to leadership in Pulse Summary (admin-only), in the client's Client 360 / churn insight panel, in the per-client Submission history timeline on this screen (any team lead who opens the card), and in your own My Submissions list. They also feed a downstream pipeline into the wider Nova reporting stack, where voided rows are retained for audit.

Q: What does "Missed Performance Goals?" actually mean? A: The in-app helper text defines Yes as "Client has missed their performance goals two months in a row." Beyond that helper text, the precise adjudication process (who decides, against which tracker) is not defined in the app repo — follow your team's operating guidance.

Q: Is this the same "Pulse" as the Employee Pulse / Compliance Dashboard? A: No. Submit Pulse Data is Client Pulse — client health check-ins by ADs and GDs. Employee Pulse (/pulse-coverage, admin-only) tracks completion of Nova's internal employee survey. Confusingly, the admin-only "Compliance Dashboard →" link at the top of this screen goes to that Employee Pulse tracker, not to a client-pulse dashboard.

Did this answer your question?