0.4 — BUILDING

Selected projects.

Each of these started as a real question. What a piece is actually worth, how an agent reasons about a 3D model, whether my own body data survives its own measurement error. The work was building the data infrastructure, the models, and the interfaces to answer it end to end. The six featured below are the ones I would defend line by line. Most run on Python and SQL, with TypeScript and MCP where the work reached agents and the browser.

Python SQL TypeScript MCP dbt PySpark MLflow Airflow Next.js three.js BigQuery scikit-learn
00
Agentic AI · MCP · Live

ModelSense

MCP SERVER · AGENT SDK · EVAL HARNESS

ModelSense lets you talk to a 3D model. You load a glTF/GLB scene in the browser, ask a question in plain language ("what's the highest-poly part, and how big is it?"), and a Claude agent answers by calling tools on an MCP server that actually parses the geometry. The viewer then highlights the part, frames the camera, and draws the measurement in front of you. The point of the project isn't the chat. It's the machinery that makes an agent's tool use checkable: a stateless MCP server with a typed tool surface, human approval on the one tool that writes, per-turn Langfuse traces, and a Python eval harness that scores the agent against a golden set the server computes itself.

The server exposes nine tools over Streamable HTTP: eight read-only (list and load models, scene stats, find and highlight elements, focus the camera, measure, suggest optimizations) and one gated report export that only runs after an explicit approval. Every tool returns text plus typed structuredContent, so the React Three Fiber viewer applies scene commands directly instead of guessing. Loaded models live in a session-keyed in-memory cache, which keeps the server stateless per request and pushes the identity of a scene into a session_id that every later tool carries.

MCP tools9 · 8 read + 1 gated
Golden evals50 · 100% pass
Context fidelity4.48 / 5

Tools & skills

Claude Agent SDKMCPTypeScriptReact Three Fiberthree.jsLangfuseZodPythonVitestCI/CD

What I learned

  • The eval harness is the project, not the agent. A Python harness drives the real /chat endpoint through fifty golden tasks (lookup, multi-step, measurement, optimization, guardrail) and scores tool selection, argument validity, outcome, budget, and a guardrail safety invariant, with a Haiku judge for context fidelity. The golden answers are computed from the GLBs by the same server logic, not hand-typed, so the test can't quietly drift away from the geometry.
  • It caught a real bug. The baseline scored 94%, with the measurement category at 62% because the agent answered size questions from memory instead of calling measure. One system-prompt change that forces the tool took measurement to 100% and the suite to 100%. The number moved because the harness made the failure legible, which is the whole argument for building it.
  • Human-in-the-loop belongs at the agent layer, not inside the tool. The eight read tools are allowlisted and auto-approved; export_report is gated through canUseTool, which emits an approval event and blocks until the client posts back to /chat/approve. The invariant the harness enforces is that a gated tool can never succeed without that approval.
  • The free tier checks the design. The first deploy on Render's 512 MB instance ran five tasks and then 502'd from an out-of-memory on the combined agent process, which is exactly the kind of constraint that decides whether "stateless per request" was a real choice or a slogan.
LIVE · VIEWER + AGENT · ONE TOOL-DRIVEN REQUEST
ModelSense running live: a Cesium Milk Truck glTF model loaded in the React Three Fiber viewer on the left, and the agent chat on the right answering 'find every node with wheel in the name and highlight the largest one' by reporting two wheel nodes tied at 768 triangles each and highlighting the front pair, with a per-turn readout of 4 turns, 12.0s, $0.0665, and 3394/523 tokens
FIG · AGENT + MCP ARCHITECTURE
Viewer + ChatReact Three Fiber · applies scene commands
Agent loopClaude Agent SDK · SSE /chat · HITL approval
MCP serverstateless Streamable HTTP · 9 typed tools
glTF domaingltf-transform · session-id LRU cache
Model sourceKhronos GLB · allowlisted sample URLs

↳ side channel · Langfuse trace per turn · 50-task eval gates CI

EVAL HARNESS · BASELINE → AFTER ONE PROMPT FIX
category tasks baseline now
lookup 12 100% 100%
multi-step 14 100% 100%
measurement 8 62% 100%
optimization 10 100% 100%
guardrail 6 100% 100%
all tasks 50 94% 100%

agent · claude-sonnet-5  ·  context-fidelity judge · claude-haiku-4-5

LANGFUSE · ONE TURN · 3 TOOL SPANS + COST
Langfuse trace of one ModelSense turn: the agent answers 'find every node with wheel in the name and highlight the largest one' with three tool spans (load_model, find_elements, highlight_elements) over 4 turns in 28.2s, 3,394 input and 493 output tokens, $0.15, traced through the langfuse-sdk over OpenTelemetry
01
MLOps · Machine Learning

