Skip to content
All work

Case studyFrontend Architecture2022

Healthcare Analytics Platform

An interface for exploring healthcare datasets: summary views showing the shape of a population, over-time views showing how it changes, and detail tables for the records behind either.

analytics · summary + over time

Cohort

Coverage

Utilisation

Trend over time

Monthly
MeasurePeriodSegmentValue
Three views, one set of piecesSummary cards, an over-time chart and a detail table, none of which contains a chart implementation. All three are composed from the same frame, legend, axis formatter and state handling, which is why a fix to any of those lands everywhere at once.

Interface recreated for portfolio purposes.

01

Problem to outcome

The complete case study in seven parts. The sections below provide the engineering detail behind this summary.

Problem

Each new business question arrived as a request for another dashboard. Answering each with its own rendering code would have produced a dozen dashboards that looked almost but not quite the same, and a dozen places to fix any given bug.

Context

An interface for exploring healthcare datasets: summary views showing the shape of a population, over-time views showing how it changes, and detail tables for the records behind either.

Constraints

  • Confidential data, including here
  • A filter space too large to enumerate
  • Absent data is meaningful

My role

Senior Frontend Engineer, focused on the visualisation system and the data layer beneath it, rather than on the medical domain.

Approach

Dashboards are built from a few shared pieces rather than from separate code each time. Adding a view means configuring what already exists, which is also why fixing a tooltip or an empty state fixes it everywhere at once.

Solution

Build a small set of shared chart pieces that every view is composed from, so adding a dashboard becomes configuration rather than code, and fixing a tooltip fixes it everywhere.

Outcome

Adding a dashboard became a matter of composing existing pieces with a data source, which changed the unit of work from weeks to days and kept every view consistent with the others.
view types composed from one set of pieces: summary, over-time, detail
place to fix a tooltip, a legend or an empty state
to add a dashboard, once composition replaced implementation
kinds of nothing told apart: empty, suppressed, pending
02

What is the healthcare platform?

An analytics interface for exploring healthcare datasets: summary views for the shape of a population, over-time views for how it moves, and detail tables for the specific records behind either.

This case study is deliberately about the interface system rather than the medical domain. The engineering (shared pieces, caching, state modelling) is the transferable part.

How a dashboard gets added

  1. 01

    Take the question

    What this view has to answer

  2. 02

    Point at a data source

    A pre-aggregated endpoint

  3. 03

    Compose the pieces

    Frame, legend, axes, tooltip

  4. 04

    Declare the filters

    The view inherits the model

  5. 05

    States come free

    Empty, suppressed and partial handled

  6. 06

    Accessibility comes free

    Naming, table view, keyboard

  7. 07

    Ship

    No new chart implementation exists

Steps five and six are the point of the system: the states nobody demos, and the accessibility work, arrive already done.

What it does

Shared chart system
Frame, legend, axis formatting and tooltips: the pieces every dashboard in the product is composed from.
Honest empty states
Empty, partial and suppressed data are modelled separately, so the interface says which kind of nothing it is showing.
Filter model
A canonical signature reduced from the filter state, which is what makes caching hit across a filter space too large to enumerate.
Summary, over-time and detail views
Three ways of asking about the same population, built from the same pieces rather than three implementations.
Large tables
Sorting, grouping and column management on Material React Table, behind a wrapper thin enough to stay replaceable.
Exports with context
Screenshot and export workflows carry the filters they were taken under, so the output can be cited.
03

Why the problem was difficult

One dashboard is a week's work. The difficulty is the twelfth, when the product has been answering a growing list of questions about the same underlying data and each answer arrived as a request for another view.

Built the obvious way, that produces a dozen dashboards that look almost but not quite the same: twelve tooltips, twelve legends, twelve slightly different empty states, and a dozen places to fix any given bug. The inconsistency is the visible symptom; the maintenance cost is the real one.

Underneath that sits a subtler problem. The filter surface is wide enough that the same data gets sliced many ways in one session, so caching behaviour is something users feel directly, and absent data is common and meaningful, which means an empty chart is technically true and practically misleading.

How do you make adding a view cheap without making every view the same, and without the pieces becoming a framework nobody can safely change?

The question the rest of this page answers
04

Contribution and ownership

Visualisation system

What is a chart made of?

  • The shared chart pieces: frame, legend, axis formatting, tooltips
  • Explicit handling for empty, partial and suppressed data
  • Summary, over-time and detail views built on top of them

Data layer

What makes it fast?

  • The filter model and its canonical signature
  • Caching keyed on what filters mean rather than how they were written
  • Prepared data reused across identical filter combinations

Tables

How are records read?

  • The large-table experience on Material React Table
  • Sorting, grouping and column management
  • A wrapper thin enough that the library stays replaceable

Quality

What kept it consistent?

  • Accessibility built into the shared frame rather than per view
  • Tests concentrated on the pieces and on what they are handed
  • Export workflows that carry their filter context

What isn’t mine

