← Work

2026Personal Project

TPM — A Decision Support Dashboard

A React dashboard sitting on top of a multi-sport ML betting pipeline — the design problem isn't 'show the data,' it's 'help the user decide.' Outcome review, governance control planes, and a parlay builder that respects model uncertainty.

ReactDecision SupportML UXInformation Architecture

Role: Designer / Engineer (solo) Type: Personal project — full-stack ML pipeline + frontend Stack: React 18, Vite, TypeScript, Tailwind, Python ML pipeline (NBA, NFL, NHL, MLB, golf, tennis)

The system

TPM (True Prop Model) is a multi-sport sports-betting decision platform. The Python side ingests odds, generates simulations, and produces consensus model outputs across NBA, NFL, NHL, MLB, golf, and tennis. The React frontend is what sits on top — a dashboard for working with the model output to make decisions.

The interesting design problem isn't "how do we visualize sports data?" It's: how do we design an interface that helps a human user make better decisions when the underlying recommendations come from a probabilistic model that is sometimes confidently wrong?

That question shaped almost every UX choice in the dashboard.

The dashboard structure

Five primary views, each tuned to a different decision moment:

  • Today — the daily front door. What does the model think is worth looking at now? Calibrated by recent performance, governance signals, and feed health.
  • Players — the searchable, filterable inventory. Filter by sport (NBA, NHL), source (Consensus, TPM-V, TPM, Simulation), and sportsbook (PrizePicks, Underdog).
  • Builder — a parlay builder that surfaces correlation, EV math, and overlap warnings inline as you compose legs.
  • Suggested — the model's own composition recommendations, presented as cards with explainers.
  • Yesterday Recap — outcome review. What did we recommend? What hit? What didn't, and why?

A persistent parlay strip rides along the bottom of the screen across views, so the cart of decisions-in-progress never gets buried.

Players view — searchable, filterable inventory of model picks; sport, position, team, source, and sportsbook all filter the same underlying set; per-row actions for Track and Add-to-parlay

Designing for model uncertainty

Three design choices that mattered most:

1. Outcome review is a first-class view, not an afterthought. Most ML-driven products show recommendations and stop there. TPM treats yesterday's outcomes as their own UI — categorized by reason (model miss, market move, late scratch, data feed issue), so the user can see why something didn't work, not just that it didn't.

Yesterday Recap — KPI strip across the top (suggested parlays, hit rate, totals), parlay summaries with EV vs. stake, and a per-pick feed showing model edge, sharp edge, bankroll allocation, and outcome

2. The Governance Control Plane. A view dedicated to model performance over time — calibration, feed health, slate health, archetype tuning. The point isn't to make the user a data scientist. It's to give them a legible picture of when the model is operating in its competence zone and when it isn't.

3. Sportsbook-themed shells. PrizePicks (yellow-green) and Underdog (purple) get distinct accent treatments. Not for branding alignment — for decision context. When you're building a PrizePicks parlay, the entire shell of the app shifts subtly. The constraints of that platform (fixed payout structures, multipliers) are different from Underdog's, and the design should remind you of that.

Suggested Parlays — model recommendations laid out across 2-leg, 3-leg, 4-leg, and Jackpot tiers; each card shows win probability, the constituent legs, and an explicit place to commit a stake

Information architecture

Nine domain hooks, each pulling a different slice of the model output:

  • useCards, useDailyReport, useFeedStatus, useGovernanceControlPlane, useOutcomeReview, useParlayBuilder, usePlayers, useRuns, useTrackedPicks

Each hook is responsible for one decision context. Components stay thin and don't know about API shapes. Drill-downs across views (e.g. clicking a flagged outcome to see the underlying governance slice) compose by passing selectors between hooks rather than by passing data.

The architectural principle: decision context is the primitive, not data type. A "player" object means different things in the Builder view than it does in the Recap view, so the hooks return decision-relevant projections rather than raw model output.

What this project proved out

When the product is decision support — not display — every UX choice has to be measured against "does this make the user's next decision better?" Pretty visualizations of probability distributions are easy. Designs that sharpen judgment under uncertainty are hard.

The same instinct that made the Optum medications work effective applies here: patients (and bettors) don't want a portal that displays data. They want one that helps them act. The product is the help, not the data.