Example Projects

These projects demonstrate the core problems I solve: making distributed systems explorable, designing effective human-in-loop AI, and building interfaces for complex technical infrastructure.

Each represents deep integration work that off-the-shelf tools couldn't solve.

  • Document Substrate: Document AI for professional practices — parse, PII redaction, schema-driven extraction and a deterministic verification engine, so a cloud LLM can work on client files it is never allowed to see
  • Cycling Training Intelligence: Interactive 3D geospatial route viewer plus a grounded AI coaching layer with three-tier memory, hybrid retrieval, a reinforcing evals loop, and an authenticated MCP server
  • Deep Search: Production AI research pipeline with observability and evaluation frameworks
  • Darwinium: Making complex fraud networks visible through interactive graph visualization
  • Syft: Real-time monitoring for globally distributed instrument fleets
  • Mersen R-TOOLS: Engineering simulation UX handling long-running async compute workloads

Document Substrate: AI on Documents You Can't Send to a Model

The Problem: The documents most worth building AI on — bank statements, tax returns, invoices, financial statements — are the documents most laden with personal information. A professional practice can't send raw client files to a cloud LLM, and the local-model alternative means accepting a capability trade-off on exactly the work that needs the capability. Meanwhile the naive pipeline fails on its own terms: tables don't survive flat text extraction, and "what were the total repairs in Q2?" is an aggregation over structured data, not a semantic search over prose. Worst of all, in accounting-adjacent work the cost of a wrong verdict is categorically higher than the cost of a wrong sentence. A hallucinated explanation is an embarrassment; a hallucinated completeness verdict is a professional liability.

What I Built: A reusable document substrate — parse, redact, classify, extract, index — with a strict three-layer separation so the model only ever sees placeholders, plus a verification-first workflow layer built on top of it for accounting practices. The governing constraint is one sentence: LLMs extract, the engine decides, humans review. No model call is allowed to determine completeness or do arithmetic. It is deployed single-tenant, one install per customer, and instantiated from a shared foundation repo.

The Ingest Pipeline

  • Six durable Inngest steps — parse → redact → classify → chunk_embed → summarize → ready — each independently retryable, idempotent at the database level, and resumable from any earlier step
  • LlamaParse handles OCR and layout reconstruction; documents over 40 pages are sharded into 25-page windows submitted concurrently and awaited with a durable sleep-poll rather than a blocking request
  • Extraction is deliberately not a pipeline step. As a step, one extract failure aborted the run before embedding and left a perfectly parseable document unsearchable — so it has its own function, its own status lane, and its own concurrency cap, and the document stays answerable in chat throughout
  • Every extracted scalar is grounded: re-located in the page text with the same matcher that highlights citations, and stored with its page and character span. Without that, a number that was never on the page is indistinguishable from one that was
  • Confidence-gated auto-classification files a document by its contents, not its filename — above the threshold the type is applied automatically (guarded so it can never overwrite a human decision), below it, it stays a suggestion

The PII Boundary

  • Three-stage hybrid redaction: regex rules for anything with a fixed shape or checksum (IRD, NZ and AU bank accounts, NZBN, credit cards with a Luhn check), a Haiku pass on every page for what only context reveals (people, organisations, addresses), and an independent detector as a post-hoc smoke check
  • Real values are encrypted into an entity vault with AES-256-GCM; the redacted text becomes canonical for everything downstream. Embeddings, chunks, summaries, extracts, prompts, transcripts and traces hold only opaque tokens
  • Placeholders are scoped per document with a short document code ([A1B2C3D4_PERSON_1]) so the same string can never denote two different people across a corpus — a subtle bug the plain [PERSON_1] form made structurally possible
  • Hydration is the linchpin: it refuses to run without an explicit document scope, refuses placeholders outside that scope, runs server-side after the model has finished reasoning, and writes an immutable audit row every time
  • A pre-flight scan on every outbound prompt fails closed if it matches PII. Cross-document identity resolution — linking the same person across files — is fuzzy-clustered, LLM-adjudicated, and then human-gated, because a false merge surfaces one person's data under another's name

Retrieval and Query

  • Six parallel retrieval legs (vector, keyword, entity, fuzzy, plus boost legs) fused with Reciprocal Rank Fusion, preceded by a document-grain router that answers "which document?" before chunk search answers "where exactly?"
  • The user's question is itself redacted before retrieval — plaintext mentions are rewritten into the tokens the index actually holds
  • Citations deep-link to the page, and clicking one opens the source document beside the answer with the passage highlighted
  • Two deterministic gates on the way out: an output guard that repairs or withholds the answer, and a figure guard that flags any number no excerpt supports. The model is never permitted to do the arithmetic — code sums the stored figures, kept separate per currency