Clinical and analytical interpretation belonged to qualified domain experts. The data team owned the model, APIs, and server-side aggregation, while the design team owned visual direction. The transferable frontend contribution covered the interface system and its supporting data layer.

05

The constraints shaped the product

Confidential data, including here

Nothing recognisable can leave the product. That applies to this case study too, which is why the visuals are abstractions and the discussion stays at the level of the interface system.

The engineering has to be explicable without a single real screen, so the architecture had to be the interesting part.

A filter space too large to enumerate

With this many dimensions, you cannot pre-configure cache keys for the combinations users will actually ask for, and two identical requests routinely arrive written differently.

Caching had to be derived from the meaning of the filters rather than configured per combination.

Absent data is meaningful

Healthcare analytics is full of legitimately missing numbers: filtered to nothing, too small to report, not yet available. Showing all three as an empty chart is technically true and practically misleading.

Each state is modelled explicitly and presented differently, so the interface says which kind of nothing it is showing.

Exports have to be citable

An export that does not state what it was filtered to cannot be used as evidence in anything.

Export and screenshot workflows carry their filter context with them rather than producing a bare image.

The number of views would keep growing

The product's job is to answer new questions about the same data, so 'more dashboards' is not a phase that ends.

A growing number of views could not be allowed to become a growing number of chart implementations.

06

How it fits together

Dashboards are built from a few shared pieces rather than from separate code each time. Adding a view means configuring what already exists, which is also why fixing a tooltip or an empty state fixes it everywhere at once.

  1. Analyst

    Asking about a population

  2. Views

    SummaryOver-timeDetail

  3. Shared pieces

    FrameLegendAxesEmpty and suppressed states

  4. Filter model

    Reduced to one canonical signature

  5. Data layer

    Cached per signaturePrepared data reused

  6. Analytics APIs

    Pre-aggregated, owned by the data team

Views are assembled from shared pieces rather than written. Adding one means configuring what already exists, which is also why fixing a tooltip or an empty state fixes it everywhere at once.
07

Decisions that shaped the product

Each of these solved a real problem and cost something in return. The trade-off is stated alongside the decision, because a decision without one was not really a decision.

Decision 01

Building shared pieces before building more dashboards

Problem

The first two dashboards were built independently. A third request would have produced three subtly different legends, three tooltip behaviours and three ways of showing 'no data'.

Decision

Extract a small set of shared pieces (chart frame, legend, axis formatting, empty and suppressed states) and compose dashboards from them. Extracted at the third instance, not the first.

Trade-off

Shared pieces constrain what a one-off dashboard can look like, so an unusual request becomes either a new option or an argued exception. That friction is the point, but it is still friction.

Result

New views became configuration rather than new code, and fixing something fixes it everywhere.

Decision 02

Making 'no data' say which kind of nothing it is

Problem

Healthcare analytics is full of legitimately absent data: filtered to nothing, too small to report, not yet available. Showing all of these as an empty chart is technically true and practically misleading.

Decision

Model each of those states explicitly and give each its own presentation, so the interface says which kind of nothing it is showing.

Trade-off

Every shared piece carries more state handling than a happy-path chart would, and each view has to decide what its states mean. More code for a case that never appears in a demo.

Result

Users can tell 'nothing matched your filters' from 'this cannot be shown'. That is the difference between a working filter and a broken dashboard.

Decision 03

Caching on what the filters mean, not how they are written

Problem

With this many filter dimensions, two identical requests arrive written differently (different key order, different ways of saying empty), so a naive cache misses almost every time.

Decision

Reduce the filter state to one canonical signature and cache against that, so requests that mean the same thing are treated as the same request.

Trade-off

That signature is infrastructure that must stay in step with the filter model. Get it wrong and you serve stale data, which is worse than a cache miss. It is unit-tested for exactly that reason.

Result

Returning to a view the user has already seen is instant rather than another round trip.

Decision 04

Adopting a table library instead of building one

Problem

The table needed sorting, grouping, column visibility, pinning and export. Building that well is a project; building it badly is a support queue.

Decision

Adopt Material React Table, and keep our own wrapper thin enough that the library stays replaceable.

Trade-off

It is a substantial dependency with its own opinions and upgrade cost, and some styling has to be argued with rather than written. The alternative was owning a grid nobody was funded to own.

Result

Full-featured tables consistent with the design system, without a grid to maintain.
08

Composing dashboards instead of writing them

A component library is easy to start and hard to get right. Extract too early and you encode assumptions from the only view that exists; extract too late and you are reconciling five implementations that already drifted.

The pieces here came out of the third dashboard, not the first. By then the shape was visible in real usage rather than guessed, and the thing worth stealing from this project is that timing, not the components.

