Commercial Command Tower
A closed-loop commercial operating system: detect anomalies → diagnose root causes → recommend playbooks → assign owners → measure outcomes. Built for executive use, with an AI explanation layer over every surface.
1. The problem
A national telecom operator’s consumer division needed continuous commercial performance oversight across mobility, fixed-line, and growth services. The existing state was periodic reporting - by the time a metric problem reached leadership, the window to correct it had usually closed.
The requirement was not another dashboard. It was an operating loop: something that surfaces a deviation, explains why it happened, proposes what to do, routes it to an owner, and then tracks whether the intervention actually worked. A dashboard tells you a number moved; a command tower closes the loop between noticing and fixing.
Scoping identified 39 candidate signals across commercial KPIs, sales and channel, market and competition, customer care, marketing, loyalty, and network quality - so a further problem was prioritisation: which signals actually earn a place in an executive’s field of view.
2. The closed loop
Detect → Diagnose → Recommend → Assign → Measure
Five screens deliver that loop end to end, with an AI assistant layered across all of them for scoped briefings and free-form questions.
Cockpit - the executive landing surface
One screen, one scroll, every critical signal.
- KPI strip - headline metrics (revenue, ARPU, sales, subscribers, gross adds, churn), each tile carrying current value, week-on-week delta with health colouring, variance to target, an 8-week sparkline, and an always-visible Explain action that returns a 2–4 sentence LLM briefing scoped to that KPI and the active filters.
- Health donut - KPI counts by green/amber/red, click-through to filter.
- Heatmap - KPI × dimension grid (region / segment / channel), cells coloured by deviation against target. Clicking a cell does two things at once: applies that slice as a global filter cascading to every screen, and opens the Explain panel scoped to that exact slice.
- Now / Next stripes - open anomalies by severity, and forecast breaches.
- Global filter bar - time window, business unit, region, segment; state held in context and URL search params, persisted locally, and cascading to every chart on every screen.
Signal Radar - the anomaly workbench
Split into Now (currently breached) and Next (forecast to breach within the window), in a list-to-detail layout.
- Now detail: KPI mini-trend, contributing-driver breakdown with factor weights, estimated financial impact, and a suggested playbook with one-click handoff.
- Next detail: forecast band chart with breach probability and driver evidence.
- Workflow controls: forward/escalate to a role, assign an owner from a role-filtered list, and transition status (investigating / resolved / dismissed).
What the detection and diagnosis layers actually are - stated precisely
This matters more than the feature list, and I’d rather be exact than impressive:
Anomaly detection is a statistical rule, not machine learning. Per KPI, over a 52-week window: baseline is a rolling mean of the last 8 periods excluding the current one, with rolling standard deviation. Deviation is computed as a percentage against that mean and compared to per-KPI amber and red thresholds (defaulting to 8% and 15%). The severity score is a bounded z-score - min(1.0, z/3) - which is a crude normalisation, not a calibrated anomaly probability.
That is the correct engineering choice for weekly commercial KPIs on a demo-scale dataset, and I’d defend it: with 52 points per series and thresholds the business already reasons in, a seasonal-decomposition or learned model would add opacity and tuning burden without improving the decision. But it is a rolling-mean threshold detector and should be described as one. An anomaly-detection library sits in the dependency list unused - the residue of an earlier plan I abandoned in favour of the simpler approach, which I’d remove rather than leave implying capability.
Root-cause analysis is rule-based and heuristic, not causal inference. The primary contributing factor is the KPI’s configured go-to-market lever, weighted by severity. Root-cause categories come from keyword matching on the KPI name combined with the sign of the deviation, plus a competitor-action tag above a deviation threshold. Related levers come from a hand-written adjacency map. The confidence figure is a step function of how many periods of data exist - a data-sufficiency heuristic, explicitly not a statistical confidence.
The LLM sits on top of these already-fixed rule outputs to narrate them in prose. It performs no causal analysis - it is given the weighted factors and writes about them. Calling this “AI root-cause analysis” would misrepresent it; it is a rules engine with a natural-language presentation layer, and the honest version is still a useful product.
The “Next” forecast tab is a seeded synthetic mock, not a model. Breach probabilities, drivers, and horizons are generated from a fixed random seed for demo purposes. It is deterministic and reproducible, so it demos consistently - but there is no forecasting model behind it. I’m recording this explicitly because the surrounding documentation reads as though there is, and that gap is exactly the kind of thing that should be corrected before anyone repeats it in a client conversation. Shipping a designed-but-unbuilt surface as a visual placeholder is a legitimate demo decision; describing it as a forecast model is not.
Investigate - the diagnostic workbench
Walks from a KPI down to the specific slice moving it.
- Driver waterfall decomposing the week-on-week deviation into ranked contributing factors
- Related KPIs panel - correlation-based, with click-to-pivot
- Top slices - best and worst performers on a chosen dimension
- Slice comparator - two slices side by side with metric, deviation, driver breakdown, and recommended action each. This is the drill-in that answers “where is the problem, and how big is it?”
Playbook and Outcome Tracker
Intervention library, assignment, status progression, logged learnings, and measured outcomes - the half of the loop that most analytics tools omit entirely. Interventions carry before-and-after KPI values so an action’s effect is recorded rather than assumed. A later addition allows externally-logged interventions with no originating anomaly, because in practice teams take actions the system didn’t prompt, and a tracker that can’t record those tells a false story about what drove a metric.
Report Studio - effectively a second application surface
A section-based report builder with template gallery, saved reports, live data fetch at render time, and LLM-generated narrative structured on a four-beat spine - what changed / why / what matters / what to do - with PDF export.
Two design decisions worth naming: reports store builder state only, not cached section data, so a saved report always renders current numbers rather than silently ageing; and because narrative generation takes ~12 seconds, narratives are cached in a separate table keyed by a scope hash, which also avoided a schema migration on the reports table.
Supporting layers
A hand-built KPI taxonomy (~470 lines) defining the KPI hierarchy, category and subcategory structure, region lists, and per-KPI dimension eligibility - this static configuration is what drives filters, heatmap axes, and slicing app-wide, and getting it right was a substantial modelling exercise rather than boilerplate. Plus ingestion with upload, quality validation, source health and freshness tracking, and a business-event annotation system letting users mark campaign launches, pricing changes and outages onto Investigate charts as context (annotations only - deliberately not fed into detection, so a human-entered event can never distort a measured signal).
3. Architecture and engineering
Backend. FastAPI on Python 3.13 with fully async SQLAlchemy 2 over SQLite. Domain models are properly separated - anomaly, diagnosis, intervention, KPI, event, report, audit, user, data - rather than collapsed into a generic metrics table. API surface is split by concern: dashboards, anomalies, alerts, investigate, interventions, playbooks, KPI, ingestion, reports, agent, admin, auth.
Frontend. React 19 + Vite + Tailwind v4 + Recharts in TypeScript. Filter state is deliberately URL- and localStorage-backed, so an executive can share or restore an exact view.
Layered architecture. Route handlers in the API layer are deliberately thin; the real engine lives in a separate modules package (~2,400 LOC) covering the KPI taxonomy and computation, health classification, anomaly detection, the RCA engine, intervention matching, ingestion quality, and all LLM prompt construction. Business logic is therefore testable and reusable independently of HTTP.
Role-based access control - six distinct personas, each unlocking specific surfaces and actions:
| Role | Sees | Acts on |
|---|---|---|
| Executive | All commercial screens | Forward signals, approve playbooks |
| Business-unit head | All commercial screens | Assign interventions, mark outcomes |
| Functional leader | Functional view, own assignments | Update status, log learnings |
| Consultant | Read-only commercial screens | Comments only |
| Data engineer | Ingestion, source health | Configure connectors, retry ingestion |
| Administrator | Admin console | Full CRUD on KPIs, playbooks, escalation rules, users, sources |
Roles are enforced by JWT-claim role guards on write and admin routes (admin CRUD, interventions, ingestion, detection runs, report packs) and reflected in navigation visibility.
An honest gap I’d flag in review: enforcement is not uniform. Read surfaces - dashboards, investigate, alerts, playbook recommendations - require authentication but not a specific role, so any authenticated user can read them regardless of persona. Meanwhile the frontend carries a finer-grained capability model (6 roles × ~17 capabilities) that the backend does not mirror. For a single-tenant internal demo with six seeded accounts that is a defensible scope decision, but it means UI-hidden actions are not all API-hard, and it is the first thing I would close before this served real users with real segregation-of-duty requirements. Stating it plainly is more useful than claiming defence-in-depth the code doesn’t implement.
AI explanation layer. All LLM calls route through the firm’s internal gateway. The design decision worth naming: explanations are always scoped, never generic. The model receives the specific KPI, filters, region, segment, and channel - plus the underlying anomaly row and parent trend - so answers are grounded in the exact slice the user is looking at. It also falls back to deterministic summaries when no model key is configured, so the app never hard-fails on an LLM dependency.
Deployment. Azure App Service for Linux behind a private endpoint in a dedicated subnet, with outbound VNet integration for internal DNS resolution. Infrastructure defined as ARM/Bicep templates. JWT bearer auth with HS256.
5. What I’d highlight
- Closed-loop design, not dashboard design. Detect-diagnose-recommend-assign-measure is a fundamentally harder product to build than a reporting surface, and it’s the half that produces actual business change.
- Filter cascade as a first-class concern. One click on a heatmap cell simultaneously filters every screen and rescopes the AI explanation. Getting that state architecture right - context + URL + persistence - is what makes the tool feel like one system rather than six pages.
- Explanations grounded in slice context, which is the difference between a useful briefing and generic LLM filler.
- Graceful degradation - deterministic fallback when the LLM is unavailable.
- Six-persona RBAC designed around who actually acts on a signal, enforced at the API layer.
6. Skills demonstrated
closed-loop analytics architecture · anomaly detection and forecasting surfaces · root-cause decomposition (driver waterfall) · FastAPI + async SQLAlchemy 2 · React 19 / TypeScript / Recharts · complex cascading filter state · role-based access control · JWT auth · LLM explanation layer with scoped grounding · graceful LLM degradation · Azure App Service + private endpoints · infrastructure as code