Verification-First Workflows

  • A workflow is a saved job over a client collection — job type, reporting period, file selection — with a two-axis status model that never collapses "the job crashed" into "the client hasn't sent their bank statements"
  • Layer A checks what goes in: a pure TypeScript engine assigns documents to slots by confirmed type and checks presence, period and cross-document consistency. Same files, same verdict, every time. A check that cannot run returns needs_review — never a silent pass
  • Layer B checks what comes out: the generated workpaper is reconciled against the documents it was built from, and a figure that traces to nothing is marked unsupported rather than passed
  • 22 document types carry extraction schemas; 18 of them carry arithmetic checks — a bank statement must foot from opening to closing balance, an invoice's lines must add to its subtotal, dates must sit inside the stated period. A failed reading gets exactly one repair attempt, accepted only if it introduces no figure absent from the page
  • One verification queue collects everything waiting on a person: stale runs, readings whose checks fired, and open exceptions — so a reviewer works a queue instead of hunting through files
  • Completed runs generate an .xlsx workpaper and a .docx draft client letter through deterministic renderers, with no LLM in the mapping, so the letter cannot state a figure the workpaper doesn't carry

The Practice Manual

  • Four external reports recommended an ontology, possibly graph-backed. The underweighted finding was that the registries already are the ontology — extract schemas declare the nouns, workflow definitions declare the rules — so what shipped is a pure function over those registries emitting a machine-readable manifest, computed on request and never checked in, so it cannot drift
  • The graph database lost on three arguments, the decisive one structural: entity names are exactly what the vault encrypts, so a graph outside Postgres must either replicate cleartext PII outside RLS and the audit trail, or store tokens nobody can query by name. Both branches lose
  • Alongside it sits the practice manual — the firm's own written account of how each job is worked, in prose, versioned in git. A contract test walks every chapter and fails the build if a named rule no longer exists, or if a workflow gains a rule no chapter covers, so the manual cannot rot
  • The manual explains and links; it never drives engine behaviour. Authority flows one way: registry → manifest → manual

The Quality Loop

  • Thumbs on any answer land in a Triage inbox, where each rated answer is promoted into the permanent golden set or captured as a regression case — so every future change is tested against both before it ships
  • Evalite suites split into deterministic scorers that gate CI and LLM-judged suites that are read as a trend, never a gate. On the redaction suite, NoLeak must stay at 1.0 across every case
  • The evals run the same executor the production route runs. The two drifted apart once, and the evals scored a pipeline that wasn't the one in production
  • Langfuse and OpenTelemetry for prompt-versioned traces, per-user spend caps and daily rate limits that render as an explaining assistant turn rather than a dead send

Stack: Next.js 16 (App Router), TypeScript, Supabase (Postgres + pgvector HNSW + RLS + private Storage), Inngest, LlamaParse, LlamaExtract, Vercel AI SDK + AI Gateway, Claude Sonnet and Haiku, text-embedding-3-small, AES-256-GCM, Uppy + TUS, Zod, TanStack Query, Jotai, Evalite, Langfuse, OpenTelemetry

Related writing:


Cycling Training Intelligence

The Problem: Indoor cycling training data was trapped in binary files — just numbers and GPS coordinates. Athletes needed to explore their training routes cinematically, and needed an AI coach whose advice was grounded in their actual power data, fitness trajectory, and past coaching conversations rather than the generic blog content every off-the-shelf LLM falls back on.

What I Built: A full coaching platform — an interactive 3D route viewer that turns FIT files into cinematic experiences, plus an AI coaching layer with intent routing, layered prompts, three-tier memory, and hybrid retrieval, plus a reinforcing evals loop that improves the coach with every user interaction, plus an authenticated MCP server that makes the same coach composable inside Claude Desktop, n8n, Zapier, and other agent hosts. The coaching layer reasons over the Performance Management Chart (CTL/ATL/TSB) — the same fifty-year-old impulse-response model human coaches use — so every recommendation is anchored in the rider's own numbers, not textbook averages.

3D Route Viewer

  • 3D terrain rendering with cinematic camera animations following route progress
  • Real-time synchronized charts for elevation, speed, power, heart rate, and cadence
  • Binary FIT file parsing and validation with resumable TUS uploads
  • M4 downsampling algorithm for 60fps performance with thousands of data points

