Skip to main content

Signal — Portfolio Review

Written by Schae Lilley

Portfolio Review is Signal's single-screen client command center at /predictions/portfolio, available to any signed-in user above the base Member tier (in practice Team Leads, Admins, and above). It shows one row per active client — revenue (MGP/MRR/VABO from Vena Finance actuals), churn-risk and expansion-readiness scores (data-science models), weekly Pulse check-ins and QBP status (entered by Account Directors in Signal), and a follow-up accountability chip — pre-prioritized into P1/P2/P3 tiers so Group Directors and Account Directors know exactly which clients to act on this week. Top-level data origins: Vena (finance actuals), Nova (client roster, Group/Account Director assignments), the data-science model pipeline (churn/SE/priority scores), Gong (call sentiment), Signal's own records (Pulse submissions, notes, action triage), Asana (tasks), and Anthropic Claude (on-demand Brand Intel).

Field

Value

Route

/predictions/portfolio and /predictions/portfolio/:clientId (deep link auto-expands that client)

Access

Any signed-in user above the base Member tier (MemberAllowedRoute); base Members are redirected to /summary. Team Lead is the typical minimum working tier.

Nav section

Client Health → "Portfolio Review" (link hidden from base Members)

Primary data sources

Vena finance actuals; Nova client/roster records; data-science model outputs (churn, SE readiness, priority tier); Gong call intelligence; Signal's own records (Pulse, QBP, AD overrides, action triage, notes); Asana; Anthropic Claude (Brand Intel)

Writes

Pulse weekly GD Review Notes + WoW Changes; client notes; recommended-action triage + action notes (embedded Churn tab); Asana task creation; Brand Intel generation (cached); usage-analytics events; side-effect Nova→Signal AD sync

Key code refs

src/views/PortfolioReviewView.tsx, src/views/predictions/PortfolioOverviewTab.tsx, src/views/predictions/insightPanel/ClientInsightPanel.tsx, src/views/predictions/components/WeekBriefStrip.logic.ts, server/index.ts (GET /api/predictions/portfolio, pulse endpoints), server/predictions.ts (fetchPortfolioData), server/activeBook.ts, server/nova-ad-sync.ts, server/brandIntel.ts

Last verified

staging @ 98df0c4, verified 2026-07-07

Purpose

Portfolio Review exists so account leadership can run a whole book-of-business review in one place instead of stitching together the Churn Risk screen, the Service Expansion screen, weekly Pulse submissions, and finance spreadsheets. It answers three questions at once: (1) Where is the money? — Monthly Gross Profit (MGP), MRR, past-due AR, and VABO per client from Vena Finance actuals; (2) Which clients need attention right now? — a pre-computed P1/P2/P3 priority tier, churn-risk score, contract-expiry countdowns, and Pulse sentiment/pacing; (3) Is anyone following up? — a per-client Status chip (Acknowledged / Reviewed / Needs Review / Unassigned) that tracks whether the assigned Account Director or Group Director has triaged the system's recommended actions. Portfolio Review shows only the active book: churned/inactive clients are filtered out server-side (fetchActiveBook in server/activeBook.ts) before data reaches the browser.

Access & visibility

  • Route guard: both Portfolio Review routes (/predictions/portfolio and /predictions/portfolio/:clientId) are wrapped in MemberAllowedRoute in src/App.tsx. This guard redirects base-tier "Members" to /summary; every non-Member falls through.

  • Who is a "Member": in src/context/AuthContext.tsx, a Member is a signed-in user with none of the elevated flags: not Team Lead, not Admin, not Gandalf, not Sauron, and holding no Growth capability (isGrowthAllowed, canViewRadar, canManageGrowthAccess). Consequence: a Growth-only user (someone granted Growth People/Radar access but no Team Lead flag) is NOT a Member and can technically open Portfolio Review. In practice the intended audience is Team Lead and above.

  • Nav flag: the "Portfolio Review" link sits in the Client Health sidebar section with no per-link gate, so every non-Member sees it. Base Members' sidebars are stripped to Summary and Employees only.

  • How the Team Lead flag is granted: roles live in Signal's own app_users table (Signal's records, managed by Signal admins via the General Access screen). Additionally, on every sign-in, POST /api/auth/ensure-provisioned auto-sets is_team_lead = true for any user whose email matches a Nova client's executive_sponsor_id (Account Director) or group_director_id — i.e., ADs and GDs are auto-upgraded; the flag is never auto-downgraded. Admin and Gandalf implicitly include Team Lead.

  • API: GET /api/predictions/portfolio requires an authenticated session (requireAuth); there is no additional role check on the endpoint itself.

  • Sign-in is Google OAuth restricted to Power Digital and Cardinal email domains (ALLOWED_DOMAINS in AuthContext.tsx, includes cardinaldigitalmarketing.com).

