Skip to main content

Architecture Overview

TransPlan uses a two-layer architecture: a static frontend (Phase 1) augmented by a Python backend for probabilistic simulation (Phase 2+). The backend is deployed as a Vercel Python function (Phase 3) and simulates across all 248 SRTR transplant centers (Phase 4).

System Diagram

┌──────────────────────────────── Vercel ─────────────────────────────────┐
│ │
│ ┌──────────────────────────┐ ┌────────────────────────────────────┐ │
│ │ CDN (Static Files) │ │ Python Function (api/index.py) │ │
│ │ GET / → index.html │ │ FastAPI app (backend/main.py) │ │
│ │ GET /simulator.html │ │ │ │
│ │ GET /algorithm.js │ │ POST /simulate (248 centers) │ │
│ │ GET /styles.css │ │ POST /sensitivity │ │
│ │ GET /data/*.json │ │ POST /equity-analysis │ │
│ │ │ │ POST /what-if │ │
│ │ (rewrites route API │ │ POST /score │ │
│ │ paths to function) │ │ GET /centers │ │
│ └──────────────────────────┘ │ GET /health │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
↓ same-origin requests (Vercel rewrites)
┌──────────────────────────────────────────────────────────────┐
│ Browser (Frontend) │
│ │
│ index.html ← Landing page (features, CTA) │
│ simulator.html ← Simulation tool (form, results, map) │
│ algorithm.js ← Phase 1: deterministic scoring engine │
│ api-client.js ← Phase 2+: calls /simulate + /sensitivity │
│ + /equity-analysis + /score + /centers │
│ script.js ← UI, form handling, results display │
│ charts.js ← Chart.js radar/bar/donut │
│ probability-charts.js ← CDF curves + competing risks + │
│ tornado sensitivity chart │
│ equity-charts.js ← Blood type/age/Gini equity charts │
│ data-loader.js ← loads data/*.json from server │
│ themes.css ← 4-theme design system overrides │
│ theme-switcher.js ← Theme picker UI │
└──────────────────────────────────────────────────────────────┘
↓ data loaded at runtime
┌──────────────────────────────────────────────────────────────┐
│ Data Layer (data/) │
│ │
│ 10 JSON files auto-updated by GitHub Actions / manual │
│ Fallback to hardcoded defaults if unavailable │
└──────────────────────────────────────────────────────────────┘
↑ updated by
┌──────────────────────────────────────────────────────────────┐
│ GitHub Actions (Data Pipeline) │
│ │
│ fetch-traffic.js ← NHTSA FARS (retired; seed only) │
│ fetch-air-quality.js ← EPA AQS API │
│ fetch-hospital-quality.js ← CMS Provider Data │
│ fetch-cost-of-living.js ← BLS API v2 │
│ fetch-health-data.js ← CDC SODA API │
│ fetch-srtr-excel.py ← SRTR PSR Excel downloads │
│ parse-srtr-reports.py ← Excel → JSON │
└──────────────────────────────────────────────────────────────┘

Key Architectural Decisions

Vercel Deployment (Phase 3)

The Python backend is deployed as a Vercel serverless function (api/index.py). Static frontend files are served by Vercel's CDN. API paths (/simulate, /health, etc.) are routed to the Python function via vercel.json rewrites. The frontend uses relative URLs with no hardcoded backend URL, so the same code works locally and on Vercel.

For local development, FastAPI serves both the API endpoints and the static frontend on a single port (same-origin, no CORS needed).

No Build Step Frontend

The frontend is plain HTML/CSS/JS with no bundler. This avoids build toolchain complexity and makes the code directly debuggable in browser DevTools. CDN imports (Chart.js, Leaflet) include CDN fallback guards.

Graceful Degradation

The simulator requires the backend (Vercel deployment or local uvicorn) and covers all 248 SRTR centers filtered by the patient's organ. The old client-side 22-city fallback scorer was retired (#293): with no backend, the UI reports the backend as unavailable rather than silently serving a degraded scorer.

Data at Rest vs Runtime

Public API data is fetched by GitHub Actions on a weekly or bimonthly schedule and committed as JSON to the repo. The frontend loads this JSON at runtime. There is no database and no server-side rendering. This keeps deployment simple: Vercel handles the landing page and docs, while uvicorn runs locally for Phase 2 simulation.

Component Inventory

Frontend Files

FilePurpose
index.htmlLanding page: features, how-it-works, CTA to simulator
simulator.htmlSimulation tool: form, 3-tab results, modals, map, methodology
scoring-constants.jsScoring category constants for the weight-slider UI (parity-tested against backend/services/scoring.py)
script.jsUI orchestration: form, results display, map controls, city detail modal, comparison
api-client.jsAPI client: POST /simulate, /sensitivity, /equity-analysis, graceful fallback
probability-charts.jsCDF line charts, competing risks bars, tornado sensitivity chart (Chart.js)
equity-charts.jsBlood type disparity, age bracket disparity, Gini by city charts (Chart.js)
charts.jsPhase 1 charts: radar, weighted bar, donut (Chart.js)
data-loader.jsRuntime JSON loader with hardcoded fallback defaults
session.jsSession bar: health check, "End Session" button
styles.cssAll CSS: design tokens, landing page, components, responsive breakpoints
themes.cssTheme overrides: Clinical, Research, Government (+ landing page per-theme)
theme-switcher.jsFloating theme picker (4 themes)

Backend Files

FilePurpose
backend/main.pyFastAPI app, startup data load, static file mounting
backend/config.pyConfiguration: DATA_DIR, SIMULATION_ITERATIONS, ALLOWED_ORIGINS
backend/models/schemas.pyPydantic schemas: PatientProfile, SimulationResult, SensitivityResult, EquityAnalysisResult
backend/routers/simulate.pyPOST /simulate endpoint
backend/routers/sensitivity.pyPOST /sensitivity endpoint
backend/routers/equity.pyPOST /equity-analysis endpoint
backend/routers/health.pyGET /health endpoint
backend/routers/shutdown.pyPOST /shutdown endpoint (local session management)
backend/services/monte_carlo.pyMonte Carlo simulation engine
backend/services/distributions.pyLog-normal wait time model
backend/services/competing_risks.pyMortality/delisting exponential models
backend/services/sensitivity.pySensitivity analysis: parameter impact on p_transplant_24mo
backend/services/equity.pyDemographic equity analysis (48 profiles x all centers, Gini coefficient)
backend/services/brier_score.pyBrier score calibration: Monte Carlo vs analytical validation
backend/services/data_loader.pyLoads and caches data/*.json at startup

Data Flow: Simulation Request

User submits form
→ script.js collects form values
→ api-client.js normalizes (camelCase → snake_case, string → number)
→ POST /simulate { patient: {...} }
→ backend validates with Pydantic
→ data_loader.py provides wait-time-distributions.json + competing-risks.json
→ monte_carlo.py runs 1000 iterations × N centers (filtered by organ)
→ returns SimulationResult JSON
→ api-client.js parses response
→ probability-charts.js renders CDF curves + competing risks bars
→ script.js renders probability cards

Deployment Targets

ModeCommandNotes
Local (macOS)Double-click TransPlan.appNo Terminal, background app
Local (any)./start.commandBash script, opens browser
Local (manual)uvicorn backend.main:appFull control
ProductionVercelStatic files via CDN + Python function for API
Dockerdocker compose upLocal dev with all dependencies