Sneaker Price MLOps Pipeline

SNEAKER-INTEL · PHASE 2

The deliberate Phase 2 of Sneaker Resale Intelligence. That project built the data-engineering foundation, a hand-written Postgres star schema over ~99K StockX sales with a full dbt layer. This one builds the ML layer on top of it: an end-to-end MLOps pipeline that predicts a sneaker's resale price premium. The model itself is a plain feedforward net on purpose. The whole point is everything around it, the parts that have to hold up to a "why did you build it that way" question, in reliability, reproducibility, and scale.

The topology is a chain of stages that talk to each other only through typed Parquet at an S3 path. An ingest stage acts as an anti-corruption layer over the sneaker-intel Postgres star schema (or synthetic fixtures in dev and CI) and lands a raw zone partitioned by run_date. PySpark builds the feature set partitioned by brand; Pandera validates it as a data contract and writes a validation report; Ray Train + PyTorch trains the regression net to model.pt while logging to MLflow tracking; and a register stage promotes the run into the MLflow registry as sneaker-price-model @staging. An Airflow DAG threads the whole thing with XCom paths, owning the topology but none of the logic, and one fsspec I/O layer means the same code runs against S3 in prod and local disk in CI.

Real StockX sales99,956
Val RMSE≈ 0.21
Validation row retention100%

Tools & skills

PythonPySparkPyTorchRay TrainMLflowAirflowPanderaAWS S3CI/CD

What I learned

  • Airflow should own the topology, not the logic. Each task just calls a stage's run(), state passes between stages only as S3 paths, and the heavy libraries import inside the callables so the DAG parses fast and stays testable without Spark or Torch installed.
  • A temporal train/val split is non-negotiable for forecasting. Training on earlier sales and validating on later ones is the only honest way to judge a model that has to predict forward in time. A random split leaks the future.
  • Promotion should be a gate, not a default. Every run is registered for lineage, but the staging alias only moves on a strict val-RMSE improvement, and every decision leaves a promotion_report.json.
  • Real data is the real test. Connecting the live warehouse surfaced four contract mismatches the synthetic fixtures had hidden (release-type vocabulary, premium outliers at 20× retail, transaction grain, pre-release sales). Pandera caught each one, and I fixed each as a documented config change.
FIG · PIPELINE TOPOLOGY
End-to-end pipeline topology: sneaker-intel Postgres star schema and synthetic fixtures feed an ingest anti-corruption layer, into a raw zone of Parquet by run_date, then PySpark features partitioned by brand, Pandera validation with a validation report, Ray Train + PyTorch producing model.pt and MLflow tracking, then a register stage promoting to the MLflow registry as sneaker-price-model @staging, all orchestrated by an Airflow DAG
HYPERPARAMETER SWEEP · VAL RMSE
Parallel-coordinates hyperparameter sweep over hidden_dim and learning_rate, colored by final validation RMSE
AIRFLOW DAG · SNEAKER_TRAINING_PIPELINE
Airflow DAG: ingest → feature_engineering → validate → train → register, each a PythonOperator
View on GitHub
02
Data Product · Full-Stack · Live

ReliQuery

SOLD COMPARABLES · ARCHIVE MENSWEAR

ReliQuery answers one question about archive and avant-garde menswear: what a piece is actually worth, not what it's listed at. The hard part isn't scraping. It's that the same Rick Owens piece sells on Grailed and eBay under different titles, in different languages, with no shared key, so before you can quote a comparable you have to prove two listings are the same physical piece. ReliQuery is the resolution layer that does that, then turns sparse, noisy sold records into a price you can defend down to the sales behind it.

Apify actors pull sold listings from Grailed and eBay on a scheduled GitHub Actions job, never on the web server. Every source enters through a typed anti-corruption adapter that normalizes currency and shape into one canonical row, so a renamed or broken scraper is a one-file change and no source-specific quirk leaks past the boundary. Those rows land in Neon Postgres as a star schema; a staged rapidfuzz resolver reads brand first, then archetype, model, and season, and only forms a piece when it has the brand plus at least an archetype or model. dbt builds the comparables, velocity, and spread marts, and a thin Next.js frontend on Vercel reads the marts with no business logic of its own.

Sold comps4,280
Resolved pieces531
Rows resolved~86%

Tools & skills

TypeScriptPythonNext.jsApifyNeon PostgresdbtrapidfuzzpgvectorOpenCLIPVercel