Data sources & lineage

Portfolio Review loads three API responses on mount (/api/predictions/portfolio, /api/predictions/churn, /api/predictions/service-expansion), reads shared revenue/department data from the app-wide DataContext (GET /api/assignments), and reads several of Signal's own record tables directly from the browser. Expanding a client row triggers further on-demand fetches. In the table below, "Signal's own records" means data entered by users in Signal (stored in Signal's application database).

On-screen element

Origin system

Code path

Refresh

Caveat

Client rows: name, ID, tier, division, industry, churn status, contract dates/length/days-to-end, services, past-due AR

Nova client records + the data-science modeled client feature set (FINAL_FEATURES)

GET /api/predictions/portfolio → fetchPortfolioData() in server/predictions.ts

Base response cached server-side 15 min (stale-while-revalidate); upstream model tables refresh on the data team's roughly weekly cadence

Contract span parsed from a JSON contract-details field with all-or-nothing fallback to single-contract date columns

MGP, MRR, Last Mo VABO, VABO-2mo (drives the "VABO only" badge)

Vena (Finance actuals) — FINAL_VENA_CLIENTS columns

Same portfolio call, merged from the churn feed (fetchVenaClients in server/predictions.ts)

15-min server cache

Column ⓘ tooltips in the UI name Vena as the source; MGP can be $0 for newly active contracts before Finance posts entries

Active-book membership (which clients appear at all)

Nova client records (churn_date) + Vena current-month MGP + VABO + modeled churn status

fetchActiveBook() in server/activeBook.ts, applied inside fetchPortfolioData()

15-min cache (metadata:active-book-v1)

Pulse-eligibility rules (server/pulseEligibility.ts) decide who counts as active; churned/inactive clients never reach the browser

Churn Risk score and risk-derived fields

Data-science churn model (CHURN_SCORES output)

Same portfolio call (merges fetchChurnData()) plus separate GET /api/predictions/churn

15-min cache

Model output; refresh cadence owned by the data team

SE Readiness, SE Status (Open/Failed SE), SE Opps, Top Recs, SE sentiment/performance scores, Gong Sentiment

Data-science service-expansion model (SE_SCORES, SE_SERVICE_RECOMMENDATIONS, FINAL_SERVICE_HISTORY, SE_MODEL_CONFIG threshold); Gong feeds the sentiment inputs

Same portfolio call (merges fetchSeData()) plus separate GET /api/predictions/service-expansion

15-min cache

Portfolio deliberately uses the raw SE_SCORES readiness value (not the historical override the SE view uses); a Gong score of exactly 0.5 renders as "no data"

Group Director column

Nova (CLIENTS.group_director_id joined to USERS, terminated users excluded)

Portfolio call → fetchPortfolioData()

15-min cache

Sole source for GD; Nova data replicated into the warehouse

Account Director column

Signal's own records (client_account_directors — manual Manage Leads picks + synced values), falling back to Nova CLIENTS.executive_sponsor_id → USERS

Portfolio call → fetchPortfolioData(); override map wins when it points to an active user

15-min cache; sync side effect has 1-hour TTL

Loading the page fire-and-forget triggers syncNovaAccountDirectors() + syncConsultingRoles() (Nova CLIENT_ACCOUNT_ASSIGNMENT_HISTORY → Signal), freshness-guarded so it never clobbers newer Manage Leads edits

"Cardinal" badge / "Cardinal only" filter

Nova client records (ACQUISITION_ID = '1005')

Portfolio call → fetchPortfolioData()

15-min cache

Acquisition ID 1005 = Cardinal is a code constant as of Jul 2026

Priority (P1/P2/P3) chip, summary cards, grouping

Data-science pipeline — pre-computed FINAL_FEATURES.PRIORITY_TIER; NOT computed in the app

Portfolio call → fetchPriorityTierMap() in server/vena-kpi-engine.ts; computePriority() in PortfolioOverviewTab.tsx

