The Staffing view (route /staffing in Signal, Power Digital's internal BI web app) is a sandbox "what-if" board for staffing decisions. It shows one column per employee, with a card for each client that employee leads, plus live before → after MRR (Monthly Recurring Revenue) and RPE (Revenue Per Employee) figures. Team Leads and any other elevated-role user (base-tier "members" are blocked) drag clients between employees and tweak revenue-allocation percentages to preview the financial impact of a reassignment — without touching real data. The board answers: "If this person's clients move to that person, whose book gets too heavy and what happens to everyone's revenue numbers?" Nothing on this view writes to any shared system: scenarios live only in the user's own browser. Underlying data comes from Vena (contracted client revenue), Nova (employee roster, client team assignments, Account Director designations) and Signal's own records entered by users in Signal (manual lead assignments, revenue-split overrides, on-leave flags).
Field | Value |
Route | /staffing |
Access | Any signed-in non-member (Team Lead, Admin, Gandalf, Sauron, Growth-access users); base-tier members are redirected to /summary by MemberAllowedRoute |
Nav section | People (between "Employees" and "Manage Leads") |
Primary data sources | Vena (client contract revenue); Nova (employee roster, client-team seat history, Account Director / Sr. Account Director designations); Signal's own records — entered by users in Signal (manual lead assignments, revenue-split overrides, on-leave flags) |
Writes | None to shared systems — simulation scenario persists only to the user's browser localStorage; CSV export is a local file |
Key code refs | src/views/StaffingView.tsx, src/utils/staffingSimulation.ts, src/utils/allocation.ts, src/utils/employeeRevenue.ts, src/App.tsx (route + nav), src/context/DataContext.tsx (data feeds), server/vena-kpi-engine.ts (getAssignmentsData), server/index.ts (/api/employees, /api/assignments, /api/team-assignments, /api/nova/assignments) |
Last verified | staging @ 98df0c4, verified 2026-07-07 |
Purpose
The Staffing view in Signal exists so that leaders can simulate client reassignments before making them real. When someone resigns, goes on leave, or is overloaded, a lead has to decide who absorbs their clients. Doing that directly in Manage Leads (Signal's live assignment editor) is risky because every change there is real and visible to everyone. The Staffing board solves this by being a pure simulator: it renders each employee as a column of client cards with their MRR (Monthly Recurring Revenue) and RPE (Revenue Per Employee), then shows a live before → after comparison as the user moves clients between columns or edits allocation percentages. Because the "before" numbers are computed by the exact same shared code the Employees view uses (src/utils/employeeRevenue.ts), the baseline always matches what leadership already sees elsewhere in Signal, so the simulated "after" numbers are trustworthy. A permanent banner states: "Simulation mode — changes are local only and never saved."
Access & visibility
The Staffing view at /staffing is guarded by MemberAllowedRoute (defined in src/App.tsx). This guard blocks only the base tier: a "member" is any signed-in user with none of Signal's elevated role flags. Members hitting /staffing are redirected to /summary. Anyone with at least one elevated flag passes: Team Lead (is_team_lead), Admin (is_admin), Gandalf (is_gandalf), Sauron (super-admin), or any Growth-access capability (Growth viewer, Performance Radar viewer, Growth access manager). In practice the intended audience is Team Lead and above.
How the flags are granted: the role flags is_team_lead, is_admin, and is_gandalf live in Signal's own records (the app_users table, entered by users in Signal) and are managed on the General Access screen (/users), which only Gandalf-level users can open. Sign-in itself is Google OAuth restricted to the @powerdigitalmarketinginc.com domain.
Nav placement: "Staffing" appears in the People section of the left sidebar, between "Employees" and "Manage Leads". The People section has no section-level gate, and the Staffing link has no link-level flag — so every non-member sees it. Members see a reduced sidebar containing only Summary and Employees, so the Staffing link is hidden from them as well as route-blocked.
Data sources & lineage
Rule of thumb for the Staffing view: everything visible is read-only inputs; the simulation itself never leaves the browser.
On-screen element | Origin system | Code path | Refresh | Caveat |
Employee columns (name, title, division, department) | Nova (employee/org records) | GET /api/employees in server/index.ts → shared DataContext | Server-side cache 1 hour (metadata:employees); ?refresh=1 busts it | Roster excludes terminated users and non-company email domains; missing division/department shows "Unknown" |
Employee roles (drive the Roles filter and Lead/Support + Consulting weighting) | Nova (client-team seat history) | Role field on GET /api/assignments rows → DataContext builds employee.roles | Same cadence as assignments load (on month change / page load) | Group Director role is stripped app-wide before display |
Client cards — which clients an employee leads | Nova (seat history) overlaid by Signal's own records (manual lead assignments entered in Manage Leads) | GET /api/assignments / /api/assignments/batch → getAssignmentsData() in server/vena-kpi-engine.ts; overlay from GET /api/team-assignments (Signal's team_assignments) and GET /api/managed-clients | On page load and whenever selected months change | Manual assignments entered in Signal win over the Nova-detected team; VABO department rows never appear as cards |
Revenue behind each card (feeds MRR/RPE dollars) | Vena (contracted revenue; Vena writes, Signal reads) | FINAL_VENA_CONTRACTS read in server/vena-kpi-engine.ts (one row per blueprint × department × service × month) | Server-side cached per year envelope; refreshed on server restart / cache expiry | Rows with negative revenue are dropped; zero-revenue dormant department rows are filtered out |
Account Director / Sr. Account Director appearing as extra leads on cards ("AD injection") | Nova (executive sponsor / Sr. AD designations) — live Nova API merged with a cache in Signal's own records | GET /api/nova/assignments (Nova GraphQL, 5-minute server cache) merged with GET /api/client-account-directors and GET /api/client-senior-account-directors in DataContext | Nova payload cached 5 minutes server-side; loaded on page load and on refresh calls | Since Jul 2026, live Nova wins over the cached copy for non-consulting single-division clients; consulting and multi-division clients use Signal-side records with Nova only filling gaps. Injected ADs add to MRR but are neutral to the RPE split |
MRR before → after per employee column | Derived (computed in the browser) | computeSimMetrics in src/utils/staffingSimulation.ts | Recomputed instantly on every scenario edit | Full client revenue counted once per assigned active client, divided by number of selected months; excludes VABO |
RPE before → after per employee column | Derived (computed in the browser) | computeEmployeeRpe in src/utils/employeeRevenue.ts — the same function the Employees view uses | Recomputed instantly on every scenario edit | "Before" reproduces the Employees view exactly; includes the employee's VABO component even though VABO clients show no cards |
Allocation % on each card (default split) | Derived (in-code allocation table) | calculateAllocations in src/utils/allocation.ts | Static per deploy | Percentages are code constants explicitly marked "placeholders for John to finalize" as of Jul 2026 (e.g. 2-person 60/40; Consulting 4-person 30/35/25/10) |
Saved revenue-split overrides (base layer under simulated % edits) | Signal's own records (entered by users in the Employees view) | getOverridesForMonth in src/services/supabase.ts → revenue_split_overrides | Reloaded whenever selected months or accounts change; keyed to the first selected month | Scenario % edits layer on top per account + employee |
On-leave adjustment (metric parity with Employees view) | Signal's own records (entered by users in Signal) | getEmployeeFlags('on_leave') → employee_flags | Loaded once on page load | On-leave leads fall back to account_manager weighting in Consulting splits |
Month scope (how many months the averages cover) | Vena (available revenue months) | GET /api/months → computeMonths() in server/vena-kpi-engine.ts; selected via the global MonthSelector in the app header | On page load | Capped at the current calendar month |
Division / Department filter options | Derived from the loaded employees/accounts | DataContext (divisions, departments) | Follows the data load | Shared filters — a selection made on Staffing persists to other views such as Employees |
The simulation scenario itself (reassignments + % overrides) | Browser-local (the user's own edits) | localStorage key signal-staffing-scenario:<userId> via loadScenario/saveScenario in src/utils/staffingSimulation.ts | Auto-saved 200 ms after every change; restored on reload | Per user, per browser; never leaves the machine |
CSV export | Browser-local (generated client-side) | handleDownloadCsv in src/views/StaffingView.tsx | On demand | File named signal-staffing-YYYY-MM-DD.csv; contains only the currently visible board |
Metrics & definitions
All metrics on the Staffing view are computed in the browser and shown twice per employee column: a "before" value (real data, no simulation) and an "after" value (with the user's simulated changes applied). A green/red delta appears in parentheses when they differ.
MRR (Monthly Recurring Revenue), per employee — the total monthly contracted revenue of all active clients the employee is assigned to. Basis: for each active non-VABO client where the employee appears as a lead (including injected Account Director roles), the FULL client revenue is counted once (not the employee's share), summed, then divided by the number of months selected in the global MonthSelector. Origin: Vena contract revenue + Nova/Signal team assignments. Code: computeSimMetrics in src/utils/staffingSimulation.ts.
RPE (Revenue Per Employee), per employee — the employee's attributed share of client revenue per month. Basis: client revenue is split across the client's leads using Signal's allocation rules (role-weighted for Consulting; seniority-ordered lead/support splits elsewhere), honoring saved revenue-split overrides from the Employees view and any simulated % edits; slices of the same client + department (e.g. Retainer + Project) are merged before splitting; the employee's VABO component is added on top; the total is divided by the number of selected months. Origin: Vena revenue, Nova/Signal assignments, Signal-entered overrides. Code: computeEmployeeRpe in src/utils/employeeRevenue.ts — the identical function the Employees view uses, so before-values match that view exactly.
Allocation % (shown on each client card) — the employee's percentage share of that client's revenue under current rules. Defaults are code constants in src/utils/allocation.ts (as of Jul 2026, marked in-code as placeholders pending finalization): single person 100%; two people 60/40; three people 50/35/15 (Consulting) or 50/30/20 (other departments); four-plus 30/35/25/10 (Consulting: Sr. AD/AD/AM/PM) or 30/30/20/20 (other). Users can type a 0–100 replacement to simulate a change.
Role chip (Lead / Support) — whether the employee is the lead or a support on the account, derived from role seniority ordering (ROLE_PRIORITY in src/utils/allocation.ts; e.g. Sr. Account Director outranks Account Director outranks Account Manager). Origin: Nova role data.
Delta (the parenthesized +/− figure) — after minus before, rounded to whole dollars; hidden when the rounded delta is zero.
"Inactive" tag — the client's contract is not active for the selected month(s); inactive clients do not appear as cards (cards show only active assignments) but the tag guards edge cases in rendering.
"Modified" highlight / scenario-active chip — visual flags meaning the card (or any card) has simulated changes; not a data metric.
How it's used
Plan a departure or leave of absence. Filter the Staffing board to the affected division/department, find the departing employee's column, and use the reassign (⇄) button on each of their client cards to hand clients to teammates. Every receiving column's MRR/RPE "after" numbers update live — the decision is whether anyone's book gets too heavy. Export the CSV as the transition plan.
Rebalance workload across a team. Pull up a department on the Staffing board, scan RPE across columns to spot outliers (one person carrying far more revenue than peers), and simulate moving one or two clients from the heaviest column to the lightest until the numbers look defensible. This answers "is the team fairly loaded?" with dollars instead of impressions.
Pressure-test an allocation change. On a shared client, edit one person's allocation % (e.g. drop the lead from 60% to 50% because a support person is doing more of the work) and read the RPE deltas for everyone on the account — useful before proposing a real revenue-split override in the Employees view.
Prepare a headcount case. Simulate absorbing an open or departing role's book of clients across the existing team and export the CSV; the before/after RPE gap quantifies the case for or against a backfill.
Commit an agreed plan for real. The Staffing board never saves to shared systems. Once a scenario is agreed, make the actual lead changes in Manage Leads (Signal's live assignment editor) and any percentage changes in the Employees view (which writes revenue-split overrides), then press "Reset all" on the Staffing board.
What users can change here
Nothing shared — the Staffing view is read-only toward every shared system. The simulation code (src/utils/staffingSimulation.ts) states in its header that nothing in it writes to Supabase, Vena, or shared application state, and the view exposes no save/submit action. What a user CAN do, and where it lands:
Reassign a client between employees (⇄ button) — recorded only in the in-browser scenario. Lands in the user's browser localStorage (key signal-staffing-scenario:<userId>), auto-saved 200 ms after each change. Side effects: none outside the user's own browser; invisible to other users and to the same user on another device.
Edit a card's allocation % (0–100, Enter or blur to commit, Escape to cancel) — same browser-local scenario storage; layered on top of any saved revenue-split overrides for display math only.
Revert one card (↺) / Reset all — removes entries from the browser-local scenario.
Download CSV — writes a local file (signal-staffing-YYYY-MM-DD.csv) containing the visible board with before/after MRR and RPE, role, allocation %, monthly revenue, and a Modified flag.
To make a simulated change real, users must leave the Staffing view: lead reassignments are committed in Manage Leads (which writes to Signal's own assignment records, and — since the Jul 2026 Nova migration — Account Director / Sr. AD designations write through to Nova), and allocation-percentage changes are committed in the Employees view (revenue-split overrides in Signal's own records).
Caveats & data quality
Default allocation percentages are provisional code constants. The split table in src/utils/allocation.ts (2-person 60/40; 3-person 50/35/15 Consulting / 50/30/20 other; 4-plus 30/35/25/10 Consulting / 30/30/20/20 other) is commented "placeholders for John to finalize" as of Jul 2026. Treat exact numbers as changeable per deploy.
The Roles filter list is hardcoded — seven staffable roles (Sr Account Director, Account Director, Account Manager, Project Manager, Sr Strategist, Strategist, Associate Strategist) in src/views/StaffingView.tsx. If org roles change, the list requires a code change. Group Director is filtered out of the entire app by design.
VABO is excluded from the board but included in RPE. VABO (performance-based revenue) department rows never appear as cards on the Staffing board — they are not staffable. However, the RPE figures in each column header DO include the employee's VABO component, for parity with the Employees view. MRR excludes VABO entirely.
A division and/or department filter is mandatory. With no division or department selected, the board renders only the prompt "Select a division and/or department to view the board" — the full company would be too many columns.
Scenario persistence is per-user, per-browser only. Scenarios do not sync across devices or users; clearing browser data deletes them. The CSV export is the only sharing mechanism.
Before-numbers depend on the globally selected months. MRR and RPE are averages over the months chosen in the app-header MonthSelector; two users with different month selections see different numbers. Saved revenue-split overrides are loaded for the first selected month only.
Account Director injection is MRR-relevant but RPE-neutral. An Account Director or Sr. AD injected onto a client from Nova designations counts toward that person's MRR and board presence but contributes 0 to (and does not dilute) the RPE split.
Employee roster freshness: the /api/employees payload is cached server-side for 1 hour (code constant as of Jul 2026); brand-new hires or same-day org changes can lag up to an hour.
Nova assignment payload freshness: the live Nova read behind AD/Sr. AD designations (/api/nova/assignments) is cached server-side for 5 minutes (code constant as of Jul 2026).
Data-quality inheritance: cards inherit any upstream issues in Vena contract rows or Nova seat history; a client missing here usually means it has no active revenue row for the selected month(s) or no detected/manual team.
Refresh cadence of the underlying Vena replica is not defined in the app repo — the app reads whatever the warehouse currently holds; how often Vena data lands there is managed outside this codebase.
Whether the placeholder allocation splits were ever formally ratified is not defined in the app repo.
Related views
Employees view (/employees) — the twin view. Both compute RPE/MRR from the same shared module (src/utils/employeeRevenue.ts), so the Staffing "before" numbers always equal the Employees view. Real revenue-split overrides are entered there.
Manage Leads (/leads, Team Lead-only) — the write path. Staffing simulates what Manage Leads makes real: lead assignments (Signal's own records, with AD/Sr. AD designations writing through to Nova since Jul 2026). The natural loop is: simulate on Staffing → commit in Manage Leads → Reset all on Staffing.
Summary (/summary) — company-level rollup fed by the same Vena revenue engine.
ETCR (Beta) (/etcr), Employee Pulse (/pulse-coverage, Admin-only), KPI Targets (/kpi-targets, Admin-only) — neighbors in the People nav section; different gates, unrelated data flows.
Global MonthSelector (app header) — sets the month range that MRR/RPE averages cover on Staffing and every other view.
FAQ
Q: Do changes I make on the Staffing board affect real assignments or anyone else's numbers? A: No. The Staffing view is a pure simulator — reassignments and percentage edits are stored only in your own browser (localStorage) and never write to any shared system. A banner on the view says "Simulation mode — changes are local only and never saved." To make a change real, use Manage Leads (for lead assignments) or the Employees view (for revenue-split percentages).
Q: Why is the Staffing board empty when I open it? A: The board requires at least one Division or Department filter before it renders — otherwise you see "Select a division and/or department to view the board." Pick a division and/or department in the filters bar at the top. If you have filters set and still see nothing, no employees match the combined division/department/role filters.
Q: Why don't the MRR numbers here match what Finance or Vena reports? A: Three usual reasons. (1) MRR on the Staffing board is an average over the months selected in the app-header MonthSelector — a different month selection gives different numbers. (2) MRR counts the FULL revenue of every active client the employee is assigned to (not the employee's share), so summing MRR across employees double-counts shared clients. (3) VABO (performance-based) revenue is excluded from Staffing MRR. The underlying revenue itself comes from Vena contract data, so at the single-client, single-month level it matches Vena.
Q: What's the difference between MRR and RPE on this board? A: MRR is the total monthly revenue of all clients the employee touches — full client value, counted once per client. RPE is the employee's attributed slice of that revenue after splitting each client across its team using allocation percentages (and any overrides). MRR answers "how much business does this person touch"; RPE answers "how much revenue is credited to this person."
Q: Where do the default allocation percentages (like 60/40) come from and are they official? A: They are code constants in Signal (src/utils/allocation.ts): as of Jul 2026 — one person 100%; two people 60/40; three people 50/35/15 in Consulting (50/30/20 elsewhere); four-plus 30/35/25/10 in Consulting by role Sr. AD/AD/AM/PM (30/30/20/20 elsewhere). The code explicitly marks them as placeholders pending finalization, so treat exact values as provisional. Real per-client overrides entered in the Employees view take precedence over these defaults.
Q: I made a scenario yesterday — will it still be there, and can my VP see it? A: Your scenario auto-saves to your own browser about 200 ms after every change and is restored when you reload, so it survives closing the tab. But it is per-user and per-browser: no one else can see it, and it won't follow you to another laptop. Clearing browser data deletes it. To share a scenario, use the "Download CSV" button and send the file.
Q: Why can't I find a VABO client on the Staffing board? A: VABO (performance-based revenue) rows are excluded from the Staffing board by rule — they're a revenue-uplift line, not staffable work. Note that each employee's RPE figure on the board still includes their VABO share (for consistency with the Employees view); only the cards and MRR exclude VABO.
Q: Who can access the Staffing view, and why does a colleague get bounced to Summary? A: Any signed-in Signal user with an elevated role — Team Lead, Admin, Gandalf, Sauron, or Growth-access capabilities — can open /staffing. Base-tier users ("members", who have none of those flags in Signal's access records) are redirected to the Summary page and don't see the Staffing link in the sidebar. Role flags are managed by Gandalf-level users on the General Access screen; ask your manager or a Signal admin if you need elevated access.
Q: I reassigned a client in the simulation — why did the receiving person's RPE barely move? A: RPE is the employee's split share, so it depends on the receiving client's team composition and allocation rules: if the client has several leads, the newcomer gets only their percentage slice (e.g. 40% as a second person, or a role-weighted share in Consulting). Also, if an Account Director is on the card via Nova designation ("AD injection"), that AD counts for MRR but takes 0% of the RPE split. Check the allocation % shown on the card for the exact share.
Q: After I moved a client here, will the Staffing board update when the real change is made in Manage Leads? A: Yes — the board's "before" state always reflects current real data (Nova-detected teams overlaid with manual assignments from Manage Leads). Once the real change lands, your simulated version of it becomes redundant; press "Reset all" (or the per-card revert ↺) to clear the scenario, otherwise your local overrides keep layering on top of the new reality.
Q: How fresh is the data behind the board? A: The employee roster from Nova is cached server-side for 1 hour; the Nova Account Director designations are cached for 5 minutes; assignments and revenue reload when you change the selected months or reload the page. The cadence at which Vena contract data reaches Signal's warehouse replica is managed outside the app and is not defined in the app repo.