What I learned

  • Entity resolution is the entire product. Scraping is a solved, boring problem; deciding that two listings in two languages are the same jacket is the thing worth building. The resolver assigns a canonical piece to about 86% of rows, which is what turns a pile of listings into comparables you can price against.
  • The anti-corruption adapter earned its keep. One typed boundary per source means the messy reality of each marketplace stops at the adapter, and the canonical row the rest of the system sees never learns that eBay and Grailed disagree about everything. When an actor changed shape, the fix was one file.
  • A code review made me delete a headline number. An early version reported 172 cross-marketplace spreads; the review pushed on how many were real matches versus fuzzy collisions, and the honest count that clears the bar is closer to 15. Reporting the small true number instead of the big shaky one was the right call.
  • The price model doesn't beat the median, and the case study says so. A LightGBM regressor over the comparables lands within about 1% of just taking the brand-and-archetype median (MAE around $240), so I report it as evaluated, not as a win. The defensible answer here is a well-resolved comparable, not a model pretending to know more than the data does.
  • There's a second way in: image search. A Modal-hosted OpenCLIP worker embeds a photo and finds the nearest pieces through a pgvector index, which reaches 95% brand and 86% exact-piece accuracy leave-one-out on the reference set. Same resolution problem, approached from pixels instead of text.
FIG · SOLD-COMPARABLES PIPELINE
SourcesApify actors · Grailed + eBay · GH Actions cron
Adapterstyped anti-corruption · one per source → canonical row
StoreNeon Postgres · canonical star schema
Resolvestaged rapidfuzz · brand → archetype → model → season
Transformdbt · comps · velocity · spread marts
ServeNext.js on Vercel · thin read layer
LIVE · RESOLVED PIECE · PRICED OFF ITS OWN COMPS
ReliQuery piece view for the Rick Owens Ramones: 51 sold comps across eBay and Grailed resolve to a recommended list price of $375, graded A, with a P10-to-P90 price band from $265 to $665 around the $375 median
LIVE · CROSS-MARKETPLACE SPREAD · SAME PIECE, TWO MARKETS
ReliQuery cross-marketplace spread for the same Rick Owens Ramones: Grailed median $370 over 43 comps versus eBay median $448 over 8 comps, a 17 percent gap, plus a markdown readout of 7 percent median and 25 percent P90 across 43 Grailed listings

Repo is named TrueComp: ReliQuery is the site, TrueComp is the pipeline underneath it.

03
Analytics Engineering · Live

Data-Driven Fitness

BODY DATA × POPULATION BASELINES

An end-to-end data engineering and analytics project that links my own body data (two DEXA scans, Apple Health, calorie and protein logs, tracked lifts) to population baselines from NHANES and ACSM, to answer five concrete physiology questions. The hook: my two DEXA scans sit a month apart and report a −9.8 lb fat, +8.4 lb lean change, which is biologically implausible over 30 days. So instead of plotting the numbers and calling it a body recomposition, the project quantifies the measurement uncertainty, shows where the real change sits inside the instrument's noise floor, and reconciles what's left against energy balance and population data.

Under the hood it's a proper warehouse. Python and Polars stream-parse the 328 MB Apple Health XML and the NHANES files into bronze Parquet; dbt builds silver and gold marts across two targets (DuckDB for private local dev, BigQuery for the de-identified public layer) with tests and contracts; Airflow orchestrates ingest → build → test → analyze → dashboard; and an Evidence.dev site serves it publicly. Raw health data never leaves the machine, so only de-identified aggregates reach the cloud and the repo.

Apple Health XML328 MB
DEXA fat / lean Δ−9.8 / +8.4 lb
Real muscle≤ 4.3 lb of the +8.4

Tools & skills

PythonSQLPolarsdbtDuckDBBigQueryAirflowEvidence.devCI/CD

What I learned

  • The headline finding is a measurement-uncertainty result: the +8.4 lb "lean gain" is mostly glycogen and hydration water (about 4.1 lb), not muscle. At most ~4.3 lb is plausible newbie muscle in a month. Honest analysis meant decomposing the number before celebrating it.
  • A contract on fct_daily with a guaranteed grain and fixed types let the marts and the analysis trust their inputs, which is what makes the uncertainty math defensible.
  • The DuckDB-dev / BigQuery-prod split was a privacy and dev/prod-parity choice, not a scale one. It keeps raw data on my machine and exposes only aggregates to the cloud and the public site.
  • Airflow is overkill for a single-machine pipeline. I used it for the recognizable, production-shaped orchestration and the discipline it forces.
