Customer Success Intelligence
A customer-health dashboard for account managers at a multi-country distribution business, unifying order, financial, and training data behind one API — plus a companion gamified training PWA for field staff.
- Role
- Software Engineer
- Timeframe
- Mar 2026 — Present
The problem
Account managers at a distribution business operating across a dozen-plus countries had no single view of account health. Order history lived in one internal system, financial data (invoices, credit notes, budgets) lived in the company's ERP over SQL Server, and staff training completion lived in a separate learning system. Judging whether a customer relationship was healthy or slipping meant manually cross-referencing three places — or relying on gut feel.
I built the aggregation API and dashboard UI that answers that question directly: one screen per account manager, one composite health score per customer, backed by a single API that does the cross-referencing so nobody has to do it by hand.
Role & scope
This was a small team, not a solo build — a colleague owned a separate LLM-based assistant feature that reads from the same underlying data. My scope:
- Designed and built the customer-success aggregate API: order cadence, financial performance, fiscal-year-aware date logic, and training/learning outcomes, merged into one per-account response.
- Designed a weighted composite customer health score with graceful handling of missing data.
- Built the account-manager-facing dashboard UI end-to-end: budget pacing, year-over-year revenue, drill-downs, CSV export, and call-prep views.
- Rebuilt the frontend of a companion gamified training PWA for field staff, including a voice-response submission flow with transcription-based feedback.
Architecture
Data sources
Aggregation layer
Consumers
The API layer is a set of independent, composable loaders — order cadence, financial performance, learning outcomes — that a single orchestrator runs in parallel and merges by customer ID. Both the dashboard UI and my teammate's AI assistant sit on top of the same loaders, so a metric means the same thing in both places instead of drifting apart.
Key engineering decisions
Composite health score with graceful degradation
Account health depends on six signals (revenue, order cadence, catalogue coverage, training engagement, contact recency, trend) but not every customer has data for every signal — a new account has no trend history, a customer outside the training program has no education data.
- Default missing dimensions to zero — simple, but unfairly tanks the score for customers who are healthy on every dimension that does have data.
- Skip customers with any missing dimension — loses coverage on exactly the accounts that need attention.
- Score only on dimensions with signal, and redistribute their weight proportionally.
Each dimension is scored independently and returns null when there's no signal. The orchestrator drops null dimensions from the weighted average entirely, so a customer with 4 of 6 dimensions still gets a fair score. If every dimension is null, the result is an explicit “Unknown” state — never a misleading zero.
More orchestration logic than a flat weighted-average formula, but the score stays meaningful for the partial-data accounts that are common in a growing customer base.
Fiscal-year-aware date handling
Account managers think in fiscal years and month-over-month pacing, not calendar months. Comparing revenue to a prior period only makes sense if both periods are defined the same way — and budgets have their own fiscal-year boundary that doesn't always match the calendar.
- Use calendar-year comparisons everywhere and let the frontend relabel them.
- Compute fiscal-year start, prior-year comparison window, and pacing entirely server-side.
The API owns fiscal-year logic completely: FY start defaults through the last closed month, prior-year-over-year windows are computed to match the current range exactly (same span, minus one year), and budget pacing flags are derived server-side rather than approximated in the UI.
The API takes on more date-math complexity, but every consumer of the data — dashboard, exports, the AI assistant — gets identical fiscal-period logic instead of each reimplementing it slightly differently.
Separating order cadence from net revenue
A naive revenue query lumps invoices and credit notes into one running total. That hides two very different signals: how often a customer is actually ordering, versus how much net revenue they represent after returns and corrections.
- One combined revenue figure per customer.
- Split invoice activity (cadence signal) from net revenue (invoices minus credit notes) as two distinct metrics.
Order cadence is tracked from invoice activity alone; net revenue nets invoices against credit notes separately. The health score's revenue and cadence dimensions consume these as independent signals instead of one blended number.
Two queries and two mental models instead of one, but a customer with frequent orders and occasional corrections no longer looks artificially dormant or artificially high-revenue.
The training PWA
Alongside the dashboard, I rebuilt the frontend of a gamified training app for field staff: a Vue 3 / Pinia progressive web app with multiple-choice question streaks, a live leaderboard, and round-complete recaps. The most interesting part was the narrative-response flow — staff record a spoken answer, it's transcribed, and they get feedback on their submission. I hardened that flow for the realities of on-device recording: short or unclear audio, submission errors, and mid-session refreshes, all surfaced as inline, recoverable states rather than dead ends.