Skip to main content

Signal — Divisional P&Ls

Written by Schae Lilley

The Divisional P&Ls module in Signal (Power Digital's internal BI web app) is a month-by-month profit-and-loss statement for each client-vertical division (B2B, Core, Consumer Services, CPG, Fashion, Lifestyle, plus Healthcare and Shared Services, both labeled "nuanced"). It lives at route /divisional-pl, is visible only to Gandalf-tier users (the highest access tier), and answers the question: "Is this division actually profitable — revenue by service department, minus outsource COGS, minus real payroll cost — and how will the rest of the year look?" Revenue and COGS originate in Vena (finance's planning system, per-blueprint contract data), payroll costs originate in ADP (US), Deel (international), Siesa (Colombia) and NetSuite (benefits/tax pools and contractor bills) matched against Nova employee records, and the forecast rows are Signal's own records typed in by users on the module's Forecast Tool tab.

Field

Value

Route

/divisional-pl

Access

Gandalf tier (route guard GandalfRoute); payroll section additionally restricted to a server-side email allowlist

Nav section

"Finance" → "Divisional P&Ls" (link is Gandalf-only; the Finance section itself is Team-Lead-and-up)

Primary data sources

Vena (contract revenue, VABO, outsource COGS, contract end dates); ADP / Deel / Siesa / NetSuite payroll actuals; Nova (employee names, titles, divisions, departments); Signal's own records (division forecasts, access flags)

Writes

Division forecast cells → Signal's own division_forecasts table (Supabase)

Key code refs