15-min cache

Clients missing a tier default to P3 in the UI; the business rules behind the tier live upstream with the data team

Pulse columns (Sentiment, Pacing to Goal, WoW Changes, GD Review Notes, goals, rollover/early-out/term details, tracker URL, last check-in, submitted flag) + Week dropdown

Signal's own records — pulse_weekly_submissions (status=active, for the selected Monday week), entered by ADs in the Pulse Submission module

Portfolio call, pulse enrichment stage (fetchPulseEnrichmentData in server/index.ts)

Never cached — every request reads fresh Pulse data

Default week = most recent submitted week, else current Monday; week_start query param must be a Monday

QBP columns (submitted, quarter, narrative URL, presentation URL)

Signal's own records — pulse_qbp_submissions (status=active; quarter derived from the selected week's date)

Portfolio call, pulse enrichment stage

Fresh per request

QBP dates may be entered up to 1 year ahead (forward-looking QBP change, Jul 2026)

Status chip (Acknowledged / Reviewed / Needs Review / Unassigned)

Signal's own records — recommended_action_feedback + app_users (name↔email resolution); recommended actions are generated client-side from churn signals (recommendedActions.ts)

Computed in the browser (ackByClientId in PortfolioOverviewTab.tsx); feedback read directly from Signal's records

Fresh per page load

"Needs Review"/"Unassigned" nudges apply only to the priority set: top-25 clients by risk × MGP among those with churn risk ≥ 0.5 (code constants as of Jul 2026)

Departments column, "Dept MGP (filtered)" column, department filter and summary card

Vena (Finance actuals — one row per blueprint × department × service × month)

App-wide DataContext → GET /api/assignments (latest month), indexed in src/utils/multiDivision.ts

DataContext load cadence

The Dept MGP column auto-appears when a Department filter is set

