Featured project
MyLocalHealth
MyLocalHealth turns local environmental, respiratory, forecast, and community context into a plain-language health snapshot for any US ZIP code. It is an informational tool, not a diagnostic one — the goal is to make public signals that affect breathing, heat exposure, allergies, outdoor activity, and community vulnerability legible in one place.
External data sources
7+
Calibrated ML models in production
9
Calibration error reduction (ECE)
0.31 -> 0.01
Census tracts in spatial model
128
The problem
Air quality, heat, pollen, flu activity, and wastewater trend data all exist in public datasets, but each lives in a separate government source with its own format and update cadence. There is no single, plain-language view of what those signals mean for a specific location on a specific day.
What I built
A ZIP-code lookup that resolves to city, state, and coordinates, then pulls air quality and pollutant context, CDC flu activity by state, COVID wastewater activity, and weather/heat/UV/pollen forecast signals into one risk-scoring engine.
A personalized layer on top of the population-level forecast: saved locations, symptom check-ins, and an AI health assistant and daily health plan, optionally grounded in a curated public-health knowledge base through retrieval-augmented generation.
A transparency layer — every prediction surfaces its underlying data sources, freshness, and confidence, rather than presenting a single opaque number.
System architecture and data integrations
The application layer is TypeScript/Next.js, handling UI, API routes, feature flags, observability, and rate limiting. It integrates 7+ external data sources spanning CDC, Census, NOAA weather, air quality, and wastewater surveillance data.
Supabase (Postgres + pgvector) backs auth, row-level security, saved user data, and cosine-similarity search for the RAG knowledge base. Cross-instance rate limiting is implemented with atomic Postgres upsert functions rather than in-memory state, so it holds up correctly across multiple running instances.
Machine learning and risk scoring
9 explainable ML classifiers (scikit-learn, XGBoost) power the symptom-risk models, with SHAP-based feature attribution so predictions can be explained rather than treated as a black box.
Platt-scaling calibration cut calibration error by 97% (ECE 0.31 to 0.01) against raw model output on holdout data, meaning the model's confidence scores actually track real-world accuracy.
On top of the population models, a personal Bayesian risk calibration layer blends a population-pooled baseline with a signed-in user's own logged environment-symptom correlation (Spearman rank, tested for significance with a permutation test rather than an assumed threshold) using a conjugate Normal-Normal update — with a visible "trust weight" that shifts toward personal data as check-ins accumulate.
A Bayesian hierarchical spatial model (BYM2) smooths CDC PLACES diabetes prevalence over a 128-census-tract pilot region, using true tract-boundary adjacency and a measurement-error likelihood, validated against a non-spatial baseline with PSIS-LOO comparison (PyMC, ArviZ).
Validation and reliability infrastructure
A CI/CD pipeline retrains and validates all 9 models on every push, so the deployed models can't silently drift from the training data.
A native C++17 risk-scoring kernel is compiled to WebAssembly (via Emscripten) and called from the risk API at request time to independently cross-check the TypeScript implementation of the same math — an extra layer of verification on production-critical scoring logic.
OpenTelemetry provides real distributed tracing (not just log lines) across every external call: ML serving, RAG retrieval, the WASM cross-check, and rate limiting.
Technical challenges
Keeping a Python ML serving layer (FastAPI, scikit-learn, XGBoost, SHAP) correctly wired into a TypeScript application with graceful fallback if the model service is unavailable, gated behind a feature flag rather than a hard dependency.
Getting calibration right: raw classifier outputs are not probabilities, so Platt scaling plus bootstrap confidence intervals were needed before the risk scores could be presented as honest, decision-useful numbers.
Validating a spatial statistical model (BYM2) with an honest comparison against a simpler baseline (PSIS-LOO) instead of assuming the more complex model was automatically better.