Live Demo: https://traigegeist.streamlit.app/
3-class emergency triage decision aid built on NHAMCS 2018-2022 real-world ED visits.
This project targets a practical Kaggle-style workflow:
- π§ͺ messy raw healthcare data
- π§± reproducible feature pipelines
- π‘οΈ leakage-aware validation
- π interpretable model outputs
- π₯οΈ deployable Streamlit interface
We predict triage acuity in 3 clinically actionable classes:
- π΄ Urgent: ESI 1-2
- π Emergent: ESI 3
- π’ Non-Urgent: ESI 4-5
Core idea:
- π§ combine tabular clinical signals, emergency keyword flags, and fine-tuned clinical NLP probabilities
- π train both ordinal-aware regressors and multiclass classifiers
- ποΈ stack everything with a logistic meta-learner
Primary paper-level summary (from project write-up):
- π― weighted F1: 0.59
- π¨ urgent recall: 0.65
- π QWK: 0.5214
- β accuracy: 58.82%
Notebook execution snapshot in this repo (modelling output):
- π Stacked Logistic Meta-Learner OOF QWK: 0.5122
- π― weighted F1: 0.5829
- π¨ urgent recall: 0.6542
ED triage is high-stakes and noisy in real life. Even trained staff can disagree on ESI labels, and undertriage can delay critical interventions.
This repository focuses on:
- π real public NHAMCS data (not synthetic)
- π ordinal-aware modelling (misranking by 2 levels is worse than by 1)
- π§ͺ robust out-of-fold evaluation
- π§ clinically interpretable drivers (vitals, history, complaint signals, time effects)
Source: NHAMCS emergency department public-use data (2018-2022).
Raw files:
- data/ED2018-stata.dta
- data/ED2019-stata.dta
- data/ed2020-stata.dta
- data/ed2021-stata.dta
- data/ed2022-stata.dta
Format maps:
- format/ed18for.txt
- format/ed19for.txt
- format/ed20for.txt
- format/ed21for.txt
- format/ed22for.txt
Processed cohort used across modelling notebooks:
- 58,124 rows after dropping invalid or missing triage targets
Observed ESI distribution before 3-class collapse (from notebook outputs):
- ESI 1: 846
- ESI 2: 8,597
- ESI 3: 29,568
- ESI 4: 16,715
- ESI 5: 2,398
Notebook: notebooks/data_processing.ipynb
What happens:
- loads all 5 STATA year files
- extracts required columns (arrival/time, demographics, vitals, RFV complaint fields, injury, chronic history, target-related columns)
- decodes coded variables using STATA label maps and format text files
- merges years into a single dataframe
- renames columns into model-ready names
- saves:
- working_data/nhamcs_data_2018_22.csv
Notebook: notebooks/text_processing.ipynb
What happens:
- reads working_data/nhamcs_data_2018_22.csv
- normalizes complaint/injury text
- expands abbreviations (example: sob -> shortness of breath, cp -> chest pain)
- applies strict negation handling (example: no chest pain should not trigger)
- starts from a broad emergency keyword set, then keeps matched project columns
- final selected 14 keyword flags:
- chest_pain
- shortness_of_breath
- syncope
- assault
- vaginal_bleeding
- violence
- burn
- head_injury
- suicide_attempt
- cardiac_arrest
- gunshot_wound
- throat_swelling
- paralysis
- sepsis
- saves:
- working_data/nhamcs_emergency_keyword_flags_matched_only.csv
Notebook: notebooks/nlp_dl.ipynb
What happens:
- cleans chief complaint text
- fine-tunes nlpie/distil-clinicalbert with a CORN ordinal head
- performs year-bucketed GroupKFold OOF generation
- writes NLP logits/probabilities for stacking
- saves:
- working_data/nlp_oof_logits_probs.csv
- results/model_artifacts/distilbert_corn_seed42/*
Notebook: notebooks/modelling.ipynb
What happens:
- reads:
- working_data/nhamcs_data_2018_22.csv
- working_data/nlp_oof_logits_probs.csv
- working_data/nhamcs_emergency_keyword_flags_matched_only.csv
- applies cyclical time features, clinical ratios, missingness flags, NEWS2 approximation
- trains base learners:
- xgb_reg, lgb_reg (ordinal via post-hoc cutpoints)
- xgb_cls, lgb_cls (multiclass)
- NLP OOF probs as another base signal
- stacks with logistic regression meta-learner
- saves final model files into results/models in notebook execution
3-class mapping:
- 0: Urgent (ESI 1-2)
- 1: Emergent (ESI 3)
- 2: Non-Urgent (ESI 4-5)
- Tabular clinical tower
- demographics, arrival context, vitals, chronic history
- engineered features: shock index, MAP, pulse pressure, age-heart rate interaction, NEWS2 approximation
- explicit missingness indicators
- Keyword flag tower
- emergency term indicators from complaint/injury text with negation-aware extraction
- Chief complaint NLP tower
- Distil ClinicalBERT backbone
- CORN ordinal head
- out-of-fold class probabilities for stacking
- Meta-learner
- multinomial logistic regression over stacked base probabilities
From notebooks/modelling.ipynb OOF output (3-class):
| Model | QWK | Weighted F1 |
|---|---|---|
| XGB Regressor (ordinal cutpoints) | 0.3998 | 0.4534 |
| LGBM Regressor (ordinal cutpoints) | 0.3917 | 0.4492 |
| XGB Classifier | 0.4071 | 0.4632 |
| LGBM Classifier | 0.3991 | 0.4587 |
| NLP-only signal (OOF-derived) | 0.4740 | 0.5579 |
| Simple Average Meta | 0.4552 | 0.5012 |
| Weighted Average Meta | 0.4620 | 0.5071 |
| Stacked Logistic Meta | 0.5122 | 0.5829 |
Stacked Logistic class-wise recall (OOF):
- Urgent: 0.6542
- Emergent: 0.4733
- Non-Urgent: 0.7135
Notebook: notebooks/model_interpretation.ipynb
Includes:
- model-native feature importance plots
- SHAP summary analysis
- cross-model comparison in results/feature_importance and plot/
Notebook: notebooks/nlp_lime_explanations.ipynb
Saves:
- per-case HTML LIME reports in results/lime
- keyword weight CSVs in results/lime
- visualization PNGs in plot/
File: app.py
UI summary:
- chief complaint text box
- vitals input panel
- history/comorbidity toggles
- context inputs (arrival date/time, EMS, seen in last 72h)
- triage card output with confidence and per-class probability bars
- expandable base-model probability table
Inference backend:
- utils.py -> TriageInference class
- combines xgb/lgb regressors + classifiers + NLP probabilities
- final prediction via stacked meta-learner
traigegeist/
βββ app.py
βββ utils.py
βββ pyproject.toml
βββ data/ # raw NHAMCS STATA files (2018-2022)
βββ format/ # STATA format maps
βββ working_data/ # processed CSVs used by modelling
βββ notebooks/
β βββ data_processing.ipynb
β βββ text_processing.ipynb
β βββ eda.ipynb
β βββ nlp_dl.ipynb
β βββ modelling.ipynb
β βββ comprehensive_triage_modelling.ipynb
β βββ model_interpretation.ipynb
β βββ nlp_lime_explanations.ipynb
βββ scripts/ # reusable pipeline modules
βββ results/
β βββ classification_reports/
β βββ feature_importance/
β βββ lime/
β βββ model_artifacts/
βββ plot/ # generated EDA + interpretation figures
- notebooks/data_processing.ipynb
- notebooks/text_processing.ipynb
- notebooks/nlp_dl.ipynb
- notebooks/modelling.ipynb
- notebooks/model_interpretation.ipynb
- notebooks/nlp_lime_explanations.ipynb
- notebooks/eda.ipynb (exploration/reporting at any point after step 1)
- Python 3.12+
- dependencies from pyproject.toml
Using pip:
pip install -e .streamlit run app.py- The modelling notebook writes final tabular/meta models to results/models.
- The NLP notebook writes model artifacts to results/model_artifacts/distilbert_corn_seed42.
- The current utils.py app loader expects an NLP artifact folder named:
- results/model_artifacts/nlpie-distil-clinicalbert_corn_seed42
If you only have distilbert_corn_seed42, either:
- rerun scripts/nlp.py (which uses the safe model-name tag), or
- update the artifact path in utils.py.
This repository is organized for competition-style iteration:
- Build stable feature assets once
- run data processing + text processing notebooks
- keep working_data CSVs versioned or checkpointed
- Iterate on towers independently
- tabular experiments in notebooks/modelling.ipynb
- NLP experiments in notebooks/nlp_dl.ipynb
- Stack and compare
- track QWK, weighted F1, and urgent recall together
- keep every report in results/classification_reports
- Explain before submit
- verify key drivers in notebooks/model_interpretation.ipynb
- review local text behavior in notebooks/nlp_lime_explanations.ipynb
- Demo package
- run Streamlit app for qualitative case checks before finalizing model snapshots
If you use this repository, cite NHAMCS and the methods used in this project write-up (ordinal modelling, stacked ensembling, SHAP, and LIME).
Key methods and references are documented in the project manuscript draft and notebook outputs.