Skip to content
All work

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
VueNuxtNode.jsMongoDBSQL Server ERPAWS
A note on this case studyThis is enterprise software built for a real employer on a small team. Company name, product branding, and any customer-identifying details have been generalized. All architecture, decisions, and figures below are accurate to what I built.
01

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.

02

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.
03

Architecture

Data sources

Internal orders & customers (MongoDB)
ERP financials (SQL Server)
Training / LMS completions

Aggregation layer

CS dashboard API — per-account loaders + health score

Consumers

Account-manager dashboard (Vue/Nuxt)
LLM assistant (built by a teammate, same data layer)

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.

04

Key engineering decisions

Composite health score with graceful degradation

Problem

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.

Options considered
  • 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.
Decision

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.

Trade-off

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

Problem

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.

Options considered
  • Use calendar-year comparisons everywhere and let the frontend relabel them.
  • Compute fiscal-year start, prior-year comparison window, and pacing entirely server-side.
Decision

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.

Trade-off

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

Problem

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.

Options considered
  • One combined revenue figure per customer.
  • Split invoice activity (cadence signal) from net revenue (invoices minus credit notes) as two distinct metrics.
Decision

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.

Trade-off

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.

05

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.

06

Working on a shared codebase

Team contextUnlike STDNT GEAR, this wasn't a solo build. Working here meant keeping my aggregation logic consistent with a teammate's AI assistant reading the same data — coordinating on shared loader modules and data shapes rather than working in isolation, and fixing parity issues when the two surfaces disagreed on a number.
07

Scale

6
weighted health-score dimensions
3
systems unified into one API
12+
countries served by the business
2
products shipped (dashboard + PWA)