MasterAdmin Enterprise Console — Design Spec
Date: 2026-06-15 Status: Design (approved in brainstorm) Supersedes/extends:2026-06-12-network-operations-console-design.md (this is the full module-by-module superset; the prior spec’s Financial slice is already partly built and folds in unchanged).
First test surface: ssh & Associates (shared app) as HQ/main branch + seeded sub-branches. Pattern also applies to isolated enterprise instances (DentoCorrect).
1. Problem
A multi-branch owner/Head of Operations cannot run a network from what we have. Today the Network Hub/network is a vanity rollup (today’s appointments, revenue, no-show rate, branch list) and the org-insights API exposes only /financial. The metrics engine has only financial.ts. Everything else still requires logging into each branch one at a time and hand-gathering reports — exactly the pain to kill.
The Master Admin needs, across all branches at once: where the money is (billed vs collected vs outstanding), where it’s leaking (no-shows, overdue AR, low collection, expense bloat, lab delays, absenteeism), and which branch/doctor is underperforming — with one-click drill-down into the why and into the branch itself to act, plus targets they’re measured against and a Ruby layer that reads the network for them.
The bar is “better than the generic CRM they’re using” — and the CRM knows nothing about a chair, a no-show, a treatment plan, or a dentist’s daily production.
2. Core insight — mostly reuse
Per-clinic analytics already compute the hard parts; they’re just filtered to a singleclinicId. The console is the same queries run with clinicId IN (branchIds) and GROUP BY clinic (or doctor), plus presentation. ~90% reuse. The genuinely net-new pieces are: the Ruby network layer, predictive KPIs, and cross-branch suggestions (e.g. inventory rebalance).
3. Locked decisions (from brainstorm)
- Role — reuse existing
org_admin, branded “Master Admin” (Head of Operations) in the UI. Sits above clinic admin, separate from platform superadmin — exactly the missing tier.branch_manager= same console scoped to one branch. - Navigation — console at HQ altitude; branch rows are click-through into that branch’s full dashboard with a persistent ← Back to Network. The clinic switcher is kept as plumbing but demoted: it becomes the in-console scope selector (
Viewing: Whole Network ▾ / <branch>). The standalone top-bar switcher stays only for non-enterprise multi-clinic owners (unchanged). - Landing default — Master Admin lands in their clinic as today, and opts up to the Network console via the scope selector. Login experience does not change. (Contrast DentoCorrect, where HQ is admin-only and lands on the network hub.)
- Ruby — all four roles, phased: Digest + Branch-benchmarking in P1; Per-module strips + Predictive in P3. DeepSeek only, cost-disciplined (digest = one cached call/day per org).
- Demo seed — ssh & Associates = HQ; seed 3
DEMO--prefixed sub-branches with deliberately differentiated profiles; one-command teardown. Creating org tables + branch rows on shared prod is a live-tenant action → explicit per-action confirmation required at execution (no-live-tenant-execution rule).
4. What stays unchanged (de-risking the test tenant)
- The ssh & Associates clinic and every module inside it — untouched. It remains a normal operating clinic.
- Every other user in the clinic (doctors, reception, other admins) sees zero change; they never get the org layer.
- The org layer is additive + reversible: a mapping (
organization_clinics) + a role assignment (user_organization_assignments). Remove the assignment → plain clinic user again. - The shared per-clinic analytics (
routes/analytics.ts,routes/reports.ts) are never touched — they also ship to the main app (prime directive: never destabilize the main app). The engine is NEW code, additive-only, parity-tested against those routes.
5. Approach (chosen: A)
- A — Extend the metrics-engine pattern already started (
server/src/lib/metrics/financial.ts+routes/org-insights.ts). One pure function per domain, each takingMetricScope { clinicIds[], from, to, groupBy? }, doingWHERE clinicId IN (...) GROUP BY clinicin SQL against the read replica (getReadDb()/ANALYTICS_DATABASE_URL). Additive-only; parity-tested so single-branch console == that branch’s own dashboard. Chosen — lowest risk, egress-disciplined, already in flight. - B — Materialized rollup tables fed by cron. Faster at scale but new infra + sync lag + drift. Overkill at 4–40 branches. Rejected.
- C — Client-side fan-out to each branch’s per-clinic endpoints. N× egress, slow, violates egress rule. Rejected.
6. Architecture
6.1 Metrics engine (server/src/lib/metrics/)
Pure functions reading from the read replica, common options object:
{ totals, groups }:
financial.ts(exists) — billed, collected, outstanding (AR), AR aging 0–30/31–60/61–90/90+, collection rate, gross profit, margin, avg ticket, payment-method mix, status counts, trend.appointments.ts(new) — booked/arrived/completed/cancelled/no_show/missed + rates; no-show PKR bleed = no_show × avg completed ticket; new vs returning; chair & doctor utilization = booked minutes ÷ capacity (doctor_schedules× working days,roomsfor chairs).treatments.ts(new) — plans created, pipeline value, acceptance/conversion %, avg plan value, completion rate, top procedures by volume & revenue, per-doctor acceptance.patients.ts(new) — active patients, new (period), returning %, recall-due/overdue, churn risk (no visit 9mo+).accounting.ts(new) — cash-basis net profit (receipts − direct cost − opex − payroll), opex by category, payroll total, expense/revenue ratio.staff.ts(new) — headcount by role, attendance/absenteeism rate, present-today, on-leave, late arrivals, payroll cost, revenue-per-staff. Reads the new attendance/day-summary tables (kiosk punch system,feat/staff-hub-hr).inventory.ts(new) — stock value on hand, low/out-of-stock SKU count, items expiring, consumption rate, reorder-needed; cross-branch rebalance candidates.lab.ts(new) — open lab cases, avg turnaround (TAT), overdue, lab cost, by vendor.
AT TIME ZONE 'Asia/Karachi' (PKT convention). Each engine ships with a parity test: engine(single branch) == existing per-clinic route output.
6.2 API surface (server/src/routes/org-insights.ts, mounted /api/v1/org/insights)
Middleware requireInsightsScope resolves c.get('insightsScope') = { clinicIds, level }:
- org_admin → all
organization_clinicsfor their org →level:'network'. - branch_manager →
[assigned clinicId]→level:'branch'.
?from&to, optional ?branch=<clinicId> to focus one branch within scope; out-of-scope branch= → 403):
GET /financial(exists),GET /appointments,GET /clinical,GET /patients,GET /accounting,GET /staff,GET /inventory-lab— each →{ totals, perBranch[] }.GET /command→ KPI rollup + branch leaderboard + alerts (targets evaluated) + Ruby digest reference + live feed.GET /ruby/digest→ cached daily network digest (§7).GET /ruby/branch/:clinicId→ on-demand benchmarking commentary (§7).GET /targets,PUT /targets(exist) — per-branch / network targets (org_admin only).
6.3 Targets + alerts
Existingapp.ops_targets (organization_id, clinic_id nullable, metric, value, period). metric ∈ revenue_monthly | noshow_rate_max | collection_rate_min | utilization_min (extend as engines land). Alerts evaluator compares current-period actuals (engine output) to targets → { metric, clinicId, actual, target, severity }[], surfaced in Command Center + per-branch badges.
7. Module KPI catalog (the deliverable)
Console tabs map to engine domains. Each tab: network hero cards (KPIs), per-branch drill (leaderboard/table → click into branch dashboard), data source, Ruby hook. PKR currency,Banknote icon (never DollarSign), no raw clinic IDs surfaced.
| Tab | Network hero cards / KPIs | Per-branch drill | Data source | Ruby |
|---|---|---|---|---|
| Command Center | Ruby network digest · Revenue MTD vs target · Collection % · No-show bleed PKR · Net profit · Alert strip (targets breached) · Branch leaderboard · Network live feed | Rank branches; flag worst no-show & AR | all engines rolled up | Digest (daily) |
| Financial | Billed · Collected · Outstanding AR · Collection rate % · Gross profit + margin · Avg ticket · AR aging 0–30/31–60/61–90/90+ · Payment-method mix | Per-branch table + trend; click → branch Financial Hub | invoices, payments, receipts, quotations | Benchmark + digest |
| Operations | Booked→arrived→completed funnel · No-show / cancel rate · No-show PKR bleed · Chair & doctor utilization % | Per-branch rates + bleed + utilization; per-doctor | appointments, doctor_schedules, rooms | Predict no-show risk |
| Clinical | Plans created · Pipeline value (proposed PKR) · Acceptance / conversion % · Avg plan value · Completion rate · Top procedures by vol & revenue | Per-branch + per-doctor acceptance | treatment_plans, treatment_plan_procedures, quotations | Benchmark (“replicate top-acceptance branch”) |
| Patients | Active patients · New (period) · Returning % · Recall-due / overdue · Churn risk (no visit 9mo+) | Per-branch growth + retention | patients, appointments | Predict churn list |
| Accounting | Cash-basis Net profit · Opex by category · Payroll total · Expense / revenue ratio | Per-branch P&L comparison | expenses, payroll, receipts | Benchmark expense bloat |
| Staff | Headcount by role · Attendance / absenteeism · Present today · On leave · Late arrivals · Payroll cost · Revenue-per-staff | Per-branch roster + attendance + productivity | attendance/day-summary (kiosk), leave, payroll, users | Benchmark |
| Inventory & Lab | Stock value on hand · Low/out-of-stock SKUs · Items expiring · Open lab cases · Avg lab TAT · Overdue lab · Lab cost | Per-branch stock + low-stock; lab TAT by vendor; rebalance (X overstocked, Y short) | inventory, lab_tracking | Benchmark + transfer suggestion |
| Settings → Targets | Per-branch + network target editor | — | ops_targets | — |
8. Ruby layer (all four, phased)
- Network digest (P1): one DeepSeek call/day per org, cached; reads rolled-up engine output → narrative + anomalies + 3 ranked actions on Command Center. Highest signal, bounded cost.
- Branch benchmarking (P1): on-demand when a branch is opened → ranks that branch’s drivers vs network medians (gaps computed in SQL; Ruby narrates).
- Per-module insight strips (P3): one short read per tab, cached per range.
- Predictive KPIs (P3): month-end revenue projection, no-show risk, churn risk. Net-new modeling; validated before surfacing as numbers.
9. UI / navigation model
- One app, two altitudes governed by the scope selector (the demoted switcher):
Viewing: <branch> ▾default (normal clinic UI) → toggle ▲ Whole Network = console tabs. - Console tabs render premium KPI cards (sparklines + vs-target deltas), branch leaderboard, charts — not plain shadcn wraps. Visual pass required before “done” (reload + screenshot / Playwright).
- Every branch row → that branch’s existing dashboard (clinic-context + hard reload, reusing switcher plumbing) with persistent ← Back to Network.
- Branch managers see the same tabs scoped to their branch (leaderboard collapses to “your branch vs network average”).
10. Demo seed plan (removable)
ssh & Associates = HQ/main branch. Seed 3 sub-branches, deliberately differentiated so the leaderboard + benchmarking visibly earn their keep:DEMO-Gulberg— underperformer: no-show ~19%, heavy 60d+ AR, expense bloat, low utilization.DEMO-Johar-Town— strong: on-target revenue, high treatment acceptance, low no-show.DEMO-Karachi— mid: insurance-claim AR drag.
DEMO- prefixed → one-command teardown.
Prerequisite: org tables (organizations, organization_clinics, user_organization_assignments, ops_targets) and any drifted columns must exist on the shared prod DB — ensured via idempotent CREATE TABLE IF NOT EXISTS ensure-steps (sidebar-order / ensureOpsTargets precedent). Touching shared prod = explicit per-action confirmation at execution.
11. Error handling
- Empty branch set → zeroed totals + empty table (never throw), mirrors current
/org/overview. - Out-of-scope
branch=→ 403; branch managers cannot widen scope. - Missing reused table/column (pre-sync) → endpoint returns typed
degraded: true+ which module is unavailable → UI shows “needs setup” instead of 500. - Ruby call failure → console renders fully without the digest/strip (Ruby is enhancement, never a hard dependency).
- All queries wrapped in
handleErrorper route convention.
12. Testing
- Unit: each engine with a fixture of 2 clinics × known data → assert totals + per-branch split + boundary math (AR aging buckets, no-show bleed, utilization, TAT, attendance rate).
- Scope: branch_manager cannot read another branch (
branch=403; absent → own clinic only). - Parity: per-clinic dashboard numbers == console single-branch numbers (proves the shared engine).
- Smoke: the seeded ssh & Associates network is the live smoke target; eyeball each tab (visual pass) before calling UI done.
13. Phasing
- P1 (spine — money & leaks): Command Center + Ruby digest + Branch benchmarking + Financial + Operations + Accounting. (Financial half-built.)
- P2: Clinical + Patients + Staff (leans on the just-shipped attendance kiosk).
- P3: Inventory & Lab + per-module insight strips + Predictive + Communication/WA automation-gap signals.
14. Out of scope (this spec)
- Doctor commissions (net-new finance subsystem) — later.
- Cross-branch patient transfer (patients stay branch-local).
- Self-serve enterprise signup (manual provisioning only).
- Converging the legacy per-clinic routes onto the shared engine (separate, separately-verified step — not part of this build).