AI Coaching Architecture

  • PII redaction in-place at the top of the pipeline — single source of truth, no downstream code path can skip it
  • Haiku-as-router classifies every message into one of five intents (workout generation, ride analysis, route search, coaching chat, off-topic) with a hard short-circuit for off-topic that saves the Sonnet call entirely
  • Ten-layer system prompt assembled per-request (safety block, view-specific base prompt, profile memory, ride data, route context, search context, episodic context, intent hint, skills manifest) — context injected as XML tags, not free prose, so the model treats it as data rather than instructions
  • Three-tier memory: permanent profile memory (FTP, weight, goals) on the user row; per-session episodic memories with key insights, athlete signals, and open questions; and full chat-message indexing chunked on structural boundaries
  • Hybrid search via Reciprocal Rank Fusion across four parallel Supabase RPCs (keyword + semantic on chat messages and session memories), with a Haiku-extracted query analysis step targeting the right slice of history
  • Skills system — markdown-with-frontmatter SKILL.md files for /ride, /week, /form, /training — preloaded for known intents and runtime-discoverable via list_skills / load_skill tools
  • Per-task model slot system through Vercel AI Gateway: Haiku for cheap high-frequency tasks, Sonnet for coaching responses, Gemini Flash Lite for insights, OpenAI embeddings for vectors — swapping a model is a one-line config change

Reinforcing Evals Loop

  • Prompt versioning with stable IDs flowing into both the traces table and Langfuse metadata, so every AI response is permanently linked to the exact prompt version that produced it
  • Admin trace triage UI that promotes real production failures into Evalite golden + regression fixtures
  • CI-runnable Evalite suite that gates prompt and skill changes against the fixture set
  • OpenTelemetry + Langfuse telemetry for P50/P95 latency, token-per-response trends, cost breakdowns by model tier, and full multi-step span trees per prompt version

MCP Server

  • OAuth 2.1 + PKCE for interactive clients (Claude Desktop) plus Personal Access Tokens for scripts and cron jobs, both resolving to the same opaque-token table with SHA-256 hashing at rest
  • Tools, resources, and prompts mirroring the web chat surface (search rides, get fitness state, generate workouts, ask the coach) with coarse-grained additive scopes (rides:read, insights:generate, chat:send, …) and double-checked authorization at registration and execute time
  • The ask_coach MCP tool reuses the exact same runChatPipeline() the web UI calls — same PII redaction, same intent router, same off-topic short-circuit, same safety layer — so security and coaching-quality logic never drift between transports
  • Per-user, per-client, per-cost-class rate limits so a chatty Zapier cron can't starve the user's daily generation budget

Domain Model: PMC, TSS, CTL/ATL/TSB

  • Critical Power and Normalized Power computed from raw .fit files, feeding TSS for every ride
  • Performance Management Chart with exponentially weighted Fitness (CTL ~42d), Fatigue (ATL ~7d), and Form (TSB) — the quantitative model the coach reasons in for every "how am I going?" and "should I push today?"
  • Subjective Feeling and RPE captured per ride and merged with the objective metrics, so the coach can distinguish "TSB says you're fresh but you reported feeling cooked"

Stack: React, Next.js, TypeScript, Mapbox GL JS, Turf.js, Supabase (Postgres + pgvector HNSW + GIN full-text), Vercel AI SDK, Vercel AI Gateway, Claude Sonnet 4.6, Claude Haiku 4.5, OpenAI text-embedding-3-small, Gemini Flash Lite, Evalite, Langfuse, OpenTelemetry, @modelcontextprotocol/sdk, OAuth 2.1 + PKCE

3D Route Viewer

3D Cinematic Route — Pico del Teide, Tenerife, Canary Islands.

3D Route Viewer with workout performance charts

3D Cinematic Route with Workout Performance Charts — Rolling Thunder Workout, Villenotte to Saint-Seine-l'Abbaye, France.

CTI AI Coaching Chat on the Globe View

Grounded AI Coach — Coaching responses anchored in the rider's actual rides, fitness state, and past conversations. The chat sits alongside the globe view; the same runChatPipeline() runs whether the question came from the web UI or an MCP client.

CTI Performance Management Chart

Performance Management Chart — CTL (Fitness), ATL (Fatigue), TSB (Form) computed from daily TSS. The quantitative model the coach reasons in for every load and recovery decision.

The CTI series — full case studies:

CTI is live in production at routebook.app.


Deep Search: Production AI Research Pipeline

The Problem: Generic AI search tools couldn't handle rate-limiting, prompt versioning, or provide observability needed for production use. Teams needed AI search they could actually deploy and monitor.

What I Built: A production-grade deep search application with full engineering observability, evaluation frameworks, and prompt management. Not a demo—a system designed to run reliably at scale.

Core Capabilities:

  • Distributed rate-limiting across search providers
  • Local evaluation framework for rapid iteration
  • Full observability with traces and metrics
  • Prompt versioning and A/B testing infrastructure

Stack: React, Next.js, AI SDK, Postgres, Redis

LinkedIn Article: Building a Production-Ready Deep Search Application: Architecture, Evals, and Telemetry

Deep Search Sources

Deep Search Sources — Expandable showing sources scraped for detailed information.

Deep Search Research

Deep Search Research — Expandable showing LLM response with planned actions.

Deep Search Answer