LIVE DASHBOARD · DEXA, DECOMPOSED
Data Driven Fitness dashboard — the DEXA lean and fat change decomposed against measurement uncertainty and population baselines
FIG · PIPELINE ARCHITECTURE
SourcesApple Health XML · DEXA · LoseIt · Strong · NHANES
IngestPython + Polars → bronze Parquet
Warehousedbt · DuckDB dev → silver / gold marts
Promotede-identified marts → BigQuery prod
ServeEvidence.dev dashboard · Airflow-orchestrated
04
ML Lifecycle · Live

Voice Assistant Review Sentiment Classifier

REPRODUCIBLE ML · HONEST METRICS

A binary sentiment classifier on Amazon Alexa reviews, rebuilt as a full ML lifecycle rather than a model. The identity of the project is reproducible experimentation infrastructure and honest metrics, not the models themselves. The first thing I checked was what the feedback label actually is, and it turned out to be the whole project: feedback == 1 if and only if rating ≥ 3, so the label is a hard threshold on the star count, not human sentiment. The data is about 92% positive, which makes accuracy a trap, so everything is reported against a majority baseline, negative class first.

Around that sits the infrastructure: dataset curation with a Pandera schema that enforces the label rule itself, a config-driven harness that runs cross-validated experiments across scikit-learn, PyTorch, and TensorFlow through one shared evaluation function, MLflow tracking, PR-AUC model selection with bootstrap confidence intervals, a containerized FastAPI service deployed live on a Hugging Face Space, drift monitoring on PSI/KS, and CI that lints, tests, and builds the image on every push.

Curated reviews2,384 · 8.6% neg
Best PR-AUC0.575 vs 0.086
Frameworkssklearn · torch · TF

Tools & skills

Pythonscikit-learnPyTorchTensorFlowMLflowPanderaFastAPIDockerCI/CD

What I learned

  • The label was the whole project. Once I saw feedback was just a thresholded star rating, the right questions followed: what does accuracy hide, and which rows actually need a text model at all.
  • The most useful part was catching my own error. Selecting on cross-validated negative recall collapsed the linear model into a degenerate "always negative" predictor. Switching selection to PR-AUC, which is threshold-independent, fixed it. Model selection and the decision threshold are two separate choices.
  • The result worth reporting wasn't a win. I bootstrapped the PR-AUC, and the control's interval [0.42, 0.72] was wide enough that the neural nets sat inside it, so I stopped claiming the linear model beats them and started saying it matches them at a fraction of the complexity.
  • On a small, imbalanced dataset a regularized linear model is hard to beat. The deep-learning variants earned their keep as evidence that the harness is framework-agnostic and the finding holds across backends.
ABLATION · NEG CLASS, RANKED BY PR-AUC
model PR-AUC neg rec spec
sklearn logreg (L2) 0.575 0.244 0.991
PyTorch MLP 0.526 0.415 0.961
TensorFlow MLP 0.523 0.390 0.963
majority baseline 0.086 0.000 1.000
FIG · ML LIFECYCLE
Curate + ValidatePandera schema · curated.parquet
Harnessconfig-driven · seeded · MLflow
Variantssklearn · PyTorch · TensorFlow
Selectablation · PR-AUC · bootstrap CIs
ServeFastAPI · Docker · Hugging Face Space
LIVE DEMO · CLASSIFY AN ALEXA REVIEW

Waking the live demo. The free-tier Space sleeps after inactivity, so the first load can take 30 to 60 seconds. Paste a review, run it, and it classifies right here.

This live demo loads with JavaScript. Open it in a new tab below.

Open in a new tab
05
Data Engineering · Live

Sneaker Resale Intelligence

RESALE WAREHOUSE · LIVE DASHBOARD

An end-to-end data engineering project that turns real resale data into an analytical warehouse and a live dashboard. It answers the questions I actually find interesting about the resale market: which sneakers carry the highest premium over retail, how that premium decays after a release, and how it varies across sizes and silhouettes. The pipeline ingests about 100K real StockX resale sales, plus Google Trends as a live demand signal, through typed Python clients, lands them as raw JSON, and bulk-loads them into a hand-written PostgreSQL star schema through an idempotent loader.

From there, dbt transforms the raw tables across staging, intermediate, and mart layers, computing the premium economics with SQL window functions. A thin Streamlit dashboard reads the marts directly, so every figure on screen traces back to a tested, modeled table. It's built in public with a clean commit history and full-pipeline CI, with a deployed instance on Streamlit Community Cloud backed by Neon Postgres. The ML layer on top of this warehouse is Phase 2, the Sneaker Price MLOps Pipeline.

Real StockX sales99,956
Peak premium21.3× over retail
dbt models9 · 3 layers

Tools & skills

PythonSQLPostgreSQLdbtStreamlitStar SchemaWindow FunctionsCI/CD