src/views/DivisionalPLView.tsx, src/views/divisionalPl/ (ForecastTab, ClientRosterTab, forecastMetrics), server/index.ts (endpoints /api/division-pl/*, /api/division-forecast, /api/payroll/personnel-expense*, /api/vabos/by-division, /api/vena/outsource), server/vena-kpi-engine.ts, server/payroll.ts, server/payroll-actuals.ts

Last verified

staging @ 98df0c4, verified 2026-07-07

Purpose

The Divisional P&Ls module replaces the finance team's hand-assembled Excel divisional P&L workbooks. Power Digital runs its client business through vertical divisions (B2B, Core, Consumer Services, CPG, Fashion, Lifestyle, and the "nuanced" Healthcare and Shared Services). For each division, leadership needs to know: how much revenue each service department produces, what outsourced delivery costs (COGS), what the people delivering it cost (payroll across four payroll systems in three countries), and therefore whether the division is profitable — plus a forward view for the rest of the year.

The module reproduces finance's Excel P&L layout — its revenue rollup was verified to the penny against finance's Vena totals (Rodolfo, June 2026) — and adds capabilities Excel cannot offer: hovering any payroll cell shows exactly which employees compose that number, a shared team-editable forecast grid whose inputs flow directly into the P&L, and a per-client roster showing which contracts produce the division's revenue and when each contract ends.

Access & visibility

The Divisional P&Ls view at /divisional-pl is gated at three levels:

  1. Route guard. The route is wrapped in GandalfRoute in src/App.tsx; a user whose isGandalf flag is false is redirected away. isGandalf is read from the is_gandalf column of the app_users table in Signal's own records (Supabase) at sign-in and cached in the browser's sessionStorage. The flag is granted by a Gandalf user on Signal's "General Access" page (/users, in the "Access" nav section).

  2. Nav flag. The sidebar link ("Finance" section → "Divisional P&Ls") is marked gandalfOnly: true, so non-Gandalf users never see the link. The Finance nav section as a whole is teamLeadOnly because it also contains Revenue Overlap; only the Divisional P&Ls link inside it requires Gandalf.

  3. Server-side payroll allowlist. The Personnel Expenses section calls GET /api/payroll/personnel-expense and GET /api/payroll/sources/status, which require an authenticated Signal session and membership in PAYROLL_AUTHORIZED_EMAILS — a hardcoded list of exactly 15 emails in server/index.ts (executives, finance, and the engineers who maintain the pipeline) as of Jul 2026. A Gandalf user who is not on that list sees the full revenue/COGS P&L but the Personnel section shows a loading/empty state.

Additional gates on specific actions:

  • Per-worker personnel CSV export (POST /api/payroll/personnel-expense/export): requires (a) authenticated session, (b) is_gandalf = true in app_users, and (c) an X-Export-Password header matching the server environment variable PERSONNEL_EXPORT_PASSWORD. The export button in the UI renders for Gandalf or Admin users, but the server enforces Gandalf strictly.

  • Forecast writes (PUT /api/division-forecast): requires only an authenticated Signal session — not Gandalf. The read endpoint (GET /api/division-forecast) and the revenue/COGS/VABO/roster endpoints are open reads (no auth middleware), matching the posture of Signal's other Vena revenue endpoints.

  • Google OAuth for Signal overall is restricted to @powerdigitalmarketinginc.com accounts.

Data sources & lineage

All backend data flows through Signal's Express server (server/index.ts); the browser never queries source systems directly. Origin systems below are the true systems of record.

On-screen element

Origin system

Code path

Refresh

Caveat

Revenue rows (department × month), In Contract Revenue subtotal, Total/Core Revenue summary cards

Vena (finance's per-blueprint contract revenue, FINAL_VENA_CONTRACTS model)

GET /api/division-pl/revenue in server/index.ts (sums MONTHLY_REVENUE by department × division × month; dept names normalized via VENA_DEPT_CODE_MAP + CONTRACT_DEPT_ALIAS)

Server in-memory cache (venaCache) + browser cache with 30-min TTL (code constant as of Jul 2026); underlying Vena data loads on finance's cadence

Corporate dept (Vena code 1024) excluded as a forecast placeholder (per Rodolfo, 2026-06-12); VABO rows excluded here and shown separately

VABO row (out-of-contract revenue uplift, rendered like a department)

Vena (VABO_DETAILS per-VASO-slot records maintained by finance)

GET /api/vabos/by-division → fetchVabosByDivisionForYear in server/vena-kpi-engine.ts

Same caching as revenue

Division is the finance-assigned attribution on each VABO slot, not the client's division; Retail Marketing slots excluded (they belong to the Retail Marketing dept P&L); negative amounts (credits) kept

COGS rows (outsource cost per department × month)

Vena (OUTSOURCE_COST column on contract rows)

GET /api/vena/outsource → fetchOutsourceCosts in server/vena-kpi-engine.ts

Same caching as revenue

Only positive costs counted

Personnel Expenses rows, division personnel subtotal, headcount card, per-cell worker tooltips

Composite: Nova (employee names, titles, division/department assignment, termination dates) matched to payroll workers; monthly cost actuals from ADP (US W-2 gross), Deel (international contractors/EOR invoice line items), Siesa (Colombia payroll, COP→USD), NetSuite (benefits pool GL accts 60500+66000, payroll-tax pool accts 60300+65100 pro-rated over ADP gross, plus FT-CONT contractor bill payments)

GET /api/payroll/personnel-expense (requires auth + payroll allowlist) → computePersonnelExpense in server/payroll.ts with actuals loaded by server/payroll-actuals.ts; nova↔payroll match cache maintained by scripts/sync-payroll-actuals.ts

ADP and Deel actuals synced nightly at 04:00 UTC by a scheduled job into staging tables (adp_monthly_gross, deel_monthly_actuals); Siesa is a manual xlsx import; server response cached 30 min

The page never calls ADP/Deel live. Per-worker monthly cost falls back through: ADP gross + pro-rata NetSuite pools → Siesa → NetSuite contractor bills → Deel invoices → annualized salary ÷ 12 estimate. Only division/department totals reach the UI; tooltips show names + titles, never pay figures

"Data sources" legend under the table (live / xlsx / no-data badges per payroll feed)

ADP / Deel / Siesa / NetSuite feed status (row counts, latest month, last sync time of the payroll staging tables, plus presence of API credentials)

GET /api/payroll/sources/status (requires auth + payroll allowlist)

Computed on request

"Live" means synced within 48 hours (code constant as of Jul 2026)

Team-forecast rows in the P&L, Team Forecast summary card, entire Forecast Tool grid

Signal's own records (entered by users in Signal) — division_forecasts table

GET /api/division-forecast (open read) / PUT /api/division-forecast (auth required) in server/index.ts

Immediate on save

Applied only to forecast months (later than the actuals cutoff) of the selected year; actual months always come from source systems

Client Roster tab (per-blueprint monthly revenue, contract end date, retainer/project type)

Vena (per-blueprint revenue, contract type, contract end = last scheduled contracted month); client display names from Nova client records

GET /api/division-pl/client-roster → getDivisionClientRoster in server/vena-kpi-engine.ts

Same caching as revenue

VABO excluded (in-contract revenue only); "—" end date means Vena has no scheduled revenue end for that blueprint

Per-worker personnel CSV export

Same composite payroll pipeline as Personnel Expenses, but per-worker rows

POST /api/payroll/personnel-expense/export in server/index.ts

Uses the 30-min cached computation when warm

Server-side division allowlist: only rows in the eight P&L divisions are exported; rows with a missing/unknown division are dropped (added Jul 2026)

Gandalf flag that unlocks the view

Signal's own records — app_users.is_gandalf

src/context/AuthContext.tsx

Checked at sign-in, cached in sessionStorage

Granted via the General Access page

Corporate Expenses (Allocation) section: placeholder only — no data source; the on-screen note says allocation methodology is TBD.

Browser-side caching (as of Jul 2026): revenue, outsource, VABO, personnel, and source-status responses are cached in the tab with a 30-minute TTL and are cleared on sign-out, so a long-lived tab picks up the nightly payroll sync and a later user in the same tab never sees a previous user's payroll data.

Metrics & definitions

All metrics on the Divisional P&Ls view are computed for one selected division × one selected calendar year (2024, 2025, or 2026 as of Jul 2026).

  • Actuals vs Forecast cutoff — months up to and including 2 calendar months before the current month are "Actuals"; later months are "Forecast" (code constant, getActualCutoff in DivisionalPLView.tsx; rationale: data finalizes ~15 days after month close). The cutoff re-evaluates as the month rolls over, even in a long-lived tab.

  • Department revenue row — SUM of Vena per-blueprint MONTHLY_REVENUE for that department in the division, per month. Origin: Vena.

  • VABO — out-of-contract revenue uplift, summed per finance-assigned division per month from Vena VASO slot records; Retail Marketing slots excluded; credits (negatives) included. Origin: Vena.

  • In Contract Revenue — sum of all department revenue rows (VABO excluded). Origin: Vena.

  • Total Revenue (incl. VABO) — In Contract Revenue + VABO; only rendered when the division has VABO. Origin: Vena.

  • Revenue (excl. Shared Svc) / Core Revenue — In Contract Revenue minus revenue from shared-service departments (the shared-services department list plus nova, Corporate, Fusepoint, Web Development, TikTok — code constant EXCLUDED_FROM_CORE_REVENUE); only shown when such revenue exists in the division. Origin: Vena.

  • Team Forecast Revenue — sum of user-entered forecast revenue metrics (Projected VABO, New Biz MRR/Project, SE MRR/Project, Churn — churn entered as a negative) for forecast months only. Origin: Signal's own records.

  • Total [Division] Revenue — core revenue basis + Team Forecast Revenue.

  • COGS — outsource cost per department per month from Vena OUTSOURCE_COST, plus the user-entered Projected OS COGS forecast row. Origin: Vena + Signal's own records.

  • Gross Profit — Revenue − COGS, including team-forecast adjustments for forecast months. Computed on in-contract revenue (excluding shared-services revenue when present), not on the VABO-inclusive total. % Margin = Gross Profit ÷ that same revenue basis.

  • Personnel Expenses (per department / division subtotal) — fully-loaded monthly people cost. Per worker per month, highest-priority available source wins: (1) ADP gross pay + pro-rata share of NetSuite Benefits pool (GL 60500+66000) and Payroll Tax pool (GL 60300+65100); (2) Siesa Colombia total cost (payroll + consolidados + social security, ÷ COP→USD FX); (3) NetSuite contractor bill payments (FT-CONT/FT-CONT-INT); (4) Deel invoice actuals (EOR funding statements excluded, EOR fee included); (5) annualized salary ÷ 12 estimate. Hire/termination dates gate which months a worker contributes. Origin: ADP/Siesa/NetSuite/Deel + Nova.

  • Team-forecast personnel rows — New Hire Expenses, Non-Backfill Reductions (displayed negative; reduces expense), Planned Salary Increases. Origin: Signal's own records.

  • Corporate Expenses (Allocation) — placeholder; no values. Methodology (headcount vs revenue share) undecided as of Jul 2026.

  • [Division] Operating Expenses — currently equals Personnel Expenses (corporate allocation pending).

  • [Division] Contribution Margin — Gross Profit − Operating Expenses, with % Margin. Total [Division] Profit — currently identical to Contribution Margin until corporate allocation ships. These cells render a dash until payroll has loaded so a half-computed profit never displays.

  • YoY Change (Core) summary card — (selected-year Core Revenue − compare-year Core Revenue) ÷ compare-year Core Revenue; shown only when Compare is on.

  • Headcount (Personnel summary card) — count of matched workers in the division ("headcount via ADP+Deel").

  • Client Roster: Contract End — the last month with scheduled contracted revenue for the blueprint in Vena, projected to month-end; FY total — sum of that blueprint's monthly revenue in the selected year; Retainer/Project — Vena contract type.

How it's used

  1. Monthly division review. A finance or executive user opens Divisional P&Ls, picks a Division and the current Year, scans the summary cards, then walks the table from department revenue down to Total Profit, using the Actuals/Forecast column labels to separate closed months from projections. Decision: is the division tracking to plan, and which departments drive any miss?

  2. Year-over-year comparison. Set the Compare dropdown to "vs 2025" or "vs 2024"; the table shows the full compare year's 12 months + total alongside the selected year, and the YoY Change (Core) card summarizes growth. Decision: is the division growing, and from the same departments as last year?

  3. Diagnosing a payroll number. Hover any Personnel Expenses cell to see the exact employees (name, title, and payroll system — ADP, Deel, Siesa, NetSuite contractor, or annualized estimate) composing that month's cost; check the "Data sources" legend at the foot of the table to confirm the feeds are live before trusting the number. Decision: is a cost spike a real hire/raise or a stale-data artifact?

  4. Entering the division forecast. On the Forecast Tool tab, type projected new-business MRR, expected churn (as a negative), projected outsource COGS, and planned hires/raises/reductions into future months, then Save. Flip back to the P&L tab — saved values immediately appear in the forecast rows, Gross Profit, and margin lines without a reload. Decision: can the division afford planned hires and still hit its margin target?

  5. Contract-risk scan. On the Client Roster tab (default sort: Contract End ascending), see which blueprints end soonest and how much monthly revenue leaves with them; filter Retainer vs Project to separate recurring from one-off revenue; search by client name, blueprint ID, or Nova ID. Decision: where to focus renewal/expansion effort.

  6. Handing data to finance. Export the revenue table as CSV, export the client roster as CSV, or (Gandalf + password) download the per-worker personnel-expense CSV for reconciliation against ADP/Deel/Siesa/NetSuite.

What users can change here

  • Division forecast cells (Forecast Tool tab) — the only write. Saving calls PUT /api/division-forecast, which upserts into the division_forecasts table in Signal's own records (Supabase), keyed on (division, month, metric), stamping the editor's email and a timestamp. Only months after the actuals cutoff are editable (past months are locked in the grid); the ten metrics are a fixed vocabulary enforced server-side (projected_vabo, projected_new_biz_mrr, projected_new_biz_project, projected_se_mrr, projected_se_project, projected_churn, projected_os_cogs, new_hire_expenses, non_backfill_reductions, planned_salary_increases). Side effects: saved values immediately feed the P&L tab's Team Forecast rows, Gross Profit, Operating Expenses, Contribution Margin, and margin percentages — for forecast months of the selected year only. The grid's toolbar shows who last updated it and when. Note the write endpoint requires only login, not Gandalf.

  • Everything else is read-only. Revenue, VABO, COGS, contract end dates, and payroll all come from upstream systems (Vena, ADP, Deel, Siesa, NetSuite, Nova) and cannot be edited in Signal.

  • Exports (user actions, not writes): revenue-table CSV; client-roster CSV; and the password-gated per-worker personnel CSV (POST /api/payroll/personnel-expense/export; the export window defaults to the selected year through the actuals cutoff).

Caveats & data quality

  • Corporate expense allocation is unbuilt. The Corporate Expenses (Allocation) section on the Divisional P&Ls view is an on-screen placeholder ("methodology TBD" — headcount- or revenue-share allocation is mentioned but undecided). Consequently Operating Expenses = Personnel Expenses and Total Profit = Contribution Margin today; the bottom of the P&L will change when allocation ships.

  • Current and prior month are provisional. The actuals cutoff is 2 months back (code constant as of Jul 2026, reflecting ~15-day close); anything later is a forecast column.

  • Payroll freshness varies by feed. ADP and Deel actuals are synced nightly at 04:00 UTC by a scheduled job; Siesa (Colombia) is a manual xlsx import that can lag (owner/cadence of the upload: not defined in the app repo); workers with no actuals in any system show an annualized-salary ÷ 12 estimate. The on-page "Data sources" legend flags each feed as live API (synced <48h), xlsx imports, no data → estimate, API blocked, or not configured.

  • Staging-only table swap. On the staging branch, Siesa and Deel actuals temporarily read from siesta_payroll_test / deel_monthly_actuals_test, and a temporary full Deel roster is read from deel_people_test (all marked TEMPORARY in server/payroll-actuals.ts, to be reverted before promotion to prod) — staging numbers can differ from production.

  • Caching. Revenue/COGS/VABO responses are cached in server memory and in the browser tab with a 30-minute TTL (code constant as of Jul 2026); the personnel computation is server-cached 30 minutes. A freshly closed month can lag up to the TTL. Browser caches are cleared on sign-out.

  • Hardcoded lists likely to evolve: the 15-email payroll allowlist (PAYROLL_AUTHORIZED_EMAILS), the division list and "(nuanced)" labeling of Healthcare and Shared Services, the department display order, and the shared-services exclusion set are all code constants.

  • Fresh finance decisions encoded in code: the revenue basis was switched in June 2026 from Vena's Account-40000 forecast cube to per-blueprint contract data (FINAL_VENA_CONTRACTS), which was verified to the penny against finance's totals; the Corporate-dept exclusion and Retail-Marketing-VABO exclusion follow Rodolfo's 2026-06-10/12 decisions and could be revisited.

  • VABO attribution is finance-assigned, not client-division: a client's VABO can sit in Shared Services even if the client is CPG. VABO is excluded from Gross Profit and margins.

  • Forecast write gate is looser than the view gate: the UI lives behind Gandalf, but PUT /api/division-forecast requires only an authenticated Signal session — any signed-in user hitting the API directly could write forecast cells.

  • Personnel export division filter: the per-worker CSV drops rows whose division is missing or not one of the eight P&L divisions (server-side allowlist, added Jul 2026) — export totals can therefore be slightly lower than an unfiltered payroll total.

  • Why Healthcare and Shared Services are "(nuanced)": not defined in the app repo beyond the label.

  • 2024 data reliability: the year dropdown offers 2024 and the personnel query includes terminations back to 2024-01-01, but no explicit validation note for 2024 revenue exists in the app repo.

Related views

  • Division Forecast (/predictions/retention, "Client Health" nav section) — a different Signal module despite the similar name: churn/retention predictions per division. The Divisional P&Ls Forecast Tool is where humans commit division-level numbers; its metric vocabulary intentionally mirrors other modules (Projected Churn ↔ Churn Risk view, Projected SE MRR/Project ↔ Service Expansion view, Projected New Biz ↔ pipeline).

  • Analytics, Client Profitability, ETCR, Revenue Overlap — share the same Vena revenue engine (server/vena-kpi-engine.ts) and division/department normalization, so revenue figures agree across these modules.

  • Employees / People views — share the same nightly payroll sync and nova↔payroll matching pipeline; the Divisional P&Ls view is the only place payroll appears as a division P&L expense line.

  • Client-facing views (e.g. Portfolio Review) — show VABO client-by-client; the Divisional P&Ls VABO row aggregates by finance-assigned division instead (deliberately different attribution).

  • General Access (/users) — where the Gandalf flag that unlocks Divisional P&Ls is granted.

FAQ

Q: Who can see the Divisional P&Ls page? A: Only Gandalf-tier users — the flag is is_gandalf on your Signal user record, granted by an existing Gandalf user on Signal's General Access page (/users). The link appears under the "Finance" section of the sidebar. Additionally, the Personnel Expenses section only populates for the ~15 people on a hardcoded server-side payroll email allowlist; other Gandalf users see revenue and COGS but not payroll.

Q: Why doesn't the revenue here match Vena? A: It should — the Divisional P&Ls revenue is the department × division × month rollup of Vena's per-blueprint contract revenue and was verified to the penny against finance's Vena totals (June 2026). Differences usually mean one of: (1) you're comparing against Vena's Account-40000 forecast cube, which drifts for closed months and disagrees on some clients' divisions — Signal deliberately uses contract data instead; (2) VABO — shown as its own row, excluded from In Contract Revenue and from Gross Profit; (3) the Corporate department (Vena 1024), which Signal excludes as a forecast placeholder; (4) caching — responses are cached up to 30 minutes.

Q: Why is the current month showing as "Forecast" when it's already started? A: The view treats months up to 2 calendar months before today as "Actuals" and everything later as "Forecast", because financial data finalizes roughly 15 days after month close. The current and prior month are provisional by design.

Q: What exactly is in a Personnel Expenses number, and can I see who's in it? A: Each cell is the fully-loaded monthly people cost of that department in the division. Per worker, the most authoritative source wins: ADP gross pay plus a pro-rata share of NetSuite benefits (GL 60500+66000) and payroll-tax (GL 60300+65100) pools; else Siesa (Colombia) actual cost; else NetSuite contractor bill payments; else Deel invoice actuals; else annualized salary ÷ 12. Hover any personnel cell to see the people (name, title, payroll system) behind it — dollar amounts per person are never shown in the UI.

Q: How fresh is the payroll data on the Divisional P&Ls page? A: ADP and Deel actuals are synced nightly at 04:00 UTC into Signal's payroll store; the page never calls ADP or Deel live. Siesa (Colombia) is a manual spreadsheet import and can lag. The "Data sources" legend under the P&L table shows each feed's status: live API (synced within 48 hours), xlsx imports, no data → estimate, API blocked, or not configured.

Q: Who can enter the division forecast, and where do those numbers go? A: Any signed-in Signal user who can reach the Forecast Tool tab (the page itself is Gandalf-gated) can type values into future months and Save. Values are stored in Signal's own division_forecasts records with the editor's email and timestamp, and immediately appear in the P&L tab as Team Forecast rows for forecast months — they never override actual months. Ten fixed metrics are supported: Projected VABO, New Biz MRR, New Biz Project, SE MRR, SE Project, Churn (enter as a negative), Projected OS COGS, New Hire Expenses, Non-Backfill Reductions, Planned Salary Increases.

Q: Why can't I edit January's forecast cell? A: Months at or before the actuals cutoff (2 months back from today) are locked in the Forecast Tool grid — those months' numbers come from source systems (Vena revenue/COGS and the payroll pipeline), not from user-entered forecasts.

Q: Why is "Total Profit" the same as "Contribution Margin"? A: Corporate expense allocation has not been built yet — the Corporate Expenses (Allocation) section is a placeholder with methodology TBD. Until it ships, Operating Expenses equals Personnel Expenses, so Total Division Profit equals Contribution Margin. Treat Total Profit as pre-corporate-allocation.

Q: What does the VABO row mean, and why is a CPG client's VABO not in the CPG P&L? A: VABO is out-of-contract revenue uplift that finance tracks per VASO arrangement in Vena. On the Divisional P&Ls view it is aggregated by the division finance assigned to each VABO slot — not by the client's own division — so retail-only VABO, for example, can be finance-assigned to Shared Services even when the client lives in CPG. Retail Marketing VABO slots are excluded entirely (that revenue belongs to the Retail Marketing department P&L). VABO is also excluded from Gross Profit and margin calculations.

Q: How do I get the per-worker personnel cost CSV, and why does it ask for a password? A: On the P&L tab, the "Export personnel (all divisions)" button prompts for a password and downloads one row per worker per month with cost. It is triple-gated — Signal session, Gandalf flag, and a shared export password (X-Export-Password header checked against a server environment variable) — because unlike the on-screen P&L (division/department totals only), the CSV contains individual compensation-level cost. The window defaults to the selected year through the actuals cutoff, and only workers in the eight P&L divisions are included. For the password, ask the Signal engineering team; for access-tier issues, IT is the default contact.

Q: What does the "—" contract end date mean on the Client Roster tab? A: Vena has no scheduled contracted-revenue end for that blueprint — the roster derives each contract end from the last month with scheduled contracted revenue in Vena. A red end date means the contract end is already in the past. The roster shows in-contract revenue only (VABO excluded).

Did this answer your question?