Deep Search Answer — Final answer with links for reference sources.

Deep Search Local Evals

Deep Search Local Evals — Local evals run on your development server and make iteration quick and easy.

Deep Search Engineering Observability

Deep Search Engineering Observability — Platform provides continuous observability with traces, evals and provides prompt management and metrics for continual improvement.


Darwinium: Making Fraud Networks Visible

The Problem: Fraud detection generates complex relationship graphs that are impossible to understand with standard dashboards. Analysts needed to explore network patterns in real-time, not just view static reports.

What I Built: Interactive network visualization and real-time dashboards that made fraud patterns explorable. Custom graph layouts, filtering, and grouping that revealed patterns hidden in the raw data.

Core Capabilities:

  • Interactive network graphs with custom node types and relationships
  • Real-time dashboard system with user-configurable layouts
  • Geospatial visualization with Mapbox integration
  • High-performance rendering for large-scale fraud networks

Stack: React, AntV (G6), Mapbox, GraphQL

Interactive Network Graphs

Identity Graph — Interactive Network Graphs with custom nodes, grouping, linking and filtering.

Dashboard Cards

Dashboard Cards — Configurable cards allow selecting custom data rendering components for each card.

Dashboards with configurable tabs

Dashboard Layout — Multiple dashboards are supported using tabs. Each dashboard has user configurable resizable panels and tab layouts. Panels have user configurable card layouts and card positioning.


Syft: Distributed Instrument Fleet Monitoring

The Problem: Mass spectrometry instruments deployed globally generated performance data that was invisible until failures occurred. Teams needed real-time visibility into instrument health across entire fleets.

What I Built: Real-time monitoring dashboard that ingested IoT data streams and surfaced instrument health patterns. Made distributed hardware infrastructure understandable through visual summaries and performance timelines.

Core Capabilities:

  • Fleet-wide health visualization at a glance
  • Real-time performance tracking for individual instruments
  • IoT data ingestion and processing pipeline
  • Context-aware help system tailored to customer configurations

Stack: React, Recoil, AntD, Python GraphQL, AWS IoT/data stack

Instrument Fleet

Instrument Fleet — Cards provide a quick visual overview of the health of all instruments deployed in the fleet.

Instrument Fleet

Instrument Quick View — Easily see more details for the reagent of a specific instrument from the fleet list view.

Instrument Fleet

Instrument Details — Selecting an instrument from the fleet view opens a new instrument tab with performance timeline, detail for timeline segment, and hardware information.

Instrument Fleet

Help Panel — Help information easily updated and tailed for specific customers, fleets and types of instrument.


Mersen R-TOOLS: Complex Engineering Simulation UX

The Problem: Heat sink design required running expensive thermal simulations that took minutes. Engineers needed an interface that made complex thermal engineering accessible while handling long-running compute workloads gracefully.

What I Built: Design and simulation tool that made thermal engineering approachable. Visual layout tools for heat sources and cooling systems, with async simulation handling that didn't block user workflow.

Core Capabilities:

  • Visual design interface for complex thermal layouts
  • Async simulation execution (minutes-long compute)
  • Heat sink recommendation engine based on thermal requirements
  • Non-blocking UX that allows parallel design work

Stack: React, Recoil, AntD, AWS (Lambda, EC2 simulation servers)

R-TOOLS MAXX Air and Liquid Cooled Heat Sink Selector Tool

Projects List

Projects List — List of all the user's designs and summary of key parameters

AquaFlow heat source layout

Heat Source Layout — Specify the size and parameters of heat sources, add and remove heat sources, drag to position.

Mechanically Bonded Fin Selector

Mechanically Bonded Fin Selector — The fin selector recommends suitable heat sink extrusions from the specified the heat sources and flow conditions.

Mechanically Bonded Fin thermal simulation

Mechanically Bonded Fin Thermal Simulation — Simulations run on separate EC2 servers and depending on the design complexity may take minutes to complete. Users can continue with other designs and come back to review the completed simulation.

AquaFlow pipe layout

AquaFlow Pipe Layout — AquaFlow heatsinks have embedded coolant pipes in the heat sink base. Users can add and remove pipes, position them and set the flow direction.

Thermal simulation of AquaFlow pipes

AquaFlow Thermal Simulation — Simulations run on separate EC2 servers and depending on the design complexity may take minutes to complete. Users can continue with other designs and come back to review the completed simulation.


Common Patterns

These projects share core characteristics:

  • Complex systems made explorable through custom visualization
  • Real-time or async data handled gracefully in the UX
  • AI held to account — deterministic checks, grounded figures, and evals around the model, not trust in it
  • Technical depth in both frontend engineering and system architecture
  • Bespoke solutions for problems off-the-shelf tools couldn't solve

Looking for someone to build something similar for your systems? Get in touch