Skip to main content

Signal — Data Source Map

Written by Schae Lilley

Machine-verified mapping of every data source feeding each view of Signal (Power Digital's internal BI web app), and everything each view can write. Generated from staging @ commit 98df0c4, verified 2026-07-07.

Every source in this document is named at its core-origin layer — the business system where the data is actually created and maintained. The Snowflake warehouse, warehouse replicas (e.g. ENTERPRISE.RDS_PG_NOVA), Supabase-as-a-database, server caches, and mirrored views are transport, never sources: when Signal reads the Nova roster through a warehouse replica, the source is the Nova platform; when it reads the CSM renewal tracker through a mirrored Snowflake view, the source is the Google Sheet. The one deliberate exception is "Signal's own records" — data entered by users inside Signal itself (stored in Signal's Supabase tables), for which Signal is the true origin. Each system section and the Views table below are self-contained and can be read independently.

Source systems

Vena (vena)

Finance's planning system and the system of record for contract revenue: per-client, per-department, per-month revenue, MGP (gross profit), outsource costs (COGS), and VABO. Vena writes; Signal only reads.

Owner: Finance. Relationship to Signal: External system, read-only from Signal.

Views in Signal fed by Vena:

View

What it provides

Notes

Summary

MRR (retainer-only recurring revenue), in-contract revenue (retainer+project), MGP (revenue minus outsource cost), outsource costs, VABO (historical months from contracts data; current month from LATEST_VABO_VALUE_2MO override), active-client activity gate, available-months list, and the entire metric-over-time chart

VABO excluded from all revenue/MGP rollups and surfaced on its own tile; project revenue excluded from MRR; months capped at current calendar month; server caches ~30-60 min

Employees

Client gross profit per blueprint × department × month (FINAL_VENA_CONTRACTS; Vena writes, Signal reads) — drives RPE, Total Gross Profit, MRR, averages, leaderboards; current-month VABO substituted from FINAL_VENA_CLIENTS.LATEST_VABO_VALUE_2MO

Via getAssignmentsData() in server/vena-kpi-engine.ts; per-year master cache

Staffing

Client contract revenue behind every card and all MRR/RPE dollar figures (FINAL_VENA_CONTRACTS, one row per blueprint × department × service × month; Vena writes, Signal reads), plus the available-months list that the global MonthSelector offers

Read via server/vena-kpi-engine.ts (enrichRevenue, computeMonths); negative-revenue rows dropped; zero-revenue dormant department rows filtered; VABO rows excluded from the board but included in RPE

Manage Leads

Client list, department rows, monthly revenue / MGP / outsource cost per department-month (FINAL_VENA_CONTRACTS via GET /api/assignments → getAssignmentsData), month range for the MonthSelector, priority tier P1–P3 (FINAL_FEATURES), and the multi-division client universe

Vena writes, Signal reads only; 4-hour default in-memory server cache; VABO rows shown but excluded from seat/revenue rollups

Submit Pulse Data

Account list behind the client cards (pass 1 from DataContext accounts) plus current-month MGP and latest VABO feeding the pulse-eligibility rule

Active book cached 15 min (server/activeBook.ts); clients join cached 1 hour; on a name collision the Nova client id wins over the Vena-derived id so writes agree with the server ownership map

My Submissions (Client Pulse)

Client display names in the Client column — accounts built from Vena contract/revenue records keyed by Nova client id (parentName/name from DataContext)

Unmatched client ids display as raw numeric ids

Pulse Summary

Eligibility revenue checks (current-month MGP from FINAL_VENA_CONTRACTS, LATEST_VABO_VALUE_2MO from FINAL_VENA_CLIENTS) and the revenue-based Department filter membership (accountsByMonth slices in DataContext)

Department filter is revenue-derived (positive revenue in latest loaded month, VABO excluded) and does not apply to the By Division table

Portfolio Review

MGP, Dept MGP, MRR, Last Month VABO, VABO-2mo ('VABO only' badge), departments column and department revenue breakdown (FINAL_VENA_CLIENTS / FINAL_VENA_CONTRACTS)

UI tooltips explicitly name Vena as source; 15-min server cache; MGP can be $0 before Finance posts entries

POC Management

Revenue-active client gating (active book: current-month contract MGP > 0 or recent VABO) that determines which clients appear at all

server/activeBook.ts, 15-min cache; same base as Portfolio Review

Churn Risk

MGP, Current MRR, Last Month VABO columns and the current-month Department filter values

Finance actuals; MGP can be $0 for new contracts before Finance posts.

Service Expansion

Current-month monthly GP, MRR, VABO figures in the client detail

Via the pipeline's Vena clients table; shared 15-min cached fetch with Churn/Portfolio

Client Profitability

Contracted monthly revenue per client (Revenue/MRR column and the revenue side of Cost/Profit/Margin) via getFinanceData()/enrichRevenue() — the identical path as the Summary view; VABO rows excluded, churned clients filtered

Contract dataset cached up to 4 h server-side (default TTL); hours-months missing a Vena revenue row borrow the latest available month's revenue as an MRR proxy; $0-revenue clients hidden entirely

Divisional P&Ls

Department × division × month contract revenue rows, In Contract Revenue, VABO row (per finance-assigned VASO slot, Retail Marketing slots excluded), outsource COGS, and Client Roster per-blueprint revenue + contract end dates + retainer/project type

GET /api/division-pl/revenue, /api/vabos/by-division, /api/vena/outsource, /api/division-pl/client-roster; Corporate dept (Vena 1024) excluded; verified to the penny vs finance (Jun 2026); server venaCache + 30-min browser TTL cache

Revenue Overlap

Account revenue, MGP, division, department, contract type (FINAL_VENA_CONTRACTS, Vena-written / Signal read-only) and the selectable month list (capped at current calendar month; VABO rows excluded from this view)

Served via GET /api/assignments[/batch] and /api/months → getAssignmentsData()/computeMonths() in server/vena-kpi-engine.ts; server cache 4h default (venaCache 240-min TTL); Vena Blueprint Data Load refreshes ~weekly per code comment

Capacity Planning (Role + Company)

Contract revenue per client/department/month — drives RPE, MRR, Revenue Gap, RPE Trend chart, and the available-months list (capped at current calendar month)

Signal read-only; served via GET /api/assignments and /api/months from getAssignmentsData()/computeMonths() in server/vena-kpi-engine.ts. VABO included in individual RPE, excluded from the trend chart.

Growth (People Dash + Performance Radar)

RPE (revenue per employee) and # Accounts

Vena contract + monthly revenue allocated client-side across Signal's team-assignment records (same engine as the Employees view); joined to Growth rows by normalized name.

AI Solutions

All business metrics on the Baseline vs Current cards, Trend Over Time chart, and per-employee breakdown: total/attributed revenue, MRR, VABO, RPE, seats, active client counts, retention rate (per data month); also MRR context inside health objects and department mapping used in target-to-client resolution

Computed in the browser via captureSolutionMetrics() from DataContext time-series/employee history (GET /api/assignments); baseline frozen as JSON on the baseline snapshot, Current recomputed from the latest data month independent of the global month picker

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

Employee RPE/MRR actuals behind the Hiring Need verdict (via the server KPI engine and DataContext employee metrics)

Thresholds ≥100/85–99/<85 are code constants in HiringPipelineView.tsx

Platform Foundation (app shell)

Monthly revenue, outsource cost (COGS), MGP per client×department×month; the available-months calendar for the period selector (future/zero months excluded server-side)

Read-only via server/vena-kpi-engine.ts; server cache with /api/vena/cache/invalidate; 30-min months cache

NetSuite (netsuite)

The accounting general ledger: GL revenue accounts (Summary's closed-month Total Revenue basis), benefits and payroll-tax cost pools, contractor bill payments, and an independent-contractor classification list.

Owner: Finance / Accounting. Relationship to Signal: External system, read-only from Signal.

Views in Signal fed by NetSuite:

View

What it provides

Notes

Summary

Total Revenue tile and RPE numerator on the unfiltered view — per-month sums of GL revenue accounts 42100/43300/43400/43500/43550/43560 (REVENUE_ACCOUNT_CODES, server/netsuite.ts:178) via GET /api/netsuite-revenue

Closed, positive-figure months only; open month and incomplete closed months (non-positive = reversals only) fall back to Vena+VABO with subtitle/tooltip; live SuiteQL pull if the latest closed month is missing from the pipeline; 30-min server cache, client refetch on tab visibility max every 5 min; Summary view only, never under filters

Divisional P&Ls

Benefits pool (GL 60500+66000) and payroll-tax pool (GL 60300+65100) pro-rated over ADP gross; FT-CONT/FT-CONT-INT contractor bill payments

Read from FINAL_NETSUITE_ACCOUNT_COSTS / FINAL_NETSUITE_CONTRACTOR_PAYMENTS models in server/payroll-actuals.ts

Growth (People Dash + Performance Radar)

Independent Contractor classification pill + division/department overrides for ~22 people

Hardcoded mirror of JT's NetSuite IC List in server/payroll-overrides.ts — manual code refresh, not a live integration.

Nova platform (nova)

Power Digital's internal org & client platform: employee roster and org structure, client master records, client-team assignment history, and weekly employee Pulse check-ins. Since July 2026 Signal also writes Account Director / Sr. Account Director assignments back to Nova via its GraphQL API.

Owner: Nova platform team. Relationship to Signal: External system, read + written by Signal.

Views in Signal fed by Nova platform:

View

What it provides

Notes

Summary

Client records incl. churn_date (retention), division/tier/vertical; employee roster and full-time headcount (USERS + JOB_TITLES + DIVISIONS); seat assignments (CLIENT_ACCOUNT_ASSIGNMENT_HISTORY) behind Total Seats and Assigned Leads; Nova org profile for the Delivery Employees tile

1-hour server caches on roster/headcount/client metadata; retention is point-in-time from churn_date vs month-end

Employees

Roster (name, email, job title, division, department, is_fulltime) via GET /api/employees; complete org lookup incl. terminated users via /api/employees/nova-org; detected seats from client account assignment history; drawer profile (hire date, manager, employment type, contractor); Pulse hours (question 1006, Laravel-decrypted); client churn dates driving retention; since 2026-07-01 cutover, live GraphQL API is source of truth for AD/Sr-AD assignments and post-cutover team history

Roster/org cached 1h server-side (reaches Signal via warehouse replica); Nova GraphQL assignments cached 5 min; roster filtered to company email domains + non-terminated + test-name denylist

Staffing

Employee roster (name, title, division, department) via GET /api/employees (1-hour server cache); client-team seat history that determines which clients each employee leads and their roles; Account Director / Sr. Account Director designations via GET /api/nova/assignments (live Nova GraphQL, 5-minute server cache) that inject ADs as extra leads on cards

New since Jul 6: the live Nova GraphQL read layer (PRs #94/#97, merged 2026-07-07) — Nova now wins over Signal's cached AD rows for non-consulting single-division clients. Roster enriched server-side with ADP employment type (not shown on this board)

Manage Leads

Auto-detected department teams (CLIENT_ACCOUNT_ASSIGNMENT_HISTORY seats), employee roster and picker, client metadata (tier/vertical/division/churn), and — new — live AD/Sr. AD designations via Nova GraphQL (GET /api/nova/assignments, clients.executive_sponsor / senior_account_directors, 5-min server cache) plus Nova assignment history endpoints (/history, /at/:date, cutover 2026-07-01)

Since PR #97 live Nova wins over Signal's cached AD/SrAD rows for non-consulting single-division clients

Employee Pulse (Pulse Coverage)

Weekly employee Pulse check-ins: submission existence (coverage), per-client + non-client hours (question 1006, decrypted server-side with NOVA_APP_KEY), sentiment 0-5 (question 1001), bandwidth 0-10 (question 1002); plus the active-employee roster (users, roles, departments, job titles) that forms the coverage denominator and the fine-grained Nova department labels

Queried live per request via GET /api/pulse-coverage and /api/pulse-coverage/sentiment; replication lag from the Nova platform is ~15 min-6 h (code-comment estimate). Denominator applies hardcoded exclusions (Revenue/Sales/Accounting/Finance/Corporate/Nova depts, exec titles, 23 test accounts, 3 named individuals) and dedupes duplicate Nova accounts by name.

Employee Pulse (Pulse Coverage)

Employee names, divisions, titles, and coarse fallback departments via the shared GET /api/employees roster consumed through DataContext

Server-cached 1 hour; view prefers the finer Nova department from the pulse-coverage roster over this one.

KPI Targets

Active employee roster and job titles used for title→role matching, the sync employee total, and (via /api/employees + DataContext) the coverage-bar population

Origin is Nova HR/ops; the code reads it via the warehouse replica (transport). Sync query counts all active titled employees; coverage bar counts only employees with retained client assignments.

Submit Pulse Data

Client records (id, name, churn date), user directory (whoami email→Nova id defaulting the AD picker, submitter display names), group-director assignment history (GD dropdown + GD submit rights), and executive-sponsor fallback for clients with no Signal AD row

Ownership caches: email→Nova-id map 5 min (PULSE_OWNERSHIP_TTL_MS), exec-sponsor fallback and client/GD lists 1 hour; a Manage Leads reconcile invalidates the pulse AD cache immediately

My Submissions (Client Pulse)

Caller identity (JWT email → Nova employee id via resolvePulseCallerEmpId, 5-min server cache), employee display names for the Submitted column (GET /api/employees, 1-hr cache), and the team-lead auto-grant basis (executive sponsor / group director on a Nova client)

Page errors with 'account not linked to a Nova user' if the login email is absent from Nova's user directory

Pulse Summary

Client master records (id, name, churn date) from ENTERPRISE.RDS_PG_NOVA.CLIENTS that define the candidate universe for the pulse-eligible active book

Filtered by pulseEligibility; server-cached 15 min in activeBook.ts; same universe as Submit Pulse and Portfolio Review

Portfolio Review

Client roster and active-book membership (churn_date), Group Director (group_director_id→USERS), Account Director fallback (executive_sponsor_id), Cardinal badge (ACQUISITION_ID='1005'), AD/GD auto-team-lead grant at sign-in, Nova→Signal AD sync (CLIENT_ACCOUNT_ASSIGNMENT_HISTORY)

Nova data reaches the app via warehouse replica; sync is freshness-guarded, 1-hour TTL

POC Management

Client names/tiers/divisions, Group Director (assignment history role=group_director), Account Director fallback (executive sponsor), and the employee directory powering the relationship-owner typeahead

GD/AD chips read-only here; AD chip = manual Supabase pick with Nova exec-sponsor fallback

Churn Risk

Revenue column (Total Active Retainer), contract dates/weeks-to-end, per-contract Active Contract Details JSON (Advanced Details), and the Open-in-Nova deep link

Contract facts reach Signal via the model feature table.

Division Forecast

Client roster (executive_sponsor / group_director emails) used to auto-grant the is_team_lead flag at sign-in via POST /api/auth/ensure-provisioned — access gating only, no on-screen data on this view

Role flags themselves are stored in Signal's own records (app_users).

Service Expansion

Client attributes (tier, division, industry, group/account director) via the pipeline's client-features table

SE view reads them from the ML feature table, not live Nova

Client Profitability

Self-reported hours per client per month from the weekly Pulse check-in (question 1006, Laravel-encrypted, decrypted server-side with NOVA_APP_KEY), Pulse submission records for the Pulse Coverage KPI numerator, client display names, and the employee roster + client-team assignments behind the delivery-employee denominator

Hours fetched fresh per page load and attributed to the calendar month of the submission date; undecryptable rows silently skipped; per-submission hours above 250 clamped to 250 (code constant as of Jul 2026)

Divisional P&Ls

Employee names, titles, division/department assignment, termination dates for payroll matching; client display names for the Client Roster

ENTERPRISE.RDS_PG_NOVA.USERS/CLIENTS queries; nova↔payroll match cache populated by scripts/sync-payroll-actuals.ts

Revenue Overlap

Team seats per account (client_account_assignment_history/users/departments — every seated member, lead and support; AM/AD/PM seats match all departments on their client) and each employee's home division/department (users/divisions/departments; the matrix row axis)

Seat query getSeatAssignmentsQuery() in server/snowflake-only-queries.ts; roster via GET /api/employees (1h cache); Group/Managing Directors stripped from Consulting seats unless deliberately assigned; home division read as-of-now, retroactively recolors history

Capacity Planning (Role + Company)

Employee roster (names, titles, divisions, departments), client-team seat/assignment history used to attribute revenue to people, and live AD/Sr AD assignments (executive_sponsor / senior_account_directors) via GET /api/nova/assignments

Roster via GET /api/employees (1-hour cache); seats joined to Vena revenue in TypeScript; live Nova AD/Sr AD now wins over Signal's cached copy for non-consulting single-division clients (Jul 2026).

Growth (People Dash + Performance Radar)

Person roster (name, division, department, title, reports-to, role start), pulse sentiment/bandwidth/weekly hours, novaTalent self/manager scores, Vital 5 & Hedgehog answers, Culture Index profile

Roster served via 3-tier cache (fresh 15 min, up to 24 h stale-while-revalidate). Weekly-hours answers are encrypted and decrypted server-side with NOVA_APP_KEY.

Growth Access management

Division and department picker vocabulary — the distinct novaDivision/novaDepartment values on employee records in the cached Growth People dataset (visibleDivisionsFromPeople; external contractors, blanks, placeholders excluded). PUT validation rejects divisions/departments not present in this vocabulary.

People dataset is fresh 15 min / stale-served up to 24 h with background refresh (code constants); if never built, the picker is empty until the People tab is opened once. Department-scopable divisions (Shared Services, G&A, Healthcare) are a code constant in src/utils/growthDepartments.ts.

AI Solutions

Employee roster in the single/multi-employee target pickers (names + departments, active company-email staff only)

GET /api/employees reads Nova's user directory (1-hour server cache); division/department picker options are hardcoded canonical Nova org names in src/utils/orgStructure.ts (9 divisions, 31 departments) — code constants, not live

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

Client/employee names for Assignment History diffs; email→division/department lookup for Analytics 'Adoption by division' (1h server cache); AD role list used to auto-grant Team Lead at provisioning

fetchEmailDivisions in server/usageEnrichment.ts

Platform Foundation (app shell)

Employee roster, client metadata (vertical/tier/division/churn), departments, headcount, client-team assignment history, and live AD/Sr.AD assignments via the new Nova GraphQL connection (/api/nova/*)

1-hour caches for roster/clients; 5-min cache for Nova assignments; AD/GD emails from Nova auto-grant Team Lead at provisioning

Views in Signal that write into Nova platform:

View

What it writes

Manage Leads

AD / Sr. AD chip saves now go DIRECTLY to Nova via POST /api/nova/assignments → Nova updateClient GraphQL mutation (executive_sponsor_id / senior_account_director_ids); Nova auto-writes its client_account_assignment_history. Consulting team saves still push the reconciled AD to Nova server-side via the reconcile endpoint (updateClientAccountDirector), failure shown as 'Saved in Signal, but Nova sync failed'

Platform Foundation (app shell)

Boundary case (Manage Leads module, foundation-adjacent): AD/assignment changes write back to Nova via POST /api/nova/assignments (updateClient GraphQL mutation); never writes to Vena/finance data

Payroll (ADP / Deel / Siesa) (payroll)

Three payroll sub-systems: ADP (US employees, live API), Deel (international employees and contractors, live API), and Siesa/SIESTA (Colombia, monthly spreadsheet upload). Feeds salaries, employment types, and personnel costs.

Owner: People / Finance operations. Relationship to Signal: External system, read-only from Signal.

Views in Signal fed by Payroll (ADP / Deel / Siesa):

View

What it provides

Notes

Employees

Employment type enrichment (Full Time / Part Time / Apprentice) from ADP workerType, name-matched onto the Nova roster

ADP, not Deel, on this view; best-effort name matching, null on mismatch/outage

Employee Pulse (Pulse Coverage)

ADP employment-type enrichment on the shared /api/employees roster

Not displayed anywhere on the Employee Pulse view; incidental to the shared roster fetch.

Divisional P&Ls

Personnel Expenses per department/division per month via cost waterfall: ADP gross + pro-rata NetSuite pools → Siesa Colombia → NetSuite contractor bills → Deel invoices → annualized salary ÷ 12; worker tooltips; source-status legend; per-worker CSV export

ADP/Deel synced nightly 04:00 UTC (render.yaml cron); Siesa manual xlsx; staging reads siesta_payroll_test / deel_monthly_actuals_test / deel_people_test (TEMPORARY); endpoints gated by 15-email PAYROLL_AUTHORIZED_EMAILS allowlist

Capacity Planning (Role + Company)

ADP employment-type enrichment (Full Time/Part Time) attached to each roster row inside GET /api/employees

Name-matched; null when ADP unreachable. Rides on employee objects; not displayed on capacity tables. Origin is ADP, not Deel.

Growth (People Dash + Performance Radar)

Annual salary, hire date, worker type/category, country, seniority, contract status, DOB, raise history, YTD commission/bonus/total comp, compensation change history

Three sub-systems: ADP (US employees, live API), Deel (international + contractors, live API), Siesta/SIESA (Colombia, monthly spreadsheet upload). Raises/comp-YTD derived from nightly-ingested monthly gross timelines; matched to roster by name. Staging reads TEMP deel_monthly_actuals_test tables.

KPI Targets Google Sheet (sheets-kpi)

The People team's 'JD Review Process' / KPI Revision Workstream Google Sheet: the authoritative role-based RPE, MRR, and retention targets per job title (plus JD links on the same physical sheet).

Owner: People team. Relationship to Signal: External system, read-only from Signal.

Views in Signal fed by KPI Targets Google Sheet:

View

What it provides

Notes

Employees

Role-based KPI targets (RPE/MRR/retention per job title) — Google Sheet → Supabase signal_roles → in-memory cachedTitleTargets served at /api/employee-kpi-targets

Sync runs at server startup and manually from the KPI Targets admin view; no scheduled interval in repo; title-based matching with division-suffix fallback

KPI Targets

Authoritative RPE/MRR/retention target numbers per role, parsed from free-text KPI cells of the 'CX JD Review Process' and 'SS JD Review Process' tabs (sheet ID 1JdhP_o1LHq44X9jJPSGiHAe2UiMHS9HZfrJ4wc1_NrY, KPI_SHEET_ID env override)

Pulled into signal_roles during every sync (Phase 0 of runKpiMatchSync); heuristic parsing — tab-name hints with auto-discovery fallback, values under $200 assumed thousands, archived CX rows skipped. Same physical sheet also feeds JD links.

Capacity Planning (Role + Company)

Official KPI targets per job title (RPE/MRR/retention) — the basis of Official Target, % to Target, and Above/At/Below classification

KPI Revision Workstream Google Sheet (KPI_SHEET_ID env, hardcoded default) synced by runKpiMatchSync() ~30 s after server boot and on demand via POST /api/kpi-targets/sync into Supabase signal_roles → GET /api/employee-kpi-targets; frontend matches exact title then 'title, division'.

Platform Foundation (app shell)

Role-based KPI targets synced into Signal's kpi_targets records at server startup (POST /api/kpi-targets/sync)

Drives the Above/At/Below (>105% / 95–105% / <95%) status badges

JD Map Google Sheet (sheets-jd)

A Google Sheet mapping employee names/titles to job-description Google Docs, read via a service account.

Owner: Ops / People team. Relationship to Signal: External system, read-only from Signal.

Views in Signal fed by JD Map Google Sheet:

View

What it provides

Notes

Employees

Job-description doc links per person/title (live JD map sheet over static server/jd-data.ts snapshot) via /api/jd-links

Pulled at startup + Gandalf-only POST /api/jd-sheet/sync; name match first, then title

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

JD Sheet Sync source: Name/Title → JD Google Doc map read via Google service account

Sync result held in server memory only; reverts to static snapshot (server/jd-data.ts) on restart/deploy; POST sync is server-enforced Gandalf

CSM Renewal Tracker Google Sheet (sheets-renewal)

The Client Success team's 2026 client renewal tracker: account leaders' declared renewal, churn, downchurn, project-end, and saved statuses per contract per month. Reaches Signal through a mirrored warehouse view.

Owner: Client Success (CSM) team. Relationship to Signal: External system, read-only from Signal.

Views in Signal fed by CSM Renewal Tracker Google Sheet:

View

What it provides

Notes

Division Forecast

All on-screen data on all six tabs: revenue at risk (CURRENT_RETAINER), forecasted churn (FORECASTED_CHURN_AMOUNT_FORMULA sheet formula), churn statuses, renewal stages, account/contract/division/Account Director names, and available months — via the mirrored view NOVAX_CUSTOMER_MANAGEMENT.PROD_MODEL.FINAL_CLIENT_RETENTION_FORECASTING_2026, served by GET /api/retention-forecast with a 15-minute server cache

Single API call powers the whole view; sheet→mirror pipeline cadence not defined in the app repo. Endpoint has no server-side auth middleware — gate is frontend-only.

Hiring Pipeline Google Sheet (sheets-hiring)

The finance-owned hiring pipeline sheet listing open roles per division. It is the system of record: Signal reads roles from it and writes approvals and 'Future Backfill' rows back into it.

Owner: Finance. Relationship to Signal: External system, read + written by Signal.

Views in Signal fed by Hiring Pipeline Google Sheet:

View

What it provides

Notes

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

Hiring Pipeline roles for both tabs (Power Digital 'Divison Sal Cap', Cardinal tab auto-discovered); Approve writes stage '2 - Approved' + date back to the sheet

15-min server cache (code constant), 'Sync Sheet' bypasses; GET/approve endpoints have no server-side role middleware — Approve is UI-gated to Admin+

Views in Signal that write into Hiring Pipeline Google Sheet:

View

What it writes

Growth (People Dash + Performance Radar)

One-time 'Future Backfill' row appended to the Hiring Pipeline Google Sheet when Backfill is first set to Yes (idempotent via backfill_pushed_at; best-effort, failure doesn't block the save)

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

Hiring Approve button writes stage '2 - Approved' + today's date to the finance hiring Google Sheet (the system of record) and invalidates the server's 15-min hiring cache

Salesforce (salesforce)

The CRM: open and closed opportunities, service-expansion deals, won/lost contracts, and close rates. Reaches Signal pre-joined into the AI/ML pipeline's tables — Signal never queries Salesforce directly.

Owner: Sales / RevOps. Relationship to Signal: External system, read-only from Signal.

Views in Signal fed by Salesforce:

View

What it provides

Notes

Portfolio Review

Salesforce history section inside the embedded SE detail tab (FINAL_SALESFORCE_HISTORY); contract wins/losses 3m likely opportunity-derived

Won/lost-contracts upstream origin not definitively stated in repo

Churn Risk

Detail-panel Salesforce section: open opportunities, open SE opportunities, close rates, won/lost contracts last 3 months, failed-SE-in-last-2-weeks flag, freshness timestamp

Joined into the model score table upstream.

Service Expansion

Open SE / Failed SE status, won/lost opportunities last 3 months (with verbatim lost-deal explanations), close rate, last closed-won contract, full opportunity history tab

Carried through the weekly pipeline as JSON columns; history via GET /api/predictions/se/salesforce-history/:clientId

Gong call analysis (gong)

AI call-analysis sentiment derived from recorded client calls: per-contact (POC) satisfaction, engagement, mood, churn-risk, and call-rate scores, plus a client-level Gong sentiment score. Produced by an AI/ML pipeline over Gong recordings; Signal reads the pipeline's output tables.

Owner: AI/ML team (pipeline over Gong recordings). Relationship to Signal: External system, read-only from Signal.

Views in Signal fed by Gong call analysis:

View

What it provides

Notes

Portfolio Review

Gong Sentiment column, gong activity status, POC Contacts tab (FINAL_POC_SENTIMENT per-contact satisfaction/engagement/mood/influence)

Gong score of exactly 0.5 is a no-data convention and renders as '—'

POC Management

Per-contact rolling 5-week call-sentiment scores (satisfaction, engagement, churn risk, decision influence, mood, positive/negative call rates) from the AI call-analysis pipeline over recorded client calls (FINAL_POC_SENTIMENT; upstream pipeline defined outside the app repo, call-derived sentiment labeled 'gong' elsewhere in Signal)

Read via fetchPocContactsByClient in server/predictions.ts; 15-min server cache; is_decision_maker stubbed false pending upstream column

Churn Risk

Call-sentiment inputs (GONG_SCORE) to the sentiment sub-scores and the entire POC Contacts accordion (per-contact satisfaction/engagement/mood/call rates over 5 weeks)

is_decision_maker hardcoded false pending upstream column.

Service Expansion

POC contact sentiment (satisfaction, engagement, mood, churn risk, call rates; 5-week rolling), Gong sentiment score feeding the Sentiment composite

is_decision_maker hardcoded false pending upstream column

AI Solutions

Sentiment inputs behind the Sentiment Risk (0-1) metric — Gong call-recording-derived sentiment (source label 'gong'); Pulse survey responses are the other input (label 'gsheets')

Only clients with a real sentiment source count toward avgSentimentRisk; model defaults excluded; sentimentAsOf = latest model-run week shown in UI

ML models (data-science pipeline) (ml)

The AI/ML team's weekly model outputs: churn risk scores, service-expansion readiness scores, ETCR tables, priority tiers, assembled client feature sets, and weekly historical score archives. Derived data — the models' own upstream inputs include Vena, Nova, Salesforce, Gong, and Pulse.

Owner: AI/ML team. Relationship to Signal: Derived/generated data.

Views in Signal fed by ML models (data-science pipeline):

View

What it provides

Notes

ETCR (Beta)

All ETCR metrics: per-AD/GD/Division monthly rows (ETCR, retainer/project MRR, confirmed/forecasted churn, commit/best-case pipeline, projected expansion/churn, floor/ceiling) from the upstream ETCR pipeline's four tables FINAL_ETCR_MONTHLY_{AD,GD,DIVISION,DIVISION_DETAILED} in NOVAX_CUSTOMER_MANAGEMENT.PROD_MODEL, read via GET /api/etcr (server/index.ts:11355, SELECT *, requireAuth)

Pipeline built and owned by the AI/ML team (Lily) outside the app repo; the business-system origins of its inputs (confirmed vs forecasted churn, Commit/Best Case expansion stages — possibly Salesforce per adjacent design docs) are not defined in the app repo. Division-detailed table is fetched fail-soft. All ratios, the 60% goal (ETCR_GOAL=0.60), and the project-MRR ÷6 conversion are frontend code constants/computations in ETCRView.tsx.

Submit Pulse Data

weeks_until_end churn feature and churn status used in the pulse-eligibility rule — churned clients excluded; otherwise a client needs current-month MGP > 0, VABO > 0, or weeks-until-end > 0 to appear in the picker

isPulseEligible() in src/utils/pulseEligibility.ts, mirrored in server/pulseEligibility.ts with test-enforced parity

Pulse Summary

Churn risk score/category (CHURN_SCORES; thresholds 0.6/0.5/0.3 as code constants), client division and name enrichment, and SE status fields OPEN_SE_CONTRACTS / WON_CONTRACTS_3M / FAILED_SE_IN_LAST_2_WEEKS (FINAL_FEATURES)

AI/ML pipeline tables; refresh cadence not defined in the app repo; clients missing from FINAL_FEATURES get blank division and Unknown SE/churn

Portfolio Review

Churn Risk / Profile Risk scores (CHURN_SCORES), SE Readiness + sentiment/performance sub-scores (SE_SCORES), SE recommendations, Priority P1/P2/P3 (FINAL_FEATURES.PRIORITY_TIER), tier/industry/contract-details/churn-status/past-due-AR fields, historical score trends (HISTORICALS)

Priority rules and refresh cadence (~weekly) owned by data team; missing tier defaults to P3; portfolio uses raw SE_SCORES readiness

Churn Risk

Weekly 0–1 churn score plus Client Profile / Sentiment / Performance sub-scores, performance and sentiment sub-signals (SENT_/PERF_ fields), risk-score history (weekly archive), and the assembled client feature set

Model runs outside the Signal repo; scores passed through, never recomputed. Bands 0.3/0.5/0.6 and at-risk threshold 0.30 are Signal code constants.

Service Expansion

Readiness score + Blueprint/Performance/Sentiment composites and sub-scores, Is Ready flag, dynamic ready threshold (model config, 0.7 code fallback), service recommendations with sub-scores, service history (running/ran/never-ran), weekly historical score trend, contract features (days to end, blueprints)

Weekly data-science pipeline; Signal merges/labels/serves with a 15-min server cache; table score overridden with latest weekly historical snapshot

AI Solutions

All client-health metrics: 0-1 churn risk scores (avg risk, % at risk >=0.30, Low/Medium/High/Critical tiers), SE readiness and retention scores, plus the weekly historical score runs behind the trend chart and reconstructed baselines

Same churn/SE model feeds and ~15-min server cache as the Churn Risk and Service Expansion views; server/aiSolutionHealth.ts + server/predictions.ts

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

Weekly point-in-time churn bands (and SE readiness) for Analytics 'Usage by client risk' and 'Actions on flagged clients' — risk as of the event week, not today

POST /api/usage/client-status → HISTORICALS CHURN_SCORES/SE_SCORES; card-scoped degradation on failure

Signal's own records (signal)

Data entered by users in Signal itself and stored in Signal's Supabase tables: team assignments and revenue splits, pulse submissions, notes and action triage, data-issue flags, access tiers and Growth grants, KPI role copies, saved AI text, and more. Signal is the origin — no upstream business system holds this data.

Owner: Signal users / Signal (AI/ML) team. Relationship to Signal: Signal-origin data.

Views in Signal fed by Signal's own records:

View

What it provides

Notes

Summary

Team-assignment overrides (team_assignments) that win over Nova-detected seats where a client's team is managed in Signal (affects Assigned Leads, Total Seats, per-employee attribution); role flags (app_users) and growth-access grants that decide the Member vs full experience; department_mapping for department display identity

Render-disk assignment backups serve as a Supabase-outage fallback for the override pipeline

Employees

Signal's own records (entered by users in Signal): saved team assignments from Manage Leads (team_assignments — win over Nova-detected seats for managed clients), allocation % overrides (revenue_split_overrides — applied in every RPE/share calculation), on-leave flags (employee_flags — flagged people excluded from aggregates in every period), VABO client tags (client_flags — drive the drawer's VABO Allocations table), synced KPI-target role copies (signal_roles behind /api/employee-kpi-targets), and app_users role flags that decide the Member 'Your Record' vs full-roster experience

team_assignments read via the service-role proxy GET /api/team-assignments (Render in-memory backups as Supabase-outage fallback); employee_flags / client_flags / revenue_split_overrides read directly from the browser (src/services/supabase.ts); point-in-time team lookups for dates ≥ 2026-07-01 read Nova history instead

Staffing

Signal's own records (entered by users in Signal): manual lead assignments from Manage Leads (team_assignments — win over Nova-detected teams), managed-clients registry, saved revenue-split overrides from the Employees view (revenue_split_overrides — base layer under simulated % edits), on-leave employee flags (employee_flags — Consulting weighting fallback), cached AD/Sr.AD tables (client_account_directors / client_senior_account_directors), and app_users role flags that gate access

Overrides loaded for the first selected month only

Manage Leads

Signal's own records (entered by users in Signal): saved team assignments that override the auto-detected team (team_assignments via GET /api/team-assignments service-role read, direct database read as fallback), managed-client adopt/start-blank registry (managed_clients), custom percentage splits (revenue_split_overrides), Consulting role snapshots (consulting_roles, Nova role fallback), save-history Log modal (change_history, last 200 entries), per-division assignments for ~10 multi-division clients (multi_division_assignments), cached AD/Sr. AD tables (client_account_directors / client_senior_account_directors) merged with live Nova, and the app_users is_team_lead flag gating the view

Once a client has saved assignments or a managed decision, the saved team always wins over Nova auto-detection; live reads per month switch with optimistic updates on save

Employee Pulse (Pulse Coverage)

Access gating only: app_users is_admin/is_gandalf flags checked by AdminRoute (frontend) and requireAdmin (backend, 5-min cache); hardcoded Sauron super-admin email list bypasses DB state

No on-screen data comes from Signal's own records.

KPI Targets

Working copy of roles and targets (signal_roles), employee-title match log (signal_match_log), sync history / 'Last synced' (signal_sync_log), and admin/Gandalf role flags (app_users)

Supabase = Signal's own records. In-memory server caches (match counts, title→targets map, coverage) back these when RLS blocks reads or before first sync; caches reset on restart and are rebuilt by a 30s startup auto-sync.

KPI Targets

Curated title→role mapping JD_TITLE_MAP (server/jd-title-map.ts, 165 entries from a 2026-04-17 'Signal Title / JD' Excel export) that deterministically decides which Nova titles match which roles or are excluded

Hardcoded code constant; strict mode — no fuzzy fallback for employees, no auto-create (banner's auto-created count is always 0). Fixing unresolved titles requires a code change or a sheet change.

Submit Pulse Data

Weekly and QBP form contents, prefill, Submitted/Missing pills, and the submission-history timeline (pulse_weekly_submissions / pulse_qbp_submissions, voided rows shown struck-through); AD/Senior-AD submit rights (client_account_directors / client_senior_account_directors, maintained via Manage Leads); and app_users tier flags (is_team_lead/is_admin/is_gandalf) gating the view

Pulse rows read directly from the browser via authenticated Supabase REST (src/services/supabase.ts); all writes go through the backend — RLS (migration 20260509b_pulse_rls_tightening.sql) blocks direct browser writes

My Submissions (Client Pulse)

Weekly and QBP submission rows (pulse_weekly_submissions / pulse_qbp_submissions): client, week/quarter, sentiment, pacing, QBP date, status (active/voided), submitted_by, updated_at — Signal's own records entered via /pulse/submit; served by GET /api/pulse/my-submissions filtered to the caller

Includes voided rows; tab counters count voided; division column stored but not displayed

Pulse Summary

Weekly pulse answers (sentiment, pacing to goal, goals established, business performance YoY) from pulse_weekly_submissions (active rows only); week dropdown options; department_mapping canonical department identity; app_users role flags that gate access

Entered by Account Directors on Submit Pulse; live, upserted per (client, week); voided submissions excluded

Portfolio Review

Pulse weekly + QBP columns (pulse_weekly_submissions/pulse_qbp_submissions, never cached), AD overrides (client_account_directors), Status chip inputs (recommended_action_feedback, app_users), client notes, action notes, cached Brand Intel (macro_brand_insights), asana_links, role flags (app_users)

Supabase = Signal's own records entered by users in Signal

POC Management

POC cards (owners, context, title overrides, LinkedIn, reports-to, last 1:1, prospect cards) in poc_cards; AD overrides (client_account_directors), Senior AD rows (client_senior_account_directors), latest-month team rosters (team_assignments) for view-only scope, and app_users role/admin flags

Cards read directly from the browser via open-read RLS (listPocCards); all writes via gated backend

Churn Risk

Status chips via recommended_action_feedback, manual follow-ups, action notes, client notes, AI summaries/journey narratives (client_summaries), brand intel (macro_brand_insights), recommended-actions snapshots, asana_links, app_users name resolution; also Client Pulse submissions entered in Signal feed the model's sentiment inputs upstream ('gsheets' label is legacy naming for Pulse)

Signal's own records = entered by users in Signal (Supabase transport).

Service Expansion

Dismissals/auto-resurface state (se_client_feedback), change audit (change_history), action feedback + notes, manual follow-ups, client notes, ★ favorites (user_account_favorites), persisted AI text

Supabase = Signal's own records entered by users; never holds scores

Client Profitability

team_assignments lead corrections from Manage Leads that shape the delivery-employee (Pulse Coverage) denominator, and app_users admin flags gating the route; Cost/Profit/Margin are derived in code at HOURLY_RATE = 175 $/hour (server/index.ts:3345 — a hardcoded constant, not payroll data)

Since Jul 2026 live Nova AD/Sr-AD assignments override Signal's cached lead rows for non-consulting single-division clients, so the denominator can shift without a Signal edit; the $175 rate, 40% margin tier, 250-hour clamp, and coverage cutoffs are all code constants

Divisional P&Ls

division_forecasts table (ten-metric forecast grid feeding Team Forecast P&L rows) and app_users.is_gandalf access flag

Supabase; GET /api/division-forecast open read, PUT requires auth only (not Gandalf)

Revenue Overlap

Signal's own records: team_assignments + managed_clients overrides from Manage Leads (saved teams always win over nova-detected teams), app_users access flags (is_team_lead/is_admin/is_gandalf gating the view), department mapping table, Render-disk assignment backups as Supabase-outage fallback

Merged client-side in DataContext via buildManageLeadsDisplayAccounts; revenue_split_overrides exist in Signal's records but are IGNORED by this view

Capacity Planning (Role + Company)

Signal's own records (entered by users in Signal): team_assignments lead overrides, revenue_split_overrides, synced signal_roles targets, and the app_users is_admin flag that gates access

team_assignments read uncached via GET /api/team-assignments; splits applied in calculateAllocationsWithOverrides; admin flag read by AuthContext from app_users and cached in sessionStorage.

Growth (People Dash + Performance Radar)

Manual judgments (performance rating, backfill, retention risk, cannot-lose, on-leave, career path, radar status, WW1–WW3, notes), access grants (growth_access_grants) and per-tab capabilities (growth_access_capabilities), manual Culture Index URL / Vital 5 goal fallbacks

Signal's own records entered by users in Signal; all reads/writes go through division-scope-checked backend proxies; every save stamps editor email + timestamp.

Growth Access management

Access records shown in the grants table: division/department grant rows (growth_access_grants, active = revoked_at IS NULL) and per-user capability flags for People / Performance Radar / Manage access (growth_access_capabilities), plus the registered-user dropdown (app_users). All entered by users in Signal.

Read via GET /api/growth/access (service-role, RLS-no-policies tables) except app_users which the view reads directly via the Supabase client. Soft-revoked rows retained as audit trail.

AI Solutions

Solution registry (ai_solutions), snapshots (ai_solution_snapshots: baseline/periodic/manual with metrics/health/capture_meta JSON), cron run log (ai_solution_health_runs), team assignments used to resolve employee targets, revenue split overrides, and the is_gandalf access flag

Signal's own records (entered by users in Signal); frontend reads/writes ai_solutions tables directly via the Supabase client; server uses service-role REST

Corrections

Entire view: corrections queue rows, status tab counts, expanded detail (current/expected value, reason, reviewer fields, timestamps) — all from the data_corrections table entered by users in Signal, read directly from the browser via src/services/supabase.ts (no Express backend involved)

Disputed figures originate in Vena and are shown on other Signal views, but Corrections never queries Vena — it only stores what the human reported. Counts computed client-side by fetching all rows; 10s query timeout and PAGE_SIZE=50 are code constants.

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

app_users (user tiers, toggles), change_history (audit log), signal_usage_events (Analytics events), recommended_action_feedback (actions-on-flagged-clients), kpi_targets configuration feeding the Hiring Need verdict

Users/Assignment History/Analytics read Supabase directly from the browser; role toggles write app_users; app_users rows provisioned server-side on sign-in with AD auto-team-lead

Platform Foundation (app shell)

Signal's own records (entered by users in Signal): app_users tier flags, growth_access_grants + growth_access_capabilities, team_assignments/managed_clients overrides, revenue_split_overrides, client/employee flags, user_account_favorites, kpi_targets, signal_usage_events, llm_generation_logs

Supabase is the store; described as Signal's own records per doc rules. Role checks cached 2–5 min server-side + sessionStorage in browser

Views in Signal that write into Signal's own records:

View

What it writes

Summary

Flag-a-data-issue from any drill-down row inserts a data_corrections record (status pending) directly from the browser (submitDataCorrection, src/services/supabase.ts:1539); reviewed by admins in Corrections, trackable in My Submissions

Employees

Admin-only Mark On Leave / Remove Leave toggle in the employee drawer writes flag 'on_leave' to employee_flags (direct browser write, optimistic with rollback); side effect: person excluded from ALL aggregate metrics across Signal, all months (global flag, not month-scoped)

Manage Leads

team_assignments (team membership per client × department × month, add-before-delete with read-back verify + retries); revenue_split_overrides (custom %); consulting_roles snapshot (refreshed server-side after Consulting saves); managed_clients (adopt/start-blank decision, adoption seeds team_assignments); change_history (audit row per team save); multi_division_assignments (per-division assignments for ~10 multi-division clients — server path exists, not yet wired to the view's chips)

Manage Leads

Fire-and-forget assignment backup on every team save (POST /api/assignments/backup → server-side store on Render disk)

KPI Targets

Re-sync Matches (POST /api/kpi-targets/sync → runKpiMatchSync): updates/creates/renames/deletes signal_roles rows from the Google Sheet (incl. division-suffix propagation at fuzzy score ≥ 0.7 and synonym canonicalization); clears and re-inserts the full signal_match_log; appends a success row to signal_sync_log; deletes roles with zero matched employees; rebuilds in-memory caches (match counts, title→targets map served at /api/employee-kpi-targets, coverage breakdown). Same pipeline auto-runs ~30s after every backend restart. No direct target editing on the screen — targets are only editable in the Google Sheet, mappings only in jd-title-map.ts.

Submit Pulse Data

Weekly pulse upsert via POST /api/pulse/weekly into pulse_weekly_submissions (one row per client × week × division; server re-checks ownership via assertPulseOwnership() and stamps submitted_by from the caller's token; re-saving preserves GD Review Notes; re-submitting a voided week revives the row)

Submit Pulse Data

QBP upsert via POST /api/pulse/qbp into pulse_qbp_submissions (one row per client × quarter × division; QBP Date required and allowed up to 1 year ahead, narrative + presentation links required)

Submit Pulse Data

Soft-void via POST /api/pulse/weekly/void and /api/pulse/qbp/void — status flips to 'voided' with deleted_by + trigger-set deleted_at; never hard-deleted; allowed only for the original submitter or a Gandalf admin

Portfolio Review

GD Review Notes + WoW Changes saved to pulse_weekly_submissions via POST /api/pulse/weekly (ownership-gated: assigned AD/GD or Pulse admin only)

Portfolio Review

Client notes (client_notes, churn + SE modules), recommended-action triage (recommended_action_feedback) and action notes (action_notes) via embedded Churn Risk tab — flips the Status chip

Portfolio Review

Brand Intel results persisted to macro_brand_insights (one per client per month); Asana task links to asana_links

Portfolio Review

Side effect on page load: Nova→Signal AD sync writes client_account_directors + team_assignments (freshness-guarded, 1-hour TTL)

POC Management

Table poc_cards: create a note card on an existing sentiment-tracked contact (is_prospect=false; owner, title override, LinkedIn, reports-to, last 1:1 date/person, context), create a prospect card (is_prospect=true; name required), edit any of those fields, and soft-archive prospects (archived_at/archived_by; never hard-deleted). Every write attributed to the verified caller email and gated server-side (assertPocEditAllowed) to the client's GD / AD (incl. Senior AD and Nova exec-sponsor fallback) / admin via POST /api/poc-management/cards and PATCH /api/poc-management/cards/:id. No side effects on any other module — as of Jul 2026 nothing else reads poc_cards.

Churn Risk

Recommended-action Done/Dismiss/Undo events (recommended_action_feedback, drives status chips)

Churn Risk

Manual follow-ups create/edit/archive (manual_recommended_actions)

Churn Risk

Per-action threaded notes (action_notes)

Churn Risk

Client-level notes (client_notes)

Churn Risk

AI Summary regenerate and Iris Draft generate — overwrite/insert weekly rows in client_summaries (types churn / churn_journey)

Churn Risk

Brand Intel generation results inserted into macro_brand_insights (per-user daily cap, default 50)

Churn Risk

Asana task↔client link rows (asana_links), archivable from the card

Service Expansion

se_client_feedback (dismiss/reactivate with reason, score-at-dismissal, resurface threshold; auto-resurface at ≥0.15 score move) + change_history audit rows

Service Expansion

client_summaries upserts for type=se (AI Summary regenerate/lazy-generate) and type=se_roadmap (roadmap generate/regenerate), attributed to user email

Service Expansion

macro_brand_insights (Brand Intel generation, one row per client per month, attributed; counts against per-user daily cap)

Service Expansion

recommended_action_feedback (done/dismiss auto actions), action_notes (per-action notes), manual_recommended_actions (user follow-ups), client_notes (module-scoped se), user_account_favorites (★), signal_usage_events (telemetry)

Divisional P&Ls

Division forecast cells: PUT /api/division-forecast upserts (division, month, metric, value) into division_forecasts with editor email + timestamp; fixed 10-metric vocabulary enforced server-side; only forecast months (after the 2-months-back actuals cutoff) are editable in the UI; saved values immediately feed the P&L tab's Team Forecast rows, Gross Profit, Operating Expenses, and Contribution Margin

Growth (People Dash + Performance Radar)

employee_performance upserts: performance rating, backfill, retention risk + context, cannot lose, on leave, career path score, culture index URL, vital 5 goal, radar_status (PM auto-seeded from Active PM rating; RT/TERM manual), WW1–WW3 dates, notes — editor email + timestamp recorded

Growth (People Dash + Performance Radar)

growth_access_grants (division/department scope rows, revoke-before-insert) and growth_access_capabilities (can_view_people / can_view_radar / can_manage_access) via PUT /api/growth/access from the /growth/access management screen

Growth Access management

growth_access_grants: full reconcile of a user's division/department scope per save — rows no longer in scope are soft-revoked (revoked_at stamped, never hard-deleted; revoke applied before insert so failures err toward less access), new rows inserted with granted_by = saving manager's email. 'All divisions' = one division=NULL row; each specific department = its own row.

Growth Access management

growth_access_capabilities: upsert of the user's single row (can_view_people, can_view_radar, can_manage_access, updated_by/updated_at); row deleted when a save leaves no scope and no capabilities (full revoke). Side effect: backend permission cache invalidated for that email (2-min TTL elsewhere); target user's tab visibility updates on their next sign-in/reload.

AI Solutions

Create solution → insert into ai_solutions + immediate baseline row into ai_solution_snapshots (browser-computed metrics + frozen health)

AI Solutions

Delete solution (confirm dialog) → removes ai_solutions row and all its snapshots

AI Solutions

Change status (Active/Paused/Completed/Archived) → updates ai_solutions.status; only active solutions get weekly health snapshots, archived also drop from the list Health column

AI Solutions

Edit notes → updates ai_solutions.notes

AI Solutions

Take Snapshot → inserts a 'manual' row into ai_solution_snapshots (latest-month metrics + current health)

AI Solutions

Regenerate Baselines → patches metrics/snapshot_month/capture_meta on baseline snapshots that captured empty data

AI Solutions

System writes: weekly cron (Mondays 09:30 + startup catch-up) inserts 'periodic' health-only snapshots and logs to ai_solution_health_runs; startup backfill patches reconstructed health onto baselines missing it; ops-only manual trigger POST /api/ai-solutions/snapshot-health (X-Snapshot-Secret)

Corrections

Insert new flag into data_corrections (status pending, submitter's Google email) via FlagIssueModal — reachable from this page and from Summary drill-down flag icons

Corrections

Update flag status (pending→reviewed/resolved, reviewed→resolved) with reviewed_by email, optional reviewer_notes, and resolved_at timestamp — admin-only in UI

Corrections

Delete a flag — admin-only in UI, restricted to pending rows by database policy (20260506 migration); reviewed/resolved rows immutable for audit trail

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

Users screen toggles write is_team_lead / is_admin / is_gandalf on the person's app_users row (Sauron rows locked; takes effect on the target's next session/refresh)

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

JD 'Resync JD Sheet' refreshes an in-memory server map from the JD Google Sheet (no database write; lost on restart/deploy). Assignment History and Analytics are read-only.

Platform Foundation (app shell)

First-sign-in auto-provisioning creates the user's app_users row (Team Lead auto-set for Nova Executive Sponsors/Group Directors); never downgrades

Platform Foundation (app shell)

My Accounts favorites (user_account_favorites) per user

Platform Foundation (app shell)

llm_generation_logs: every Iris and AI-search generation (user email, full prompt, response, tokens, latency)

Anthropic Claude (claude)

AI-generated text produced on demand via the Anthropic API: client AI summaries, growth roadmaps, Brand Intel briefings (with web search), capacity insights, Iris chat, and AI search answers. Generated content is usually persisted back into Signal's own records.

Owner: AI/ML team (via Anthropic API). Relationship to Signal: Derived/generated data.

Views in Signal fed by Anthropic Claude:

View

What it provides

Notes

Portfolio Review

Brand Intel tab — on-demand macro/brand analysis via claude-sonnet-4-5 with web search

~$0.50/fresh generation; cached per client per month; 50/user/day cap (env-tunable)

Churn Risk

AI Summary text (claude-sonnet-4-6), Brand Intel briefing (claude-sonnet-4-5 + web search, prompt brand_intel_v2, ~$0.46–0.50/run, default 50/user/day cap), and Iris Draft journey narrative with [D:field=value] grounding validation

All generated server-side and persisted to Signal's own records.

Service Expansion

AI Summary (claude-sonnet-4-6, weekly Mon 09:15 cron + lazy + manual), Strategic Growth Roadmap (claude-opus-4-6, on demand, browser-built prompt), Brand Intel (claude-sonnet-4-5 + live web search, monthly per client)

All persisted in Signal's own records (client_summaries, macro_brand_insights); Brand Intel per-user daily cap default 50

Capacity Planning (Role + Company)

Capacity Insights — on-demand 3-5 prioritized AI-deployment recommendations plus follow-up chat over a text snapshot of the on-screen capacity numbers

POST /api/claude proxy, default model claude-sonnet-4-6, last 20 messages kept; no logContext passed, so NOT recorded in llm_generation_logs.

Platform Foundation (app shell)

Global Search 'Ask AI' answers (Haiku claude-haiku-4-5-20251001) and the Iris chat drawer (Opus claude-opus-4-7, Sonnet claude-sonnet-4-6 overload fallback), proxied through POST /api/claude

Iris sees only a client-side snapshot of on-screen data; every generation logged to llm_generation_logs with user email, prompt, response, tokens, latency

Views in Signal that write into Anthropic Claude:

View

What it writes

Capacity Planning (Role + Company)

Sends the on-screen capacity snapshot (headlines, division/role breakdown, logged AI notes) to the Anthropic API via POST /api/claude; response displayed only, never persisted anywhere

Asana (asana)

The task-management tool: Signal reads live task status and creates real Asana tasks from recommended actions; task-to-client links are stored in Signal's own records (asana_links) and sync back via webhook.

Owner: Ops / delivery teams. Relationship to Signal: External system, read + written by Signal.

Views in Signal fed by Asana:

View

What it provides

Notes

Employees

External intake form for title/dept/division change requests (hardcoded URL, link-out only — no data read)

SYSTEM_CHANGE_BOARD_URL in EmployeeView.tsx

Portfolio Review

Asana Tasks card in expanded row (live task list via Asana API)

Task↔client links stored in Signal's asana_links

Churn Risk

Live task status in the Asana Tasks card; task creation from recommended actions; webhook sync back into asana_links

Shows a not-configured state if server lacks the Asana token.

Service Expansion

Live Asana tasks card, task creation, webhook + 60s pull sync

Links stored in asana_links

Views in Signal that write into Asana:

View

What it writes

Portfolio Review

Real task creation via POST /api/asana/tasks from the '+ New task' modal

Churn Risk

Real Asana task creation via server bot token, pre-filled from the recommended action

Service Expansion

Creates real tasks in Asana via API; Signal↔Asana link rows stored in asana_links; status syncs back via webhook + 60s pull

Signal usage logs (usage)

Signal's own telemetry table (signal_usage_events): automatic page views, feature actions, and freeze/long-task performance events. Written invisibly by almost every view and read only by the Sauron-only Analytics view.

Owner: Signal (AI/ML) team. Relationship to Signal: Signal-origin data.

Views in Signal fed by Signal usage logs:

View

What it provides

Notes

Churn Risk

Invisible telemetry: page_view (module 'churn') and client_detail_view events into signal_usage_events; app-wide freeze/longtask perf events added Jul 7

Feeds adoption reporting.

Service Expansion

signal_usage_events telemetry: page opens, client_detail_view, roadmap generated/copied, continue-on-Iris clicks

Powers adoption reporting

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

App-wide page_view events (usePageViewTracking on every route change) plus feature actions for churn/se/portfolio modules

Fetch capped at 5,000 events / 10,000 taken-actions per range (code constants)

Platform Foundation (app shell)

page_view events per route change plus freeze/longtask telemetry into signal_usage_events; feeds the Sauron-only Analytics view

Churn/SE/Portfolio views self-instrument and are skipped by the route hook to avoid double counting

Views in Signal that write into Signal usage logs:

View

What it writes

Summary

Automatic page_view usage event with module 'summary' into signal_usage_events (src/hooks/useUsageTracking.ts), feeding the Sauron-only Analytics view

ETCR (Beta)

Automatic page_view usage event with module name 'etcr' logged to Signal's own usage records on each visit (app-wide useUsageTracking hook → logUsageEvent); not user-initiated. No other writes — the view is strictly read-only, no edits/overrides/annotations.

Pulse Summary

Passive page_view usage-telemetry event (user email, module 'pulse-summary') logged to Signal's signal_usage_events table on each visit via usePageViewTracking; otherwise the view is strictly read-only (CSV export is a download, filters are local UI state)

Portfolio Review

signal_usage_events: page views, client row expands, briefing-strip clicks (direct browser writes; feeds Analytics/Sauron)

Churn Risk

Invisible usage events (signal_usage_events, module 'churn': page_view, client_detail_view)

Client Profitability

Passive page_view telemetry only (module 'profitability') into signal_usage_events via usePageViewTracking; otherwise strictly read-only — the Excel export (signal-profitability.xlsx) is a client-side download and filters are local UI state

Revenue Overlap

Passive page_view telemetry only: logUsageEvent() via usePageViewTracking route hook writes a signal_usage_events row (module 'revenue-overlap') on route entry, feeding the Analytics/adoption view. No user-facing writes — the view is read-only; filter dropdowns don't persist and incoming URL params are consumed then cleared.

Platform Foundation (app shell)

signal_usage_events: page_view per route change + longtask freeze telemetry (fire-and-forget)

Browser-local storage (browser-local)

Per-browser, per-user state that never leaves the machine: localStorage/sessionStorage preferences (columns, theme, pinned views), staffing what-if scenarios, drawer states, and client-side CSV/Excel exports.

Owner: Individual user. Relationship to Signal: Signal-origin data.

Views in Signal fed by Browser-local storage:

View

What it provides

Notes

Summary

Pinned-views list defaulting to /summary (localStorage key signal-pinned-views) and session-shared division/department filter state

View state only, no business data

Employees

Column show/hide preferences (useTableConfig 'employee_list' / 'kpi_rollup'), pinned nav views, CSV exports (employees.csv, kpi_rollup.csv)

localStorage only; no shared state

Staffing

The simulation scenario itself (reassignments + allocation-% overrides), persisted to localStorage key signal-staffing-scenario:, auto-saved 200 ms after each change; CSV export generated client-side

Per user, per browser; never leaves the machine; the only 'write' surface of the module

Manage Leads

Draft percentage-override edits and optimistic UI state before Save

Nothing persists locally beyond the session

ETCR (Beta)

'Show forecast inputs' drawer open/closed state (sessionStorage key etcr.showDetails)

Per browser tab session; never sent to any server.

Employee Pulse (Pulse Coverage)

Excel exports (Coverage: per-employee per-week status/hours/client breakdown; Sentiment: per-employee 4w + last-week values) built entirely from data already loaded in the page

Reflects active filters at export time.

Portfolio Review

Column visibility/category presets (localStorage key portfolio-review-columns-v9), pinned nav views

Per-browser only, not persisted to any database

Churn Risk

Table column show/hide configuration (useTableConfig key 'churn-overview' in localStorage)

Per browser, not synced.

Service Expansion

Column visibility persistence, pinned views, CSV export of filtered table

localStorage / client-side only

Capacity Planning (Role + Company)

AI Influence Log entries (localStorage key signal_ai_capacity_notes_v1) and table column show/hide preferences (useTableConfig)

Per-browser, per-machine; not shared or backed up.

Platform Foundation (app shell)

Theme (signal-theme), pinned views (signal-pinned-views), sidebar collapsed state, and the in-memory Growth unlock key (never persisted; relocks after 5 min backgrounded)

Per-device only; Growth key also cleared on reload/sign-out

Views in Signal that write into Browser-local storage:

View

What it writes

Staffing

Simulation scenario (client reassignments and allocation-% overrides) saved to the user's own localStorage (key signal-staffing-scenario:, debounced 200 ms); Reset all / per-card revert delete entries; Download CSV writes a local file signal-staffing-YYYY-MM-DD.csv. No writes to Vena, Nova, or Signal's shared records — the view is read-only toward every shared system

ETCR (Beta)

sessionStorage key etcr.showDetails ('1'/'0') persisting the forecast-inputs drawer state for the tab session.

Portfolio Review

Column config persisted under localStorage key portfolio-review-columns-v9

Churn Risk

Column visibility preferences in localStorage

Capacity Planning (Role + Company)

Create/delete AI Influence Log entries (division, solution name, notes, plus a frozen snapshot of that division's At/Above %, headcount, and revenue gap) in localStorage key signal_ai_capacity_notes_v1

Capacity Planning (Role + Company)

Table column visibility preferences on Role Capacity tables via useTableConfig (cosmetic)

Platform Foundation (app shell)

Theme, pinned views, sidebar collapsed (localStorage); Growth unlock key in memory only

Views

One row per Signal view (Power Digital's internal BI web app, staging @ 98df0c4, 2026-07-07). "Source systems" lists core-origin systems only — warehouse/replica/cache layers are transport and intentionally omitted.

View

Route

Access

Source systems

What can be edited

Summary

/summary

All signed-in users (base-tier Members get a reduced read-only experience: tiles + trend chart only)

Vena; NetSuite; Nova platform; Signal's own records; Browser-local storage

Flag a data issue on any drill-down row (creates a pending correction in Signal's records)

Employees

/employees

All signed-in users (Members see only their own row; full roster for Team Lead+; extra affordances for Admin+)

Nova platform; Vena; Signal's own records; Payroll (ADP / Deel / Siesa); KPI Targets Google Sheet; JD Map Google Sheet; Asana; Browser-local storage

Admin-only Mark On Leave flag; everything else read-only (allocation edits happen in Manage Leads)

Staffing

/staffing

Team Lead and above (any non-Member; base-tier Members redirected to /summary)

Vena; Nova platform; Signal's own records; Browser-local storage

Nothing shared — what-if scenarios live only in the user's own browser

Manage Leads

/leads

Team Lead and above (TeamLeadRoute; Admin/Gandalf/Sauron implicitly qualify)

Vena; Nova platform; Signal's own records; Browser-local storage

Team membership, revenue-split %, Consulting roles, managed-clients adoption (Signal records); AD/Sr. AD chips write directly to Nova

ETCR (Beta)

/etcr

Team Lead and above (any non-Member; base-tier Members redirected to /summary)

ML models (data-science pipeline); Browser-local storage

Nothing — read-only

Employee Pulse (Pulse Coverage)

/pulse-coverage

Admin and above (AdminRoute: Admin, Gandalf, Sauron)

Nova platform; Payroll (ADP / Deel / Siesa); Signal's own records; Browser-local storage

Nothing — read-only (client-side Excel exports only)

KPI Targets

/kpi-targets

Admin or Gandalf (AdminRoute; nav link adminOnly)

KPI Targets Google Sheet; Nova platform; Signal's own records

Re-sync Matches (rebuilds roles/match log from the Google Sheet); target numbers are editable only in the sheet

Submit Pulse Data

/pulse/submit

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

Signal's own records; Nova platform; Vena; ML models (data-science pipeline)

Weekly pulse rows and QBP rows (upserts through the Signal backend, ownership-checked server-side); soft-void of either kind — original submitter or Gandalf admin only, never hard-deleted

My Submissions (Client Pulse)

/pulse/my-submissions

Team Lead and above (TeamLeadRoute)

Signal's own records; Nova platform; Vena

Nothing — read-only (rows deep-link to Submit Pulse Data)

Pulse Summary

/predictions/pulse-summary

Admin and above (AdminRoute; server endpoint enforces sign-in only)

Signal's own records; ML models (data-science pipeline); Nova platform; Vena

Nothing — read-only (CSV export only)

Portfolio Review

/predictions/portfolio

Any non-Member (MemberAllowedRoute; Team Lead is the typical minimum working tier)

Vena; Nova platform; ML models (data-science pipeline); Gong call analysis; Salesforce; Signal's own records; Asana; Anthropic Claude; Browser-local storage

GD Review Notes + WoW Changes, client/action notes, action triage, Brand Intel generation, Asana task creation/links

POC Management

/predictions/poc-management

Any non-Member; visibility scoped to the caller's book; edits limited to the client's GD/AD (incl. Sr. AD) or admins

Gong call analysis; Nova platform; Vena; Signal's own records

POC cards: relationship owner, title, LinkedIn, reporting line, last 1:1, context; prospect cards (soft-archivable)

Churn Risk

/predictions/churn

Team Lead and above (any non-Member; base-tier Members redirected to /summary)

ML models (data-science pipeline); Vena; Nova platform; Salesforce; Gong call analysis; Signal's own records; Anthropic Claude; Asana; Signal usage logs; Browser-local storage

Action triage (done/dismiss), manual follow-ups, action/client notes, AI Summary + Iris Draft regeneration, Brand Intel, Asana tasks

Division Forecast

/predictions/retention

Team Lead and above (any non-Member; frontend guard only — the API endpoint has no server-side auth middleware)

CSM Renewal Tracker Google Sheet; Nova platform

Nothing — read-only (mirror of the CSM renewal tracker sheet)

Service Expansion

/predictions/se

Team Lead and above (any non-Member; base-tier Members redirected to /summary)

ML models (data-science pipeline); Salesforce; Gong call analysis; Vena; Nova platform; Anthropic Claude; Signal's own records; Asana; Signal usage logs; Browser-local storage

Dismiss/reactivate clients, action triage, follow-ups, notes, favorites, AI Summary/Roadmap/Brand Intel generation, Asana tasks

Client Profitability

/profitability

Admin and above (AdminRoute: Admin or Gandalf; nav link adminOnly)

Nova platform; Vena; Signal's own records

Nothing — read-only (client-side Excel export only; passive page-view telemetry)

Divisional P&Ls

/divisional-pl

Gandalf tier (GandalfRoute); payroll data additionally gated by a server-side email allowlist (PAYROLL_AUTHORIZED_EMAILS)

Vena; Payroll (ADP / Deel / Siesa); NetSuite; Nova platform; Signal's own records

Division forecast cells (10-metric grid, forecast months only)

Revenue Overlap

/revenue-overlap

Team Lead and above (TeamLeadRoute)

Vena; Nova platform; Signal's own records

Nothing — read-only

Capacity Planning (Role + Company)

/capacity, /company-capacity

Admin and above (AdminRoute: Admin or Gandalf)

Vena; Nova platform; Payroll (ADP / Deel / Siesa); KPI Targets Google Sheet; Signal's own records; Anthropic Claude; Browser-local storage

AI Influence Log entries and column preferences (browser-local only); Capacity Insights sends a snapshot to Claude (response never persisted)

Growth (People Dash + Performance Radar)

/growth/people, /growth/performance-radar

Per-tab Growth capabilities (can_view_people / can_view_radar) + division/department scope grant + shared unlock password per tab

Payroll (ADP / Deel / Siesa); Nova platform; NetSuite; Vena; Signal's own records

Performance judgments (rating, retention risk, backfill, cannot-lose, radar status, WW dates, notes); backfill push to the Hiring sheet; access grants via /growth/access

Growth Access management

/growth/access

canManageGrowthAccess capability (delegated managers or Sauron) + unlock password on every management request

Signal's own records; Nova platform

Growth access grants (division/department scope) and per-user capabilities (People / Radar / Manage)

AI Solutions

/solutions, /solutions/:id

Gandalf only (GandalfRoute; nav link gandalfOnly)

Vena; ML models (data-science pipeline); Gong call analysis; Signal's own records; Nova platform

Solution registry: create/delete solutions, status, notes, manual snapshots, baseline regeneration

Corrections

/corrections

Admin and above (AdminRoute; Gandalf-but-not-Admin sees a read-only queue)

Signal's own records

Flag triage (pending → reviewed → resolved), reviewer notes, delete pending flags; new flags via the same modal

Admin Tools (General Access, Assignment History, Hiring Pipeline, JD Sheet Sync, Analytics)

/users, /audit-log, /hiring, /jd-sync, /analytics

Gandalf for /users, /audit-log, /hiring, /jd-sync; Sauron only for /analytics

Signal's own records; Hiring Pipeline Google Sheet; JD Map Google Sheet; Vena; Nova platform; ML models (data-science pipeline); Signal usage logs

User tier toggles (app_users); hiring approvals (written back to the hiring Google Sheet); JD sheet resync (in-memory)

Platform Foundation (app shell)

cross-cutting (all routes; / redirects to /summary)

All tiers — documents the access model itself

Vena; Nova platform; Signal's own records; Anthropic Claude; KPI Targets Google Sheet; Browser-local storage; Signal usage logs

My Accounts favorites; theme/pinned views (browser); auto-provisioning + telemetry are automatic system writes

Did this answer your question?