What I learned

  • The decision I spent the most time on wasn't code, it was data sourcing. I started with live APIs (eBay, Reddit) and a tempting StockX scraper, then realized a fragile live feed is a bad foundation for something I want to demo reliably. Choosing a real public dataset as the backbone, and reframing the API clients as documented, tested, future-ready extensions, taught me that "real data" doesn't have to mean "live API."
  • The engineering lessons were all about getting the boring things right: putting idempotency in the schema (natural-key constraints plus ON CONFLICT DO NOTHING) rather than the loader, keeping one clear grain per fact table instead of unioning mismatched sources, and computing a metric like premium exactly once in an intermediate model so every downstream model agrees.
  • A sharp reminder that execute_values reports only its last page's row count, which sent me to RETURNING to report loads honestly.
  • Deploying against a managed Postgres surfaced the real-world details: SSL modes, pooled versus direct connections for DDL, and Google rate-limiting live Trends pulls.
LIVE DASHBOARD · PREMIUM TRAJECTORY
Sneaker Resale Intelligence dashboard — Shoe Deep Dive showing the premium trajectory for the Adidas Yeezy Boost 350 V2 Zebra decaying from roughly 1.8x toward 0.5x over time
FIG · PIPELINE ARCHITECTURE
SourcesStockX ~100K sales · Google Trends
Ingesttyped Python clients → raw JSON
Loadidempotent loader → Postgres star schema
Transformdbt · staging → intermediate → marts
ServeStreamlit dashboard · reads marts
LIVE · SNEAKER RESALE DASHBOARD

Waking the live dashboard. Streamlit Community Cloud sleeps the app after about 12 hours; if a wake button appears inside, click it or open the dashboard in a new tab. Every figure reads straight from the dbt marts.

This live dashboard loads with JavaScript. Open it in a new tab below.

Open in a new tab
§   ARCHIVE · MORE BUILDS

Digital i-D

An interactive educational web app that teaches how online data collection actually works through experience rather than explanation. I worked the data-science side: turning raw interaction events into the behavioral signals and inferences the app surfaces.

Behavioral DataJavaScriptFirebasePrivacy

Grailed Luxury Market Analysis

A data story on the Grailed resale market, built from thousands of menswear listings. Python and Tableau Prep shaped the scrape; SQL aggregations cross-checked how brand prestige, condition, and origin drive resale value in a heavily right-skewed market.

PythonSQLTableauFeature Engineering

TensorFlow Architecture Analysis

A development-view analysis of the TensorFlow codebase that maps how one of the largest open-source ML frameworks is organized, from the tf.* Python frontend down through the C API, the C++ core, the MLIR/XLA compiler stack, and TF Lite. Python scripts the dependency map so the write-up stays honest.

PythonRepo AnalysisSoftware Architecture

Equality-Driven HRIS

A redesign of a global retailer's HRIS (4,000+ stores) built around one goal: detect and close gender- and race-based pay disparities. A SQL schema for employees, compensation history, and audit trails feeds a Python pay-gap regression that controls for role, level, and tenure.

PythonSQLstatsmodelsRBAC

IMDb Database & Query Optimization

A relational database built over the full public IMDb dataset on Azure SQL. I loaded the raw .tsv exports into a normalized schema, benchmarked a real query workload, found the full-table scans, and tuned runtime with covering and composite indexes matched to each query's pattern.

SQLAzure SQLIndexingQuery Tuning

King County Metro Database

A relational database designed for King County Metro to address accessibility and route efficiency. I modeled the full domain (riders, routes, fares, operations, vehicles, drivers, calendars) into a normalized schema with a complete ERD, then framed a pandas analysis layer for ridership and delay patterns.

SQLER ModelingNormalizationpandas

Privacy-Aware Fashion Review Risk Detector

A classifier that flags fashion reviews likely to overshare sensitive personal information (body measurements, health, pregnancy) before they're posted, prompting the writer to reconsider. TF-IDF plus a logistic-regression model tuned deliberately for recall on the risk class.

Pythonscikit-learnNLPPrivacy

Fashion Subscription Return Risk Predictor

A decision-support model for a subscription clothing service that estimates, before a box ships, whether each item will be kept or returned. Built on the Rent-the-Runway fit dataset, with SQL joins assembling per-customer fit history as the features that actually carried the signal.

PythonSQLscikit-learnFeature Engineering

COVID-19 Regional Analysis

An analysis of COVID-19 trends, impact, and government responses across US states, tracking new cases over time against the timing of state-level policy responses. pandas reshaped the series and SQL aggregated by state and month before plotting the annotated case trajectories.

PythonSQLMatplotlibTime-Series