Extract on the third, not the first
Doing it at the first dashboard would have produced the wrong abstraction confidently. Waiting for three gave a shape drawn from real usage.
The frame carries the hard states
Empty, partial and suppressed live in the shared frame, so the states nobody demos are handled identically everywhere instead of per view.
Accessibility is inherited
Accessible naming, table equivalents, keyboard-reachable controls and non-colour-only encoding come with the frame rather than being remembered per chart.
One tooltip, one legend, one formatter
A fix to any of them lands across every dashboard at once. That is the entire return on the abstraction.
Thin wrappers over libraries
Material React Table sits behind a wrapper deliberately kept thin, so the dependency stays replaceable rather than becoming load-bearing.
Composition, not configuration sprawl
A view declares what it shows and what it filters on. When a piece needs a genuinely new capability, that is a signal to look at the piece, not to add another flag.
09

Frontend architecture

The organising idea is that a dashboard should not contain a chart implementation. What each layer owns follows from that, and so does where the tests are worth writing.

Views

Which question is being asked, and of what

Deliberately thin. A view is a data source, a filter declaration and a composition. If it grows rendering logic, something has gone wrong upstream.

Shared pieces

Frame, legend, axes, tooltips, empty and suppressed states

The only place a chart is actually implemented, which is what makes a single fix land everywhere.

Filter model

The filter state and its canonical signature

The signature, not the raw filter object, is what the cache keys on, so requests that mean the same thing are the same request.

Server state

Pre-aggregated responses per signature

Cached and revalidated in the background, which turns most navigation into a repaint rather than a request.

10

Deep dive: caching on what the filters mean

With this many filter dimensions, two identical requests arrive written differently: different key order, different ways of expressing empty, keys that do not affect the result at all. A cache keyed on the raw query misses almost every time, and the user feels every miss.

?to=mar&from=jan, ?from=jan&to=mar, ?from=jan&to=mar&sort=asc all resolve into One canonical signature (Key order normalised · empties unified · irrelevant keys dropped), which is then read by Cache hit and No second request.

UI
Returning to a slice the analyst has already seen is instant. Most navigation becomes a repaint rather than a round trip.
State
The signature is the cache key and the memoisation key, so prepared chart data is reused whenever the filters mean the same thing as before.
Risk
The signature must stay in step with the filter model. Get it wrong and you serve stale data, which is worse than a cache miss, so it is unit-tested for exactly that.
Cost
It is infrastructure rather than a feature. Nobody sees it, it has to be maintained alongside every filter change, and the product is noticeably worse without it.
11

Trade-offs I had to make

Shared pieces vs per-view freedom

A shared system makes the twelfth dashboard cheap and makes any view that genuinely needs to be different harder to build.

ChoiceCompose from shared pieces, and treat a view that fights them as a signal to look at the piece rather than to add an escape hatch. Documenting their constraints properly would have made that boundary clearer than it was.

Handling every empty state vs shipping the happy path

Modelling empty, partial and suppressed separately means every shared piece carries more state handling than a happy-path chart would, for a case that never appears in a demo.

ChoiceModel them anyway. Telling 'nothing matched your filters' from 'this cannot be shown' is the difference between a working filter and an apparently broken dashboard.

Adopting a table library vs owning one

Material React Table is a substantial dependency with its own opinions, an upgrade cost, and styling that sometimes has to be argued with rather than written.

ChoiceAdopt it, behind a thin wrapper. Building sorting, grouping, pinning and export well is a project; building it badly is a support queue nobody was funded to staff.

12

Where it is now

Adding a dashboard became a matter of composing existing pieces with a data source, which changed the unit of work from weeks to days and kept every view consistent with the others.

The states nobody demos (no data, suppressed, partial) are handled the same way everywhere, which is where analytics interfaces usually lose their users' trust.

Built
A shared chart system every dashboard in the product is composed from
Designed
A canonical filter signature that makes caching hit across an unenumerable filter space
Modelled
Empty, partial and suppressed data as distinct states with distinct presentation
Changed
Adding a dashboard from weeks of work to days, and kept every view consistent with the others
13

What this experience changed

Extract the abstraction on the third one

The first is a guess and the fifth is a reconciliation. Waiting until the third gave a shape drawn from real usage, and it is the decision from this project I would keep unchanged.

Shared code needs written boundaries, not reference implementations

Reading existing dashboards is a workable way to learn a system and a bad way to learn its limits. Twice, someone reasonably extended a piece in a way that broke it elsewhere.

Empty states are where analytics tools lose trust

The states nobody demos are the ones that decide whether a user believes the dashboard. An empty chart that could mean three different things is worse than an error.

Cache on meaning, not on spelling

Users experience a cache miss as slowness, not as a technical event. Normalising what a request means turned a cache that almost never hit into one that almost always does.

14

What I'd improve next

  • 01Document the shared pieces as a real internal API with their constraints written down, rather than relying on existing dashboards as reference implementations
  • 02Visual regression tests on the chart pieces, which is the clearest case for them and where review was doing the work instead
  • 03A stated contract for what a view may and may not override, so extending a piece is an explicit decision
  • 04Surface the filter context inside the interface, not only on exports
  • 05Cache warming for the handful of slices that open almost every session

05 / Contact

Have a complex product problem worth solving?

Available for senior frontend, product engineering, and AI product engineering opportunities.

connection.openavailable
  1. 01
  2. 02
  3. 03

    Location

    Bangalore, India · Remote