"Your week" briefing strip (Today's calls, SE-ready, acks last 7 days)

Derived in the browser from already-loaded data

WeekBriefStrip.logic.ts: top-3 P1 by risk × retainer; top-2 with readiness ≥ 0.7 and ≥ 90 quiet days (or no SE on record) and no open/failed SE; ack timestamps from recommended_action_feedback

Recomputed per render

Thresholds are code constants as of Jul 2026

Usage analytics (page views, row expands, strip clicks)

Signal's own records — signal_usage_events

Direct write from the browser (logUsageEvent in src/services/supabase.ts)

Immediate

Feeds the internal Analytics (Sauron) view

Expanded row: Pulse History tab

Signal's own records — pulse_weekly_submissions (last 5 weeks by default) + latest pulse_qbp_submissions row

GET /api/pulse/history?client_id=&weeks=

Fresh per open

Expanded row: Asana Tasks card

Asana (live API) + Signal's own records (asana_links task↔client links)

GET /api/asana/clients/:client_id/tasks

Refresh button forces re-fetch

Expanded row: Notes panels (Churn + SE)

Signal's own records — client_notes

Direct reads/writes from the browser (ClientNotesPanel)

Fresh per open

Shared with the Churn and Service Expansion screens

Expanded row: Churn Risk tab (score trend, recommended actions, action notes)

Data-science historical score snapshots (HISTORICALS) + Signal's own records (recommended_action_feedback, action_notes, manual actions)

Embedded ChurnClientDetail component; /api/* score-history endpoints

Fresh per open

Same component as the Churn Risk module — triage done here or there is shared

Expanded row: POC Contacts tab

Gong call intelligence (FINAL_POC_SENTIMENT: satisfaction, engagement, mood, influence per contact)

Delivered inside the churn/SE feeds (fetchPocSentiment in server/predictions.ts)

15-min cache

UI empty state: "No POC available from Gong or Salesforce"

Expanded row: Salesforce history (inside the embedded SE tab)

Salesforce (FINAL_SALESFORCE_HISTORY)

GET /api/predictions/se/salesforce-history/:clientId

Fresh per open

Read-only

Expanded row: Contracts tab (won/lost last 3 months)

Contract/opportunity records prepared upstream in the data team's client feature model (WON_CONTRACTS_3M / LOST_CONTRACTS_3M)

Churn/SE feeds → ContractHistory3M

15-min cache

Exact upstream origin (Salesforce vs Nova contracts) is not defined in the app repo

Expanded row: Brand Intel tab

Anthropic Claude (model claude-sonnet-4-5 with web search), cached in Signal's own records (macro_brand_insights, keyed by client + month)

POST /api/macro-brand-insights/:clientId/generate (server/brandIntel.ts)

One cached result per client per month; re-clicks return the cache

~$0.50 per fresh generation; per-user cap of 50 fresh generations/day (env-tunable BRAND_INTEL_DAILY_CAP_PER_USER, code default 50 as of Jul 2026)

Confirmed Churn MRR, Per-Client ETCR columns

None — placeholders

Render "—" with tooltip "ETCR pipeline pending — owned by Lily"

n/a

Not sortable; will populate when the ETCR pipeline ships

Metrics & definitions

All metrics below appear on the Portfolio Review screen (/predictions/portfolio).

  • MGP (Monthly Gross Profit) — gross profit actually booked for the current month for this client. Origin: Vena Finance actuals. May be $0 for newly active contracts before Finance posts entries. Default table sort is MGP descending.

  • Dept MGP (filtered) — the client's MGP within the currently filtered department only, with "% of book" (department MGP ÷ client's whole-book MGP). Origin: Vena Finance actuals per blueprint × department. Only populated when a Department filter is active.

  • MRR (Monthly Recurring Revenue) — gross monthly revenue from active retainer contracts for the current month; the top-line counterpart to MGP. Origin: Vena Finance actuals.

  • Past Due AR — total unpaid amount due for the client (TOTAL_AMOUNT_UNPAID_DUE in the modeled feature set). Origin: finance records surfaced via the data team's pipeline; the specific upstream finance system is not defined in the app repo.

  • Last Mo VABO / VABO-2mo — VABO revenue (performance-based revenue; the acronym's expansion is not defined in the app repo) for the last month and two months back. Origin: Vena Finance actuals. VABO-2mo > 0 with no active contract and MGP ≤ 0 marks a client "VABO only".

  • Churn Risk — the data-science churn model's enhanced risk score, 0–1. Risk categories used by the filter (code constants as of Jul 2026): Critical ≥ 0.6, High 0.5–0.6, Medium 0.3–0.5, Low < 0.3.

  • Profile Risk — the normalized profile-risk component (risk_norm) of the churn model. Origin: data-science churn model.

  • Readiness Score (SE Readiness) — the service-expansion model's 0–1 readiness score; Portfolio uses the raw current model value. Origin: data-science SE model.

  • SE Sentiment / SE Performance — SE model sub-scores, rendered as text labels: Strong ≥ 0.7, Moderate ≥ 0.4, Low < 0.4 (code constants as of Jul 2026, matching the Service Expansion view's buckets). Origin: data-science SE model (Gong and Pulse inputs).

  • Gong Sentiment — Gong-derived call-sentiment score for the client; a value of exactly 0.5 is a "no data" convention and renders as "—". Origin: Gong via the SE model.

  • Priority (P1/P2/P3) — pre-computed priority tier from the data-science pipeline (FINAL_FEATURES.PRIORITY_TIER). UI labels: "P1 — Act now", "P2 — Plan this week", "P3 — Healthy / Monitor". Clients with no tier default to P3. The trigger rules are owned by the data team and can change without an app deploy.

  • Status chip — computed in the browser from recommended-action triage (Signal's own records): Acknowledged = the assigned AD/GD completed or dismissed every recommended action; Reviewed = someone other than the assigned owner triaged everything; Needs Review = actions remain untriaged (shown only for the priority set: top-25 by risk × MGP among clients with risk ≥ 0.5); Unassigned = no AD/GD on record (priority set only); no chip = client has no generated actions or is outside the nudge set.

  • Pulse Sentiment / Pacing to Goal / WoW Changes / GD Review Notes — fields from the Account Director's weekly Pulse submission for the selected Monday week. Origin: Signal's own records (entered by ADs in Signal).

  • Pulse Submitted / QBP Submitted — Yes/No flags: whether an active weekly Pulse row exists for the selected week / an active QBP row exists for the quarter derived from the selected week.

  • Days to End / Contract Ending filter — days until the latest active contract end date (computed server-side from the contract-details JSON); negative (expired) renders red. Filter buckets: within 30/60/90 days.

  • Active Book MGP (summary card) — sum of MGP across currently filtered clients, with client count. P1/P2/P3 summary cards show per-tier count + MGP and toggle a priority filter on click.

  • "Your week" stripToday's calls: top 3 P1 clients ranked by churn risk × active retainer value; SE-ready: up to 2 clients with readiness ≥ 0.7, ≥ 90 days since last SE (or no SE on record), and no open/failed SE; acknowledged last 7 days: count of scoped clients whose Status is Acknowledged with a triage timestamp in the last 7 days (clicking filters the table). All thresholds are code constants as of Jul 2026.

How it's used

  1. Monday book review (GD/AD). Open Portfolio Review, filter to yourself via the Account Director or Group Director dropdown. Read the "Your week" strip: call the top-3 P1 clients today; open the SE-ready picks for expansion conversations. Click the P1 summary card to isolate act-now clients, expand each row, review Signals and Pulse History, and log follow-ups as Asana tasks or notes. Decision: where do my limited hours go this week?

  2. Accountability sweep (division leadership). Set the Status filter to "Needs Review" to list priority clients whose assigned owner hasn't triaged the recommended actions, and "Unassigned" to catch high-risk clients with no AD/GD on record. Chase owners. Decision: is the retention playbook actually being executed?

  3. Contract renewal radar. Set "Contract Ending: within 60 days" (or group by Contract Expiry), then enable the "Pulse Contract Details" column preset to see rollover triggers, early-out dates, and term types side by side. Decision: which renewals need a plan started now; which contracts have imminent early-out exposure?

  4. Pulse compliance and client mood check. Pick the current week in the Week selector, sort by "Pulse Submitted" to find ADs who haven't submitted, then filter Sentiment/Pacing to spot off-goal or unhappy clients. QBP columns show which quarterly business plans exist, with links to the narrative and deck. Decision: where are we blind, and which client conversations are trending negative?

  5. Department P&L slice. Filter by a Department (e.g., Paid Media): a department MGP summary card and a per-client "<Department> MGP" column appear automatically, showing each client's revenue in that department and its share of their whole book. Decision: how big and how healthy is this department's book?

  6. Export for offline review. Use the toolbar CSV export — visible columns (portfolio-review.csv) or every column (portfolio-review-all.csv) — to take the current filtered slice into a QBR deck or leadership spreadsheet. A "Cardinal" column is always appended for acquisition-cohort analysis.

What users can change here

The Portfolio Review table itself is read-only; all writes happen in the expanded client panel or as side effects.

  • GD Review Notes + WoW Changes editors (expanded panel) — edit the current week's "GD Review Notes" and "week-over-week changes" fields on the client's weekly Pulse submission. Saves go through POST /api/pulse/weekly into Signal's pulse_weekly_submissions (the same store the Pulse Submission module writes). The endpoint enforces ownership: only the client's assigned AD, its GD, or a Pulse admin may save (others get a 403). Past weeks are read-only; if no Pulse exists for the current week the editor links to the Pulse submit form instead.

  • Create Asana task (expanded panel) — "+ New task" opens a modal that creates a real task via the Asana API (POST /api/asana/tasks) and records the task↔client link in Signal's asana_links table.

  • Client notes (expanded panel) — add/edit churn-side and SE-side notes, stored in Signal's client_notes; visible from the Churn and Service Expansion screens too.

  • Recommended-action triage (embedded Churn Risk tab) — marking actions done/dismissed writes Signal's recommended_action_feedback; this is exactly what flips the Status chip to Acknowledged/Reviewed. Action-level notes land in Signal's action_notes.

  • Generate Brand Intel (expanded panel) — triggers a paid Anthropic Claude generation (~$0.50), persisted to Signal's macro_brand_insights (one cached result per client per month; per-user cap of 50 fresh generations/day, env-tunable).

  • Implicit side effects — every page view, row expand, and briefing-strip click writes Signal's signal_usage_events; loading the page also triggers the server-side Nova→Signal Account Director sync (writes Signal's client_account_directors and team_assignments, freshness-guarded so it never overwrites newer manual Manage Leads edits; 1-hour TTL).

  • Local only — column visibility and category-preset choices persist per browser under localStorage key portfolio-review-columns-v9, not to any database.

Caveats & data quality

  • "Why flagged" column is always empty. Priority moved to the warehouse-computed PRIORITY_TIER; the client-side signal chips were retired, so the column renders "—" for every client. It remains selectable and exports as empty.

  • Confirmed Churn MRR and Per-Client ETCR columns are placeholders ("ETCR pipeline pending — owned by Lily"). They render "—", are not sortable, and export empty.

  • Priority definitions live upstream. P1/P2/P3 comes from the data-science pipeline (FINAL_FEATURES.PRIORITY_TIER); the rules can change without any app deploy, and the app repo does not define them. A client missing a tier silently defaults to P3.

  • Freshness is mixed by design. The client/risk/finance base is served from a 15-minute stale-while-revalidate server cache (shared with POC Management); Pulse and QBP columns are read fresh on every request. The separate churn and SE calls have their own 15-minute caches, so signals can be up to 15 minutes apart from each other.

  • Upstream refresh cadence is not authoritative in this repo. Code comments describe a roughly weekly model refresh for the churn/SE/feature tables; the actual schedule lives in the data team's pipeline. Not defined in the app repo.

  • Code-constant thresholds (as of Jul 2026, all tunable only via deploy unless noted): risk buckets 0.3/0.5/0.6; SE Strong/Moderate/Low at 0.7/0.4; briefing-strip SE-ready readiness ≥ 0.7 and ≥ 90 quiet days; Status-chip priority set = top-25 by risk × MGP among risk ≥ 0.5; Cardinal = acquisition_id 1005; Brand Intel daily cap 50 (env-tunable); Contract Ending buckets 30/60/90 days; Pulse History fetch defaults to 5 weeks.

  • Gong sentiment of exactly 0.5 is a "no data" convention and renders as "—" rather than a score.

  • Account Director attribution is layered: manual Manage Leads picks (Signal's records) win over the Nova executive-sponsor fallback, and overrides pointing at terminated users fall back to Nova. The Nova sync only overwrites a Signal row when Nova's record is strictly newer.

  • Week selector semantics: weeks are Monday-anchored; the default selected week is the most recent week with any active Pulse submission (else the current Monday). Selecting a past week makes the Pulse-notes editors read-only. QBP columns follow the quarter derived from the selected week; QBP dates may be entered up to one year ahead.

  • Column set churns frequently — the localStorage key is at v9 — so avoid relying on exact column positions.

  • Who holds Team Lead in practice is runtime data in Signal's app_users table (plus the AD/GD auto-grant at sign-in); the roster is not in code.

Related views

  • Churn Risk (/predictions/churn) — Portfolio Review consumes the same churn feed and embeds the churn client detail as its "Churn Risk" tab; recommended-action triage is shared (same recommended_action_feedback rows), so acknowledging in either view updates both.

  • Service Expansion (/predictions/se) — same relationship: the SE feed is merged into Portfolio Review and the SE client detail (including its Salesforce history tab) is embedded; Strong/Moderate/Low buckets intentionally match.

  • Pulse Submission / My Submissions (/pulse/submit, /pulse/my-submissions) — ADs submit weekly Pulse and QBP there; Portfolio Review is the primary consumption surface, and its GD Review Notes / WoW Changes editors write back to the same table.

  • Pulse Summary (/predictions/pulse-summary, admin-gated) — sentiment/pacing across the portfolio in a Pulse-first layout.

  • POC Management (/predictions/poc-management) — shares the exact same server-side portfolio base cache (a full churn+SE recompute happens at most once per 15 minutes across both views).

  • Manage Leads (/leads) — Account Director attribution flows through the same client_account_directors table Manage Leads edits; the AD shown in Portfolio Review reflects those edits plus the Nova sync.

  • ETCR (Beta) (/etcr) — the two ETCR placeholder columns in Portfolio Review will light up when the ETCR pipeline ships.

  • Analytics (/analytics, Sauron-gated) — usage events logged from Portfolio Review surface in the internal adoption dashboard.

FAQ

Q: Why doesn't the revenue in Portfolio Review match Vena? A: The MGP/MRR/VABO figures ARE Vena Finance actuals — but they pass through a server-side cache that refreshes on a 15-minute stale-while-revalidate cycle, and the underlying Vena extract refreshes on the data team's pipeline cadence (roughly weekly for model tables). A brand-new contract can also show $0 MGP until Finance posts entries for the month. If a specific number looks wrong, flag it via the Corrections screen or ask the data team; the app does not modify Vena values.

Q: Why is a client missing from Portfolio Review? A: Portfolio Review only shows the "active book". A client is excluded server-side if the active-book rules (server/activeBook.ts + pulse-eligibility logic) classify it as churned or inactive — driven by Nova churn dates, current-month MGP from Vena, VABO, and the modeled churn status. Also check your filters: an Account Director filter with zero matches shows an "assigned book empty" empty state, and other filters show "filters too tight" with a clear-all shortcut.

Q: What do P1, P2, and P3 mean, and who decides them? A: P1 = "Act now", P2 = "Plan this week", P3 = "Healthy / Monitor". The tier is pre-computed by the data-science pipeline (PRIORITY_TIER in the modeled feature set) — Signal only displays it. The exact trigger rules are owned by the data team and can change without an app release. A client with no tier value defaults to P3 in the UI.

Q: What does the Status chip (Acknowledged / Reviewed / Needs Review / Unassigned) mean? A: It tracks triage of the system-generated recommended actions for a client. Acknowledged = the assigned Account Director or Group Director completed/dismissed every action; Reviewed = someone other than the assigned owner did; Needs Review = actions remain untriaged; Unassigned = no AD/GD is on record. Needs Review and Unassigned nudges only appear for the "priority set" (top-25 clients by churn-risk × MGP among those with risk ≥ 0.5) to keep red chips tractable. Triage happens in the expanded row's Churn Risk tab (or on the Churn Risk screen — they share the same records).

Q: Why can't I edit the WoW Changes or GD Review Notes for a client? A: Three gates: (1) the Week selector must be on the current week — past weeks are read-only; (2) a Pulse submission must already exist for this week (otherwise the panel links you to the Pulse submit form); (3) the save endpoint only accepts the client's assigned Account Director, its Group Director, or a Pulse admin — anyone else gets a permission error. If you believe you're the assigned AD/GD and are still blocked, your assignment record in Signal/Nova is likely stale — fix it via Manage Leads or in nova.

Q: Why does the Account Director shown here differ from Nova? A: The AD column layers two sources: a manual/synced override in Signal's own records (client_account_directors, edited via Manage Leads) wins; otherwise it falls back to Nova's executive sponsor. Overrides pointing at terminated employees are ignored. A background Nova→Signal sync (runs on page load, 1-hour TTL) copies Nova's latest assignment in — but only when Nova's record is strictly newer than Signal's, so a recent Manage Leads edit is never clobbered. If it still looks wrong after an hour, update the assignment in Nova or Manage Leads.

Q: Why is the "Why flagged" column always empty? A: It's a retired feature. Priority scoring moved upstream to the data-science PRIORITY_TIER, and the client-side "why" signal chips were removed, so the column renders "—" for every client. It's kept for backward compatibility in the column picker and CSV export.

Q: Why do Confirmed Churn MRR and Per-Client ETCR show "—"? A: They are placeholder columns awaiting the ETCR data pipeline (tooltip: "ETCR pipeline pending — owned by Lily"). They are not sortable and export as empty until that pipeline ships.

Q: Does generating Brand Intel cost money every time I click it? A: A fresh generation calls Anthropic Claude (model claude-sonnet-4-5 with web search) and costs roughly $0.50. Results are cached in Signal's records per client per calendar month — re-opening the tab or re-clicking within the same month returns the cached report at no cost. There is also a per-user cap of 50 fresh generations per day (code default as of Jul 2026, env-tunable). Use it deliberately for clients you're actively reviewing.

Q: Why does Gong Sentiment show "—" for a client that has calls? A: A Gong score of exactly 0.5 is the pipeline's "no data" convention, and Signal hides it rather than displaying a misleading neutral score. If the score is genuinely absent (no Gong coverage), it also renders "—".

Q: What's the difference between the two CSV exports? A: "Export CSV" (portfolio-review.csv) exports only the currently visible columns for the currently filtered rows. "Export all columns" (portfolio-review-all.csv) exports every defined column regardless of visibility. Both always append a "Cardinal" column marking Cardinal-acquisition clients, and both respect the active filters.

Q: Who can see Portfolio Review? A: Any signed-in user above the base Member tier — Team Leads, Admins, Gandalf/Sauron super-admins, and (as a side effect of the access model) users holding any Growth capability. Base Members are redirected to the Summary page and don't see the nav link. Team Lead is granted in Signal's app_users table by admins, and is auto-granted at sign-in to anyone who is an Account Director (executive sponsor) or Group Director on a Nova client. For a new seat or tier change, ask your manager; for access errors, contact IT.

Did this answer your question?