Vitals  /  Results

Results

The MVP vertical slice runs end-to-end (make run) and produces these real, reproducible outputs from 600 synthetic patients. All numbers are deterministic (seeded).

1. Data quality — bronze (messy) → silver (trusted)

The silver layer earns its keep. Before/after on the same data:

DimensionBronze (raw)Silver (clean)
Patient rows629600 (29 exact duplicates removed)
Glucose units in use2 (mg/dL and mmol/L)1 (standardized to mg/dL)
Conditions coded to ICD-1081.3%100% (112 recovered from free text)
Observations missing a value3.9%0% (completeness gate)
Missing gender / birthdate8.3% / 5.2%handled (PHI removed; age capped at 90)

PHI is dropped at the silver boundary — a de-id assertion fails the build if any PHI column survives. Dates are shifted per-patient to preserve intervals. Great Expectations gates silver in CI: 14 expectations, 14/14 pass (make dq).

2. Analytics mart — gold.mart_condition_outcomes

Per primary condition: cohort size, surgery rate, mean pain, mean adherence (built in dbt, tested).

ConditionICD-10PatientsSurgery rateAvg painAvg adherence %
Lumbar disc displacementM51.261320.3334.7452.1
Knee osteoarthritis (bilateral)M17.01250.3044.7955.6
Low back painM54.51320.0684.5556.5
Pain in right kneeM25.5611040.0674.5756.2
Rotator cuff tearM75.1001070.0654.9350.7

dbt build: 3 models + 8 data tests, all passing (uniqueness, not-null, accepted-values, referential integrity).

3. Feature store — gold.patient_features

600 patients × 20 time-aware features spanning four source types, materialized into a live Feast store (sqlite online + file offline):

Both store paths are parity-checked against the offline parquet: online_parity.all_match = true (low-latency inference path) and historical_parity.all_match = true (point-in-time training join, leakage-safe).

4. Vector index + RAG

390 clinical notes indexed. pgvector (Docker, HNSW cosine, fastembed bge-small-en-v1.5 384-d) is the real store when Docker is running; TF-IDF is the clone-and-run fallback. The demo below ran with TF-IDF.

Query · "severe lower back pain worse with sitting, poor adherence"
Top match (0.69): "Patient reports severe lower back pain, worse with prolonged sitting. Adherence to home program poor. Plan: continue PT, reassess in 8 weeks."

5. Demo model — surgery-risk (tracked in MLflow)

Logistic regression on a curated 10-feature subset of the store, predicting surgery_within_90d:

MetricValue
ROC-AUC0.748
Accuracy0.84
Train / test450 / 150
Model features10 (curated from 20)

The learned coefficients are clinically coherent — disability (ODI +0.72), age (+0.64), and pain raise risk; adherence (−0.21) and active minutes (−0.74) lower it. Multi-source features (claims imaging, ODI, wearables) sit among the top predictors.

6. OMOP CDM (Phase 2)

Silver is also conformed to the OMOP Common Data Model in dbt, with source codes mapped to standard concepts:

OMOP tableRowsNotes
omop_person600standard gender concepts (8507 / 8532)
omop_condition_occurrence600ICD-10 → standard concept (M54.5 → 194133 "Low back pain")
omop_measurement5,303LOINC → standard concept (2339-0 → 3004501 "Glucose")

7. Multi-source ingestion (Phase 2)

Three more source types flow through bronze → silver → dbt gold, each cleaned at the silver gate:

SourceBronze messSilver fix
Claims (837/835-style, 1,510 rows)9.7% missing paid; ~5% billed as stringstring→numeric (96% recovered); denials flagged
PRO surveys (ODI, 1,718 rows)1.9% scores out of range (>100)clamped → 0 remaining
Wearables (daily batch, 15,169 rows)3.0% outlier step countsnulled → 0 remaining

Cost mart — gold.mart_cost_outcomes

A value-based-care view: conservative-care spend, imaging rate, and surgery rate per condition.

ConditionPatientsSurgery rateAvg conservative spendImaging rate
Lumbar disc displacement1290.341$1,00470.5%
Low back pain1150.035$89059.1%
Pain in right knee1160.017$81660.3%
Knee osteoarthritis (bilateral)1240.282$78454.0%
Rotator cuff tear1160.052$72962.1%

dbt now totals 1 seed + 12 models + 29 data tests, all passing (incl. the MetricFlow semantic layer).

8. Streaming + Spark at scale (Phase 3)

Wearables arrive continuously in production, so they also run as a Spark Structured Streaming job — cleaning outliers on the fly and writing a checkpointed Parquet stream. The stream reads from a real local Kafka broker via .readStream.format("kafka"). Parity is proven: make stream-parity asserts identical cleaned output across the file-source and Kafka paths.

Events streamed15,169
Outliers nulled in-stream448
Sinkcheckpointed Parquet (data/stream/cleaned)

A PySpark-at-scale batch transform mirrors the silver logic on Spark — including a window function (7-observation rolling mean of pain per patient) — the way it runs on Databricks against Delta.

Reproduce
make setup && make run → writes data/results.json, the DuckDB lakehouse, and an MLflow run. Phase 3 Spark jobs: make spark-deps && make stream && make spark.