Skip to main content

Superadmin Tenant Activity Intelligence

Date: 2026-05-20 Goal: Replace platform-level-only analytics with per-tenant × per-user × per-module daily activity intelligence, with on-demand Ruby AI briefs.

Architecture

Cron (nightly 02:00 PKT)
  └─ rollup-tenant-activity → INSERT tenant_daily_activity (closed days)

UI (/dashboard?view=analytics, master-detail)
  ├─ GET /platform/tenants?range=7d              (rollup + today live)
  ├─ GET /platform/tenants/:cid/users?date=      (per-user breakdown)
  ├─ GET /platform/tenants/:cid/modules?date=    (per-module counts)
  ├─ GET /platform/tenants/:cid/feed?date=       (raw audit slice)
  └─ GET /platform/tenants/:cid/brief?date=      (Ruby on-demand + cache)

Schema (Drizzle / Neon)

tenant_daily_activity (rollup, partitioned by month long-term)

coltypenote
clinic_iduuidFK clinics
user_iduuidnullable for system events
module_keytextmaps from audit_logs.entity_type
activity_datedateday in PKT
total_actionsintsum
action_countsjsonb{create: 5, update: 2, delete: 1, view: 12, ...}
phi_access_countintfrom audit_logs.accessed_phi non-null
PK(clinic_id, user_id, module_key, activity_date)
Indexes: (clinic_id, activity_date DESC), (activity_date DESC).

tenant_daily_briefs (Ruby outputs)

coltypenote
clinic_iduuid
brief_datedate
headlinetextone-liner (Busy / Quiet / ⚠ Anomalies)
bulletsjsonbarray of bullet strings
anomaliesjsonbarray, can be empty
model_usedtexte.g. deepseek-chat
trace_idtextLangfuse trace
generated_attimestamptz
PK(clinic_id, brief_date)

Module Mapping

entity_typemodule_key:
  • appointmentsappointments
  • patients, patient-filespatients / patient_files
  • invoices, expenses, quotationsfinance
  • clinical-notesclinical_notes
  • treatment-planstreatment_plans
  • prescriptionsprescriptions
  • lablab_tracking
  • chat, messagescommunication
  • dicom, aidicom_imaging / ai_insights
  • … (fallback _other)
Mapping lives in server/src/lib/activity-modules.ts.

Cron

  • server/src/cron/rollup-tenant-activity.ts
  • Runs 02:00 PKT (21:00 UTC) via existing cron handler
  • For yesterday (PKT): INSERT ON CONFLICT DO UPDATE aggregation from audit_logs
  • Backfill script for first 30 days on deploy: server/scripts/backfill-tenant-activity.ts

Egress Optimization (Neon)

  • Tenant list query: read only rollup columns + clinic name (no joins, single indexed range scan)
  • Today’s live slice: WHERE clinic_id = ? AND created_at >= today_start_pkt (uses existing audit_logs index on created_at)
  • No SELECT * anywhere; explicit column lists
  • Per-user / per-module queries limited to single selected clinic_id (never full-table scans)

Ruby Brief (Langfuse-managed)

  • Prompt key: superadmin_tenant_daily_brief
  • Pushed via existing server/scripts/push-langfuse-prompts.ts
  • Output shape: { headline: string, bullets: string[], anomalies: string[] } (JSON mode)
  • Variables: clinic_name, date, total_actions, top_users[], modules[], phi_access_count, deletes, off_hours_actions, new_ips[]
  • Agent: server/src/lib/ai/agents/tenant-brief.ts
  • Cached in tenant_daily_briefs; regenerate button in UI forces refetch

UI (full master-detail replace)

  • ui/src/components/superadmin/PlatformAnalytics.tsx becomes the tab router shell
  • New components:
    • TenantIntelligence.tsx — outer layout
    • TenantList.tsx — virtualized list with search + sort (activity / phi / name) + filter (status, plan, has-anomaly)
    • TenantDetail.tsx — right pane
    • RubyBriefCard.tsx — headline + bullets + anomalies + “Regenerate” button
    • UserActivityTable.tsx
    • ModuleActivityBars.tsx
    • RawFeedDrawer.tsx
  • Platform overview = first virtual row “All Tenants” (pseudo-tenant)
  • Default range: 7 days; date picker for any prior day

Permissions

  • All /platform/* endpoints already requireSuperadmin
  • Brief generation logs to audit_logs (action: superadmin_view_tenant_brief)
  • PHI access counts shown but no raw PHI in any response or Ruby prompt

Out of Scope (V1)

  • Real-time websocket updates
  • Cross-tenant comparison charts
  • Export (CSV / PDF)
  • Alerting on anomalies (separate work)