Problem
The pipeline predicts PAD from data a hospital already collects: labs, demographics, comorbidities and medications. The copilot answers why this score? with SHAP attributions and cited passages from public clinical references.
Users
Researchers and clinicians reviewing a risk score, for research use only. This is not a medical device and has not been validated prospectively or externally.
Constraints
- Data use agreement. MIMIC-IV values cannot be sent to a third party under the PhysioNet agreement, so the language model runs locally through Ollama and no API is used.
- Hardware. Both models (llama3.1:8b and nomic-embed-text) have to fit together on an 8 GB GPU.
- Noisy label. The label is a billing code, which can miss or mislabel patients, and MIMIC-IV is an ICU population, sicker than the general public.
- Missing signals. There is no ankle-brachial index, no smoking status and no imaging in these tables.
What I built
The model. A matched case-control cohort from MIMIC-IV, 13 engineered features (demographics, labs, comorbidities, medications) and six classifiers: logistic regression, random forest, SVM, MLP, XGBoost and LightGBM. The winner is chosen by grouped 5-fold cross-validation and scored once on a held-out test set.
Leakage controls, each covered by a test that was checked to fail against the earlier behaviour:
- Patient-level cohort and split, so no one appears as both a case and a control, or in both train and test.
- Comorbidities and medications come only from strictly earlier admissions. Statins are the standard PAD treatment, so counting them from the index stay would leak the diagnosis.
- Controls matched 1:1 on age bracket × gender, so the model has to learn something clinical.
- Drug names are matched exactly. A bare
statinsubstring would also match nystatin, an antifungal.
The copilot. It scores the record, computes SHAP attributions, retrieves passages from an index of NHLBI and CDC pages plus a model card, asks a local LLM to explain citing those passages, then verifies every citation in code. Any [n] that doesn't resolve to a retrieved passage is stripped and flagged. There's a Streamlit UI on top.
Metrics
| Measure | Result |
|---|---|
| Best model AUC | 0.88 |
| Retrieval hit@6 (24-question golden set) | 24/24 |
| Retrieval hit@1 | 19/24 |
| Citations valid (no unresolvable citation survived) | 24/24 |
| Answers citing a source | 22/24 |
| Empty answers | 0/24 |
| Automated tests (synthetic MIMIC-shaped fixture) | 54 |
The two answers without a citation are marked as ungrounded in the UI.
Trade-offs I made
- A local 8B model.The data agreement rules out hosted models, so the copilot runs llama3.1:8b locally. Its answers are weaker, which is one reason citations are checked in code.
- The copilot can decline to answer.If no passage clears the relevance threshold, the LLM is never called and the copilot says so.
- The test embedder must be asked for by name.An offline hashing embedder exists for tests. It scores 0.79 / 0.54 (hit@6 / hit@1) against 1.00 / 0.79 for real embeddings, so it is never swapped in automatically.
- SHAP factors are worded as score movement.Given "statin therapy increases", the model once wrote that statins increase PAD risk. Factors are now phrased as "pushed the score up/down".
- Only prior admissions for comorbidities and medications.This gives up some signal to avoid leaking the diagnosis into the features.
What I'd do next
- Calibrate the probabilities (Platt or isotonic). Matched controls change the base rate, so today's scores rank patients well but need calibrating before they can be read as probabilities.
- Rerank retrieved passages before generation.
- Add features from MIMIC-IV-Note clinical notes, such as smoking status and mentions of claudication.
